/* * #%L * Nazgul Project: nazgul-core-algorithms-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.algorithms.api.types; import se.jguru.nazgul.core.algorithms.api.TypeAlgorithms; import se.jguru.nazgul.core.algorithms.api.Validate; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlTransient; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; /** * Holder for type information, extracted from a source Class. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ @XmlTransient public class TypeInformation implements Serializable, Comparable<TypeInformation> { // Internal state private Class<?> source; private List<Class<?>> classHierarchy; private SortedMap<Class<?>, List<Class<?>>> class2InterfaceMap; private SortedMap<Class<?>, List<Annotation>> class2AnnotationMap; /** * Default constructor. */ public TypeInformation() { // Create internal state this.classHierarchy = new ArrayList<>(); this.class2InterfaceMap = new TreeMap<>(TypeAlgorithms.CLASSNAME_COMPARATOR); this.class2AnnotationMap = new TreeMap<>(TypeAlgorithms.CLASSNAME_COMPARATOR); } /** * Compound constructor creating a TypeInformation instance populated with * the state extracted from the supplied Class. * * @param aClass The Class which should be */ public TypeInformation(@NotNull Class<?> aClass) { // Check sanity Validate.notNull(aClass, "aClass"); // Assign internal state initializeWith(aClass); } /** * Initializes this TypeInformation object by extracting data from the supplied Class. * This implementation ignores the Object.class within the interfaceMap and annotationMap, since Object.class * does not sport interfaces or annotations. * * @param pointOfOrigin A non-null Class. */ public final void initializeWith(@NotNull Class<?> pointOfOrigin) { // Check sanity Validate.notNull(pointOfOrigin, "pointOfOrigin"); // Create internal state this.source = pointOfOrigin; this.classHierarchy = new ArrayList<>(); this.class2InterfaceMap = new TreeMap<>(TypeAlgorithms.CLASSNAME_COMPARATOR); this.class2AnnotationMap = new TreeMap<>(TypeAlgorithms.CLASSNAME_COMPARATOR); // Parse and insert the information. for (Class current = pointOfOrigin; current != null; current = current.getSuperclass()) { // #1) Add the current Class classHierarchy.add(current); // We can safely ignore Object.class since it does not add any Interfaces or Annotations. if (current != Object.class) { // #2) Add the Interface types implemented by the current Class unless already added // ... and ignore adding Object.class, since it does not add any Interfaces. final List<Class<?>> currentInterfaceList = class2InterfaceMap.computeIfAbsent( current, k -> new ArrayList<>()); final Class[] interfaces = current.getInterfaces(); if (interfaces != null && interfaces.length > 0) { Arrays.stream(interfaces) .filter(anInterface -> !currentInterfaceList.contains(anInterface)) .forEach(currentInterfaceList::add); } // #3) Add the annotation types for the current Class unless already added final List<Annotation> currentAnnotationList = class2AnnotationMap.computeIfAbsent( current, k -> new ArrayList<>()); final Annotation[] annotations = current.getAnnotations(); if (annotations != null && annotations.length > 0) { Arrays.stream(annotations) .filter(anAnnotation -> !currentAnnotationList.contains(anAnnotation)) .forEach(currentAnnotationList::add); } } } } /** * @return The Class which is the source of this TypeInformation, i.e. the Class * from which all information was ultimately collected. */ public Class<?> getSource() { return source; } /** * Retrieves the list of classes within the inheritance hierarchy of the source class (for which this * TypeInformation was {@link #initializeWith(Class)}'d). * * @return the list of classes within the inheritance hierarchy of the source class (for which this TypeInformation * was {@link #initializeWith(Class)}'d). */ @NotNull public List<Class<?>> getClassHierarchy() { return classHierarchy; } /** * Retrieves a SortedMap relating each class in the {@link #getClassHierarchy()} to the Interfaces it implements. * * @return a SortedMap relating each class in the {@link #getClassHierarchy()} to the Interfaces it implements. * All values within this Map are non-null, implying that an empty List will be returned for classes not * implementing any interfaces. */ @NotNull public SortedMap<Class<?>, List<Class<?>>> getClass2InterfaceMap() { return class2InterfaceMap; } /** * Retrieves a SortedSet containing the Interface types implemented by the source class (or any of its supertypes). * * @return a SortedSet containing the Interface types implemented by the source class (or any of its supertypes). * @see TypeAlgorithms#CLASSNAME_COMPARATOR */ @NotNull public SortedSet<Class<?>> getAllInterfaces() { final SortedSet<Class<?>> toReturn = new TreeSet<>(TypeAlgorithms.CLASSNAME_COMPARATOR); getClass2InterfaceMap().entrySet() .stream() .map(Map.Entry::getValue) .forEach(interfaces -> { // Add all Interfaces not already added. interfaces .stream() .filter(anInterface -> !toReturn.contains(anInterface)) .forEach(toReturn::add); }); // All Done. return toReturn; } /** * Retrieves a SortedMap relating each class in the {@link #getClassHierarchy()} to the Annotations it implements. * * @return a SortedMap relating each class in the {@link #getClassHierarchy()} to the Annotations it implements. */ @NotNull public SortedMap<Class<?>, List<Annotation>> getClass2AnnotationMap() { return class2AnnotationMap; } /** * Retrieves a SortedSet containing the Annotations implemented by the source class (or any of its supertypes), * for each Annotation which is preserved at runtime (and therefore present within the compiled bytecode file of * the {@link #getSource()} class. Hence, this method can only retrieve Annotations which have * {@link RetentionPolicy#RUNTIME } * * @return a SortedSet containing the Annotations implemented by the source class (or any of its supertypes), * for Annotations having {@link RetentionPolicy#RUNTIME}. */ @NotNull public SortedSet<Annotation> getAllAnnotations() { final SortedSet<Annotation> toReturn = new TreeSet<>(TypeAlgorithms.ANNOTATION_COMPARATOR); getClass2AnnotationMap().entrySet() .stream() .map(Map.Entry::getValue) .forEach(annotations -> { // Add all Annotations not already added. annotations .stream() .filter(anAnnotation -> !toReturn.contains(anAnnotation)) .forEach(toReturn::add); }); // All Done. return toReturn; } /** * Retrieves a Map relating the name of readable JavaBean property names to their respective getter methods. * * @return a Map relating the name of readable JavaBean property names to their respective getter methods. */ @NotNull public SortedMap<String, Method> getJavaBeanGetterMethods() { return TypeAlgorithms.FIND_JAVABEAN_GETTERS.apply(getSource()); } /** * Retrieves a Map relating the name of writeable JavaBean property names to their respective setter methods. * * @return a Map relating the name of writeable JavaBean property names to their respective setter methods. */ @NotNull public SortedMap<String, Method> getJavaBeanSetterMethods() { return TypeAlgorithms.FIND_JAVABEAN_SETTERS.apply(getSource()); } /** * Retrieves all Methods found within the {@link #getSource()} class. * * @param declaredMethods if true, retrieves the {@link Class#getDeclaredMethods()} * and otherwise {@link Class#getMethods()}. * @return A SortedSet containing all methods from the {@link #getSource()} class, sorted * according to the {@link TypeAlgorithms#MEMBER_COMPARATOR}. * @see TypeAlgorithms#MEMBER_COMPARATOR */ @NotNull @SuppressWarnings("all") public SortedSet<Method> getMethods(final boolean declaredMethods) { final SortedSet<Method> toReturn = new TreeSet<>(TypeAlgorithms.MEMBER_COMPARATOR); final Method[] methods = declaredMethods ? getSource().getDeclaredMethods() : getSource().getMethods(); for (int i = 0, methodsLength = methods.length; i < methodsLength; i++) { toReturn.add(methods[i]); } // All Done. return toReturn; } /** * Retrieves all Constructors found within the {@link #getSource()} class. * * @param declaredConstructors if true, retrieves the {@link Class#getDeclaredConstructors()} * and otherwise {@link Class#getConstructors()}. * @return A SortedSet containing all methods from the {@link #getSource()} class, sorted * according to the {@link TypeAlgorithms#MEMBER_COMPARATOR}. * @see TypeAlgorithms#MEMBER_COMPARATOR */ @NotNull @SuppressWarnings("all") public SortedSet<Constructor<?>> getConstructors(final boolean declaredConstructors) { final SortedSet<Constructor<?>> toReturn = new TreeSet<>(TypeAlgorithms.MEMBER_COMPARATOR); final Constructor<?>[] constructors = declaredConstructors ? getSource().getDeclaredConstructors() : getSource().getConstructors(); for (int i = 0, constructorsLength = constructors.length; i < constructorsLength; i++) { toReturn.add(constructors[i]); } // All Done. return toReturn; } /** * Retrieves all Fields found within the {@link #getSource()} class. * * @param declaredFields if true, retrieves the {@link Class#getDeclaredFields()} ()} * and otherwise {@link Class#getFields()} ()}. * @return A SortedSet containing all fields from the {@link #getSource()} class, sorted * according to the {@link TypeAlgorithms#MEMBER_COMPARATOR}. * @see TypeAlgorithms#MEMBER_COMPARATOR */ @NotNull @SuppressWarnings("all") public SortedSet<Field> getFields(final boolean declaredFields) { final SortedSet<Field> toReturn = new TreeSet<>(TypeAlgorithms.MEMBER_COMPARATOR); final Field[] fields = declaredFields ? getSource().getDeclaredFields() : getSource().getFields(); for (int i = 0, fieldsLength = fields.length; i < fieldsLength; i++) { toReturn.add(fields[i]); } // All Done. return toReturn; } /** * {@inheritDoc} */ @Override public boolean equals(final Object o) { // Fail fast if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } // Delegate to internal state final TypeInformation that = (TypeInformation) o; return Objects.equals(source, that.source) && Objects.equals(classHierarchy, that.classHierarchy) && Objects.equals(class2InterfaceMap, that.class2InterfaceMap) && Objects.equals(class2AnnotationMap, that.class2AnnotationMap); } /** * {@inheritDoc} */ @Override public int hashCode() { return Objects.hash(source, classHierarchy, class2InterfaceMap, class2AnnotationMap); } /** * {@inheritDoc} */ @Override public int compareTo(final TypeInformation that) { // Fail fast if (that == null) { return -1; } else if (that == this) { return 0; } // Delegate to internal state return getSource().getName().compareTo(that.getSource().getName()); } }