001package com.hammurapi.convert; 002 003import java.lang.reflect.InvocationTargetException; 004import java.lang.reflect.Method; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Map; 012import java.util.ServiceLoader; 013import java.util.Set; 014 015import com.hammurapi.common.Adaptable; 016import com.hammurapi.common.Context; 017 018public class ConvertingService { 019 020 private interface ConvertersBucket<S, T> { 021 022 T convert(S source, Context context); 023 } 024 025 /** 026 * Converter which delegates to convert() method. 027 */ 028 public static final Converter CONVERTER = new Converter() { 029 030 @SuppressWarnings("unchecked") 031 public Object convert(Object source, Class targetType, Context context) throws ConversionException { 032 return ConvertingService.convert(source, targetType, context); 033 } 034 035 }; 036 037 /** 038 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 039 * If no converter is found, this method performs converter discovery through 040 * constructors if target type is a class and through duck conversion if target 041 * type is an interface. 042 * Invocation of this method is equivalent to invocation of 043 * <code>convert(source, targetType, false)</code> 044 * @param source Source object. 045 * @param targetType Target type. 046 * @return Source object converted to target type or null if no converter 047 * is available for source/targetType combination 048 * @throws ConversionException 049 */ 050 public static <S, T> T convert(S source, Class<T> targetType) throws ConversionException { 051 return convert(source, targetType, null, false, null); 052 } 053 054 /** 055 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 056 * If no converter is found, this method performs converter discovery through 057 * constructors if target type is a class and through duck conversion if target 058 * type is an interface. 059 * Invocation of this method is equivalent to invocation of 060 * <code>convert(source, targetType, false)</code> 061 * @param source Source object. 062 * @param targetType Target type. 063 * @param context Conversion context. 064 * @return Source object converted to target type or null if no converter 065 * is available for source/targetType combination 066 * @throws ConversionException 067 */ 068 public static <S, T> T convert(S source, Class<T> targetType, Context context) throws ConversionException { 069 return convert(source, targetType, context, false, null); 070 } 071 072 /** 073 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 074 * If no converter is found, this method performs converter discovery through 075 * constructors if target type is a class and through duck conversion if target 076 * type is an interface. 077 * Invocation of this method is equivalent to invocation of 078 * <code>convert(source, targetType, false)</code> 079 * @param source Source object. 080 * @param targetType Target type. 081 * @param classLoader Class loader, source of converters. 082 * @return Source object converted to target type or null if no converter 083 * is available for source/targetType combination 084 * @throws ConversionException 085 */ 086 public static <S, T> T convert(S source, Class<T> targetType, ClassLoader classLoader) throws ConversionException { 087 return convert(source, targetType, null, false, classLoader); 088 } 089 090 private static class ConverterKey<S,T> { 091 Class<S> sourceType; 092 Class<T> targetType; 093 094 public ConverterKey(Class<S> sourceType, Class<T> targetType) { 095 super(); 096 this.sourceType = sourceType; 097 this.targetType = targetType; 098 } 099 100 public int hashCode() { 101 final int prime = 31; 102 int result = 1; 103 result = prime * result + ((sourceType == null) ? 0 : sourceType.hashCode()); 104 result = prime * result + ((targetType == null) ? 0 : targetType.hashCode()); 105 return result; 106 } 107 108 @SuppressWarnings("unchecked") 109 public boolean equals(Object obj) { 110 return 111 sourceType == ((ConverterKey<S, T>) obj).sourceType 112 && targetType == ((ConverterKey<S, T>) obj).targetType; 113 } 114 115 } 116 117 private static class ClassLoaderConvertersEntry { 118 private static final String CONVERT = "convert"; 119 120 private ClassLoader classLoader; 121 122 public ClassLoaderConvertersEntry(ClassLoader classLoader) { 123 this.classLoader = classLoader; 124 } 125 126 private Converter master = new Converter() { 127 128 @SuppressWarnings("unchecked") 129 public Object convert(Object source, Class targetType, Context context) throws ConversionException { 130 if (source==null) { 131 return null; 132 } 133 134 if (targetType.isInstance(source)) { 135 return source; 136 } 137 138 final ConvertersBucket<Object, Object> bucket = findConverter(source.getClass(), targetType); 139 if (bucket!=null) { 140 Object ret = bucket.convert(source, context); 141 if (ret!=null) { 142 return ret; 143 } 144 } 145 146 for (ConverterPart part: parts) { 147 Object ret = part.convert(source, targetType, context, this); 148 if (ret!=null) { 149 return ret; 150 } 151 } 152 153 for (Converter subConverter: subConverters) { 154 Object ret = subConverter.convert(source, targetType, context); 155 if (ret!=null) { 156 return ret; 157 } 158 } 159 160 return null; 161 } 162 163 }; 164 165// private Converter<?,?> chainingMaster = new Converter<?,?>() { 166// 167// public Object convert(Object source, Class targetType, Context context) throws ConversionException { 168// if (source==null) { 169// return null; 170// } 171// 172// if (targetType.isInstance(source)) { 173// return source; 174// } 175// 176// ConvertersBucket bucket = findChainingConverter(source.getClass(), targetType); 177// return bucket==null ? null : bucket.convert(source, context); 178// } 179// 180// }; 181 182 Collection<AtomicConverter<? extends Object,? extends Object>> atomicConverters = new ArrayList<AtomicConverter<? extends Object,? extends Object>>(); 183 184 /** 185 * Map of resolved converters: Collection[source, target] -> ConverterClosure. 186 */ 187 Map<ConverterKey<?,?>, Object> converters = new HashMap<ConverterKey<?,?>, Object>(); 188 189 Set<Class<?>> introspectedTargetTypes = new HashSet<Class<?>>(); 190 191 Set<Class<?>> introspectedSourceTypes = new HashSet<Class<?>>(); 192 193 Collection<Converter> subConverters = new ArrayList<Converter>(); 194 Collection<ConverterPart> parts = new ArrayList<ConverterPart>(); 195 196 /** 197 * Map of resolved chaining converters: Collection[source, target] -> ConverterClosure. 198 */ 199// Map chainingConverters = new HashMap(); 200 201 @SuppressWarnings("unchecked") 202 synchronized <S, T> ConvertersBucket<S, T> findConverter(Class<S> sourceType, final Class<T> targetType) { 203 ConverterKey<S, T> key = new ConverterKey(sourceType, targetType); 204 Object ret = converters.get(key); 205 if (Boolean.FALSE.equals(ret)) { // Indicates that previous converter resolution didn't yield results. 206 return null; 207 } 208 209 if (ret == null) { 210 if (!targetType.isInterface() && introspectedTargetTypes.add(targetType)) { 211 atomicConverters.addAll(ReflectionConverter.discoverConstructorConverters(targetType)); 212 } 213 214 class ConverterEntry implements Comparable<ConverterEntry> { 215 public ConverterEntry(AtomicConverter<? extends Object,? extends Object> converter, int affinity, int position) { 216 super(); 217 this.converter = converter; 218 this.affinity = affinity; 219 this.position = position; 220 } 221 AtomicConverter<? extends Object, ? extends Object> converter; 222 int affinity; 223 int position; 224 public int compareTo(ConverterEntry obj) { 225 int cret = affinity - ((ConverterEntry) obj).affinity; 226 if (cret==0) { 227 return position - ((ConverterEntry) obj).position; 228 } 229 return cret; 230 } 231 } 232 233 final List<ConverterEntry> theConverters = new ArrayList<ConverterEntry>(); 234 235 int position = 0; 236 for (AtomicConverter<? extends Object,? extends Object> ac: atomicConverters) { 237 if (ac.getSourceType().isAssignableFrom(sourceType) && targetType.isAssignableFrom(ac.getTargetType()) ) { 238 Integer newSourceAffinity = DuckConverterFactory.classAffinity(sourceType, ac.getSourceType()); 239 Integer newTargetAffinity = DuckConverterFactory.classAffinity(ac.getTargetType(), targetType); 240 if (newSourceAffinity==null || newTargetAffinity==null) { 241 throw new IllegalArgumentException("Something is wrong - source and target affinities are null"); 242 } 243 theConverters.add(new ConverterEntry(ac, newSourceAffinity.intValue()+newTargetAffinity.intValue(), ++position)); 244 } 245 } 246 if (theConverters.isEmpty() && parts.isEmpty() && subConverters.isEmpty()) { 247 converters.put(key, Boolean.FALSE); 248 } else { 249 Collections.sort(theConverters); 250 ret = new ConvertersBucket<S,T>() { 251 252 public T convert(S source, Context context) { 253 for (ConverterEntry ce: theConverters) { 254 AtomicConverter<S, T> theConverter = (AtomicConverter<S, T>) ce.converter; 255 T ret = (T) theConverter.convert(source, master, context, classLoader); 256 if (ret!=null) { 257 return ret; 258 } 259 } 260 261 for (ConverterPart part: parts) { 262 Object ret = part.convert(source, targetType, context, master); 263 if (ret!=null) { 264 return (T) ret; 265 } 266 } 267 268 for (Converter subConverter: subConverters) { 269 Object ret = subConverter.convert(source, targetType, context); 270 if (ret!=null) { 271 return (T) ret; 272 } 273 } 274 275 return null; 276 } 277 278 }; 279 converters.put(key, ret); 280 } 281 } 282 283 return (ConvertersBucket<S, T>) ret; 284 } 285 286 synchronized <S, T> ConvertersBucket<S,T> findChainingConverter(Class<S> sourceType, Class<T> targetType) { 287 ConvertersBucket<S,T> ret = findConverter(sourceType, targetType); 288 if (ret!=null) { 289 return ret; 290 } 291 292 // TODO - Resolve chain. 293 return ret; 294 } 295 296 <S,T> void addAnnotationConverters(Class<S> source) { 297 if (introspectedSourceTypes.add(source)) { 298 Z: for (Method m: source.getMethods()) { 299 if (m.getAnnotation(ConverterMethod.class)!=null && !void.class.equals(m.getReturnType())) { 300 for (Class<?> pt: m.getParameterTypes()) { 301 if (!(Context.class.equals(pt) || Converter.class.equals(pt))) { 302 continue Z; 303 } 304 } 305 final Method converterMethod = m; 306 @SuppressWarnings("unchecked") 307 AtomicConverterBase<S, T> converter = new AtomicConverterBase<S, T>(source, (Class<T>) m.getReturnType()) { 308 309 @Override 310 public T convert(S source, Converter master, Context context, ClassLoader classLoader) { 311 try { 312 Class<?>[] pt = converterMethod.getParameterTypes(); 313 Object[] args = new Object[pt.length]; 314 for (int i=0; i<pt.length; ++i) { 315 if (Context.class.equals(pt[i])) { 316 args[i] = context; 317 } else if (Converter.class.equals(pt[i])) { 318 args[i] = master; 319 } 320 } 321 return (T) converterMethod.invoke(source, args); 322 } catch (Exception e) { 323 throw new ConversionException(e); 324 } 325 } 326 }; 327 atomicConverters.add(converter); 328 } 329 } 330 } 331 } 332 333 @SuppressWarnings("unchecked") 334 void addConverters(final Object provider) { 335 if (provider instanceof AtomicConverter) { 336 atomicConverters.add((AtomicConverter<Object, Object>) provider); 337 } else if (provider instanceof AtomicConvertersBundle) { 338 atomicConverters.addAll(((AtomicConvertersBundle) provider).getConverters()); 339 } else if (provider instanceof Collection<?>) { 340 for (Object obj: (Collection<?>) provider) { 341 addConverters(obj); 342 } 343 } else if (provider instanceof Converter) { 344 subConverters.add((Converter) provider); 345 } else if (provider instanceof ConverterPart) { 346 parts.add((ConverterPart) provider); 347 } else { // Introspection 348 Class pClass = provider.getClass(); 349 Method[] methods = pClass.getMethods(); 350 for (int i=0; i<methods.length; ++i) { 351 final Method method = methods[i]; 352 if (CONVERT.equals(method.getName()) && !void.class.equals(method.getReturnType())) { 353 Class[] pTypes = method.getParameterTypes(); 354 if (pTypes.length == 1) { 355 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 356 357 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) { 358 try { 359 return method.invoke(provider, new Object[] {source}); 360 } catch (IllegalAccessException e) { 361 throw new ConversionException(e); 362 } catch (InvocationTargetException e) { 363 throw new ConversionException(e); 364 } 365 } 366 367 }); 368 } else if (pTypes.length == 2 && Converter.class.equals(pTypes[1])) { 369 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 370 371 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) { 372 try { 373 return method.invoke(provider, new Object[] {source, master}); 374 } catch (IllegalAccessException e) { 375 throw new ConversionException(e); 376 } catch (InvocationTargetException e) { 377 throw new ConversionException(e); 378 } 379 } 380 381 }); 382 383 } else if (pTypes.length == 2 && Context.class.equals(pTypes[1])) { 384 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 385 386 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) { 387 try { 388 return method.invoke(provider, new Object[] {source, context}); 389 } catch (IllegalAccessException e) { 390 throw new ConversionException(e); 391 } catch (InvocationTargetException e) { 392 throw new ConversionException(e); 393 } 394 } 395 396 }); 397 398 } else if (pTypes.length == 3 && Converter.class.equals(pTypes[1]) && Context.class.equals(pTypes[2])) { 399 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) { 400 401 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) { 402 try { 403 return method.invoke(provider, new Object[] {source, master, context}); 404 } catch (IllegalAccessException e) { 405 throw new ConversionException(e); 406 } catch (InvocationTargetException e) { 407 throw new ConversionException(e); 408 } 409 } 410 411 }); 412 413 } 414 415 } 416 } 417 } 418 } 419 420 } 421 422 /** 423 * Map of resolved converters: ClassLoader -> ClassLoaderConvertersEntry. 424 */ 425 private static Map<ClassLoader, ClassLoaderConvertersEntry> classLoaderEntries = new HashMap<ClassLoader, ClassLoaderConvertersEntry>(); 426 427 /** 428 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 429 * If no converter is found, this method performs converter discovery through 430 * constructors if target type is a class and through duck conversion if target 431 * type is an interface. 432 * @param source Source object. 433 * @param targetType Target type. 434 * @param context Conversion context. 435 * @param classLoader Class loader. 436 * @return Source object converted to target type or null if no converter 437 * is available for source/targetType combination 438 * @throws ConversionException 439 */ 440 public static <S, T> T convert(S source, Class<T> targetType, Context context, ClassLoader classLoader) throws ConversionException { 441 return convert(source, targetType, context, false, classLoader); 442 } 443 444 /** 445 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 446 * If no converter is found, this method performs converter discovery through 447 * constructors if target type is a class and through duck conversion if target 448 * type is an interface. 449 * @param source Source object. 450 * @param targetType Target type. 451 * @param chain If true, the converter will attempt to build a chain of 452 * conversions from source type to target type. 453 * For example, StringBuffer doesn't have a constructor from Integer. If chain 454 * is <code>false</code> then this method will return <code>null</code>. If 455 * <code>chain</code> is true, then the converter will convert Integer to String 456 * and then will create StringBuffer from it. If multiple conversion chains 457 * are available, the shortest is chosen. 458 * @return Source object converted to target type or null if no converter 459 * is available for source/targetType combination 460 * @throws ConversionException 461 */ 462 @SuppressWarnings("unchecked") 463 private static <S, T> T convert(S source, Class<T> targetType, Context context, boolean chain, ClassLoader classLoader) throws ConversionException { 464 if (source==null) { 465 return null; 466 } 467 468 if (targetType.isInstance(source)) { 469 return (T) source; 470 } 471 472 if (source instanceof Adaptable) { 473 T ret = ((Adaptable) source).getAdapter(targetType, context); 474 if (ret!=null) { 475 return ret; 476 } 477 } 478 479 ConverterClosure<S, T> converter = (ConverterClosure<S, T>) getConverter(source.getClass(), targetType, context, chain, classLoader); 480 481 if (converter!=null) { 482 return converter.convert(source); 483 } 484 485 ConverterClosure<S, Adaptable> adaptableConverter = (ConverterClosure<S, Adaptable>) getConverter(source.getClass(), Adaptable.class, context, chain, classLoader); 486 if (adaptableConverter==null) { 487 return null; 488 } 489 490 Adaptable adaptable = adaptableConverter.convert(source); 491 return adaptable==null ? null : adaptable.getAdapter(targetType, context); 492 } 493 494 /** 495 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 496 * If no converter is found, this method performs converter discovery through 497 * constructors if target type is a class and through duck conversion if target 498 * type is an interface. 499 * @param sourceType Source type. 500 * @param targetType Target type. 501 * @return Converter from source type to target type, or null if such converter 502 * is not available. 503 * @throws ConversionException 504 */ 505 public static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType) { 506 return getConverter(sourceType, targetType, null, false, null); 507 } 508 509 /** 510 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 511 * If no converter is found, this method performs converter discovery through 512 * constructors if target type is a class and through duck conversion if target 513 * type is an interface. 514 * @param sourceType Source type. 515 * @param targetType Target type. 516 * @param classLoader Class loader to retrieve converters from. 517 * @return Converter from source type to target type, or null if such converter 518 * is not available. 519 * @throws ConversionException 520 */ 521 public static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType, ClassLoader classLoader) { 522 return getConverter(sourceType, targetType, null, false, classLoader); 523 } 524 525 /** 526 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 527 * If no converter is found, this method performs converter discovery through 528 * constructors if target type is a class and through duck conversion if target 529 * type is an interface. 530 * @param sourceType Source type. 531 * @param targetType Target type. 532 * @param context Conversion context. 533 * @return Converter from source type to target type, or null if such converter 534 * is not available. 535 * @throws ConversionException 536 */ 537 public static <S, T> ConverterClosure<S,T> getConverter(Class<S> sourceType, Class<T> targetType, Context context) { 538 return getConverter(sourceType, targetType, context, false, null); 539 } 540 541 /** 542 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services 543 * If no converter is found, this method performs converter discovery through 544 * constructors if target type is a class and through duck conversion if target 545 * type is an interface. 546 * @param sourceType Source type. 547 * @param targetType Target type. 548 * @param context Conversion context. 549 * @param classLoader Class loader to retrieve converters from. 550 * @return Converter from source type to target type, or null if such converter 551 * is not available. 552 * @throws ConversionException 553 */ 554 public static <S, T> ConverterClosure<S,T> getConverter(Class<S> sourceType, Class<T> targetType, Context context, ClassLoader classLoader) { 555 return getConverter(sourceType, targetType, context, false, classLoader); 556 } 557 558 private static ClassLoader promote(ClassLoader present, ClassLoader candidate) { 559 ClassLoader ret = DuckConverterFactory.getChildClassLoader(present, candidate); 560 return ret==null ? present : ret; 561 } 562 563 /** 564 * 565 * @param sourceType 566 * @param targetType 567 * @param context 568 * @param chain If true, the converter will attempt to build a chain of 569 * conversions from source type to target type. 570 * For example, StringBuffer doesn't have a constructor from Integer. If chain 571 * is <code>false</code> then this method will return <code>null</code>. If 572 * <code>chain</code> is true, then the converter will convert Integer to String 573 * and then will create StringBuffer from it. If multiple conversion chains 574 * are available, the shortest is chosen. 575 * @param classLoader 576 * @return 577 */ 578 @SuppressWarnings("unchecked") 579 private static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType, final Context context, boolean chain, ClassLoader pClassLoader) { 580 if (pClassLoader==null) { 581 pClassLoader = ConvertingService.class.getClassLoader(); 582 } 583 ClassLoader classLoader = promote(pClassLoader, promote(targetType.getClassLoader(), sourceType.getClassLoader())); 584 585 ClassLoaderConvertersEntry clce; 586 synchronized (classLoaderEntries) { 587 clce = classLoaderEntries.get(classLoader); 588 if (clce == null) { 589 clce = new ClassLoaderConvertersEntry(classLoader); 590 for (Object provider: ServiceLoader.load(AtomicConvertersBundle.class, classLoader)) { 591 clce.addConverters(provider); 592 } 593 for (Object provider: ServiceLoader.load(AtomicConverter.class, classLoader)) { 594 clce.addConverters(provider); 595 } 596 for (Object provider: ServiceLoader.load(Converter.class, classLoader)) { 597 clce.addConverters(provider); 598 } 599 600 for (Object provider: ServiceLoader.load(ConverterPart.class, classLoader)) { 601 clce.addConverters(provider); 602 } 603 // TODO - Converter factories which may create converter (closure) or return null depending on context. 604 classLoaderEntries.put(classLoader, clce); 605 } 606 clce.addAnnotationConverters(sourceType); 607 } 608 final ConvertersBucket bucket = chain ? clce.findChainingConverter(sourceType, targetType) : clce.findConverter(sourceType, targetType); 609 return bucket==null ? null : new ConverterClosure() { 610 611 public Object convert(Object source) { 612 return bucket.convert(source, context); 613 } 614 615 }; 616 } 617}