001: // Copyright 2006, 2007 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.tapestry.ioc.internal.services;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newLinkedList;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
020: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
021: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newConcurrentMap;
022: import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
023:
024: import java.util.Collection;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029:
030: import org.apache.tapestry.ioc.internal.util.InheritanceSearch;
031: import org.apache.tapestry.ioc.internal.util.InternalUtils;
032: import org.apache.tapestry.ioc.services.ClassFabUtils;
033: import org.apache.tapestry.ioc.services.Coercion;
034: import org.apache.tapestry.ioc.services.CoercionTuple;
035: import org.apache.tapestry.ioc.services.TypeCoercer;
036:
037: public class TypeCoercerImpl implements TypeCoercer {
038: // Read only after constructor
039:
040: private final Map<Class, List<CoercionTuple>> _sourceTypeToTuple = newMap();
041:
042: // Access to the cache must be thread safe
043:
044: private final Map<CacheKey, Coercion> _cache = newConcurrentMap();
045:
046: static class CacheKey {
047: private final Class _sourceClass;
048:
049: private final Class _targetClass;
050:
051: CacheKey(final Class sourceClass, final Class targetClass) {
052: _sourceClass = sourceClass;
053: _targetClass = targetClass;
054: }
055:
056: @Override
057: public boolean equals(Object obj) {
058: if (obj == null)
059: return false;
060:
061: if (!(obj instanceof CacheKey))
062: return false;
063:
064: CacheKey other = (CacheKey) obj;
065:
066: return _sourceClass.equals(other._sourceClass)
067: && _targetClass.equals(other._targetClass);
068: }
069:
070: @Override
071: public int hashCode() {
072: return _sourceClass.hashCode() * 27
073: % _targetClass.hashCode();
074: }
075:
076: @Override
077: public String toString() {
078: return String.format("CacheKey[%s --> %s]", _sourceClass
079: .getName(), _targetClass.getName());
080: }
081: }
082:
083: private static final Coercion COERCION_NULL_TO_OBJECT = new Coercion<Void, Object>() {
084: public Object coerce(Void input) {
085: return null;
086: }
087:
088: @Override
089: public String toString() {
090: return "null --> null";
091: }
092: };
093:
094: public TypeCoercerImpl(Collection<CoercionTuple> tuples) {
095: for (CoercionTuple tuple : tuples) {
096: Class key = tuple.getSourceType();
097:
098: addTuple(key, tuple);
099: }
100: }
101:
102: @SuppressWarnings({"unchecked","unchecked"})
103: private void addTuple(Class key, CoercionTuple tuple) {
104: List<CoercionTuple> list = _sourceTypeToTuple.get(key);
105:
106: if (list == null) {
107: list = newList();
108: _sourceTypeToTuple.put(key, list);
109: }
110:
111: list.add(tuple);
112: }
113:
114: @SuppressWarnings("unchecked")
115: public Object coerce(Object input, Class targetType) {
116: notNull(targetType, "targetType");
117:
118: // Treat null as void in terms of locating a coercion.
119:
120: Class sourceType = input != null ? input.getClass()
121: : void.class;
122:
123: // The caller may ask for the value in a primitive type, but the best we can do is the
124: // equivalent wrapper type.
125:
126: Class effectiveTargetType = ClassFabUtils
127: .getWrapperType(targetType);
128:
129: // Is a coercion even necessary? Not if the target type is assignable from the
130: // input value.
131:
132: if (effectiveTargetType.isAssignableFrom(sourceType))
133: return input;
134:
135: Coercion coercion = findCoercion(sourceType,
136: effectiveTargetType);
137:
138: Object result;
139:
140: try {
141: result = coercion.coerce(input);
142: } catch (Exception ex) {
143: throw new RuntimeException(ServiceMessages.failedCoercion(
144: input, targetType, coercion, ex), ex);
145: }
146:
147: // Double check that the coercer provided a result of the correct type
148:
149: return effectiveTargetType.cast(result);
150: }
151:
152: @SuppressWarnings("unchecked")
153: public <S, T> String explain(Class<S> inputType, Class<T> targetType) {
154: notNull(inputType, "inputType");
155: notNull(targetType, "targetType");
156:
157: Class effectiveTargetType = ClassFabUtils
158: .getWrapperType(targetType);
159:
160: // Is a coercion even necessary? Not if the target type is assignable from the
161: // input value.
162:
163: if (effectiveTargetType.isAssignableFrom(inputType))
164: return "";
165:
166: Coercion coercion = findCoercion(inputType, effectiveTargetType);
167:
168: return coercion.toString();
169: }
170:
171: private Coercion findCoercion(Class sourceType, Class targetType) {
172: CacheKey key = new CacheKey(sourceType, targetType);
173:
174: Coercion result = _cache.get(key);
175:
176: if (result == null) {
177: result = findOrCreateCoercion(sourceType, targetType);
178: _cache.put(key, result);
179: }
180:
181: return result;
182: }
183:
184: public void clearCache() {
185: _cache.clear();
186: }
187:
188: /**
189: * Here's the real meat; we do a search of the space to find coercions, or a system of
190: * coercions, that accomplish the desired coercion.
191: * <p>
192: * There's <strong>TREMENDOUS</strong> room to improve this algorithm. For example, inheritance
193: * lists could be cached. Further, there's probably more ways to early prune the search.
194: * However, even with dozens or perhaps hundreds of tuples, I suspect the search will still
195: * grind to a conclusion quickly.
196: * <p>
197: * The order of operations should help ensure that the most efficient tuple chain is located. If
198: * you think about how tuples are added to the queue, there are two factors: size (the number of
199: * steps in the coercion) and "class distance" (that is, number of steps up the inheritance
200: * hiearchy). All the appropriate 1 step coercions will be considered first, in class distance
201: * order. Along the way, we'll queue up all the 2 step coercions, again in class distance order.
202: * By the time we reach some of those, we'll have begun queing up the 3 step coercions, and so
203: * forth, until we run out of input tuples we can use to fabricate multi-step compound
204: * coercions, or reach a final response.
205: * <p>
206: * This does create a good number of short lived temporary objects (the compound tuples), but
207: * that's what the GC is really good at.
208: *
209: * @param sourceType
210: * @param targetType
211: * @return coercer from sourceType to targetType
212: */
213: @SuppressWarnings("unchecked")
214: private Coercion findOrCreateCoercion(Class sourceType,
215: Class targetType) {
216: // These are instance variables because this method may be called concurrently.
217: // On a true race, we may go to the work of seeking out and/or fabricating
218: // a tuple twice, but it's more likely that different threads are looking
219: // for different source/target coercions.
220:
221: Set<CoercionTuple> consideredTuples = newSet();
222: LinkedList<CoercionTuple> queue = newLinkedList();
223:
224: seedQueue(sourceType, consideredTuples, queue);
225:
226: while (!queue.isEmpty()) {
227: CoercionTuple tuple = queue.removeFirst();
228:
229: // If the tuple results in a value type that is assignable to the desired target type,
230: // we're done! Later, we may add a concept of "cost" (i.e. number of steps) or
231: // "quality" (how close is the tuple target type to the desired target type). Cost
232: // is currently implicit, as compound tuples are stored deeper in the queue,
233: // so simpler coercions will be located earlier.
234:
235: Class tupleTargetType = tuple.getTargetType();
236:
237: if (targetType.isAssignableFrom(tupleTargetType))
238: return tuple.getCoercion();
239:
240: // So .. this tuple doesn't get us directly to the target type.
241: // However, it *may* get us part of the way. Each of these
242: // represents a coercion from the source type to an intermediate type.
243: // Now we're going to look for conversions from the intermediate type
244: // to some other type.
245:
246: queueIntermediates(sourceType, tuple, consideredTuples,
247: queue);
248: }
249:
250: // A default rule for coercing nulls if nothing better is found.
251:
252: if (sourceType == void.class)
253: return COERCION_NULL_TO_OBJECT;
254:
255: // Not found anywhere. Identify the source and target type and a (sorted) list of
256: // all the known coercions.
257:
258: throw new IllegalArgumentException(ServiceMessages
259: .noCoercionFound(sourceType, targetType,
260: buildCoercionCatalog()));
261:
262: }
263:
264: /**
265: * Builds a string listing all the coercions configured for the type coercer, sorted
266: * alphabetically.
267: */
268: private String buildCoercionCatalog() {
269: List<String> descriptions = newList();
270:
271: for (List<CoercionTuple> list : _sourceTypeToTuple.values()) {
272: for (CoercionTuple tuple : list)
273: descriptions.add(tuple.toString());
274: }
275:
276: return InternalUtils.joinSorted(descriptions);
277: }
278:
279: /** Seeds the pool with the initial set of coercions for the given type. */
280: private void seedQueue(Class sourceType,
281: Set<CoercionTuple> consideredTuples,
282: LinkedList<CoercionTuple> queue) {
283: // Work from the source type up looking for tuples
284:
285: for (Class c : new InheritanceSearch(sourceType)) {
286: List<CoercionTuple> tuples = _sourceTypeToTuple.get(c);
287:
288: if (tuples == null)
289: continue;
290:
291: for (CoercionTuple tuple : tuples) {
292: queue.addLast(tuple);
293: consideredTuples.add(tuple);
294: }
295: }
296: }
297:
298: /**
299: * Creates and adds to the pool a new set of coercions based on an intermediate tuple. Adds
300: * compound coercion tuples to the end of the queue.
301: *
302: * @param sourceType
303: * the source type of the coercion
304: * @param intermediateTuple
305: * a tuple that converts from the source type to some intermediate type (that is not
306: * assignable to the target type)
307: * @param consideredTuples
308: * set of tuples that have already been added to the pool (directly, or as a compound
309: * coercion)
310: * @param queue
311: * the work queue of tuples
312: */
313: @SuppressWarnings("unchecked")
314: private void queueIntermediates(Class sourceType,
315: CoercionTuple intermediateTuple,
316: Set<CoercionTuple> consideredTuples,
317: LinkedList<CoercionTuple> queue) {
318: Class intermediateType = intermediateTuple.getTargetType();
319:
320: for (Class c : new InheritanceSearch(intermediateType)) {
321: List<CoercionTuple> tuples = _sourceTypeToTuple.get(c);
322:
323: if (tuples == null)
324: continue;
325:
326: for (CoercionTuple tuple : tuples) {
327: if (consideredTuples.contains(tuple))
328: continue;
329:
330: Class newIntermediateType = tuple.getTargetType();
331:
332: // If this tuple is for coercing from an intermediate type back towards our
333: // initial source type, then ignore it. This should only be an optimization,
334: // as branches that loop back towards the source type will
335: // eventually be considered and discarded.
336:
337: if (sourceType.isAssignableFrom(newIntermediateType))
338: continue;
339:
340: // The intermediateTuple coercer gets from S --> I1 (an intermediate type).
341: // The current tuple's coercer gets us from I2 --> X. where I2 is assignable
342: // from I1 (i.e., I2 is a superclass/superinterface of I1) and X is a new
343: // intermediate type, hopefully closer to our eventual target type.
344:
345: Coercion compoundCoercer = new CompoundCoercion(
346: intermediateTuple.getCoercion(), tuple
347: .getCoercion());
348:
349: CoercionTuple compoundTuple = new CoercionTuple(
350: sourceType, newIntermediateType,
351: compoundCoercer, false);
352:
353: // So, every tuple that is added to the queue can take as input the sourceType.
354: // The target type may be another intermdiate type, or may be something
355: // assignable to the target type, which will bring the search to a succesful
356: // conclusion.
357:
358: queue.addLast(compoundTuple);
359: consideredTuples.add(tuple);
360: }
361: }
362: }
363:
364: }
|