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