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}