    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, key, 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}