All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.servicerocket.confluence.randombits.support.core.impl.DefaultConversionAssistant Maven / Gradle / Ivy

There is a newer version: 2.5.12
Show newest version
package com.servicerocket.confluence.randombits.support.core.impl;

import com.servicerocket.confluence.randombits.support.core.convert.*;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * The default implementation of {@link ConversionAssistant}.
 */
public class DefaultConversionAssistant implements ConversionAssistant {

    /**
     * This class acts as a key to identify conversions from one specific type to another.
     */
    private static class Conversion {

        private final Class source;

        private final Class target;

        public Conversion( Class source, Class target ) {
            this.source = source;
            this.target = target;
        }

        @Override
        public int hashCode() {
            return source.hashCode() + target.hashCode() + 7;
        }

        @Override
        public boolean equals( Object o ) {
            if ( o instanceof Conversion ) {
                Conversion c = (Conversion) o;
                return c.source == source && c.target == target;
            }
            return false;
        }

        @Override
        public String toString() {
            return "{" + source.getName() + " > " + target.getName() + "}";
        }
    }

    /**
     * Compares two converters by cost. Lower costs will be listed first.
     */
    private final Comparator COST_COMPARATOR = new Comparator() {
        @Override
        public int compare( Converter converter1, Converter converter2 ) {
            return converter1.getCost().compareTo( converter2.getCost() );
        }
    };

    /**
     * Always returns null. Used to cache a result for source/target combinations
     * that have already been searched but no result was found.
     */
    private final static Converter NULL_CONVERTER = new Converter() {
        @Override
        public Class getSourceType() {
            return null;
        }

        @Override
        public Class getTargetType() {
            return null;
        }

        @Override
        public ConversionCost getCost() {
            return ConversionCost.FAIL;
        }

        @Override
        public boolean canConvert( Class sourceType, Class targetType ) {
            return false;
        }

        @Override
        public boolean canConvert( Object source, Class targetType ) {
            return false;
        }

        @Override
        public  T convert( Object source, Class targetType ) throws ConversionException {
            return null;
        }
    };

    // Contains the cache of converters that by their source type.
    private final Map, Set> converterList;

    // Contains the cache of conversions that have already been calculated.
    private Map conversionCache;

    @Autowired
    public void setConverters(List converters) {
        for(Converter converter : converters) {
            addConverter(converter);
        }
    }

    public DefaultConversionAssistant() {
        conversionCache = new HashMap();
        converterList = Collections.synchronizedMap( new HashMap, Set>());
    }

    /**
     * Removes the specified converter and resets the caches.
     *
     * @param converter The converter to remove.
     */
    protected void removeConverter( Converter converter ) {
        synchronized (converterList) {
            Set converterSet = converterList.get( converter.getSourceType() );
            if ( converterSet != null ) {
                converterSet.remove( converter );
                if ( converterSet.isEmpty() )
                    converterList.remove( converter.getSourceType() );
            }
        }
        resetConversions();
    }

    /**
     * Adds the specified converter and resets the caches.
     *
     * @param converter The converter to add.
     */
    public void addConverter( Converter converter ) {
        synchronized (converterList) {
            Set converterSet = converterList.get( converter.getSourceType() );
            if ( converterSet == null ) {
                converterSet = new HashSet();
                converterList.put(converter.getSourceType(), converterSet);
            }
            converterSet.add( converter );
        }
        resetConversions();
    }

    /**
     * Resets the conversion cache.
     */
    private synchronized void resetConversions() {
        conversionCache = new HashMap( 30 );
    }

    /**
     * Checks if the source can be converted to the target type. Note that this
     * is considered to be a 'can possibly convert' - it is possible that
     * this method will return true, but the actual conversion will not
     * occur due to some other issue, and null is returned from the {@link #convert(Object, Class)}
     * method. However, in general, they will match up.
     *
     * @param source     The source object.
     * @param targetType The target type.
     * @return true if there is a converter that handles that conversion.
     */
    @Override
    public boolean canConvert( Object source, Class targetType ) {
        if ( source == null )
            return false;

        if ( targetType.isInstance( source ) )
            return true;

        Converter converter = findConverter( source, targetType );
        return converter.canConvert( source, targetType );
    }

