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    
015    package org.apache.tapestry5.ioc.internal;
016    
017    import java.util.Map;
018    
019    import org.apache.tapestry5.ioc.MappedConfiguration;
020    import org.apache.tapestry5.ioc.ObjectLocator;
021    import 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     */
039    public 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        public void add(K key, V value)
076        {
077            validateKey(key);
078    
079            if (value == null)
080                throw new NullPointerException(IOCMessages.contributionWasNull(serviceId));
081    
082            V coerced = typeCoercer.coerce(value, expectedValueType);
083    
084            ContributionDef existing = keyToContributor.get(key);
085    
086            if (existing != null)
087                throw new IllegalArgumentException(IOCMessages.contributionDuplicateKey(serviceId, existing));
088    
089            map.put(key, coerced);
090    
091            // Remember that this key is provided by this contribution, when looking
092            // for future conflicts.
093    
094            keyToContributor.put(key, contributionDef);
095        }
096    
097        private void validateKey(K key)
098        {
099            if (key == null)
100                throw new NullPointerException(IOCMessages.contributionKeyWasNull(serviceId));
101    
102            // Key types don't get coerced; not worth the effort, keys are almost always String or Class
103            // anyway.
104    
105            if (!expectedKeyType.isInstance(key))
106                throw new IllegalArgumentException(IOCMessages.contributionWrongKeyType(serviceId, key.getClass(),
107                        expectedKeyType));
108        }
109    
110        public void addInstance(K key, Class<? extends V> clazz)
111        {
112            add(key, instantiate(clazz));
113        }
114    
115        public void override(K key, V value)
116        {
117            validateKey(key);
118    
119            V coerced = value == null ? null : typeCoercer.coerce(value, expectedValueType);
120    
121            MappedConfigurationOverride<K, V> existing = overrides.get(key);
122    
123            if (existing != null)
124                throw new IllegalArgumentException(String.format(
125                        "Contribution key %s has already been overridden (by %s).", key, existing.getContribDef()));
126    
127            overrides.put(key, new MappedConfigurationOverride<K, V>(contributionDef, map, key, coerced));
128        }
129    
130        public void overrideInstance(K key, Class<? extends V> clazz)
131        {
132            override(key, instantiate(clazz));
133        }
134    }