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