001// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.ioc.internal;
016
017import java.util.Map;
018
019import org.apache.tapestry5.ioc.MappedConfiguration;
020import org.apache.tapestry5.ioc.ObjectLocator;
021import org.apache.tapestry5.ioc.def.ContributionDef;
022
023/**
024 * A wrapper around a Map that provides the {@link org.apache.tapestry5.ioc.MappedConfiguration} interface, and provides
025 * two forms of validation for mapped configurations:
026 * <ul>
027 * <li>If either key or value is null, then a warning is logged</li>
028 * <li>If the key has previously been stored (by some other {@link org.apache.tapestry5.ioc.def.ContributionDef}, then a
029 * warning is logged</li>
030 * </ul>
031 * <p/>
032 * When a warning is logged, the key/value pair is not added to the delegate.
033 * <p/>
034 * Handles instantiation of instances.
035 * 
036 * @param <K>
037 * @param <V>
038 */
039public class ValidatingMappedConfigurationWrapper<K, V> extends AbstractConfigurationImpl<V> implements
040        MappedConfiguration<K, V>
041{
042    private final TypeCoercerProxy typeCoercer;
043
044    private final Map<K, V> map;
045
046    private final Map<K, MappedConfigurationOverride<K, V>> overrides;
047
048    private final String serviceId;
049
050    private final ContributionDef contributionDef;
051
052    private final Class<K> expectedKeyType;
053
054    private final Class<V> expectedValueType;
055
056    private final Map<K, ContributionDef> keyToContributor;
057
058    public ValidatingMappedConfigurationWrapper(Class<V> expectedValueType, ObjectLocator locator,
059            TypeCoercerProxy typeCoercer, Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides,
060            String serviceId, ContributionDef contributionDef, Class<K> expectedKeyType,
061            Map<K, ContributionDef> keyToContributor)
062    {
063        super(expectedValueType, locator);
064
065        this.typeCoercer = typeCoercer;
066        this.map = map;
067        this.overrides = overrides;
068        this.serviceId = serviceId;
069        this.contributionDef = contributionDef;
070        this.expectedKeyType = expectedKeyType;
071        this.expectedValueType = expectedValueType;
072        this.keyToContributor = keyToContributor;
073    }
074
075    @Override
076    public void add(K key, V value)
077    {
078        validateKey(key);
079
080        if (value == null)
081            throw new NullPointerException(IOCMessages.contributionWasNull(serviceId));
082
083        V coerced = typeCoercer.coerce(value, expectedValueType);
084
085        ContributionDef existing = keyToContributor.get(key);
086
087        if (existing != null)
088            throw new IllegalArgumentException(IOCMessages.contributionDuplicateKey(serviceId, existing));
089
090        map.put(key, coerced);
091
092        // Remember that this key is provided by this contribution, when looking
093        // for future conflicts.
094
095        keyToContributor.put(key, contributionDef);
096    }
097
098    private void validateKey(K key)
099    {
100        if (key == null)
101            throw new NullPointerException(IOCMessages.contributionKeyWasNull(serviceId));
102
103        // Key types don't get coerced; not worth the effort, keys are almost always String or Class
104        // anyway.
105
106        if (!expectedKeyType.isInstance(key))
107            throw new IllegalArgumentException(IOCMessages.contributionWrongKeyType(serviceId, key.getClass(),
108                    expectedKeyType));
109    }
110
111    @Override
112    public void addInstance(K key, Class<? extends V> clazz)
113    {
114        add(key, instantiate(clazz));
115    }
116
117    @Override
118    public void override(K key, V value)
119    {
120        validateKey(key);
121
122        V coerced = value == null ? null : typeCoercer.coerce(value, expectedValueType);
123
124        MappedConfigurationOverride<K, V> existing = overrides.get(key);
125
126        if (existing != null)
127            throw new IllegalArgumentException(String.format(
128                    "Contribution key %s has already been overridden (by %s).", key, existing.getContribDef()));
129
130        overrides.put(key, new MappedConfigurationOverride<K, V>(contributionDef, map, key, coerced));
131    }
132
133    @Override
134    public void overrideInstance(K key, Class<? extends V> clazz)
135    {
136        override(key, instantiate(clazz));
137    }
138}