Skip to content

Commit

Permalink
#748 Document classpath scanning and type references
Browse files Browse the repository at this point in the history
  • Loading branch information
GuusLieben committed Dec 28, 2023
1 parent 6634c73 commit 1a48f3e
Show file tree
Hide file tree
Showing 17 changed files with 254 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.dockbox.hartshorn.application.environment;

import org.dockbox.hartshorn.component.condition.ConditionMatcher;
import org.dockbox.hartshorn.util.introspect.scan.ClassReferenceLoadException;
import org.dockbox.hartshorn.util.introspect.scan.TypeCollectionException;
import org.dockbox.hartshorn.util.introspect.scan.TypeReference;
Expand All @@ -37,7 +36,6 @@ public class EnvironmentTypeCollector {
private final ApplicationEnvironment environment;

public EnvironmentTypeCollector(ApplicationEnvironment environment) {
ConditionMatcher matcher = new ConditionMatcher(environment.applicationContext());
this.environment = environment;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
import java.util.HashSet;
import java.util.Set;

/**
* An {@link AggregateTypeReferenceCollector} that collects {@link TypeReference}s from multiple
* {@link TypeReferenceCollector}s. The provided collectors are invoked in no particular order.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class AggregateTypeReferenceCollector implements TypeReferenceCollector{

private final Set<TypeReferenceCollector> collectors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@

import java.util.Set;

/**
* A {@link TypeReferenceCollector} that caches the result of a delegate collector. The cache is populated on the
* first invocation of {@link #collect()}, and is reused for all subsequent invocations.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class CachedTypeReferenceCollector implements TypeReferenceCollector {

private final TypeReferenceCollector collector;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@

import java.util.Objects;

/**
* A {@link TypeReference} that references a class by its fully qualified name. This reference can be used to
* load the class, or to obtain information about the class.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class ClassNameReference implements TypeReference {

private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@

package org.dockbox.hartshorn.util.introspect.scan;

/**
* A {@link TypeReference} that represents a {@link Class}. No guarantees are made about the state of the class
* represented by this reference. It is possible that the class is not yet initialized.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class ClassReference implements TypeReference {

private final Class<?> type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* A {@link TypeReferenceCollector} that provides a predefined set of {@link TypeReference}s. The provided set is
* returned on every invocation of {@link #collect()}. This is useful when classpath scanning is not desired, but
* standalone components are to be used.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public final class PredefinedSetTypeReferenceCollector implements TypeReferenceCollector {

private final Set<TypeReference> references;
Expand All @@ -35,12 +44,26 @@ public Set<TypeReference> collect() {
return this.references;
}

/**
* Creates a new instance of {@link PredefinedSetTypeReferenceCollector} that provides the provided set of
* {@link Class classes} as {@link TypeReference}s.
*
* @param references The classes to provide as {@link TypeReference}s
* @return A new instance of {@link PredefinedSetTypeReferenceCollector}
*/
public static PredefinedSetTypeReferenceCollector of(Set<Class<?>> references) {
return new PredefinedSetTypeReferenceCollector(references.stream()
.map(ClassReference::new)
.collect(Collectors.toSet()));
}

/**
* Creates a new instance of {@link PredefinedSetTypeReferenceCollector} that provides the provided set of
* {@link Class classes} as {@link TypeReference}s.
*
* @param references The classes to provide as {@link TypeReference}s
* @return A new instance of {@link PredefinedSetTypeReferenceCollector}
*/
public static PredefinedSetTypeReferenceCollector of(Class<?>... references) {
return new PredefinedSetTypeReferenceCollector(Stream.of(references)
.map(ClassReference::new)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@

import org.dockbox.hartshorn.util.ApplicationException;

/**
* Thrown when a {@link TypeReferenceCollector} encounters an error while collecting {@link TypeReference}s.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class TypeCollectionException extends ApplicationException {
public TypeCollectionException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,43 @@

package org.dockbox.hartshorn.util.introspect.scan;

/**
* A reference to a {@link Class} that can be used to load the class, or to obtain information about the class.
* Implementations of this interface are expected to be immutable.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public interface TypeReference {

/**
* Loads the class that is referenced by this instance. Where possible, this should not initialize
* the class.
*
* @return The class that is referenced by this instance.
* @throws ClassReferenceLoadException When the class cannot be loaded.
*/
Class<?> getOrLoad() throws ClassReferenceLoadException;

/**
* Returns the fully qualified name of the class that is referenced by this instance.
*
* @return The fully qualified name of the class that is referenced by this instance.
*/
String qualifiedName();

/**
* Returns the simple name of the class that is referenced by this instance.
*
* @return The simple name of the class that is referenced by this instance.
*/
String simpleName();

/**
* Returns the name of the package that contains the class that is referenced by this instance.
*
* @return The name of the package that contains the class that is referenced by this instance.
*/
String packageName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@

import java.util.Set;

/**
* A {@link TypeReferenceCollector} is responsible for collecting {@link TypeReference}s. The result of a collection
* is a {@link Set} of {@link TypeReference}s. Type collecting is often used by application environments to discover
* types that are available on the classpath.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public interface TypeReferenceCollector extends Reportable {

/**
* Collects {@link TypeReference}s.
*
* @return A {@link Set} of {@link TypeReference}s
* @throws TypeCollectionException When an error occurs while collecting {@link TypeReference}s.
*/
Set<TypeReference> collect() throws TypeCollectionException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,41 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* A {@link TypeReferenceCollectorContext} is a {@link org.dockbox.hartshorn.context.Context} that is used
* to track {@link TypeReferenceCollector} which are used by the application to discover types on the classpath.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class TypeReferenceCollectorContext extends DefaultContext implements Reportable {

private final Set<TypeReferenceCollector> collectors = ConcurrentHashMap.newKeySet();

/**
* Registers a {@link TypeReferenceCollector} with this context.
*
* @param collector The collector to register
*/
public void register(TypeReferenceCollector collector) {
this.collectors.add(collector);
}

/**
* Creates a new {@link AggregateTypeReferenceCollector} that combines all registered collectors.
*
* @return A new {@link AggregateTypeReferenceCollector}
*/
public TypeReferenceCollector collector() {
return new AggregateTypeReferenceCollector(this.collectors);
}

/**
* Returns all registered collectors.
*
* @return A {@link Set} of {@link TypeReferenceCollector}s
*/
public Set<TypeReferenceCollector> collectors() {
return Collections.unmodifiableSet(this.collectors);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@
import java.net.URLClassLoader;
import java.nio.file.Path;

record ClassCandidateResource(URLClassLoader classLoader, Path path, String resourceName, boolean isClassResource)
implements ClassPathResource {
}
/**
* Represents a resource on the classpath. Resources are provided by {@link ClassPathScanner}s and handed
* over to {@link ResourceHandler}s for further processing.
*
* @param classLoader The classloader from which this resource was, or should be, loaded.
* @param path The path to the resource. This is the path as it is found on the classpath, and may be inside
* @param resourceName The name of the resource. For classes this is the fully qualified class name, for other
* @param isClassResource Whether this resource is a class. If {@code true}, the resource can be loaded as a class.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
record ClassCandidateResource(
URLClassLoader classLoader,
Path path,
String resourceName,
boolean isClassResource
) implements ClassPathResource { }
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,45 @@

import java.nio.file.Path;

/**
* Represents a resource on the classpath. Resources are provided by {@link ClassPathScanner}s and handed
* over to {@link ResourceHandler}s for further processing.
*
* @see ClassPathScanner
* @see ResourceHandler
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public interface ClassPathResource {

/**
* The classloader from which this resource was, or should be, loaded.
*
* @return The classloader, never {@code null}.
*/
ClassLoader classLoader();

/**
* The path to the resource. This is the path as it is found on the classpath, and may be inside
* a directory or archive.
*
* @return The path, never {@code null}.
*/
Path path();

/**
* The name of the resource. For classes this is the fully qualified class name, for other
* resources this is the path to the resource.
* @return The name, never {@code null}.
*/
String resourceName();

/**
* Whether this resource is a class. If {@code true}, the resource can be loaded as a class.
*
* @return {@code true} if this resource is a class, {@code false} otherwise.
*/
boolean isClassResource();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
import java.util.HashSet;
import java.util.Set;

/**
* A {@link ClasspathTypeReferenceCollector} that collects {@link TypeReference}s from a classpath using a
* {@link ClassPathScanner}. This automatically includes the default classpath, and filters on the configured
* package name. Scanning does not include any non-class resources.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class ClassPathScannerTypeReferenceCollector extends ClasspathTypeReferenceCollector {

public ClassPathScannerTypeReferenceCollector(String packageName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@

import org.dockbox.hartshorn.util.ApplicationException;

/**
* An exception that is thrown when an error occurs while walking the classpath.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public class ClassPathWalkingException extends ApplicationException {

public ClassPathWalkingException(String message) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@

package org.dockbox.hartshorn.util.introspect.scan.classpath;

import java.util.Objects;
import java.util.Set;

import org.dockbox.hartshorn.util.collections.ConcurrentSetMultiMap;
import org.dockbox.hartshorn.util.collections.MultiMap;
import org.dockbox.hartshorn.util.introspect.scan.TypeCollectionException;
import org.dockbox.hartshorn.util.introspect.scan.TypeReference;
import org.dockbox.hartshorn.util.introspect.scan.TypeReferenceCollector;

import java.util.Objects;
import java.util.Set;

/**
* A {@link TypeReferenceCollector} that collects {@link TypeReference}s from a classpath. This class is abstract, as
* it does not provide a mechanism for collecting {@link TypeReference}s. Instead, it provides a caching mechanism that
* is shared between all instances of this class. This caching mechanism is used to prevent the same classpath from
* being scanned multiple times.
*
* @since 0.4.13
*
* @author Guus Lieben
*/
public abstract class ClasspathTypeReferenceCollector implements TypeReferenceCollector {

private static final MultiMap<String, TypeReference> BATCH_CACHE = new ConcurrentSetMultiMap<>();
Expand All @@ -34,6 +44,11 @@ protected ClasspathTypeReferenceCollector(String packageName) {
this.packageName = packageName;
}

/**
* Returns the name of the package that is scanned by this instance.
*
* @return The name of the package that is being scanned.
*/
public String packageName() {
return this.packageName;
}
Expand All @@ -47,6 +62,14 @@ public Set<TypeReference> collect() throws TypeCollectionException {
return Set.copyOf(BATCH_CACHE.get(this.packageName));
}

/**
* Collects {@link TypeReference}s from the classpath. Implementations of this method are expected to return a
* {@link Set} of {@link TypeReference}s that are found on the classpath. Implementations are not expected to
* cache the result of this method, as the result is cached internally by this class.
*
* @return A {@link Set} of {@link TypeReference}s that are found on the classpath.
* @throws TypeCollectionException If the collection of {@link TypeReference}s fails.
*/
protected abstract Set<TypeReference> createCache() throws TypeCollectionException;

@Override
Expand Down
Loading

0 comments on commit 1a48f3e

Please sign in to comment.