001 // Copyright 2006, 2007, 2008, 2010, 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
015 package org.apache.tapestry5.ioc.internal.services;
016
017 import org.apache.tapestry5.func.F;
018 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019 import org.apache.tapestry5.ioc.internal.util.InheritanceSearch;
020 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021 import org.apache.tapestry5.ioc.internal.util.LockSupport;
022 import org.apache.tapestry5.ioc.services.Coercion;
023 import org.apache.tapestry5.ioc.services.CoercionTuple;
024 import org.apache.tapestry5.ioc.services.TypeCoercer;
025 import org.apache.tapestry5.ioc.util.AvailableValues;
026 import org.apache.tapestry5.ioc.util.UnknownValueException;
027 import org.apache.tapestry5.plastic.PlasticUtils;
028 import org.apache.tapestry5.util.StringToEnumCoercion;
029
030 import java.util.*;
031
032 @SuppressWarnings("all")
033 public class TypeCoercerImpl extends LockSupport implements TypeCoercer
034 {
035 // Constructed from the service's configuration.
036
037 private final Map<Class, List<CoercionTuple>> sourceTypeToTuple = CollectionFactory.newMap();
038
039 /**
040 * A coercion to a specific target type. Manages a cache of coercions to specific types.
041 */
042 private class TargetCoercion
043 {
044 private final Class type;
045
046 private final Map<Class, Coercion> cache = CollectionFactory.newConcurrentMap();
047
048 TargetCoercion(Class type)
049 {
050 this.type = type;
051 }
052
053 void clearCache()
054 {
055 cache.clear();
056 }
057
058 Object coerce(Object input)
059 {
060 Class sourceType = input != null ? input.getClass() : Void.class;
061
062 if (type.isAssignableFrom(sourceType))
063 {
064 return input;
065 }
066
067 Coercion c = getCoercion(sourceType);
068
069 try
070 {
071 return type.cast(c.coerce(input));
072 } catch (Exception ex)
073 {
074 throw new RuntimeException(ServiceMessages.failedCoercion(input, type, c, ex), ex);
075 }
076 }
077
078 String explain(Class sourceType)
079 {
080 return getCoercion(sourceType).toString();
081 }
082
083 private Coercion getCoercion(Class sourceType)
084 {
085 Coercion c = cache.get(sourceType);
086
087 if (c == null)
088 {
089 c = findOrCreateCoercion(sourceType, type);
090 cache.put(sourceType, c);
091 }
092
093 return c;
094 }
095 }
096
097 /**
098 * Map from a target type to a TargetCoercion for that type.
099 */
100 private final Map<Class, TargetCoercion> typeToTargetCoercion = new WeakHashMap<Class, TargetCoercion>();
101
102 private static final Coercion NO_COERCION = new Coercion<Object, Object>()
103 {
104 public Object coerce(Object input)
105 {
106 return input;
107 }
108 };
109
110 private static final Coercion COERCION_NULL_TO_OBJECT = new Coercion<Void, Object>()
111 {
112 public Object coerce(Void input)
113 {
114 return null;
115 }
116
117 @Override
118 public String toString()
119 {
120 return "null --> null";
121 }
122 };
123
124 public TypeCoercerImpl(Collection<CoercionTuple> tuples)
125 {
126 for (CoercionTuple tuple : tuples)
127 {
128 Class key = tuple.getSourceType();
129
130 InternalUtils.addToMapList(sourceTypeToTuple, key, tuple);
131 }
132 }
133
134 @SuppressWarnings("unchecked")
135 public Object coerce(Object input, Class targetType)
136 {
137 assert targetType != null;
138 Class effectiveTargetType = PlasticUtils.toWrapperType(targetType);
139
140 if (effectiveTargetType.isInstance(input))
141 {
142 return input;
143 }
144
145
146 return getTargetCoercion(effectiveTargetType).coerce(input);
147 }
148
149 @SuppressWarnings("unchecked")
150 public <S, T> Coercion<S, T> getCoercion(Class<S> sourceType, Class<T> targetType)
151 {
152 assert sourceType != null;
153 assert targetType != null;
154 Class effectiveSourceType = PlasticUtils.toWrapperType(sourceType);
155 Class effectiveTargetType = PlasticUtils.toWrapperType(targetType);
156
157 if (effectiveTargetType.isAssignableFrom(effectiveSourceType))
158 {
159 return NO_COERCION;
160 }
161
162 return getTargetCoercion(effectiveTargetType).getCoercion(effectiveSourceType);
163 }
164
165 @SuppressWarnings("unchecked")
166 public <S, T> String explain(Class<S> sourceType, Class<T> targetType)
167 {
168 assert sourceType != null;
169 assert targetType != null;
170 Class effectiveTargetType = PlasticUtils.toWrapperType(targetType);
171 Class effectiveSourceType = PlasticUtils.toWrapperType(sourceType);
172
173 // Is a coercion even necessary? Not if the target type is assignable from the
174 // input value.
175
176 if (effectiveTargetType.isAssignableFrom(effectiveSourceType))
177 {
178 return "";
179 }
180
181 return getTargetCoercion(effectiveTargetType).explain(effectiveSourceType);
182 }
183
184 private TargetCoercion getTargetCoercion(Class targetType)
185 {
186 try
187 {
188 acquireReadLock();
189
190 TargetCoercion tc = typeToTargetCoercion.get(targetType);
191
192 return tc != null ? tc : createAndStoreNewTargetCoercion(targetType);
193 } finally
194 {
195 releaseReadLock();
196 }
197 }
198
199 private TargetCoercion createAndStoreNewTargetCoercion(Class targetType)
200 {
201 try
202 {
203 upgradeReadLockToWriteLock();
204
205 // Inner check since some other thread may have beat us to it.
206
207 TargetCoercion tc = typeToTargetCoercion.get(targetType);
208
209 if (tc == null)
210 {
211 tc = new TargetCoercion(targetType);
212 typeToTargetCoercion.put(targetType, tc);
213 }
214
215 return tc;
216 } finally
217 {
218 downgradeWriteLockToReadLock();
219 }
220 }
221
222 public void clearCache()
223 {
224 try
225 {
226 acquireReadLock();
227
228 // There's no need to clear the typeToTargetCoercion map, as it is a WeakHashMap and
229 // will release the keys for classes that are no longer in existence. On the other hand,
230 // there's likely all sorts of references to unloaded classes inside each TargetCoercion's
231 // individual cache, so clear all those.
232
233 for (TargetCoercion tc : typeToTargetCoercion.values())
234 {
235 // Can tc ever be null?
236
237 tc.clearCache();
238 }
239 } finally
240 {
241 releaseReadLock();
242 }
243 }
244
245 /**
246 * Here's the real meat; we do a search of the space to find coercions, or a system of
247 * coercions, that accomplish
248 * the desired coercion.
249 * <p/>
250 * There's <strong>TREMENDOUS</strong> room to improve this algorithm. For example, inheritance lists could be
251 * cached. Further, there's probably more ways to early prune the search. However, even with dozens or perhaps
252 * hundreds of tuples, I suspect the search will still grind to a conclusion quickly.
253 * <p/>
254 * The order of operations should help ensure that the most efficient tuple chain is located. If you think about how
255 * tuples are added to the queue, there are two factors: size (the number of steps in the coercion) and
256 * "class distance" (that is, number of steps up the inheritance hiearchy). All the appropriate 1 step coercions
257 * will be considered first, in class distance order. Along the way, we'll queue up all the 2 step coercions, again
258 * in class distance order. By the time we reach some of those, we'll have begun queueing up the 3 step coercions, and
259 * so forth, until we run out of input tuples we can use to fabricate multi-step compound coercions, or reach a
260 * final response.
261 * <p/>
262 * This does create a good number of short lived temporary objects (the compound tuples), but that's what the GC is
263 * really good at.
264 *
265 * @param sourceType
266 * @param targetType
267 * @return coercer from sourceType to targetType
268 */
269 @SuppressWarnings("unchecked")
270 private Coercion findOrCreateCoercion(Class sourceType, Class targetType)
271 {
272 if (sourceType == Void.class)
273 {
274 return searchForNullCoercion(targetType);
275 }
276
277 // These are instance variables because this method may be called concurrently.
278 // On a true race, we may go to the work of seeking out and/or fabricating
279 // a tuple twice, but it's more likely that different threads are looking
280 // for different source/target coercions.
281
282 Set<CoercionTuple> consideredTuples = CollectionFactory.newSet();
283 LinkedList<CoercionTuple> queue = CollectionFactory.newLinkedList();
284
285 seedQueue(sourceType, targetType, consideredTuples, queue);
286
287 while (!queue.isEmpty())
288 {
289 CoercionTuple tuple = queue.removeFirst();
290
291 // If the tuple results in a value type that is assignable to the desired target type,
292 // we're done! Later, we may add a concept of "cost" (i.e. number of steps) or
293 // "quality" (how close is the tuple target type to the desired target type). Cost
294 // is currently implicit, as compound tuples are stored deeper in the queue,
295 // so simpler coercions will be located earlier.
296
297 Class tupleTargetType = tuple.getTargetType();
298
299 if (targetType.isAssignableFrom(tupleTargetType))
300 {
301 return tuple.getCoercion();
302 }
303
304 // So .. this tuple doesn't get us directly to the target type.
305 // However, it *may* get us part of the way. Each of these
306 // represents a coercion from the source type to an intermediate type.
307 // Now we're going to look for conversions from the intermediate type
308 // to some other type.
309
310 queueIntermediates(sourceType, targetType, tuple, consideredTuples, queue);
311 }
312
313 // Not found anywhere. Identify the source and target type and a (sorted) list of
314 // all the known coercions.
315
316 throw new UnknownValueException(String.format("Could not find a coercion from type %s to type %s.",
317 sourceType.getName(), targetType.getName()), buildCoercionCatalog());
318 }
319
320 /**
321 * Coercion from null is special; we match based on the target type and its not a spanning
322 * search. In many cases, we
323 * return a pass-thru that leaves the value as null.
324 *
325 * @param targetType
326 * desired type
327 * @return the coercion
328 */
329 private Coercion searchForNullCoercion(Class targetType)
330 {
331 List<CoercionTuple> tuples = getTuples(Void.class, targetType);
332
333 for (CoercionTuple tuple : tuples)
334 {
335 Class tupleTargetType = tuple.getTargetType();
336
337 if (targetType.equals(tupleTargetType))
338 return tuple.getCoercion();
339 }
340
341 // Typical case: no match, this coercion passes the null through
342 // as null.
343
344 return COERCION_NULL_TO_OBJECT;
345 }
346
347 /**
348 * Builds a string listing all the coercions configured for the type coercer, sorted
349 * alphabetically.
350 */
351 @SuppressWarnings("unchecked")
352 private AvailableValues buildCoercionCatalog()
353 {
354 List<CoercionTuple> masterList = CollectionFactory.newList();
355
356 for (List<CoercionTuple> list : sourceTypeToTuple.values())
357 {
358 masterList.addAll(list);
359 }
360
361 return new AvailableValues("Configured coercions", masterList);
362 }
363
364 /**
365 * Seeds the pool with the initial set of coercions for the given type.
366 */
367 private void seedQueue(Class sourceType, Class targetType, Set<CoercionTuple> consideredTuples,
368 LinkedList<CoercionTuple> queue)
369 {
370 // Work from the source type up looking for tuples
371
372 for (Class c : new InheritanceSearch(sourceType))
373 {
374 List<CoercionTuple> tuples = getTuples(c, targetType);
375
376 if (tuples == null)
377 {
378 continue;
379 }
380
381 for (CoercionTuple tuple : tuples)
382 {
383 queue.addLast(tuple);
384 consideredTuples.add(tuple);
385 }
386
387 // Don't pull in Object -> type coercions when doing
388 // a search from null.
389
390 if (sourceType == Void.class)
391 {
392 return;
393 }
394 }
395 }
396
397 /**
398 * Creates and adds to the pool a new set of coercions based on an intermediate tuple. Adds
399 * compound coercion tuples
400 * to the end of the queue.
401 *
402 * @param sourceType
403 * the source type of the coercion
404 * @param targetType
405 * TODO
406 * @param intermediateTuple
407 * a tuple that converts from the source type to some intermediate type (that is not
408 * assignable to the target type)
409 * @param consideredTuples
410 * set of tuples that have already been added to the pool (directly, or as a compound
411 * coercion)
412 * @param queue
413 * the work queue of tuples
414 */
415 @SuppressWarnings("unchecked")
416 private void queueIntermediates(Class sourceType, Class targetType, CoercionTuple intermediateTuple,
417 Set<CoercionTuple> consideredTuples, LinkedList<CoercionTuple> queue)
418 {
419 Class intermediateType = intermediateTuple.getTargetType();
420
421 for (Class c : new InheritanceSearch(intermediateType))
422 {
423 for (CoercionTuple tuple : getTuples(c, targetType))
424 {
425 if (consideredTuples.contains(tuple))
426 {
427 continue;
428 }
429
430 Class newIntermediateType = tuple.getTargetType();
431
432 // If this tuple is for coercing from an intermediate type back towards our
433 // initial source type, then ignore it. This should only be an optimization,
434 // as branches that loop back towards the source type will
435 // eventually be considered and discarded.
436
437 if (sourceType.isAssignableFrom(newIntermediateType))
438 {
439 continue;
440 }
441
442 // The intermediateTuple coercer gets from S --> I1 (an intermediate type).
443 // The current tuple's coercer gets us from I2 --> X. where I2 is assignable
444 // from I1 (i.e., I2 is a superclass/superinterface of I1) and X is a new
445 // intermediate type, hopefully closer to our eventual target type.
446
447 Coercion compoundCoercer = new CompoundCoercion(intermediateTuple.getCoercion(), tuple.getCoercion());
448
449 CoercionTuple compoundTuple = new CoercionTuple(sourceType, newIntermediateType, compoundCoercer, false);
450
451 // So, every tuple that is added to the queue can take as input the sourceType.
452 // The target type may be another intermediate type, or may be something
453 // assignable to the target type, which will bring the search to a successful
454 // conclusion.
455
456 queue.addLast(compoundTuple);
457 consideredTuples.add(tuple);
458 }
459 }
460 }
461
462 /**
463 * Returns a non-null list of the tuples from the source type.
464 *
465 * @param sourceType
466 * used to locate tuples
467 * @param targetType
468 * used to add synthetic tuples
469 * @return non-null list of tuples
470 */
471 private List<CoercionTuple> getTuples(Class sourceType, Class targetType)
472 {
473 List<CoercionTuple> tuples = sourceTypeToTuple.get(sourceType);
474
475 if (tuples == null)
476 {
477 tuples = Collections.emptyList();
478 }
479
480 // So, when we see String and an Enum type, we add an additional synthetic tuple to the end
481 // of the real list. This is the easiest way to accomplish this is a thread-safe and class-reloading
482 // safe way (i.e., what if the Enum is defined by a class loader that gets discarded? Don't want to cause
483 // memory leaks by retaining an instance). In any case, there are edge cases where we may create
484 // the tuple unnecessarily (such as when an explicit string-to-enum coercion is part of the TypeCoercer
485 // configuration), but on the whole, this is cheap and works.
486
487 if (sourceType == String.class && Enum.class.isAssignableFrom(targetType))
488 {
489 tuples = extend(tuples, new CoercionTuple(sourceType, targetType, new StringToEnumCoercion(targetType)));
490 }
491
492 return tuples;
493 }
494
495 private static <T> List<T> extend(List<T> list, T extraValue)
496 {
497 return F.flow(list).append(extraValue).toList();
498 }
499 }