/*
* #%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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jguru.nazgul.core.algorithms.api.TypeAlgorithms;
import se.jguru.nazgul.core.algorithms.api.Validate;
import se.jguru.nazgul.core.algorithms.api.types.TypeInformation;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Utility methods to retrieve, filter and handle types.
*
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB
*/
public final class TypeExtractor {
// Our Log
private static final Logger log = LoggerFactory.getLogger(TypeExtractor.class);
/**
* Hidden utility class constructor.
*/
private TypeExtractor() {
// Do nothing
}
/**
* Acquires all interfaces from the provided class which matches the provided selector's acceptance criteria.
*
* @param clazz The class from which to derive all appropriate interfaces.
* @param selector an optional (i.e. nullable) Predicate. If provided, the Predicate is used to
* filter all interfaces implemented by the supplied Class.
* @return All interfaces implemented by the provided class, and which
* matched the supplied selector's acceptance criteria.
*/
@NotNull
public static SortedSet<Class<?>> getInterfaces(@NotNull final Class<?> clazz,
final Predicate<Class<?>> selector) {
// Check sanity
Validate.notNull(clazz, "clazz");
// Extract all interfaces
final TypeInformation typeInfo = new TypeInformation(clazz);
final SortedSet<Class<?>> allInterfaces = typeInfo.getAllInterfaces();
if (log.isDebugEnabled()) {
log.debug(clazz.getName() + " implements " + allInterfaces.size() + " interfaces: "
+ allInterfaces.stream()
.map(Class::getName)
.sorted()
.reduce((l, r) -> l + ", " + r)
.orElse("<none>"));
}
// Delegate
return selector == null
? allInterfaces
: allInterfaces.stream().filter(selector).collect(TypeAlgorithms.SORTED_CLASSNAME_COLLECTOR);
}
/**
* Acquires all methods from the provided class which matches the provided selector.
*
* @param clazz The class from which to retrieve all relevant methods.
* @param selector an optional (i.e. nullable) Predicate. If provided, the Predicate is used to
* filter all Methods found within the supplied Class.
* @return All methods (including private ones) found by the provided class, and
* which matched the supplied selector's acceptance criteria.
*/
@NotNull
public static SortedSet<Constructor<?>> getConstructors(@NotNull final Class<?> clazz,
final Predicate<Constructor<?>> selector) {
// Check sanity
Validate.notNull(clazz, "clazz");
final SortedSet<Constructor<?>> toReturn = new TreeSet<>(TypeAlgorithms.MEMBER_COMPARATOR);
// Acquire all methods found within the class of the provided instance.
final SortedSet<Constructor<?>> declaredConstructors = TypeAlgorithms
.getAllTypesFor(clazz)
.getConstructors(true);
if (log.isDebugEnabled()) {
log.debug(clazz.getName() + " has " + declaredConstructors.size() + " constructors: "
+ declaredConstructors.stream()
.map(Constructor::toString)
.sorted()
.reduce((l, r) -> l + ", " + r)
.orElse("<none>"));
}
// Add all matching constructors to the return SortedSet
if (selector == null) {
toReturn.addAll(declaredConstructors);
} else {
declaredConstructors
.stream()
.filter(selector)
.forEach(toReturn::add);
}
// All Done.
return toReturn;
}
/**
* Acquires all methods from the provided class which matches the provided selector.
*
* @param clazz The class from which to retrieve all relevant methods.
* @param selector an optional (i.e. nullable) Predicate. If provided, the Predicate is used to
* filter all Methods found within the supplied Class.
* @return All methods (including private ones) found by the provided class, and
* which matched the supplied selector's acceptance criteria.
*/
public static SortedSet<Method> getMethods(@NotNull final Class<?> clazz,
final Predicate<Method> selector) {
// Check sanity
Validate.notNull(clazz, "clazz");
// Acquire all methods found within the class of the provided instance.
final SortedSet<Method> declaredMethods = TypeAlgorithms.getAllTypesFor(clazz).getMethods(true);
// All Done.
if (selector == null) {
return declaredMethods;
}
return declaredMethods
.stream()
.filter(selector)
.collect(Collectors.toCollection(TypeAlgorithms.SORTED_METHOD_SUPPLIER));
}
/**
* Acquires all fields from the given class which matches the provided selector.
*
* @param clazz The class from which to retrieve all relevant fields.
* @param selector The selector defining which fields to filter out.
* @return All fields (including private ones) within the provided instance
* or its superclasses, and which matched the supplied selector's acceptance criteria.
*/
public static SortedSet<Field> getFields(@NotNull final Class<?> clazz,
final Predicate<Field> selector) {
// Check sanity
Validate.notNull(clazz, "clazz");
// Acquire all methods found within the class of the provided instance.
final SortedSet<Field> declaredFields = TypeAlgorithms.getAllTypesFor(clazz).getFields(true);
// All Done.
if (selector == null) {
return declaredFields;
}
return declaredFields
.stream()
.filter(selector)
.collect(Collectors.toCollection(() -> new TreeSet<>(TypeAlgorithms.MEMBER_COMPARATOR)));
}
/**
* Finds the number of types between related classes source and target.
*
* @param source The source type.
* @param target The target type.
* @return The number of class hops (i.e. class/superclass relations) between the
* source and target types; if source is a superclass of target, the value
* is negative, and otherwise positive.
* @throws IllegalArgumentException if source and target classes are not related.
*/
public static int getRelationDifference(final Class<?> source, final Class<?> target)
throws IllegalArgumentException {
// Check sanity
Validate.notNull(source, "Cannot handle null source argument.");
Validate.notNull(target, "Cannot handle null target argument.");
if (source.isAssignableFrom(target)) {
int depth = 0;
for (Class<?> current = target; current != null; current = current.getSuperclass()) {
// Same type?
if (current == source) {
break;
}
// Interface implemented by the current class?
// Does the current class implement the desiredType, or *is* it the desiredType?
List<Class<?>> interfacesImplemented = new ArrayList<Class<?>>();
Collections.addAll(interfacesImplemented, current.getInterfaces());
// Done?
if (interfacesImplemented.contains(source)) {
depth--;
break;
}
// Decrease one.
depth -= 2;
}
// All done.
return depth;
}
if (target.isAssignableFrom(source)) {
int depth = 0;
for (Class<?> current = source; current != null; current = current.getSuperclass()) {
// Same type?
if (current == target) {
break;
}
// Interface implemented by the current class?
// Does the current class implement the desiredType, or *is* it the desiredType?
List<Class<?>> interfacesImplemented = new ArrayList<Class<?>>();
Collections.addAll(interfacesImplemented, current.getInterfaces());
// Done?
if (interfacesImplemented.contains(target)) {
depth++;
break;
}
// Decrease one.
depth += 2;
}
// All done.
return depth;
}
// Complain.
throw new IllegalArgumentException("Types [" + source.getSimpleName() + "] and [" + target.getSimpleName()
+ "] are not related.");
}
}