/* * #%L * Nazgul Project: nazgul-core-reflection-api * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% * */ package se.jguru.nazgul.core.reflection.api.conversion.registry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jguru.nazgul.core.algorithms.api.Validate; import se.jguru.nazgul.core.algorithms.api.collections.CollectionAlgorithms; import se.jguru.nazgul.core.algorithms.api.collections.predicate.Transformer; import se.jguru.nazgul.core.algorithms.api.collections.predicate.Tuple; import se.jguru.nazgul.core.reflection.api.TypeExtractor; import se.jguru.nazgul.core.reflection.api.conversion.Converter; import se.jguru.nazgul.core.reflection.api.conversion.TypeConverter; import javax.validation.constraints.NotNull; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; /** * Holder class for a set of prioritized TypeConverter instances, all of which convert objects * from a single source class/type. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public class PrioritizedTypeConverter<From> implements Comparable<PrioritizedTypeConverter> { // Our Log private static final Logger log = LoggerFactory.getLogger(PrioritizedTypeConverter.class); // Internal state private Class<From> sourceType; private final Object lock = new Object(); private SortedMap<Integer, Map<Class<?>, TypeConverter<From, ?>>> prioritizedTypeConverterMap; /** * Creates an empty PrioritizedTypeConverter which holds TypeConverter instances with the supplied sourceType. * * @param sourceType The class to convert from. */ public PrioritizedTypeConverter(@NotNull final Class<From> sourceType) { this(sourceType, new Object[0]); } /** * Creates a PrioritizedTypeConverter which holds TypeConverter instances with the supplied sourceType. * The supplied converters are wrapped into TypeConverter instances, and mapped at the start. * * @param sourceType The class to convert from. * @param converters The supplied converters are wrapped into TypeConverter instances, and mapped at the start. */ public PrioritizedTypeConverter(@NotNull final Class<From> sourceType, final Object... converters) { // Check sanity Validate.notNull(sourceType, "sourceType"); // Assign internal state this.prioritizedTypeConverterMap = Collections.synchronizedSortedMap( new TreeMap<Integer, Map<Class<?>, TypeConverter<From, ?>>>()); this.sourceType = sourceType; if (converters != null && converters.length > 0) { add(converters); } } /** * @return The source type of this ReflectiveMultiConverterHolder instance. */ public final Class<From> getSourceType() { return sourceType; } /** * Acquires all available targetTypes known to this PrioritizedTypeConverter instance, indicating the closure of * all Classes to which this PrioritizedTypeConverter can convert objects. * * @return An unordered Set holding all available target type objects. */ public Set<Class<?>> getAvailableTargetTypes() { final Set<Class<?>> toReturn = new HashSet<Class<?>>(); for (Map.Entry<Integer, Map<Class<?>, TypeConverter<From, ?>>> prioTypeConverterMap : prioritizedTypeConverterMap.entrySet()) { // Acquire the current TypeConverter instances. final Map<Class<?>, TypeConverter<From, ?>> from2ConverterMap = prioTypeConverterMap.getValue(); for (Map.Entry<Class<?>, TypeConverter<From, ?>> currentFrom2Converter : from2ConverterMap.entrySet()) { final TypeConverter<From, ?> currentTypeConverter = currentFrom2Converter.getValue(); final Class<?> currentToType = currentTypeConverter.getToType(); if (!toReturn.contains(currentToType)) { toReturn.add(currentToType); } } } // All done. return toReturn; } /** * {@inheritDoc} */ @Override public final int compareTo(final PrioritizedTypeConverter that) { if (that == null) { return Integer.MAX_VALUE; } // Delegate comparing to the type names of the sourceType. return sourceType.getName().compareTo(that.getSourceType().getName()); } /** * Validates and adds any converter instances from the supplied array, given that they * convert {@code sourceType} instances to some other type. * * @param converters A list of potential converters, holding methods or constructors converting * {@code sourceType} instances to some other type. Should any two converter methods (or * constructors) have identical priority, fromType and toType the result is undefined in the * sense that an arbitrary converter will be used. */ public void add(final Object... converters) { // Check sanity Validate.notEmpty(converters, "converters"); final Map<Object, Tuple<List<Method>, List<Constructor<?>>>> validConverterMap = new HashMap<Object, Tuple<List<Method>, List<Constructor<?>>>>(); // Validate that all converters are OK before modifying the internal state // of this PrioritizedTypeConverter instance. for (Object current : converters) { // Find any converter methods in the supplied converter final SortedSet<Method> methods = TypeExtractor.getMethods( current.getClass(), Converters.CONVERSION_METHOD_FILTER); // Find any converter constructors in the supplied converter final SortedSet<Constructor<?>> constructors = TypeExtractor.getConstructors( current.getClass(), Converters.CONVERSION_CONSTRUCTOR_FILTER); if (methods.size() == 0 && constructors.size() == 0) { // No converters methods or constructors found within the supplied converter. Complain. throw new IllegalArgumentException("Found no @Converter-annotated methods within class [" + current.getClass().getName() + "]."); } // Map any discovered Method and Constructor converters which // convert objects from the existing sourceType. final List<Method> validConverterMethods = new ArrayList<Method>(); final List<Constructor<?>> validConverterConstructors = new ArrayList<Constructor<?>>(); for (Method currentMethod : methods) { if (currentMethod.getParameterTypes()[0].equals(sourceType) && !validConverterMap.keySet().contains(current)) { // This is a valid ConverterMethod. validConverterMethods.add(currentMethod); } } for (Constructor<?> currentConstructor : constructors) { if (currentConstructor.getParameterTypes()[0].equals(sourceType) && !validConverterMap.keySet().contains(current)) { // This is a valid ConverterConstructor. validConverterConstructors.add(currentConstructor); } } // Map the converters validConverterMap.put(current, new Tuple<List<Method>, List<Constructor<?>>>( validConverterMethods, validConverterConstructors)); } // Find the Method or Constructor as appropriate for the distilled converters. for (Map.Entry<Object, Tuple<List<Method>, List<Constructor<?>>>> currentEntry : validConverterMap.entrySet()) { // Map all constructor converters. final Tuple<List<Method>, List<Constructor<?>>> typeConverterTuple = currentEntry.getValue(); // Start with the constructors for (Constructor<?> currentConstructor : typeConverterTuple.getValue()) { // Create and map the TypeConverter final ConstructorTypeConverter converter = new ConstructorTypeConverter(currentConstructor); addTypeConverter(converter); } for (Method currentMethod : typeConverterTuple.getKey()) { try { // Create and map the TypeConverter final MethodTypeConverter converter = new MethodTypeConverter(currentEntry.getKey(), currentMethod); addTypeConverter(converter); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("conditionalConverter method not found for converter [" + currentMethod + "]", e); } } } } /** * Retrieves the prioritized list holding all known TypeConverters able to convert between the * sourceType to the targetType of this PrioritizedTypeConverter instance. * * @param targetType The type to which all retrieved TypeConverters should be able to convert to. * @param <To> The target Type desired. * @return a prioritized list holding all known TypeConverters able to convert between the * sourceType to the targetType. */ public <To> List<TypeConverter<From, To>> getTypeConverters(@NotNull final Class<To> targetType) { // Check sanity Validate.notNull(targetType, "targetType"); final List<TypeConverter<From, To>> toReturn = new ArrayList<TypeConverter<From, To>>(); for (Map.Entry<Integer, Map<Class<?>, TypeConverter<From, ?>>> current : prioritizedTypeConverterMap.entrySet()) { // Acquire the TypeConverter to return. final Map<Class<?>, TypeConverter<From, ?>> from2TypeConvMap = current.getValue(); // Exact match? final TypeConverter<From, ?> candidate = from2TypeConvMap.get(targetType); if (candidate != null) { toReturn.add((TypeConverter<From, To>) candidate); } } for (Map.Entry<Integer, Map<Class<?>, TypeConverter<From, ?>>> current : prioritizedTypeConverterMap.entrySet()) { // Acquire the TypeConverter to return. final Map<Class<?>, TypeConverter<From, ?>> from2TypeConvMap = current.getValue(); // Fuzzy matches go after exact matches for (Map.Entry<Class<?>, TypeConverter<From, ?>> currentSourceClass2TypeConverter : from2TypeConvMap.entrySet()) { if (currentSourceClass2TypeConverter.getKey().isAssignableFrom(targetType)) { toReturn.add((TypeConverter<From, To>) currentSourceClass2TypeConverter.getValue()); } } } // All done. return toReturn; } /** * Performs a standard conversion from the supplied toConvert value to the given toType. * The known converters will be attempted in correct priority order; the first TypeConverter * instance which can handle the requested conversion will be executed to receive the results. * * @param toConvert The object which should be converted. * @param toType The type to convert to. * @param <To> The type to convert to. * @return {@code null} if toConvert could not be converted, and the resulting [converted] instance otherwise. */ public <To> To convert(@NotNull final From toConvert, @NotNull final Class<To> toType) { // Check sanity Validate.notNull(toType, "toType"); // Acquire all possible TypeConverters final List<TypeConverter<From, To>> typeConverters = getTypeConverters(toType); final String toConvertTypeName = toConvert == null ? "null" : toConvert.getClass().getSimpleName(); log.warn("Converting [" + toConvertTypeName + " --> " + toType.getSimpleName() + "] using " + typeConverters + ". My Converters: " + this.prioritizedTypeConverterMap); if (!typeConverters.isEmpty()) { for (TypeConverter<From, To> current : typeConverters) { if (current.canConvert(toConvert)) { return current.convert(toConvert); } } } // Could not convert the supplied value. return null; } /** * Prints out a debug information string about the state within this PrioritizedTypeConverter instance. * * @return The source type and possible target types of this PrioritizedTypeConverter instance. * For simplicity, the target types are sorted in alphabetical order. */ @Override public String toString() { // Acquire all target types. final SortedMap<Integer, SortedSet<String>> priorityTargetTypeMap = new TreeMap<Integer, SortedSet<String>>(); for (int currentIndex : this.prioritizedTypeConverterMap.keySet()) { // Acquire all [simple] class names of the target types. final List<String> classNames = CollectionAlgorithms.flatten( prioritizedTypeConverterMap.get(currentIndex), new Transformer<Tuple<Class<?>, TypeConverter<From, ?>>, String>() { @Override public String transform(final Tuple<Class<?>, TypeConverter<From, ?>> input) { // Just dig out the simple class name. return input.getKey().getSimpleName(); } }); // Sort and add the class names. priorityTargetTypeMap.put(currentIndex, new TreeSet<String>(classNames)); } // All done. return "PrioritizedTypeConverter: [" + getSourceType().getSimpleName() + "] --> [" + priorityTargetTypeMap + "]"; } // // Internal classes // /** * Abstract reflection-based implementation of the TypeConverter specification. * * @param <To> The type to convert to (i.e. targetType). */ abstract class AbstractReflectiveTypeConverter<To> implements TypeConverter<From, To> { // Internal state protected Class<To> toType; protected Converter converterAnnotation; /** * Creates an AbstractReflectiveTypeConverter instance converting to the supplied toType. * * @param toType The type to which this AbstractReflectiveTypeConverter should convert instances. */ protected AbstractReflectiveTypeConverter(@NotNull final Class<To> toType) { // Check sanity Validate.notNull(toType, "toType"); // Assign internal state this.toType = toType; } /** * {@inheritDoc} */ @Override public final Class<To> getToType() { return toType; } /** * {@inheritDoc} */ @Override public final Class<From> getFromType() { return PrioritizedTypeConverter.this.getSourceType(); } /** * {@inheritDoc} */ @Override public boolean canConvert(final From instance) { return instance != null || converterAnnotation.acceptsNullValues(); } /** * @return The Converter annotation for this AbstractReflectiveTypeConverter. */ public final Converter getConverterAnnotation() { return converterAnnotation; } /** * {@inheritDoc} */ public String toString() { final String typeDescription = this instanceof ConstructorTypeConverter ? "Constructor" : "Method"; StringBuilder builder = new StringBuilder(); builder.append(typeDescription).append(" converter "); if (getConverterName() != null) { builder.append("'" + getConverterName() + "' "); } builder.append("(" + getFromType().getSimpleName() + " --> " + getToType().getSimpleName() + ") "); builder.append("\n Annotation [" + converterAnnotation.toString() + "]"); // All done. return builder.toString(); } /** * @return A simple name for this converter. */ protected String getConverterName() { return null; } } /** * TypeConverter implementation for Constructor-annotated Converters. * * @param <To> The type to convert to (i.e. targetType). */ class ConstructorTypeConverter<To> extends AbstractReflectiveTypeConverter<To> { // Internal state private Constructor<To> converterConstructor; /** * Creates a new ConstructorTypeConverter instance wrapping the supplied Constructor. * * @param converterConstructor The constructor to be used in converting objects. */ public ConstructorTypeConverter(final Constructor<To> converterConstructor) { // Delegate super(converterConstructor.getDeclaringClass()); // Assign internal state this.converterConstructor = converterConstructor; converterAnnotation = converterConstructor.getAnnotation(Converter.class); } /** * {@inheritDoc} */ @Override public To convert(final From instance) { try { return converterConstructor.newInstance(instance); } catch (Exception e) { final String typeName = instance == null ? "<null>" : instance.getClass().getName(); throw new IllegalArgumentException("Could not convert [" + typeName + "] to [" + getToType().getName() + "]", e); } } } /** * TypeConverter implementation for Method-annotated Converters. * * @param <To> The type to convert to (i.e. targetType). */ class MethodTypeConverter<To> extends AbstractReflectiveTypeConverter<To> { // Internal state private Method converterMethod; private Method conditionalConvertionMethod; private Object converterInstance; /** * Creates a new MethodTypeConverter instance which invokes the supplied converter instance using the * given converterMethod. * * @param converter The converter instance in which the converter method is invoked. * @param converterMethod The method invoked to convert the object. */ public MethodTypeConverter(@NotNull final Object converter, @NotNull final Method converterMethod) throws NoSuchMethodException { // Delegate super((Class<To>) converterMethod.getReturnType()); // Check sanity Validate.notNull(converter, "converter"); // Assign internal state this.converterInstance = converter; this.converterMethod = converterMethod; converterAnnotation = converterMethod.getAnnotation(Converter.class); // Do we have a defined conditionalConversionMethod? if (!converterAnnotation.conditionalConversionMethod().equals(Converter.NO_CONVERTER_METHOD)) { // Acquire the method. conditionalConvertionMethod = converter.getClass().getMethod( converterAnnotation.conditionalConversionMethod(), PrioritizedTypeConverter.this.getSourceType()); } } /** * {@inheritDoc} */ public String toString() { final StringBuilder builder = new StringBuilder(super.toString()); if (conditionalConvertionMethod != null) { builder.append("\n Conditional Conversion Method: [" + conditionalConvertionMethod + "]"); } builder.append("\n Converter instance type [" + converterInstance.getClass().getName() + "]"); // All done. return builder.toString(); } /** * @return A simple name for this converter. */ @Override protected String getConverterName() { return converterMethod.getName(); } /** * {@inheritDoc} */ @Override public boolean canConvert(final From instance) { // Do we have a complex evaluator? if (conditionalConvertionMethod != null) { try { return (Boolean) conditionalConvertionMethod.invoke(converterInstance, instance); } catch (Exception e) { throw new IllegalStateException("Could not invoke [" + conditionalConvertionMethod + "]", e); } } // Fallback to standard mechanics. return instance != null || converterAnnotation.acceptsNullValues(); } /** * {@inheritDoc} */ @Override public To convert(final From instance) { try { return (To) converterMethod.invoke(converterInstance, instance); } catch (Exception e) { final String typeName = instance == null ? "<null>" : instance.getClass().getName(); throw new IllegalArgumentException("Could not convert [" + typeName + "] to [" + getToType().getName() + "]", e); } } } // // Private helpers // /** * Adds the supplied TypeConverter to the internal state of this PrioritizedTypeConverter, returning * any TypeConverter which was replaced (i.e. an earlier map of the same priority and toType). * * @param toAdd The TypeConverter to add to the internal structure. * @param <T> The exact type of AbstractReflectiveTypeConverter added * @return the TypeConverter which was replaced, or {@code null} if none was replaced by this add. */ protected final <T extends AbstractReflectiveTypeConverter> T addTypeConverter(final T toAdd) { // Extract required data final int priority = toAdd.converterAnnotation.priority(); final Class<?> toClass = toAdd.getToType(); Map<Class<?>, TypeConverter<From, ?>> priorityMap = prioritizedTypeConverterMap.get(priority); synchronized (lock) { if (priorityMap == null) { priorityMap = new HashMap<Class<?>, TypeConverter<From, ?>>(); prioritizedTypeConverterMap.put(priority, priorityMap); } // All done. return (T) priorityMap.put(toClass, toAdd); } } }