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}