com.tngtech.archunit.core.domain.JavaClass.java Source code

Java tutorial

Introduction

Here is the source code for com.tngtech.archunit.core.domain.JavaClass.java

Source

/*
 * Copyright 2019 TNG Technology Consulting GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
package com.tngtech.archunit.core.domain;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.ArchUnitException.InvalidSyntaxUsageException;
import com.tngtech.archunit.base.ChainableFunction;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.Function;
import com.tngtech.archunit.base.HasDescription;
import com.tngtech.archunit.base.Optional;
import com.tngtech.archunit.base.PackageMatcher;
import com.tngtech.archunit.core.MayResolveTypesViaReflection;
import com.tngtech.archunit.core.ResolvesTypesViaReflection;
import com.tngtech.archunit.core.domain.DomainObjectCreationContext.AccessContext;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
import com.tngtech.archunit.core.domain.properties.HasAnnotations;
import com.tngtech.archunit.core.domain.properties.HasModifiers;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation;
import com.tngtech.archunit.core.importer.DomainBuilders.JavaClassBuilder;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Sets.union;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
import static com.tngtech.archunit.base.ClassLoaders.getCurrentClassLoader;
import static com.tngtech.archunit.base.DescribedPredicate.equalTo;
import static com.tngtech.archunit.base.DescribedPredicate.not;
import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_SIMPLE_NAME;
import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME;
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Utils.toAnnotationOfType;
import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME;
import static com.tngtech.archunit.core.domain.properties.HasReturnType.Functions.GET_RAW_RETURN_TYPE;
import static com.tngtech.archunit.core.domain.properties.HasType.Functions.GET_RAW_TYPE;

public class JavaClass
        implements HasName.AndFullName, HasAnnotations, HasModifiers, HasDescription, HasSourceCodeLocation {
    private final Optional<Source> source;
    private final SourceCodeLocation sourceCodeLocation;
    private final JavaType javaType;
    private JavaPackage javaPackage;
    private final boolean isInterface;
    private final boolean isEnum;
    private final Set<JavaModifier> modifiers;
    private final Supplier<Class<?>> reflectSupplier;
    private Set<JavaField> fields = new HashSet<>();
    private Set<JavaCodeUnit> codeUnits = new HashSet<>();
    private Set<JavaMethod> methods = new HashSet<>();
    private Set<JavaMember> members = new HashSet<>();
    private Set<JavaConstructor> constructors = new HashSet<>();
    private Optional<JavaStaticInitializer> staticInitializer = Optional.absent();
    private Optional<JavaClass> superClass = Optional.absent();
    private final Set<JavaClass> interfaces = new HashSet<>();
    private final Set<JavaClass> subClasses = new HashSet<>();
    private Optional<JavaClass> enclosingClass = Optional.absent();
    private Supplier<Map<String, JavaAnnotation>> annotations = Suppliers
            .ofInstance(Collections.<String, JavaAnnotation>emptyMap());
    private Supplier<Set<JavaMethod>> allMethods;
    private Supplier<Set<JavaConstructor>> allConstructors;
    private Supplier<Set<JavaField>> allFields;
    private final Supplier<Set<JavaMember>> allMembers = Suppliers.memoize(new Supplier<Set<JavaMember>>() {
        @Override
        public Set<JavaMember> get() {
            return ImmutableSet.<JavaMember>builder().addAll(getAllFields()).addAll(getAllMethods())
                    .addAll(getAllConstructors()).build();
        }
    });
    private MemberDependenciesOnClass memberDependenciesOnClass;

    JavaClass(JavaClassBuilder builder) {
        source = checkNotNull(builder.getSource());
        javaType = checkNotNull(builder.getJavaType());
        isInterface = builder.isInterface();
        isEnum = builder.isEnum();
        modifiers = checkNotNull(builder.getModifiers());
        reflectSupplier = Suppliers.memoize(new ReflectClassSupplier());
        sourceCodeLocation = SourceCodeLocation.of(this);
        javaPackage = JavaPackage.simple(this);
    }

    @PublicAPI(usage = ACCESS)
    public Optional<Source> getSource() {
        return source;
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public SourceCodeLocation getSourceCodeLocation() {
        return sourceCodeLocation;
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public String getDescription() {
        return "Class <" + getName() + ">";
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public String getName() {
        return javaType.getName();
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public String getFullName() {
        return getName();
    }

    @PublicAPI(usage = ACCESS)
    public String getSimpleName() {
        return javaType.getSimpleName();
    }

    @PublicAPI(usage = ACCESS)
    public JavaPackage getPackage() {
        return javaPackage;
    }

    void setPackage(JavaPackage javaPackage) {
        this.javaPackage = checkNotNull(javaPackage);
    }

    @PublicAPI(usage = ACCESS)
    public String getPackageName() {
        return javaType.getPackageName();
    }

    @PublicAPI(usage = ACCESS)
    public boolean isPrimitive() {
        return javaType.isPrimitive();
    }

    @PublicAPI(usage = ACCESS)
    public boolean isInterface() {
        return isInterface;
    }

    @PublicAPI(usage = ACCESS)
    public boolean isEnum() {
        return isEnum;
    }

    @PublicAPI(usage = ACCESS)
    public boolean isArray() {
        return javaType.isArray();
    }

    @PublicAPI(usage = ACCESS)
    public boolean isInnerClass() {
        return enclosingClass.isPresent();
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public Set<JavaModifier> getModifiers() {
        return modifiers;
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public boolean isAnnotatedWith(Class<? extends Annotation> annotationType) {
        return isAnnotatedWith(annotationType.getName());
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public boolean isAnnotatedWith(String annotationTypeName) {
        return annotations.get().containsKey(annotationTypeName);
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public boolean isAnnotatedWith(DescribedPredicate<? super JavaAnnotation> predicate) {
        return CanBeAnnotated.Utils.isAnnotatedWith(annotations.get().values(), predicate);
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public boolean isMetaAnnotatedWith(Class<? extends Annotation> type) {
        return isMetaAnnotatedWith(type.getName());
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public boolean isMetaAnnotatedWith(String typeName) {
        return isMetaAnnotatedWith(GET_RAW_TYPE.then(GET_NAME).is(equalTo(typeName)));
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public boolean isMetaAnnotatedWith(DescribedPredicate<? super JavaAnnotation> predicate) {
        return CanBeAnnotated.Utils.isMetaAnnotatedWith(annotations.get().values(), predicate);
    }

    /**
     * @param type A given annotation type to match {@link JavaAnnotation JavaAnnotations} against
     * @return An {@link Annotation} of the given annotation type
     * @throws IllegalArgumentException if the class is note annotated with the given type
     * @see #isAnnotatedWith(Class)
     * @see #tryGetAnnotationOfType(Class)
     */
    @Override
    @PublicAPI(usage = ACCESS)
    public <A extends Annotation> A getAnnotationOfType(Class<A> type) {
        return getAnnotationOfType(type.getName()).as(type);
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public JavaAnnotation getAnnotationOfType(String typeName) {
        return tryGetAnnotationOfType(typeName)
                .getOrThrow(new IllegalArgumentException(String.format("Type %s is not annotated with @%s",
                        getSimpleName(), Formatters.ensureSimpleName(typeName))));
    }

    @Override
    @PublicAPI(usage = ACCESS)
    public Set<JavaAnnotation> getAnnotations() {
        return ImmutableSet.copyOf(annotations.get().values());
    }

    /**
     * @param type A given annotation type to match {@link JavaAnnotation JavaAnnotations} against
     * @return An {@link Optional} containing an {@link Annotation} of the given annotation type,
     * if this class is annotated with the given type, otherwise Optional.absent()
     * @see #isAnnotatedWith(Class)
     * @see #getAnnotationOfType(Class)
     */
    @Override
    @PublicAPI(usage = ACCESS)
    public <A extends Annotation> Optional<A> tryGetAnnotationOfType(Class<A> type) {
        return tryGetAnnotationOfType(type.getName()).transform(toAnnotationOfType(type));
    }

    /**
     * Same as {@link #tryGetAnnotationOfType(Class)}, but takes the type name.
     */
    @Override
    @PublicAPI(usage = ACCESS)
    public Optional<JavaAnnotation> tryGetAnnotationOfType(String typeName) {
        return Optional.fromNullable(annotations.get().get(typeName));
    }

    @PublicAPI(usage = ACCESS)
    public Optional<JavaClass> getSuperClass() {
        return superClass;
    }

    /**
     * @return The complete class hierarchy, i.e. the class itself and the result of {@link #getAllSuperClasses()}
     */
    @PublicAPI(usage = ACCESS)
    public List<JavaClass> getClassHierarchy() {
        ImmutableList.Builder<JavaClass> result = ImmutableList.builder();
        result.add(this);
        result.addAll(getAllSuperClasses());
        return result.build();
    }

    /**
     * @return All super classes sorted ascending by distance in the class hierarchy, i.e. first the direct super class,
     * then the super class of the super class and so on. Includes Object.class in the result.
     */
    @PublicAPI(usage = ACCESS)
    public List<JavaClass> getAllSuperClasses() {
        ImmutableList.Builder<JavaClass> result = ImmutableList.builder();
        JavaClass current = this;
        while (current.getSuperClass().isPresent()) {
            current = current.getSuperClass().get();
            result.add(current);
        }
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaClass> getSubClasses() {
        return subClasses;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaClass> getInterfaces() {
        return interfaces;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaClass> getAllInterfaces() {
        ImmutableSet.Builder<JavaClass> result = ImmutableSet.builder();
        for (JavaClass i : interfaces) {
            result.add(i);
            result.addAll(i.getAllInterfaces());
        }
        if (superClass.isPresent()) {
            result.addAll(superClass.get().getAllInterfaces());
        }
        return result.build();
    }

    /**
     * @return All classes, this class is assignable to, in particular
     * <ul>
     * <li>self</li>
     * <li>superclasses this class extends</li>
     * <li>interfaces this class implements</li>
     * </ul>
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaClass> getAllClassesSelfIsAssignableTo() {
        return ImmutableSet.<JavaClass>builder().add(this).addAll(getAllSuperClasses()).addAll(getAllInterfaces())
                .build();
    }

    @PublicAPI(usage = ACCESS)
    public Optional<JavaClass> getEnclosingClass() {
        return enclosingClass;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaClass> getAllSubClasses() {
        Set<JavaClass> result = new HashSet<>();
        for (JavaClass subClass : subClasses) {
            result.add(subClass);
            result.addAll(subClass.getAllSubClasses());
        }
        return result;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaMember> getMembers() {
        return members;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaMember> getAllMembers() {
        return allMembers.get();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaField> getFields() {
        return fields;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaField> getAllFields() {
        checkNotNull(allFields, "Method may not be called before construction of hierarchy is complete");
        return allFields.get();
    }

    @PublicAPI(usage = ACCESS)
    public JavaField getField(String name) {
        return tryGetField(name)
                .getOrThrow(new IllegalArgumentException("No field with name '" + name + " in class " + getName()));
    }

    @PublicAPI(usage = ACCESS)
    public Optional<JavaField> tryGetField(String name) {
        for (JavaField field : fields) {
            if (name.equals(field.getName())) {
                return Optional.of(field);
            }
        }
        return Optional.absent();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaCodeUnit> getCodeUnits() {
        return codeUnits;
    }

    /**
     * @param name       The name of the code unit, can be a method name, but also
     *                   {@link JavaConstructor#CONSTRUCTOR_NAME CONSTRUCTOR_NAME}
     *                   or {@link JavaStaticInitializer#STATIC_INITIALIZER_NAME STATIC_INITIALIZER_NAME}
     * @param parameters The parameter signature of the method specified as {@link Class Class} Objects
     * @return A code unit (method, constructor or static initializer) with the given signature
     */
    @PublicAPI(usage = ACCESS)
    public JavaCodeUnit getCodeUnitWithParameterTypes(String name, Class<?>... parameters) {
        return getCodeUnitWithParameterTypes(name, ImmutableList.copyOf(parameters));
    }

    /**
     * Same as {@link #getCodeUnitWithParameterTypes(String, Class[])}, but with parameter signature specified as full class names
     */
    @PublicAPI(usage = ACCESS)
    public JavaCodeUnit getCodeUnitWithParameterTypeNames(String name, String... parameters) {
        return getCodeUnitWithParameterTypeNames(name, ImmutableList.copyOf(parameters));
    }

    /**
     * @see #getCodeUnitWithParameterTypes(String, Class[])
     */
    @PublicAPI(usage = ACCESS)
    public JavaCodeUnit getCodeUnitWithParameterTypes(String name, List<Class<?>> parameters) {
        return getCodeUnitWithParameterTypeNames(name, namesOf(parameters));
    }

    /**
     * @see #getCodeUnitWithParameterTypeNames(String, String...)
     */
    @PublicAPI(usage = ACCESS)
    public JavaCodeUnit getCodeUnitWithParameterTypeNames(String name, List<String> parameters) {
        return findMatchingCodeUnit(codeUnits, name, parameters);
    }

    private <T extends JavaCodeUnit> T findMatchingCodeUnit(Set<T> codeUnits, String name,
            List<String> parameters) {
        Optional<T> codeUnit = tryFindMatchingCodeUnit(codeUnits, name, parameters);
        if (!codeUnit.isPresent()) {
            throw new IllegalArgumentException(
                    String.format("No code unit with name '%s' and parameters %s in codeUnits %s of class %s", name,
                            parameters, codeUnits, getName()));
        }
        return codeUnit.get();
    }

    private <T extends JavaCodeUnit> Optional<T> tryFindMatchingCodeUnit(Set<T> codeUnits, String name,
            List<String> parameters) {
        for (T codeUnit : codeUnits) {
            if (name.equals(codeUnit.getName()) && parameters.equals(codeUnit.getRawParameterTypes().getNames())) {
                return Optional.of(codeUnit);
            }
        }
        return Optional.absent();
    }

    @PublicAPI(usage = ACCESS)
    public JavaMethod getMethod(String name, Class<?>... parameters) {
        return findMatchingCodeUnit(methods, name, namesOf(parameters));
    }

    @PublicAPI(usage = ACCESS)
    public Optional<JavaMethod> tryGetMethod(String name, Class<?>... parameters) {
        return tryFindMatchingCodeUnit(methods, name, namesOf(parameters));
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaMethod> getMethods() {
        return methods;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaMethod> getAllMethods() {
        checkNotNull(allMethods, "Method may not be called before construction of hierarchy is complete");
        return allMethods.get();
    }

    @PublicAPI(usage = ACCESS)
    public JavaConstructor getConstructor(Class<?>... parameters) {
        return findMatchingCodeUnit(constructors, CONSTRUCTOR_NAME, namesOf(parameters));
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaConstructor> getConstructors() {
        return constructors;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaConstructor> getAllConstructors() {
        checkNotNull(allConstructors, "Method may not be called before construction of hierarchy is complete");
        return allConstructors.get();
    }

    @PublicAPI(usage = ACCESS)
    public Optional<JavaStaticInitializer> getStaticInitializer() {
        return staticInitializer;
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaAccess<?>> getAccessesFromSelf() {
        return union(getFieldAccessesFromSelf(), getCallsFromSelf());
    }

    /**
     * @return Set of all {@link JavaAccess} in the class hierarchy, as opposed to the accesses this class directly performs.
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaAccess<?>> getAllAccessesFromSelf() {
        ImmutableSet.Builder<JavaAccess<?>> result = ImmutableSet.builder();
        for (JavaClass clazz : getClassHierarchy()) {
            result.addAll(clazz.getAccessesFromSelf());
        }
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaFieldAccess> getFieldAccessesFromSelf() {
        ImmutableSet.Builder<JavaFieldAccess> result = ImmutableSet.builder();
        for (JavaCodeUnit codeUnit : codeUnits) {
            result.addAll(codeUnit.getFieldAccesses());
        }
        return result.build();
    }

    /**
     * Returns all calls of this class to methods or constructors.
     *
     * @see #getMethodCallsFromSelf()
     * @see #getConstructorCallsFromSelf()
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaCall<?>> getCallsFromSelf() {
        return union(getMethodCallsFromSelf(), getConstructorCallsFromSelf());
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaMethodCall> getMethodCallsFromSelf() {
        ImmutableSet.Builder<JavaMethodCall> result = ImmutableSet.builder();
        for (JavaCodeUnit codeUnit : codeUnits) {
            result.addAll(codeUnit.getMethodCallsFromSelf());
        }
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaConstructorCall> getConstructorCallsFromSelf() {
        ImmutableSet.Builder<JavaConstructorCall> result = ImmutableSet.builder();
        for (JavaCodeUnit codeUnit : codeUnits) {
            result.addAll(codeUnit.getConstructorCallsFromSelf());
        }
        return result.build();
    }

    /**
     * Returns all dependencies originating directly from this class (i.e. not just from a superclass),
     * where a dependency can be
     * <ul>
     * <li>field access</li>
     * <li>method call</li>
     * <li>constructor call</li>
     * <li>extending a class</li>
     * <li>implementing an interface</li>
     * <li>referencing in throws declaration</li>
     * </ul>
     *
     * @return All dependencies originating directly from this class (i.e. where this class is the origin)
     */
    @PublicAPI(usage = ACCESS)
    public Set<Dependency> getDirectDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        result.addAll(dependenciesFromAccesses(getAccessesFromSelf()));
        result.addAll(inheritanceDependenciesFromSelf());
        result.addAll(fieldDependenciesFromSelf());
        result.addAll(returnTypeDependenciesFromSelf());
        result.addAll(methodParameterDependenciesFromSelf());
        result.addAll(throwsDeclarationDependenciesFromSelf());
        result.addAll(constructorParameterDependenciesFromSelf());
        return result.build();
    }

    /**
     * Like {@link #getDirectDependenciesFromSelf()}, but instead returns all dependencies where this class
     * is target.
     *
     * @return Dependencies where this class is the target.
     */
    @PublicAPI(usage = ACCESS)
    public Set<Dependency> getDirectDependenciesToSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        result.addAll(dependenciesFromAccesses(getAccessesToSelf()));
        result.addAll(inheritanceDependenciesToSelf());
        result.addAll(fieldDependenciesToSelf());
        result.addAll(returnTypeDependenciesToSelf());
        result.addAll(methodParameterDependenciesToSelf());
        result.addAll(throwsDeclarationDependenciesToSelf());
        result.addAll(constructorParameterDependenciesToSelf());
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaFieldAccess> getFieldAccessesToSelf() {
        ImmutableSet.Builder<JavaFieldAccess> result = ImmutableSet.builder();
        for (JavaField field : fields) {
            result.addAll(field.getAccessesToSelf());
        }
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaMethodCall> getMethodCallsToSelf() {
        ImmutableSet.Builder<JavaMethodCall> result = ImmutableSet.builder();
        for (JavaMethod method : methods) {
            result.addAll(method.getCallsOfSelf());
        }
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaConstructorCall> getConstructorCallsToSelf() {
        ImmutableSet.Builder<JavaConstructorCall> result = ImmutableSet.builder();
        for (JavaConstructor constructor : constructors) {
            result.addAll(constructor.getCallsOfSelf());
        }
        return result.build();
    }

    @PublicAPI(usage = ACCESS)
    public Set<JavaAccess<?>> getAccessesToSelf() {
        return ImmutableSet.<JavaAccess<?>>builder().addAll(getFieldAccessesToSelf()).addAll(getMethodCallsToSelf())
                .addAll(getConstructorCallsToSelf()).build();
    }

    /**
     * @return Fields of all imported classes that have the type of this class.
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaField> getFieldsWithTypeOfSelf() {
        return memberDependenciesOnClass.getFieldsWithTypeOfClass();
    }

    /**
     * @return Methods of all imported classes that have a parameter type of this class.
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaMethod> getMethodsWithParameterTypeOfSelf() {
        return memberDependenciesOnClass.getMethodsWithParameterTypeOfClass();
    }

    /**
     * @return Methods of all imported classes that have a return type of this class.
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaMethod> getMethodsWithReturnTypeOfSelf() {
        return memberDependenciesOnClass.getMethodsWithReturnTypeOfClass();
    }

    /**
     * @return {@link ThrowsDeclaration ThrowsDeclarations} of all imported classes that have the type of this class.
     */
    @PublicAPI(usage = ACCESS)
    public Set<ThrowsDeclaration<JavaMethod>> getMethodThrowsDeclarationsWithTypeOfSelf() {
        return memberDependenciesOnClass.getMethodThrowsDeclarationsWithTypeOfClass();
    }

    /**
     * @return Constructors of all imported classes that have a parameter type of this class.
     */
    @PublicAPI(usage = ACCESS)
    public Set<JavaConstructor> getConstructorsWithParameterTypeOfSelf() {
        return memberDependenciesOnClass.getConstructorsWithParameterTypeOfClass();
    }

    /**
     * @return {@link ThrowsDeclaration ThrowsDeclarations} of all imported classes that have the type of this class.
     */
    @PublicAPI(usage = ACCESS)
    public Set<ThrowsDeclaration<JavaConstructor>> getConstructorsWithThrowsDeclarationTypeOfSelf() {
        return memberDependenciesOnClass.getConstructorsWithThrowsDeclarationTypeOfClass();
    }

    /**
     * @param clazz An arbitrary type
     * @return true, if this {@link JavaClass} represents the same class as the supplied {@link Class}, otherwise false
     */
    @PublicAPI(usage = ACCESS)
    public boolean isEquivalentTo(Class<?> clazz) {
        return getName().equals(clazz.getName());
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAssignableFrom(Class<?> type) {
        return isAssignableFrom(type.getName());
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAssignableFrom(String typeName) {
        return isAssignableFrom(GET_NAME.is(equalTo(typeName)));
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAssignableFrom(DescribedPredicate<? super JavaClass> predicate) {
        List<JavaClass> possibleTargets = ImmutableList.<JavaClass>builder().add(this).addAll(getAllSubClasses())
                .build();

        return anyMatches(possibleTargets, predicate);
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAssignableTo(Class<?> type) {
        return isAssignableTo(type.getName());
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAssignableTo(final String typeName) {
        return isAssignableTo(GET_NAME.is(equalTo(typeName)));
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAssignableTo(DescribedPredicate<? super JavaClass> predicate) {
        List<JavaClass> possibleTargets = ImmutableList.<JavaClass>builder().addAll(getClassHierarchy())
                .addAll(getAllInterfaces()).build();

        return anyMatches(possibleTargets, predicate);
    }

    private boolean anyMatches(List<JavaClass> possibleTargets, DescribedPredicate<? super JavaClass> predicate) {
        for (JavaClass javaClass : possibleTargets) {
            if (predicate.apply(javaClass)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Resolves the respective {@link Class} from the classpath.<br>
     * NOTE: This method will throw an exception, if the respective {@link Class} or any of its dependencies
     * can't be found on the classpath.
     *
     * @return The {@link Class} equivalent to this {@link JavaClass}
     */
    @ResolvesTypesViaReflection
    @PublicAPI(usage = ACCESS)
    public Class<?> reflect() {
        return reflectSupplier.get();
    }

    void completeClassHierarchyFrom(ImportContext context) {
        completeSuperClassFrom(context);
        completeInterfacesFrom(context);
        allFields = Suppliers.memoize(new Supplier<Set<JavaField>>() {
            @Override
            public Set<JavaField> get() {
                ImmutableSet.Builder<JavaField> result = ImmutableSet.builder();
                for (JavaClass javaClass : concat(getClassHierarchy(), getAllInterfaces())) {
                    result.addAll(javaClass.getFields());
                }
                return result.build();
            }
        });
        allMethods = Suppliers.memoize(new Supplier<Set<JavaMethod>>() {
            @Override
            public Set<JavaMethod> get() {
                ImmutableSet.Builder<JavaMethod> result = ImmutableSet.builder();
                for (JavaClass javaClass : concat(getClassHierarchy(), getAllInterfaces())) {
                    result.addAll(javaClass.getMethods());
                }
                return result.build();
            }
        });
        allConstructors = Suppliers.memoize(new Supplier<Set<JavaConstructor>>() {
            @Override
            public Set<JavaConstructor> get() {
                ImmutableSet.Builder<JavaConstructor> result = ImmutableSet.builder();
                for (JavaClass javaClass : getClassHierarchy()) {
                    result.addAll(javaClass.getConstructors());
                }
                return result.build();
            }
        });
    }

    private void completeSuperClassFrom(ImportContext context) {
        superClass = context.createSuperClass(this);
        if (superClass.isPresent()) {
            superClass.get().subClasses.add(this);
        }
    }

    private void completeInterfacesFrom(ImportContext context) {
        interfaces.addAll(context.createInterfaces(this));
        for (JavaClass i : interfaces) {
            i.subClasses.add(this);
        }
    }

    void completeMembers(final ImportContext context) {
        fields = context.createFields(this);
        methods = context.createMethods(this);
        constructors = context.createConstructors(this);
        staticInitializer = context.createStaticInitializer(this);
        codeUnits = ImmutableSet.<JavaCodeUnit>builder().addAll(methods).addAll(constructors)
                .addAll(staticInitializer.asSet()).build();
        members = ImmutableSet.<JavaMember>builder().addAll(fields).addAll(methods).addAll(constructors).build();
        this.annotations = Suppliers.memoize(new Supplier<Map<String, JavaAnnotation>>() {
            @Override
            public Map<String, JavaAnnotation> get() {
                return context.createAnnotations(JavaClass.this);
            }
        });
    }

    CompletionProcess completeFrom(ImportContext context) {
        enclosingClass = context.createEnclosingClass(this);
        memberDependenciesOnClass = new MemberDependenciesOnClass(context.getFieldsOfType(this),
                context.getMethodsWithParameterOfType(this), context.getMethodsWithReturnType(this),
                context.getMethodThrowsDeclarationsOfType(this), context.getConstructorsWithParameterOfType(this),
                context.getConstructorThrowsDeclarationsOfType(this));
        return new CompletionProcess();
    }

    @Override
    public String toString() {
        return "JavaClass{name='" + javaType.getName() + "\'}";
    }

    @PublicAPI(usage = ACCESS)
    public static List<String> namesOf(Class<?>... paramTypes) {
        return namesOf(ImmutableList.copyOf(paramTypes));
    }

    @PublicAPI(usage = ACCESS)
    public static List<String> namesOf(List<Class<?>> paramTypes) {
        ArrayList<String> result = new ArrayList<>();
        for (Class<?> paramType : paramTypes) {
            result.add(paramType.getName());
        }
        return result;
    }

    @PublicAPI(usage = ACCESS)
    public boolean isAnonymous() {
        return getSimpleName().isEmpty();
    }

    private Set<Dependency> dependenciesFromAccesses(Set<JavaAccess<?>> accesses) {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaAccess<?> access : filterNoSelfAccess(accesses)) {
            result.add(Dependency.from(access));
        }
        return result.build();
    }

    private Set<Dependency> inheritanceDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaClass superType : FluentIterable.from(getInterfaces()).append(getSuperClass().asSet())) {
            result.add(Dependency.fromInheritance(this, superType));
        }
        return result.build();
    }

    private Set<Dependency> fieldDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaField field : nonPrimitive(getFields(), GET_RAW_TYPE)) {
            result.add(Dependency.fromField(field));
        }
        return result.build();
    }

    private Set<Dependency> returnTypeDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaMethod method : nonPrimitive(getMethods(), GET_RAW_RETURN_TYPE)) {
            result.add(Dependency.fromReturnType(method));
        }
        return result.build();
    }

    private Set<Dependency> methodParameterDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaMethod method : getMethods()) {
            for (JavaClass parameter : nonPrimitive(method.getRawParameterTypes())) {
                result.add(Dependency.fromParameter(method, parameter));
            }
        }
        return result.build();
    }

    private Set<Dependency> throwsDeclarationDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaCodeUnit codeUnit : getCodeUnits()) {
            for (ThrowsDeclaration<? extends JavaCodeUnit> throwsDeclaration : codeUnit.getThrowsClause()) {
                result.add(Dependency.fromThrowsDeclaration(throwsDeclaration));
            }
        }
        return result.build();
    }

    private Set<Dependency> constructorParameterDependenciesFromSelf() {
        ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
        for (JavaConstructor constructor : getConstructors()) {
            for (JavaClass parameter : nonPrimitive(constructor.getRawParameterTypes())) {
                result.add(Dependency.fromParameter(constructor, parameter));
            }
        }
        return result.build();
    }

    private Set<Dependency> inheritanceDependenciesToSelf() {
        Set<Dependency> result = new HashSet<>();
        for (JavaClass subClass : getSubClasses()) {
            result.add(Dependency.fromInheritance(subClass, this));
        }
        return result;
    }

    private Set<Dependency> fieldDependenciesToSelf() {
        Set<Dependency> result = new HashSet<>();
        for (JavaField field : getFieldsWithTypeOfSelf()) {
            result.add(Dependency.fromField(field));
        }
        return result;
    }

    private Set<Dependency> returnTypeDependenciesToSelf() {
        Set<Dependency> result = new HashSet<>();
        for (JavaMethod method : getMethodsWithReturnTypeOfSelf()) {
            result.add(Dependency.fromReturnType(method));
        }
        return result;
    }

    private Set<Dependency> methodParameterDependenciesToSelf() {
        Set<Dependency> result = new HashSet<>();
        for (JavaMethod method : getMethodsWithParameterTypeOfSelf()) {
            result.add(Dependency.fromParameter(method, this));
        }
        return result;
    }

    private Set<Dependency> throwsDeclarationDependenciesToSelf() {
        Set<Dependency> result = new HashSet<>();
        for (ThrowsDeclaration<? extends JavaCodeUnit> throwsDeclaration : memberDependenciesOnClass
                .getThrowsDeclarationsWithTypeOfClass()) {
            result.add(Dependency.fromThrowsDeclaration(throwsDeclaration));
        }
        return result;
    }

    private Set<Dependency> constructorParameterDependenciesToSelf() {
        Set<Dependency> result = new HashSet<>();
        for (JavaConstructor constructor : getConstructorsWithParameterTypeOfSelf()) {
            result.add(Dependency.fromParameter(constructor, this));
        }
        return result;
    }

    private Set<JavaAccess<?>> filterNoSelfAccess(Set<? extends JavaAccess<?>> accesses) {
        Set<JavaAccess<?>> result = new HashSet<>();
        for (JavaAccess<?> access : accesses) {
            if (!access.getTargetOwner().equals(access.getOriginOwner())) {
                result.add(access);
            }
        }
        return result;
    }

    private Set<JavaClass> nonPrimitive(Collection<JavaClass> classes) {
        return nonPrimitive(classes, com.tngtech.archunit.base.Function.Functions.<JavaClass>identity());
    }

    private <T> Set<T> nonPrimitive(Collection<T> members, Function<? super T, JavaClass> getRelevantType) {
        ImmutableSet.Builder<T> result = ImmutableSet.builder();
        for (T member : members) {
            if (!getRelevantType.apply(member).isPrimitive()) {
                result.add(member);
            }
        }
        return result.build();
    }

    private static class MemberDependenciesOnClass {
        private final Set<JavaField> fieldsWithTypeOfClass;
        private final Set<JavaMethod> methodsWithParameterTypeOfClass;
        private final Set<JavaMethod> methodsWithReturnTypeOfClass;
        private final Set<ThrowsDeclaration<JavaMethod>> methodsWithThrowsDeclarationTypeOfClass;
        private final Set<JavaConstructor> constructorsWithParameterTypeOfClass;
        private final Set<ThrowsDeclaration<JavaConstructor>> constructorsWithThrowsDeclarationTypeOfClass;

        MemberDependenciesOnClass(Set<JavaField> fieldsWithTypeOfClass,
                Set<JavaMethod> methodsWithParameterTypeOfClass, Set<JavaMethod> methodsWithReturnTypeOfClass,
                Set<ThrowsDeclaration<JavaMethod>> methodsWithThrowsDeclarationTypeOfClass,
                Set<JavaConstructor> constructorsWithParameterTypeOfClass,
                Set<ThrowsDeclaration<JavaConstructor>> constructorsWithThrowsDeclarationTypeOfClass) {

            this.fieldsWithTypeOfClass = ImmutableSet.copyOf(fieldsWithTypeOfClass);
            this.methodsWithParameterTypeOfClass = ImmutableSet.copyOf(methodsWithParameterTypeOfClass);
            this.methodsWithReturnTypeOfClass = ImmutableSet.copyOf(methodsWithReturnTypeOfClass);
            this.methodsWithThrowsDeclarationTypeOfClass = ImmutableSet
                    .copyOf(methodsWithThrowsDeclarationTypeOfClass);
            this.constructorsWithParameterTypeOfClass = ImmutableSet.copyOf(constructorsWithParameterTypeOfClass);
            this.constructorsWithThrowsDeclarationTypeOfClass = ImmutableSet
                    .copyOf(constructorsWithThrowsDeclarationTypeOfClass);
        }

        Set<JavaField> getFieldsWithTypeOfClass() {
            return fieldsWithTypeOfClass;
        }

        Set<JavaMethod> getMethodsWithParameterTypeOfClass() {
            return methodsWithParameterTypeOfClass;
        }

        Set<JavaMethod> getMethodsWithReturnTypeOfClass() {
            return methodsWithReturnTypeOfClass;
        }

        Set<ThrowsDeclaration<JavaMethod>> getMethodThrowsDeclarationsWithTypeOfClass() {
            return methodsWithThrowsDeclarationTypeOfClass;
        }

        Set<JavaConstructor> getConstructorsWithParameterTypeOfClass() {
            return constructorsWithParameterTypeOfClass;
        }

        Set<ThrowsDeclaration<JavaConstructor>> getConstructorsWithThrowsDeclarationTypeOfClass() {
            return constructorsWithThrowsDeclarationTypeOfClass;
        }

        Set<ThrowsDeclaration<? extends JavaCodeUnit>> getThrowsDeclarationsWithTypeOfClass() {
            return union(methodsWithThrowsDeclarationTypeOfClass, constructorsWithThrowsDeclarationTypeOfClass);
        }
    }

    public static final class Functions {
        private Functions() {
        }

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, String> GET_SIMPLE_NAME = new ChainableFunction<JavaClass, String>() {
            @Override
            public String apply(JavaClass input) {
                return input.getSimpleName();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, String> GET_PACKAGE_NAME = new ChainableFunction<JavaClass, String>() {
            @Override
            public String apply(JavaClass input) {
                return input.getPackageName();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, JavaPackage> GET_PACKAGE = new ChainableFunction<JavaClass, JavaPackage>() {
            @Override
            public JavaPackage apply(JavaClass input) {
                return input.getPackage();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<JavaFieldAccess>> GET_FIELD_ACCESSES_FROM_SELF = new ChainableFunction<JavaClass, Set<JavaFieldAccess>>() {
            @Override
            public Set<JavaFieldAccess> apply(JavaClass input) {
                return input.getFieldAccessesFromSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<JavaMethodCall>> GET_METHOD_CALLS_FROM_SELF = new ChainableFunction<JavaClass, Set<JavaMethodCall>>() {
            @Override
            public Set<JavaMethodCall> apply(JavaClass input) {
                return input.getMethodCallsFromSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<JavaConstructorCall>> GET_CONSTRUCTOR_CALLS_FROM_SELF = new ChainableFunction<JavaClass, Set<JavaConstructorCall>>() {
            @Override
            public Set<JavaConstructorCall> apply(JavaClass input) {
                return input.getConstructorCallsFromSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<JavaCall<?>>> GET_CALLS_FROM_SELF = new ChainableFunction<JavaClass, Set<JavaCall<?>>>() {
            @Override
            public Set<JavaCall<?>> apply(JavaClass input) {
                return input.getCallsFromSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<JavaAccess<?>>> GET_ACCESSES_FROM_SELF = new ChainableFunction<JavaClass, Set<JavaAccess<?>>>() {
            @Override
            public Set<JavaAccess<?>> apply(JavaClass input) {
                return input.getAccessesFromSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<Dependency>> GET_DIRECT_DEPENDENCIES_FROM_SELF = new ChainableFunction<JavaClass, Set<Dependency>>() {
            @Override
            public Set<Dependency> apply(JavaClass input) {
                return input.getDirectDependenciesFromSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<JavaAccess<?>>> GET_ACCESSES_TO_SELF = new ChainableFunction<JavaClass, Set<JavaAccess<?>>>() {
            @Override
            public Set<JavaAccess<?>> apply(JavaClass input) {
                return input.getAccessesToSelf();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static final ChainableFunction<JavaClass, Set<Dependency>> GET_DIRECT_DEPENDENCIES_TO_SELF = new ChainableFunction<JavaClass, Set<Dependency>>() {
            @Override
            public Set<Dependency> apply(JavaClass input) {
                return input.getDirectDependenciesToSelf();
            }
        };
    }

    public static final class Predicates {
        private Predicates() {
        }

        @PublicAPI(usage = ACCESS)
        public static final DescribedPredicate<JavaClass> INTERFACES = new DescribedPredicate<JavaClass>(
                "interfaces") {
            @Override
            public boolean apply(JavaClass input) {
                return input.isInterface();
            }
        };

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> type(final Class<?> type) {
            return equalTo(type.getName()).<JavaClass>onResultOf(GET_NAME).as("type " + type.getName());
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> simpleName(final String name) {
            return equalTo(name).onResultOf(GET_SIMPLE_NAME).as("simple name '%s'", name);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> simpleNameStartingWith(final String prefix) {
            return new SimpleNameStartingWithPredicate(prefix);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> simpleNameContaining(final String infix) {
            return new SimpleNameContainingPredicate(infix);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> simpleNameEndingWith(final String suffix) {
            return new SimpleNameEndingWithPredicate(suffix);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> assignableTo(final Class<?> type) {
            return assignableTo(type.getName());
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> assignableFrom(final Class<?> type) {
            return assignableFrom(type.getName());
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> assignableTo(final String typeName) {
            return assignableTo(GET_NAME.is(equalTo(typeName)).as(typeName));
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> assignableFrom(final String typeName) {
            return assignableFrom(GET_NAME.is(equalTo(typeName)).as(typeName));
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> assignableTo(
                final DescribedPredicate<? super JavaClass> predicate) {
            return new AssignableToPredicate(predicate);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> assignableFrom(
                final DescribedPredicate<? super JavaClass> predicate) {
            return new AssignableFromPredicate(predicate);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> implement(final Class<?> type) {
            if (!type.isInterface()) {
                throw new InvalidSyntaxUsageException(String.format(
                        "implement(type) can only ever be true, if type is an interface, but type %s is not",
                        type.getName()));
            }
            return implement(type.getName());
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> implement(final String typeName) {
            return implement(GET_NAME.is(equalTo(typeName)).as(typeName));
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> implement(
                final DescribedPredicate<? super JavaClass> predicate) {
            DescribedPredicate<JavaClass> selfIsImplementation = not(INTERFACES);
            DescribedPredicate<JavaClass> interfacePredicate = predicate.<JavaClass>forSubType().and(INTERFACES);
            return selfIsImplementation.and(assignableTo(interfacePredicate))
                    .as("implement " + predicate.getDescription());
        }

        /**
         * Offers a syntax to identify packages similar to AspectJ. In particular '*' stands for any sequence of
         * characters, '..' stands for any sequence of packages.
         * For further details see {@link PackageMatcher}.
         *
         * @param packageIdentifier A string representing the identifier to match packages against
         * @return A {@link DescribedPredicate} returning true iff the package of the
         * tested {@link JavaClass} matches the identifier
         */
        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> resideInAPackage(final String packageIdentifier) {
            return resideInAnyPackage(new String[] { packageIdentifier },
                    String.format("reside in a package '%s'", packageIdentifier));
        }

        /**
         * @see #resideInAPackage(String)
         */
        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> resideInAnyPackage(final String... packageIdentifiers) {
            return resideInAnyPackage(packageIdentifiers,
                    String.format("reside in any package ['%s']", Joiner.on("', '").join(packageIdentifiers)));
        }

        private static DescribedPredicate<JavaClass> resideInAnyPackage(final String[] packageIdentifiers,
                final String description) {
            final Set<PackageMatcher> packageMatchers = new HashSet<>();
            for (String identifier : packageIdentifiers) {
                packageMatchers.add(PackageMatcher.of(identifier));
            }
            return new PackageMatchesPredicate(packageMatchers, description);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> resideOutsideOfPackage(String packageIdentifier) {
            return not(resideInAPackage(packageIdentifier)).as("reside outside of package '%s'", packageIdentifier);
        }

        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> resideOutsideOfPackages(String... packageIdentifiers) {
            return not(JavaClass.Predicates.resideInAnyPackage(packageIdentifiers))
                    .as("reside outside of packages ['%s']", Joiner.on("', '").join(packageIdentifiers));
        }

        /**
         * @see JavaClass#isEquivalentTo(Class)
         */
        @PublicAPI(usage = ACCESS)
        public static DescribedPredicate<JavaClass> equivalentTo(final Class<?> clazz) {
            return new EquivalentToPredicate(clazz);
        }

        private static class SimpleNameStartingWithPredicate extends DescribedPredicate<JavaClass> {
            private final String prefix;

            SimpleNameStartingWithPredicate(String prefix) {
                super(String.format("simple name starting with '%s'", prefix));
                this.prefix = prefix;
            }

            @Override
            public boolean apply(JavaClass input) {
                return input.getSimpleName().startsWith(prefix);
            }
        }

        private static class SimpleNameContainingPredicate extends DescribedPredicate<JavaClass> {
            private final String infix;

            SimpleNameContainingPredicate(String infix) {
                super(String.format("simple name containing '%s'", infix));
                this.infix = infix;
            }

            @Override
            public boolean apply(JavaClass input) {
                return input.getSimpleName().contains(infix);
            }
        }

        private static class SimpleNameEndingWithPredicate extends DescribedPredicate<JavaClass> {
            private final String suffix;

            SimpleNameEndingWithPredicate(String suffix) {
                super(String.format("simple name ending with '%s'", suffix));
                this.suffix = suffix;
            }

            @Override
            public boolean apply(JavaClass input) {
                return input.getSimpleName().endsWith(suffix);
            }
        }

        private static class AssignableToPredicate extends DescribedPredicate<JavaClass> {
            private final DescribedPredicate<? super JavaClass> predicate;

            AssignableToPredicate(DescribedPredicate<? super JavaClass> predicate) {
                super("assignable to " + predicate.getDescription());
                this.predicate = predicate;
            }

            @Override
            public boolean apply(JavaClass input) {
                return input.isAssignableTo(predicate);
            }
        }

        private static class AssignableFromPredicate extends DescribedPredicate<JavaClass> {
            private final DescribedPredicate<? super JavaClass> predicate;

            AssignableFromPredicate(DescribedPredicate<? super JavaClass> predicate) {
                super("assignable from " + predicate.getDescription());
                this.predicate = predicate;
            }

            @Override
            public boolean apply(JavaClass input) {
                return input.isAssignableFrom(predicate);
            }
        }

        private static class PackageMatchesPredicate extends DescribedPredicate<JavaClass> {
            private final Set<PackageMatcher> packageMatchers;

            PackageMatchesPredicate(Set<PackageMatcher> packageMatchers, String description) {
                super(description);
                this.packageMatchers = packageMatchers;
            }

            @Override
            public boolean apply(JavaClass input) {
                for (PackageMatcher matcher : packageMatchers) {
                    if (matcher.matches(input.getPackageName())) {
                        return true;
                    }
                }
                return false;
            }
        }

        private static class EquivalentToPredicate extends DescribedPredicate<JavaClass> {
            private final Class<?> clazz;

            EquivalentToPredicate(Class<?> clazz) {
                super("equivalent to %s", clazz.getName());
                this.clazz = clazz;
            }

            @Override
            public boolean apply(JavaClass input) {
                return input.isEquivalentTo(clazz);
            }
        }
    }

    class CompletionProcess {
        AccessContext.Part completeCodeUnitsFrom(ImportContext context) {
            AccessContext.Part part = new AccessContext.Part();
            for (JavaCodeUnit codeUnit : codeUnits) {
                part.mergeWith(codeUnit.completeFrom(context));
            }
            return part;
        }
    }

    @ResolvesTypesViaReflection
    @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process")
    private class ReflectClassSupplier implements Supplier<Class<?>> {
        @Override
        public Class<?> get() {
            return javaType.resolveClass(getCurrentClassLoader(getClass()));
        }
    }
}