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