001 // Copyright 2006, 2007, 2008 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.util; 016 017 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 018 import org.apache.tapestry5.ioc.internal.util.InheritanceSearch; 019 020 import java.util.Collection; 021 import java.util.List; 022 import java.util.Map; 023 024 /** 025 * A key component in implementing the "Gang of Four" Strategy pattern. A StrategyRegistry will match up a given input 026 * type with a registered strategy for that type. 027 * 028 * @param <A> the type of the strategy adapter 029 */ 030 public final class StrategyRegistry<A> 031 { 032 private final Class<A> adapterType; 033 034 private final boolean allowNonMatch; 035 036 private final Map<Class, A> registrations = CollectionFactory.newMap(); 037 038 private final Map<Class, A> cache = CollectionFactory.newConcurrentMap(); 039 040 /** 041 * Used to identify types for which there is no matching adapter; we're using it as if it were a ConcurrentSet. 042 */ 043 private final Map<Class, Boolean> unmatched = CollectionFactory.newConcurrentMap(); 044 045 private StrategyRegistry(Class<A> adapterType, Map<Class, A> registrations, boolean allowNonMatch) 046 { 047 this.adapterType = adapterType; 048 this.allowNonMatch = allowNonMatch; 049 050 this.registrations.putAll(registrations); 051 } 052 053 /** 054 * Creates a strategy registry for the given adapter type. The registry will be configured to require matches. 055 * 056 * @param adapterType the type of adapter retrieved from the registry 057 * @param registrations map of registrations (the contents of the map are copied) 058 */ 059 public static <A> StrategyRegistry<A> newInstance(Class<A> adapterType, 060 Map<Class, A> registrations) 061 { 062 return newInstance(adapterType, registrations, false); 063 } 064 065 /** 066 * Creates a strategy registry for the given adapter type. 067 * 068 * @param adapterType the type of adapter retrieved from the registry 069 * @param registrations map of registrations (the contents of the map are copied) 070 * @param allowNonMatch if true, then the registry supports non-matches when retrieving an adapter 071 */ 072 public static <A> StrategyRegistry<A> newInstance( 073 Class<A> adapterType, 074 Map<Class, A> registrations, boolean allowNonMatch) 075 { 076 return new StrategyRegistry<A>(adapterType, registrations, allowNonMatch); 077 } 078 079 public void clearCache() 080 { 081 cache.clear(); 082 unmatched.clear(); 083 } 084 085 public Class<A> getAdapterType() 086 { 087 return adapterType; 088 } 089 090 /** 091 * Gets an adapter for an object. Searches based on the value's class, unless the value is null, in which case, a 092 * search on class void is used. 093 * 094 * @param value for which an adapter is needed 095 * @return the adapter for the value or null if not found (and allowNonMatch is true) 096 * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false 097 */ 098 099 public A getByInstance(Object value) 100 { 101 return get(value == null ? void.class : value.getClass()); 102 } 103 104 /** 105 * Searches for an adapter corresponding to the given input type. 106 * 107 * @param type the type to search 108 * @return the adapter for the type or null if not found (and allowNonMatch is true) 109 * @throws IllegalArgumentException if no matching adapter may be found and allowNonMatch is false 110 */ 111 public A get(Class type) 112 { 113 114 A result = cache.get(type); 115 116 if (result != null) return result; 117 118 if (unmatched.containsKey(type)) return null; 119 120 121 result = findMatch(type); 122 123 // This may be null in the case that there is no match and we're allowing that to not 124 // be an error. That's why we check via containsKey. 125 126 if (result != null) 127 { 128 cache.put(type, result); 129 } 130 else 131 { 132 unmatched.put(type, true); 133 } 134 135 return result; 136 } 137 138 private A findMatch(Class type) 139 { 140 for (Class t : new InheritanceSearch(type)) 141 { 142 A result = registrations.get(t); 143 144 if (result != null) return result; 145 } 146 147 if (allowNonMatch) return null; 148 149 // Report the error. These things really confused the hell out of people in Tap4, so we're 150 // going the extra mile on the exception message. 151 152 List<String> names = CollectionFactory.newList(); 153 for (Class t : registrations.keySet()) 154 names.add(t.getName()); 155 156 throw new IllegalArgumentException(UtilMessages 157 .noStrategyAdapter(type, adapterType, names)); 158 } 159 160 /** 161 * Returns the registered types for which adapters are available. 162 */ 163 public Collection<Class> getTypes() 164 { 165 return CollectionFactory.newList(registrations.keySet()); 166 } 167 168 @Override 169 public String toString() 170 { 171 return String.format("StrategyRegistry[%s]", adapterType.getName()); 172 } 173 }