org.perfectable.introspection.query.InheritanceQuery Maven / Gradle / Ivy
package org.perfectable.introspection.query;
import java.lang.annotation.Annotation;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNull;
/**
* Iterable-like container that allows access to supertypes of a class.
*
* This of course only lists types that are actually defined (transitively) by initially-provided class. Although
* wildcard types, type variables might be supertype or supertype of specified type, they are not listed.
*
*
Instances of this class are immutable, each filtering produces new, modified instance. To obtain query for
* specific class, use {@link #of}.
*
*
To obtain results either iterate this class with {@link #iterator} (or in enhanced-for loop) or use one of
* {@link #stream()}, {@link #unique()}, {@link #option()} or {@link #isPresent()}.
*
*
Example usage, which registers all interfaces that DatabaseService implements that are annotated by Remote
*
* InheritanceQuery.of(DatabaseService.class)
* .annotatedWith(Remote.class)
* .onlyInterfaces()
* .stream()
* .forEach(this::register);
*
*
* @param lower bound of the searched types
*/
@SuppressWarnings({
"DesignForExtension", // class is closed because of package-private constructor
"ClassDataAbstractionCoupling"
})
public abstract class InheritanceQuery
extends AbstractQuery, InheritanceQuery> {
/**
* Creates unrestricted query that will list all the supertypes that this class or interface extends/implements.
*
* @param type class to start search with
* @param upper subtype of all results
* @return new, unrestricted inheritance query
*/
public static InheritanceQuery of(Class type) {
return new Complete<>(type);
}
/**
* Creates query which lists the same classes as this one, but only if they have an annotation with provided class.
*
* @param annotationClass annotation class that will be used
* @return query filtered for classes annotated with specified class
*/
public InheritanceQuery annotatedWith(Class extends Annotation> annotationClass) {
return annotatedWith(AnnotationFilter.single(annotationClass));
}
/**
* Creates query which lists the same classes as this one, but only if they have an annotation that matches
* specified filter.
*
* @param annotationFilter annotation filter that will be used
* @return query filtered for classes annotated with specific properties
*/
public InheritanceQuery annotatedWith(AnnotationFilter annotationFilter) {
requireNonNull(annotationFilter);
return new Annotated<>(this, annotationFilter);
}
@Override
public InheritanceQuery filter(Predicate super Class super X>> filter) {
return new Predicated<>(this, filter);
}
@Override
public InheritanceQuery sorted(Comparator super Class super X>> comparator) {
return new Sorted<>(this, comparator);
}
/**
* Create query which lists the same classes as this one, but only if they are subtypes of provided type, and are
* not this type.
*
* @param supertype supertype of the filtered classes
* @return query filtered for subtypes of specified type, excluding it
*/
public InheritanceQuery upToExcluding(Class super X> supertype) {
return new BoundingExcluded<>(this, supertype);
}
/**
* Create query which lists the same classes as this one, but only if they are subtypes of provided type.
*
* Note that each type is its own subtype, so if {@code supertype} is actually supertype of initial class
* or interface, it will be included in results.
*
* @param supertype supertype of the filtered classes
* @return query filtered for subtypes of specified type, including it
*/
public InheritanceQuery upToIncluding(Class super X> supertype) {
return new BoundingIncluded<>(this, supertype);
}
/**
* Creates query which lists the same classes as this one, but only if they are actually an interface.
*
* @return query filtered for interfaces
*/
public InheritanceQuery onlyInterfaces() {
return new InterfacesOnly<>(this);
}
/**
* Creates query which lists the same classes as this one, but only if they are actually a class (not an interface).
*
* @return query filtered for classes
*/
public InheritanceQuery onlyClasses() {
return new ClassesOnly<>(this);
}
InheritanceQuery() {
// package extension only
}
private static final class Complete extends InheritanceQuery {
private final Class initial;
Complete(Class initial) {
this.initial = initial;
}
@Override
public Stream> stream() {
return Streams.generateSingle(initial, InheritanceQuery::safeGetSupertypes);
}
@Override
public boolean contains(@Nullable Object candidate) {
if (!(candidate instanceof Class>)) {
return false;
}
@SuppressWarnings("unchecked")
Class super X> candidateClass = (Class super X>) candidate;
return candidateClass.isAssignableFrom(initial);
}
}
private abstract static class Filtered extends InheritanceQuery {
private final InheritanceQuery parent;
Filtered(InheritanceQuery parent) {
this.parent = parent;
}
protected abstract boolean matches(Class super X> candidate);
@Override
public Stream> stream() {
return this.parent.stream()
.filter(this::matches);
}
@Override
public boolean contains(@Nullable Object candidate) {
if (!(candidate instanceof Class>)) {
return false;
}
@SuppressWarnings("unchecked")
Class super X> candidateClass = (Class super X>) candidate;
return matches(candidateClass) && parent.contains(candidate);
}
}
private static final class Annotated extends Filtered {
private final AnnotationFilter annotationFilter;
Annotated(InheritanceQuery parent, AnnotationFilter annotationFilter) {
super(parent);
this.annotationFilter = annotationFilter;
}
@Override
protected boolean matches(Class super X> candidate) {
return this.annotationFilter.matches(candidate);
}
}
private static final class Predicated extends Filtered {
private final Predicate super Class super X>> filter;
Predicated(InheritanceQuery parent, Predicate super Class super X>> filter) {
super(parent);
this.filter = filter;
}
@Override
protected boolean matches(Class super X> candidate) {
return filter.test(candidate);
}
}
private static class Sorted extends InheritanceQuery {
private final InheritanceQuery parent;
private final Comparator super Class super X>> comparator;
Sorted(InheritanceQuery parent, Comparator super Class super X>> comparator) {
this.parent = parent;
this.comparator = comparator;
}
@Override
public InheritanceQuery sorted(Comparator super Class super X>> nextComparator) {
@SuppressWarnings("unchecked")
Comparator<@Nullable Object> casted = (Comparator<@Nullable Object>) nextComparator;
return new Sorted<>(parent, this.comparator.thenComparing(casted));
}
@Override
public Stream> stream() {
return this.parent.stream()
.sorted(comparator);
}
@Override
public boolean contains(@Nullable Object candidate) {
return parent.contains(candidate);
}
}
private static final class BoundingExcluded extends Filtered {
private final Class super X> supertype;
BoundingExcluded(InheritanceQuery parent, Class super X> supertype) {
super(parent);
this.supertype = supertype;
}
@Override
protected boolean matches(Class super X> candidate) {
return supertype.isAssignableFrom(candidate) && !supertype.equals(candidate);
}
}
private static final class BoundingIncluded extends Filtered {
private final Class super X> supertype;
BoundingIncluded(InheritanceQuery parent, Class super X> supertype) {
super(parent);
this.supertype = supertype;
}
@Override
protected boolean matches(Class super X> candidate) {
return supertype.isAssignableFrom(candidate);
}
}
private static final class InterfacesOnly extends Filtered {
InterfacesOnly(InheritanceQuery parent) {
super(parent);
}
@Override
protected boolean matches(Class super X> candidate) {
return candidate.isInterface();
}
}
private static final class ClassesOnly extends Filtered {
ClassesOnly(InheritanceQuery parent) {
super(parent);
}
@Override
protected boolean matches(Class super X> candidate) {
return !candidate.isInterface();
}
}
private static Stream> safeGetSupertypes(Class super X> type) {
Stream.Builder> builder = Stream.builder();
@SuppressWarnings("unchecked")
Class super X>[] interfaceArray = (Class super X>[]) type.getInterfaces();
Stream.of(interfaceArray).forEach(builder);
@Nullable Class super X> superclass = type.getSuperclass();
if (superclass != null) {
builder.accept(superclass);
}
return builder.build();
}
}