    @Override
    public  T convert( Object source, Class targetType ) throws ConversionException {
        // Check for null
        if ( source == null )
            return null;

        // Check if it's already the target type
        if ( targetType.isInstance( source ) )
            return targetType.cast( source );

        // Check for a converter.
        Converter converter = findConverter( source, targetType );

        return converter.convert( source, targetType );
    }

    @Override
    public void registerConverter(Converter converter) {
        addConverter(converter);
    }

    @Override
    public void unregisterConverter(Converter converter) {
        removeConverter(converter);
    }

    private Converter findConverter( Object source, Class targetType ) {
        Conversion conversion = new Conversion( source.getClass(), targetType );
        Converter converter = conversionCache.get( conversion );

        if ( converter == null ) {
            // Try finding a registered converter
            Set> visited = new HashSet>( 20 );
            converter = scanConverter( conversion, visited );
        }
        return converter;
    }

    private Converter scanConverter( Class sourceType, Class targetType, Set> visited ) {
        return scanConverter( new Conversion( sourceType, targetType ), visited );
    }

    private Converter scanConverter( Conversion conversion, Set> visited ) {
        // First, make sure we haven't already cached one.
        Converter bestConverter = conversionCache.get( conversion );

        if ( bestConverter == null ) {
            // Check we haven't already visited this type during this scan. If so, stop looking.
            if ( !visited.add( conversion.source ) )
                return null;

            List foundConverters = new ArrayList( 10 );

            // Search through the other converters for the source type
            Set sourceConverters = converterList.get( conversion.source );
            if ( sourceConverters != null ) {
                for ( Converter sourceConverter : sourceConverters ) {
                    // No point searching further if the new converter is already more expensive than the current one.
                    if ( sourceConverter.canConvert( conversion.source, conversion.target ) ) {
                        // We have a match!
                        foundConverters.add( sourceConverter );
                    } else {
                        // We have to look further
                        Converter targetConverter = scanConverter( sourceConverter.getTargetType(), conversion.target, visited );
                        if ( targetConverter != null && targetConverter.canConvert( sourceConverter.getTargetType(), conversion.target ) ) {
                            foundConverters.add( new ChainedConverter( sourceConverter, targetConverter ) );
                        }
                    }
                }
            }

            // Then, look through the source's interfaces
            Class[] interfaces = conversion.source.getInterfaces();
            for ( Class type : interfaces ) {
                Converter interfaceConverter = scanConverter( type, conversion.target, visited );
                if ( interfaceConverter != null && interfaceConverter.canConvert( type, conversion.target ) )
                    foundConverters.add( interfaceConverter );
            }

            // Next, try superclasses
            if ( conversion.source.getSuperclass() != null ) {
                Converter superclassConverter = scanConverter( conversion.source.getSuperclass(), conversion.target, visited );
                if ( superclassConverter != null && superclassConverter.canConvert( conversion.source.getSuperclass(), conversion.target ) )
                    foundConverters.add( superclassConverter );
            }

            // Figure out what our converter should be for this conversion.
            switch ( foundConverters.size() ) {
                case 0:
                    // If all else has failed, cache a null converter.
                    bestConverter = NULL_CONVERTER;
                    break;
                case 1:
                    // If there's just one, use it directly
                    bestConverter = foundConverters.get( 0 );
                    break;
                default:
                    // Otherwise, sort the list by cost and provide them as alternatives.
                    Collections.sort( foundConverters, COST_COMPARATOR );
                    bestConverter = new AlternativeConverter( conversion.source, conversion.target, foundConverters );
            }

            // Cache the result.
            conversionCache.put( conversion, bestConverter );
        }

        return bestConverter;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy