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 }