    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    }