001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.impl.converter; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ConcurrentMap; 028import java.util.concurrent.CopyOnWriteArrayList; 029import java.util.concurrent.ExecutionException; 030import java.util.concurrent.atomic.AtomicLong; 031 032import org.apache.camel.CamelExecutionException; 033import org.apache.camel.Exchange; 034import org.apache.camel.LoggingLevel; 035import org.apache.camel.NoFactoryAvailableException; 036import org.apache.camel.NoTypeConversionAvailableException; 037import org.apache.camel.TypeConversionException; 038import org.apache.camel.TypeConverter; 039import org.apache.camel.TypeConverterExists; 040import org.apache.camel.TypeConverterExistsException; 041import org.apache.camel.TypeConverterLoaderException; 042import org.apache.camel.TypeConverters; 043import org.apache.camel.spi.FactoryFinder; 044import org.apache.camel.spi.Injector; 045import org.apache.camel.spi.PackageScanClassResolver; 046import org.apache.camel.spi.TypeConverterAware; 047import org.apache.camel.spi.TypeConverterLoader; 048import org.apache.camel.spi.TypeConverterRegistry; 049import org.apache.camel.support.ServiceSupport; 050import org.apache.camel.util.CamelLogger; 051import org.apache.camel.util.LRUSoftCache; 052import org.apache.camel.util.MessageHelper; 053import org.apache.camel.util.ObjectHelper; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057/** 058 * Base implementation of a type converter registry used for 059 * <a href="http://camel.apache.org/type-converter.html">type converters</a> in Camel. 060 * 061 * @version 062 */ 063public abstract class BaseTypeConverterRegistry extends ServiceSupport implements TypeConverter, TypeConverterRegistry { 064 protected final Logger log = LoggerFactory.getLogger(getClass()); 065 protected final ConcurrentMap<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<TypeMapping, TypeConverter>(); 066 // for misses use a soft reference cache map, as the classes may be un-deployed at runtime 067 protected final LRUSoftCache<TypeMapping, TypeMapping> misses = new LRUSoftCache<TypeMapping, TypeMapping>(1000); 068 protected final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>(); 069 protected final List<FallbackTypeConverter> fallbackConverters = new CopyOnWriteArrayList<FallbackTypeConverter>(); 070 protected final PackageScanClassResolver resolver; 071 protected Injector injector; 072 protected final FactoryFinder factoryFinder; 073 protected TypeConverterExists typeConverterExists = TypeConverterExists.Override; 074 protected LoggingLevel typeConverterExistsLoggingLevel = LoggingLevel.WARN; 075 protected final Statistics statistics = new UtilizationStatistics(); 076 protected final AtomicLong noopCounter = new AtomicLong(); 077 protected final AtomicLong attemptCounter = new AtomicLong(); 078 protected final AtomicLong missCounter = new AtomicLong(); 079 protected final AtomicLong hitCounter = new AtomicLong(); 080 protected final AtomicLong failedCounter = new AtomicLong(); 081 082 public BaseTypeConverterRegistry(PackageScanClassResolver resolver, Injector injector, FactoryFinder factoryFinder) { 083 this.resolver = resolver; 084 this.injector = injector; 085 this.factoryFinder = factoryFinder; 086 this.typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver)); 087 088 // add to string first as it will then be last in the last as to string can nearly 089 // always convert something to a string so we want it only as the last resort 090 // ToStringTypeConverter should NOT allow to be promoted 091 addFallbackTypeConverter(new ToStringTypeConverter(), false); 092 // enum is okay to be promoted 093 addFallbackTypeConverter(new EnumTypeConverter(), true); 094 // arrays is okay to be promoted 095 addFallbackTypeConverter(new ArrayTypeConverter(), true); 096 // and future should also not allowed to be promoted 097 addFallbackTypeConverter(new FutureTypeConverter(this), false); 098 // add sync processor to async processor converter is to be promoted 099 addFallbackTypeConverter(new AsyncProcessorTypeConverter(), true); 100 } 101 102 public List<TypeConverterLoader> getTypeConverterLoaders() { 103 return typeConverterLoaders; 104 } 105 106 @Override 107 public <T> T convertTo(Class<T> type, Object value) { 108 return convertTo(type, null, value); 109 } 110 111 @SuppressWarnings("unchecked") 112 @Override 113 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { 114 if (!isRunAllowed()) { 115 throw new IllegalStateException(this + " is not started"); 116 } 117 118 Object answer; 119 try { 120 answer = doConvertTo(type, exchange, value, false); 121 } catch (Exception e) { 122 if (statistics.isStatisticsEnabled()) { 123 failedCounter.incrementAndGet(); 124 } 125 // if its a ExecutionException then we have rethrow it as its not due to failed conversion 126 // this is special for FutureTypeConverter 127 boolean execution = ObjectHelper.getException(ExecutionException.class, e) != null 128 || ObjectHelper.getException(CamelExecutionException.class, e) != null; 129 if (execution) { 130 throw ObjectHelper.wrapCamelExecutionException(exchange, e); 131 } 132 133 // error occurred during type conversion 134 if (e instanceof TypeConversionException) { 135 throw (TypeConversionException) e; 136 } else { 137 throw createTypeConversionException(exchange, type, value, e); 138 } 139 } 140 if (answer == Void.TYPE) { 141 if (statistics.isStatisticsEnabled()) { 142 missCounter.incrementAndGet(); 143 } 144 // Could not find suitable conversion 145 return null; 146 } else { 147 if (statistics.isStatisticsEnabled()) { 148 hitCounter.incrementAndGet(); 149 } 150 return (T) answer; 151 } 152 } 153 154 @Override 155 public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException { 156 return mandatoryConvertTo(type, null, value); 157 } 158 159 @SuppressWarnings("unchecked") 160 @Override 161 public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException { 162 if (!isRunAllowed()) { 163 throw new IllegalStateException(this + " is not started"); 164 } 165 166 Object answer; 167 try { 168 answer = doConvertTo(type, exchange, value, false); 169 } catch (Exception e) { 170 if (statistics.isStatisticsEnabled()) { 171 failedCounter.incrementAndGet(); 172 } 173 // error occurred during type conversion 174 if (e instanceof TypeConversionException) { 175 throw (TypeConversionException) e; 176 } else { 177 throw createTypeConversionException(exchange, type, value, e); 178 } 179 } 180 if (answer == Void.TYPE || value == null) { 181 if (statistics.isStatisticsEnabled()) { 182 missCounter.incrementAndGet(); 183 } 184 // Could not find suitable conversion 185 throw new NoTypeConversionAvailableException(value, type); 186 } else { 187 if (statistics.isStatisticsEnabled()) { 188 hitCounter.incrementAndGet(); 189 } 190 return (T) answer; 191 } 192 } 193 194 @Override 195 public <T> T tryConvertTo(Class<T> type, Object value) { 196 return tryConvertTo(type, null, value); 197 } 198 199 @SuppressWarnings("unchecked") 200 @Override 201 public <T> T tryConvertTo(Class<T> type, Exchange exchange, Object value) { 202 if (!isRunAllowed()) { 203 return null; 204 } 205 206 Object answer; 207 try { 208 answer = doConvertTo(type, exchange, value, true); 209 } catch (Exception e) { 210 if (statistics.isStatisticsEnabled()) { 211 failedCounter.incrementAndGet(); 212 } 213 return null; 214 } 215 if (answer == Void.TYPE) { 216 // Could not find suitable conversion 217 if (statistics.isStatisticsEnabled()) { 218 missCounter.incrementAndGet(); 219 } 220 return null; 221 } else { 222 if (statistics.isStatisticsEnabled()) { 223 hitCounter.incrementAndGet(); 224 } 225 return (T) answer; 226 } 227 } 228 229 protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean tryConvert) { 230 if (log.isTraceEnabled()) { 231 log.trace("Converting {} -> {} with value: {}", 232 new Object[]{value == null ? "null" : value.getClass().getCanonicalName(), 233 type.getCanonicalName(), value}); 234 } 235 236 if (value == null) { 237 // no type conversion was needed 238 if (statistics.isStatisticsEnabled()) { 239 noopCounter.incrementAndGet(); 240 } 241 // lets avoid NullPointerException when converting to boolean for null values 242 if (boolean.class.isAssignableFrom(type)) { 243 return Boolean.FALSE; 244 } 245 return null; 246 } 247 248 // same instance type 249 if (type.isInstance(value)) { 250 // no type conversion was needed 251 if (statistics.isStatisticsEnabled()) { 252 noopCounter.incrementAndGet(); 253 } 254 return type.cast(value); 255 } 256 257 // special for NaN numbers, which we can only convert for floating numbers 258 if (ObjectHelper.isNaN(value)) { 259 // no type conversion was needed 260 if (statistics.isStatisticsEnabled()) { 261 noopCounter.incrementAndGet(); 262 } 263 if (Float.class.isAssignableFrom(type)) { 264 return Float.NaN; 265 } else if (Double.class.isAssignableFrom(type)) { 266 return Double.NaN; 267 } else { 268 // we cannot convert the NaN 269 return Void.TYPE; 270 } 271 } 272 273 // okay we need to attempt to convert 274 if (statistics.isStatisticsEnabled()) { 275 attemptCounter.incrementAndGet(); 276 } 277 278 // check if we have tried it before and if its a miss 279 TypeMapping key = new TypeMapping(type, value.getClass()); 280 if (misses.containsKey(key)) { 281 // we have tried before but we cannot convert this one 282 return Void.TYPE; 283 } 284 285 // try to find a suitable type converter 286 TypeConverter converter = getOrFindTypeConverter(key); 287 if (converter != null) { 288 log.trace("Using converter: {} to convert {}", converter, key); 289 Object rc; 290 if (tryConvert) { 291 rc = converter.tryConvertTo(type, exchange, value); 292 } else { 293 rc = converter.convertTo(type, exchange, value); 294 } 295 if (rc == null && converter.allowNull()) { 296 return null; 297 } else if (rc != null) { 298 return rc; 299 } 300 } 301 302 // not found with that type then if it was a primitive type then try again with the wrapper type 303 if (type.isPrimitive()) { 304 Class<?> primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); 305 if (primitiveType != type) { 306 Class<?> fromType = value.getClass(); 307 TypeConverter tc = getOrFindTypeConverter(new TypeMapping(primitiveType, fromType)); 308 if (tc != null) { 309 // add the type as a known type converter as we can convert from primitive to object converter 310 addTypeConverter(type, fromType, tc); 311 Object rc; 312 if (tryConvert) { 313 rc = tc.tryConvertTo(primitiveType, exchange, value); 314 } else { 315 rc = tc.convertTo(primitiveType, exchange, value); 316 } 317 if (rc == null && tc.allowNull()) { 318 return null; 319 } else if (rc != null) { 320 return rc; 321 } 322 } 323 } 324 } 325 326 // fallback converters 327 for (FallbackTypeConverter fallback : fallbackConverters) { 328 TypeConverter tc = fallback.getFallbackTypeConverter(); 329 Object rc; 330 if (tryConvert) { 331 rc = tc.tryConvertTo(type, exchange, value); 332 } else { 333 rc = tc.convertTo(type, exchange, value); 334 } 335 if (rc == null && tc.allowNull()) { 336 return null; 337 } 338 339 if (Void.TYPE.equals(rc)) { 340 // it cannot be converted so give up 341 return Void.TYPE; 342 } 343 344 if (rc != null) { 345 // if fallback can promote then let it be promoted to a first class type converter 346 if (fallback.isCanPromote()) { 347 // add it as a known type converter since we found a fallback that could do it 348 if (log.isDebugEnabled()) { 349 log.debug("Promoting fallback type converter as a known type converter to convert from: {} to: {} for the fallback converter: {}", 350 new Object[]{type.getCanonicalName(), value.getClass().getCanonicalName(), fallback.getFallbackTypeConverter()}); 351 } 352 addTypeConverter(type, value.getClass(), fallback.getFallbackTypeConverter()); 353 } 354 355 if (log.isTraceEnabled()) { 356 log.trace("Fallback type converter {} converted type from: {} to: {}", 357 new Object[]{fallback.getFallbackTypeConverter(), 358 type.getCanonicalName(), value.getClass().getCanonicalName()}); 359 } 360 361 // return converted value 362 return rc; 363 } 364 } 365 366 if (!tryConvert) { 367 // Could not find suitable conversion, so remember it 368 // do not register misses for try conversions 369 misses.put(key, key); 370 } 371 372 // Could not find suitable conversion, so return Void to indicate not found 373 return Void.TYPE; 374 } 375 376 @Override 377 public void addTypeConverter(Class<?> toType, Class<?> fromType, TypeConverter typeConverter) { 378 log.trace("Adding type converter: {}", typeConverter); 379 TypeMapping key = new TypeMapping(toType, fromType); 380 TypeConverter converter = typeMappings.get(key); 381 // only override it if its different 382 // as race conditions can lead to many threads trying to promote the same fallback converter 383 384 if (typeConverter != converter) { 385 386 // add the converter unless we should ignore 387 boolean add = true; 388 389 // if converter is not null then a duplicate exists 390 if (converter != null) { 391 if (typeConverterExists == TypeConverterExists.Override) { 392 CamelLogger logger = new CamelLogger(log, typeConverterExistsLoggingLevel); 393 logger.log("Overriding type converter from: " + converter + " to: " + typeConverter); 394 } else if (typeConverterExists == TypeConverterExists.Ignore) { 395 CamelLogger logger = new CamelLogger(log, typeConverterExistsLoggingLevel); 396 logger.log("Ignoring duplicate type converter from: " + converter + " to: " + typeConverter); 397 add = false; 398 } else { 399 // we should fail 400 throw new TypeConverterExistsException(toType, fromType); 401 } 402 } 403 404 if (add) { 405 typeMappings.put(key, typeConverter); 406 // remove any previous misses, as we added the new type converter 407 misses.remove(key); 408 } 409 } 410 } 411 412 @Override 413 public void addTypeConverters(TypeConverters typeConverters) { 414 log.trace("Adding type converters: {}", typeConverters); 415 try { 416 // scan the class for @Converter and load them into this registry 417 TypeConvertersLoader loader = new TypeConvertersLoader(typeConverters); 418 loader.load(this); 419 } catch (TypeConverterLoaderException e) { 420 throw ObjectHelper.wrapRuntimeCamelException(e); 421 } 422 } 423 424 @Override 425 public boolean removeTypeConverter(Class<?> toType, Class<?> fromType) { 426 log.trace("Removing type converter from: {} to: {}", fromType, toType); 427 TypeMapping key = new TypeMapping(toType, fromType); 428 TypeConverter converter = typeMappings.remove(key); 429 if (converter != null) { 430 typeMappings.remove(key); 431 misses.remove(key); 432 } 433 return converter != null; 434 } 435 436 @Override 437 public void addFallbackTypeConverter(TypeConverter typeConverter, boolean canPromote) { 438 log.trace("Adding fallback type converter: {} which can promote: {}", typeConverter, canPromote); 439 440 // add in top of fallback as the toString() fallback will nearly always be able to convert 441 // the last one which is add to the FallbackTypeConverter will be called at the first place 442 fallbackConverters.add(0, new FallbackTypeConverter(typeConverter, canPromote)); 443 if (typeConverter instanceof TypeConverterAware) { 444 TypeConverterAware typeConverterAware = (TypeConverterAware) typeConverter; 445 typeConverterAware.setTypeConverter(this); 446 } 447 } 448 449 public TypeConverter getTypeConverter(Class<?> toType, Class<?> fromType) { 450 TypeMapping key = new TypeMapping(toType, fromType); 451 return typeMappings.get(key); 452 } 453 454 @Override 455 public Injector getInjector() { 456 return injector; 457 } 458 459 @Override 460 public void setInjector(Injector injector) { 461 this.injector = injector; 462 } 463 464 public Set<Class<?>> getFromClassMappings() { 465 Set<Class<?>> answer = new HashSet<Class<?>>(); 466 for (TypeMapping mapping : typeMappings.keySet()) { 467 answer.add(mapping.getFromType()); 468 } 469 return answer; 470 } 471 472 public Map<Class<?>, TypeConverter> getToClassMappings(Class<?> fromClass) { 473 Map<Class<?>, TypeConverter> answer = new HashMap<Class<?>, TypeConverter>(); 474 for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) { 475 TypeMapping mapping = entry.getKey(); 476 if (mapping.isApplicable(fromClass)) { 477 answer.put(mapping.getToType(), entry.getValue()); 478 } 479 } 480 return answer; 481 } 482 483 public Map<TypeMapping, TypeConverter> getTypeMappings() { 484 return typeMappings; 485 } 486 487 protected <T> TypeConverter getOrFindTypeConverter(TypeMapping key) { 488 TypeConverter converter = typeMappings.get(key); 489 if (converter == null) { 490 // converter not found, try to lookup then 491 converter = lookup(key.getToType(), key.getFromType()); 492 if (converter != null) { 493 typeMappings.putIfAbsent(key, converter); 494 } 495 } 496 return converter; 497 } 498 499 @Override 500 public TypeConverter lookup(Class<?> toType, Class<?> fromType) { 501 return doLookup(toType, fromType, false); 502 } 503 504 protected TypeConverter doLookup(Class<?> toType, Class<?> fromType, boolean isSuper) { 505 506 if (fromType != null) { 507 // lets try if there is a direct match 508 TypeConverter converter = getTypeConverter(toType, fromType); 509 if (converter != null) { 510 return converter; 511 } 512 513 // try the interfaces 514 for (Class<?> type : fromType.getInterfaces()) { 515 converter = getTypeConverter(toType, type); 516 if (converter != null) { 517 return converter; 518 } 519 } 520 521 // try super then 522 Class<?> fromSuperClass = fromType.getSuperclass(); 523 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) { 524 converter = doLookup(toType, fromSuperClass, true); 525 if (converter != null) { 526 return converter; 527 } 528 } 529 } 530 531 // only do these tests as fallback and only on the target type (eg not on its super) 532 if (!isSuper) { 533 if (fromType != null && !fromType.equals(Object.class)) { 534 535 // lets try classes derived from this toType 536 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); 537 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { 538 TypeMapping key = entry.getKey(); 539 Class<?> aToType = key.getToType(); 540 if (toType.isAssignableFrom(aToType)) { 541 Class<?> aFromType = key.getFromType(); 542 // skip Object based we do them last 543 if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) { 544 return entry.getValue(); 545 } 546 } 547 } 548 549 // lets test for Object based converters as last resort 550 TypeConverter converter = getTypeConverter(toType, Object.class); 551 if (converter != null) { 552 return converter; 553 } 554 } 555 } 556 557 // none found 558 return null; 559 } 560 561 public List<Class<?>[]> listAllTypeConvertersFromTo() { 562 List<Class<?>[]> answer = new ArrayList<Class<?>[]>(typeMappings.size()); 563 for (TypeMapping mapping : typeMappings.keySet()) { 564 answer.add(new Class<?>[]{mapping.getFromType(), mapping.getToType()}); 565 } 566 return answer; 567 } 568 569 /** 570 * Loads the core type converters which is mandatory to use Camel 571 */ 572 public void loadCoreTypeConverters() throws Exception { 573 // load all the type converters from camel-core 574 CoreTypeConverterLoader core = new CoreTypeConverterLoader(); 575 core.load(this); 576 } 577 578 /** 579 * Checks if the registry is loaded and if not lazily load it 580 */ 581 protected void loadTypeConverters() throws Exception { 582 for (TypeConverterLoader typeConverterLoader : getTypeConverterLoaders()) { 583 typeConverterLoader.load(this); 584 } 585 586 // lets try load any other fallback converters 587 try { 588 loadFallbackTypeConverters(); 589 } catch (NoFactoryAvailableException e) { 590 // ignore its fine to have none 591 } 592 } 593 594 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException { 595 List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class); 596 for (TypeConverter converter : converters) { 597 addFallbackTypeConverter(converter, false); 598 } 599 } 600 601 protected TypeConversionException createTypeConversionException(Exchange exchange, Class<?> type, Object value, Throwable cause) { 602 Object body; 603 // extract the body for logging which allows to limit the message body in the exception/stacktrace 604 // and also can be used to turn off logging sensitive message data 605 if (exchange != null) { 606 body = MessageHelper.extractValueForLogging(value, exchange.getIn()); 607 } else { 608 body = value; 609 } 610 return new TypeConversionException(body, type, cause); 611 } 612 613 @Override 614 public Statistics getStatistics() { 615 return statistics; 616 } 617 618 @Override 619 public int size() { 620 return typeMappings.size(); 621 } 622 623 public LoggingLevel getTypeConverterExistsLoggingLevel() { 624 return typeConverterExistsLoggingLevel; 625 } 626 627 public void setTypeConverterExistsLoggingLevel(LoggingLevel typeConverterExistsLoggingLevel) { 628 this.typeConverterExistsLoggingLevel = typeConverterExistsLoggingLevel; 629 } 630 631 public TypeConverterExists getTypeConverterExists() { 632 return typeConverterExists; 633 } 634 635 public void setTypeConverterExists(TypeConverterExists typeConverterExists) { 636 this.typeConverterExists = typeConverterExists; 637 } 638 639 @Override 640 protected void doStart() throws Exception { 641 // noop 642 } 643 644 @Override 645 protected void doStop() throws Exception { 646 // log utilization statistics when stopping, including mappings 647 if (statistics.isStatisticsEnabled()) { 648 String info = statistics.toString(); 649 info += String.format(" mappings[total=%s, misses=%s]", typeMappings.size(), misses.size()); 650 log.info(info); 651 } 652 653 typeMappings.clear(); 654 misses.clear(); 655 statistics.reset(); 656 } 657 658 /** 659 * Represents utilization statistics 660 */ 661 private final class UtilizationStatistics implements Statistics { 662 663 private boolean statisticsEnabled; 664 665 @Override 666 public long getNoopCounter() { 667 return noopCounter.get(); 668 } 669 670 @Override 671 public long getAttemptCounter() { 672 return attemptCounter.get(); 673 } 674 675 @Override 676 public long getHitCounter() { 677 return hitCounter.get(); 678 } 679 680 @Override 681 public long getMissCounter() { 682 return missCounter.get(); 683 } 684 685 @Override 686 public long getFailedCounter() { 687 return failedCounter.get(); 688 } 689 690 @Override 691 public void reset() { 692 noopCounter.set(0); 693 attemptCounter.set(0); 694 hitCounter.set(0); 695 missCounter.set(0); 696 failedCounter.set(0); 697 } 698 699 @Override 700 public boolean isStatisticsEnabled() { 701 return statisticsEnabled; 702 } 703 704 @Override 705 public void setStatisticsEnabled(boolean statisticsEnabled) { 706 this.statisticsEnabled = statisticsEnabled; 707 } 708 709 @Override 710 public String toString() { 711 return String.format("TypeConverterRegistry utilization[noop=%s, attempts=%s, hits=%s, misses=%s, failures=%s]", 712 getNoopCounter(), getAttemptCounter(), getHitCounter(), getMissCounter(), getFailedCounter()); 713 } 714 } 715 716 /** 717 * Represents a mapping from one type (which can be null) to another 718 */ 719 protected static class TypeMapping { 720 private final Class<?> toType; 721 private final Class<?> fromType; 722 723 TypeMapping(Class<?> toType, Class<?> fromType) { 724 this.toType = toType; 725 this.fromType = fromType; 726 } 727 728 public Class<?> getFromType() { 729 return fromType; 730 } 731 732 public Class<?> getToType() { 733 return toType; 734 } 735 736 @Override 737 public boolean equals(Object object) { 738 if (object instanceof TypeMapping) { 739 TypeMapping that = (TypeMapping) object; 740 return ObjectHelper.equal(this.fromType, that.fromType) 741 && ObjectHelper.equal(this.toType, that.toType); 742 } 743 return false; 744 } 745 746 @Override 747 public int hashCode() { 748 int answer = toType.hashCode(); 749 if (fromType != null) { 750 answer *= 37 + fromType.hashCode(); 751 } 752 return answer; 753 } 754 755 @Override 756 public String toString() { 757 return "[" + fromType + "=>" + toType + "]"; 758 } 759 760 public boolean isApplicable(Class<?> fromClass) { 761 return fromType.isAssignableFrom(fromClass); 762 } 763 } 764 765 /** 766 * Represents a fallback type converter 767 */ 768 protected static class FallbackTypeConverter { 769 private final boolean canPromote; 770 private final TypeConverter fallbackTypeConverter; 771 772 FallbackTypeConverter(TypeConverter fallbackTypeConverter, boolean canPromote) { 773 this.canPromote = canPromote; 774 this.fallbackTypeConverter = fallbackTypeConverter; 775 } 776 777 public boolean isCanPromote() { 778 return canPromote; 779 } 780 781 public TypeConverter getFallbackTypeConverter() { 782 return fallbackTypeConverter; 783 } 784 } 785}