001// Copyright 2006, 2007, 2008, 2011, 2012 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.util; 016 017import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 018import org.apache.tapestry5.ioc.internal.util.InheritanceSearch; 019 020import java.util.Collection; 021import java.util.List; 022import 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 */ 030public 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 } else 130 { 131 unmatched.put(type, true); 132 } 133 134 return result; 135 } 136 137 private A findMatch(Class type) 138 { 139 for (Class t : new InheritanceSearch(type)) 140 { 141 A result = registrations.get(t); 142 143 if (result != null) return result; 144 } 145 146 if (allowNonMatch) return null; 147 148 // Report the error. These things really confused the hell out of people in Tap4, so we're 149 // going the extra mile on the exception message. 150 151 List<String> names = CollectionFactory.newList(); 152 for (Class t : registrations.keySet()) 153 names.add(t.getName()); 154 155 throw new UnknownValueException(String.format("No adapter from type %s to type %s is available.", type.getName(), adapterType.getName()), null, null, 156 new AvailableValues("registered types", registrations)); 157 } 158 159 /** 160 * Returns the registered types for which adapters are available. 161 */ 162 public Collection<Class> getTypes() 163 { 164 return CollectionFactory.newList(registrations.keySet()); 165 } 166 167 @Override 168 public String toString() 169 { 170 return String.format("StrategyRegistry[%s]", adapterType.getName()); 171 } 172}