/* * #%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.collections.predicate.Tuple; import se.jguru.nazgul.core.reflection.api.TypeExtractor; import se.jguru.nazgul.core.reflection.api.conversion.Converter; import javax.validation.constraints.NotNull; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Function; import java.util.function.Predicate; /** * Filter utility implementations for use in discovering Converter implementations. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB * @see Converter */ @SuppressWarnings("all") public final class Converters { // Our Log private static final Logger log = LoggerFactory.getLogger(Converters.class); /** * <p>Predicate finding Methods with the following properties:</p> * <ol> * <li>Annotated with @Converter</li> * <li>Being public</li> * <li>Not returning {@code void} or {@code Void}</li> * <li>Having exactly one argument</li> * </ol> */ @SuppressWarnings("all") public static final Predicate<Method> CONVERSION_METHOD_FILTER = candidate -> { // #1) Is the candidate Method annotated with @Converter? final Converter converter = candidate.getAnnotation(Converter.class); if (converter == null) { if (log.isDebugEnabled()) { log.debug("Method [" + candidate + "] is not annotated with @Converter."); } // All Done. return false; } // #2) Is the candidate Method public? final boolean isPublic = Modifier.isPublic(candidate.getModifiers()); if (!isPublic) { if (log.isDebugEnabled()) { log.debug("Method [" + candidate + "] is not public."); } // All Done. return false; } // #3) Does the method return Void or void? final Class<?> returnType = candidate.getReturnType(); final boolean incorrectReturnType = returnType == Void.TYPE || returnType == Void.class; if (incorrectReturnType) { if (log.isDebugEnabled()) { log.debug("Method [" + candidate + "] returns void or Void."); } // All Done. return false; } // #4) Not a single parameter? final Class<?>[] parameterTypes = candidate.getParameterTypes(); if (parameterTypes.length != 1) { if (log.isDebugEnabled()) { log.debug("Method [" + candidate + "] does not have a single parameter."); } // All Done. return false; } // #5) If we have a conditionalConverterMethod defined, it must be on the form // // public boolean someMethodName(final FromType aFromType); // final String conditionalConverterMethod = converter.conditionalConversionMethod(); final boolean hasConditionalConverterMethod = !conditionalConverterMethod.equals(Converter.NO_CONVERTER_METHOD); if (hasConditionalConverterMethod) { // Create the final Class<?> fromType = parameterTypes[0]; final ConditionalConverterMethodPredicate filter = new ConditionalConverterMethodPredicate( fromType, converter.conditionalConversionMethod()); // Acquire the potential conversionMethods final SortedSet<Method> conversionMethods = TypeExtractor.getMethods( candidate.getDeclaringClass(), filter); // All done. return conversionMethods.size() > 0; } // No problems found. return true; }; /** * Singleton instance of the ConversionConstructorFilter type. */ public static final Predicate<Constructor<?>> CONVERSION_CONSTRUCTOR_FILTER = candidate -> { final Class<?>[] parameterTypes = candidate.getParameterTypes(); if (candidate.getAnnotation(Converter.class) != null && parameterTypes.length == 1) { // The only argument should not be Void (which would be funky...). return (parameterTypes[0] != Void.class && parameterTypes[0] != Void.TYPE); } // Nopes return false; }; /** * Function which extracts all methods and constructors able to convert types within a class. * Should a null class be passed as argument, an empty Function. */ @NotNull public static Function<Class<?>, Tuple<SortedSet<Method>, SortedSet<Constructor<?>>>> GET_CONVERTERS = aClass -> { // Fail fast if (aClass == null) { return new Tuple<>(new TreeSet<>(), new TreeSet<>()); } final SortedSet<Method> converterMethods = TypeExtractor.getMethods(aClass, Converters.CONVERSION_METHOD_FILTER); final SortedSet<Constructor<?>> converterConstructors = TypeExtractor.getConstructors(aClass, Converters.CONVERSION_CONSTRUCTOR_FILTER); // All Done. return new Tuple<>(converterMethods, converterConstructors); }; /* * Helper method to extract a Tuple of converter methods and constructors, as found within the supplied * object instance. * * @param object The object which should be inspected for * @return {@code null} if the object was {@code null} or no converter methods and constructors * were found within the supplied object. Otherwise returns a populated Tuple. public static Tuple<List<Method>, List<Constructor<?>>> getConverterMethodsAndConstructors( final Object object) { // Create the return variable Tuple<List<Method>, List<Constructor<?>>> toReturn = null; if (object != null) { // Find any converter methods in the supplied converter final SortedSet<Method> methods = TypeExtractor.getMethods( object.getClass(), ReflectiveConverterFilter.CONVERSION_METHOD_FILTER); // Find any converter constructors in the supplied converter final List<Constructor<?>> constructors = CollectionAlgorithms.filter( Arrays.asList(object.getClass().getConstructors()), ReflectiveConverterFilter.CONVERSION_CONSTRUCTOR_FILTER); // Only return a non-null value if we found some converter methods/constructors. if (methods.size() != 0 || constructors.size() != 0) { toReturn = new Tuple<List<Method>, List<Constructor<?>>>(methods, constructors); } } // All done. return toReturn; } */ /** * Hidden constructor. */ private Converters() { } /** * <p>Filter implementation detecting proper conditional converter methods. Converter methods must:</p> * <ol> * <li>Be public</li> * <li>Return boolean</li> * <li>Have a given method name</li> * <li>Have a single argument with a given type</li> * </ol> */ @SuppressWarnings("all") private static final class ConditionalConverterMethodPredicate implements Predicate<Method> { // Internal state private Class<?> requiredArgumentType; private String methodName; private ConditionalConverterMethodPredicate(final Class<?> fromType, final String methodName) { this.requiredArgumentType = fromType; this.methodName = methodName; } /** * {@inheritDoc} */ @Override public boolean test(final Method candidate) { // #1) Is the method public? if (!Modifier.isPublic(candidate.getModifiers())) { return false; } // #2) Does the method return boolean or Boolean? final Class<?> returnType = candidate.getReturnType(); if (!(returnType == Boolean.TYPE || returnType == Boolean.class)) { return false; } // #3) Does the method have 1 single argument of the correct type? final Class<?>[] parameterTypes = candidate.getParameterTypes(); if (parameterTypes.length != 1 || !parameterTypes[0].isAssignableFrom(requiredArgumentType)) { return false; } // #4) Does the method name match the supplied requirement? // ... which must not be the default value... if (methodName.equals(Converter.NO_CONVERTER_METHOD) || !candidate.getName().equals(methodName)) { return false; } // All done. return true; } } }