    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.ioc.MappedConfiguration;
    016import org.apache.tapestry5.ioc.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.ioc.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>
    035 * @param <V>
    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}