diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java
index 157b911001f..82cb14e3d5a 100644
--- a/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/agent/builder/AgentBuilder.java
@@ -268,6 +268,37 @@ public interface AgentBuilder {
*/
Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
+ /**
+ *
+ * Matches a type being loaded in order to apply the supplied {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s before loading this type.
+ * If several matchers positively match a type only the latest registered matcher is considered for transformation.
+ *
+ *
+ * If this matcher is chained with additional subsequent matchers, this matcher is always executed first whereas the following matchers are
+ * executed in the order of their execution. If any matcher indicates that a type is to be matched, none of the following matchers is still queried.
+ *
+ *
+ * Note: When applying a matcher, regard the performance implications by {@link AgentBuilder#ignore(ElementMatcher)}. The former
+ * matcher is applied first such that it makes sense to ignore name spaces that are irrelevant to instrumentation. If possible, it
+ * is also recommended, to exclude class loaders such as for example the bootstrap class loader.
+ *
+ *
+ * @param typeMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied on the type being
+ * loaded that decides if the entailed
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s should be applied for
+ * that type.
+ * @param classLoaderMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied to the
+ * {@link java.lang.ClassLoader} that is loading the type being loaded. This matcher
+ * is always applied second where the type matcher is not applied in case that this
+ * matcher does not indicate a match.
+ * @param moduleMatcher An {@link net.bytebuddy.matcher.ElementMatcher} that is applied to the {@link JavaModule}
+ * of the type being loaded. This matcher is always applied first where the class loader and
+ * type matchers are not applied in case that this matcher does not indicate a match. On a JVM
+ * that does not support modules, the Java module is represented by {@code null}.
+ * @return A definable that represents this agent builder which allows for the definition of one or several
+ * {@link net.bytebuddy.agent.builder.AgentBuilder.Transformer}s to be applied when both the given
+ * {@code typeMatcher} and {@code classLoaderMatcher} indicate a match.
+ */
Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
@@ -344,6 +375,31 @@ Identified.Narrowable type(ElementMatcher super TypeDescription> typeMatcher,
*/
Ignored ignore(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
+ /**
+ *
+ * Excludes any type that is matched by the provided matcher and is loaded by a class loader matching the second matcher.
+ * By default, Byte Buddy does not instrument synthetic types or types that are loaded by the bootstrap class loader.
+ *
+ *
+ * When ignoring a type, any subsequently chained matcher is applied after this matcher in the order of their registration. Also, if
+ * any matcher indicates that a type is to be ignored, none of the following chained matchers is executed.
+ *
+ *
+ * Note: For performance reasons, it is recommended to always include a matcher that excludes as many namespaces
+ * as possible. Byte Buddy can determine a type's name without parsing its class file and can therefore discard such
+ * types with minimal overhead. When a different property of a type - such as for example its modifiers or its annotations
+ * is accessed - Byte Buddy parses the class file lazily in order to allow for such a matching. Therefore, any exclusion
+ * of a name should always be done as a first step and even if it does not influence the selection of what types are
+ * matched. Without changing this property, the class file of every type is being parsed!
+ *
+ *
+ * @param typeMatcher A matcher that identifies types that should not be instrumented.
+ * @param classLoaderMatcher A matcher that identifies a class loader that identifies classes that should not be instrumented.
+ * @param moduleMatcher A matcher that identifies a module that identifies classes that should not be instrumented. On a JVM
+ * that does not support modules, the Java module is represented by {@code null}.
+ * @return A new instance of this agent builder that ignores all types that are matched by the provided matcher.
+ * All previous matchers for ignored types are discarded.
+ */
Ignored ignore(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
@@ -423,6 +479,14 @@ interface Matchable> {
*/
T and(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
+ /**
+ * Defines a matching that is positive if both the previous matcher and the supplied matcher are matched.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @param classLoaderMatcher A matcher for the type's class loader.
+ * @param moduleMatcher A matcher for the type's module. On a JVM that does not support modules, the Java module is represented by {@code null}.
+ * @return A chained matcher.
+ */
T and(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
@@ -453,6 +517,14 @@ T and(ElementMatcher super TypeDescription> typeMatcher,
*/
T or(ElementMatcher super TypeDescription> typeMatcher, ElementMatcher super ClassLoader> classLoaderMatcher);
+ /**
+ * Defines a matching that is positive if the previous matcher or the supplied matcher are matched.
+ *
+ * @param typeMatcher A matcher for the type being matched.
+ * @param classLoaderMatcher A matcher for the type's class loader.
+ * @param moduleMatcher A matcher for the type's module. On a JVM that does not support modules, the Java module is represented by {@code null}.
+ * @return A chained matcher.
+ */
T or(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
ElementMatcher super JavaModule> moduleMatcher);
@@ -577,6 +649,7 @@ interface RawMatcher {
* @param typeDescription A description of the type to be instrumented.
* @param classLoader The class loader of the instrumented type. Might be {@code null} if this class
* loader represents the bootstrap class loader.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param classBeingRedefined The class being redefined which is only not {@code null} if a retransformation
* is applied.
* @param protectionDomain The protection domain of the type being transformed.
@@ -722,10 +795,13 @@ class ForElementMatchers implements RawMatcher {
private final ElementMatcher super TypeDescription> typeMatcher;
/**
- * The class loader to apply to a {@link java.lang.ClassLoader}.
+ * The class loader matcher to apply to a {@link java.lang.ClassLoader}.
*/
private final ElementMatcher super ClassLoader> classLoaderMatcher;
+ /**
+ * A module matcher to apply to a {@code java.lang.reflect.Module}.
+ */
private final ElementMatcher super JavaModule> moduleMatcher;
/**
@@ -733,9 +809,9 @@ class ForElementMatchers implements RawMatcher {
* supplied {@link TypeDescription} and its {@link java.lang.ClassLoader} against two matcher in order
* to decided if an instrumentation should be conducted.
*
- * @param typeMatcher The type matcher to apply to a
- * {@link TypeDescription}.
- * @param classLoaderMatcher The class loader to apply to a {@link java.lang.ClassLoader}.
+ * @param typeMatcher The type matcher to apply to a {@link TypeDescription}.
+ * @param classLoaderMatcher The class loader matcher to apply to a {@link java.lang.ClassLoader}.
+ * @param moduleMatcher A module matcher to apply to a {@code java.lang.reflect.Module}.
*/
public ForElementMatchers(ElementMatcher super TypeDescription> typeMatcher,
ElementMatcher super ClassLoader> classLoaderMatcher,
@@ -754,6 +830,12 @@ public boolean matches(TypeDescription typeDescription,
return moduleMatcher.matches(module) && classLoaderMatcher.matches(classLoader) && typeMatcher.matches(typeDescription);
}
+ /**
+ * Represents this matcher in a disjunction together with the supplied matcher.
+ *
+ * @param other The other matcher to combine with this matcher in a disjunction.
+ * @return A disjunction matching this matcher or the other matcher.
+ */
protected RawMatcher or(RawMatcher other) {
return new Conjunction(this, other);
}
@@ -1210,6 +1292,7 @@ interface Listener {
*
* @param typeDescription The type that is being transformed.
* @param classLoader The class loader which is loading this type.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param dynamicType The dynamic type that was created.
*/
void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType);
@@ -1219,6 +1302,7 @@ interface Listener {
*
* @param typeDescription The type being ignored for transformation.
* @param classLoader The class loader which is loading this type.
+ * @param module The ignored type's module or {@code null} if the current VM does not support modules.
*/
void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module);
@@ -1227,6 +1311,7 @@ interface Listener {
*
* @param typeName The type name of the instrumented type.
* @param classLoader The class loader which is loading this type.
+ * @param module The instrumented type's module or {@code null} if the current VM does not support modules.
* @param throwable The occurred error.
*/
void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable);
@@ -1236,6 +1321,7 @@ interface Listener {
*
* @param typeName The binary name of the instrumented type.
* @param classLoader The class loader which is loading this type.
+ * @param module The instrumented type's module or {@code null} if the current VM does not support modules.
*/
void onComplete(String typeName, ClassLoader classLoader, JavaModule module);
@@ -4600,6 +4686,7 @@ protected interface Transformation {
*
* @param typeDescription A description of the type that is to be transformed.
* @param classLoader The class loader of the type being transformed.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param classBeingRedefined In case of a type redefinition, the loaded type being transformed or {@code null} if that is not the case.
* @param protectionDomain The protection domain of the type being transformed.
* @param ignoredTypeMatcher Identifies types that should not be instrumented.
@@ -4741,13 +4828,17 @@ class Unresolved implements Resolution {
*/
private final ClassLoader classLoader;
+ /**
+ * The non-transformed type's module or {@code null} if the current VM does not support modules.
+ */
private final JavaModule module;
/**
* Creates a new unresolved resolution.
- * @param typeDescription The type that is not transformed.
+ *
+ * @param typeDescription The type that is not transformed.
* @param classLoader The unresolved type's class loader.
- * @param module
+ * @param module The non-transformed type's module or {@code null} if the current VM does not support modules.
*/
protected Unresolved(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
this.typeDescription = typeDescription;
@@ -4924,6 +5015,9 @@ protected static class Resolution implements Transformation.Resolution.Decoratab
*/
private final ClassLoader classLoader;
+ /**
+ * The transformed type's module or {@code null} if the current VM does not support modules.
+ */
private final JavaModule module;
/**
@@ -4946,6 +5040,7 @@ protected static class Resolution implements Transformation.Resolution.Decoratab
*
* @param typeDescription A description of the transformed type.
* @param classLoader The class loader of the transformed type.
+ * @param module The transformed type's module or {@code null} if the current VM does not support modules.
* @param protectionDomain The protection domain of the transformed type.
* @param transformer The transformer to be applied.
* @param decorator {@code true} if this transformer serves as a decorator.
@@ -5216,8 +5311,14 @@ public String toString() {
*/
protected static class ExecutingTransformer implements ClassFileTransformer {
+ /**
+ * A factory for creating a {@link ClassFileTransformer} that supports the features of the current VM.
+ */
protected static final Factory FACTORY;
+ /*
+ * Creates a factory for a class file transformer that supports the features of the current VM.
+ */
static {
Factory factory;
try {
@@ -5231,18 +5332,18 @@ protected static class ExecutingTransformer implements ClassFileTransformer {
ProtectionDomain.class,
byte[].class)).onSuper().withAllArguments())
.make()
- .load(ExecutingTransformer.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
- .getLoaded()
- .getDeclaredConstructor(ByteBuddy.class,
- TypeLocator.class,
- TypeStrategy.class,
- Listener.class,
- NativeMethodStrategy.class,
- AccessControlContext.class,
- InitializationStrategy.class,
- BootstrapInjectionStrategy.class,
- RawMatcher.class,
- Transformation.class));
+ .load(ExecutingTransformer.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded()
+ .getDeclaredConstructor(ByteBuddy.class,
+ TypeLocator.class,
+ TypeStrategy.class,
+ Listener.class,
+ NativeMethodStrategy.class,
+ AccessControlContext.class,
+ InitializationStrategy.class,
+ BootstrapInjectionStrategy.class,
+ RawMatcher.class,
+ Transformation.class));
} catch (RuntimeException exception) {
throw exception;
} catch (Exception ignored) {
@@ -5343,9 +5444,19 @@ public byte[] transform(ClassLoader classLoader,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] binaryRepresentation) {
- return transform(JavaModule.UNDEFINED, classLoader, internalTypeName, classBeingRedefined, protectionDomain, binaryRepresentation);
+ return transform(JavaModule.UNSUPPORTED, classLoader, internalTypeName, classBeingRedefined, protectionDomain, binaryRepresentation);
}
+ /**
+ * Applies a transformation for a class that was captured by this {@link ClassFileTransformer}.
+ *
+ * @param rawModule The instrumented class's Java {@code java.lang.reflect.Module}.
+ * @param internalTypeName The internal name of the instrumented class.
+ * @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
+ * @param protectionDomain The instrumented type's protection domain.
+ * @param binaryRepresentation The class file of the instrumented class in its current state.
+ * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
+ */
protected byte[] transform(Object rawModule,
String internalTypeName,
Class> classBeingRedefined,
@@ -5360,6 +5471,17 @@ protected byte[] transform(Object rawModule,
binaryRepresentation);
}
+ /**
+ * Applies a transformation for a class that was captured by this {@link ClassFileTransformer}.
+ *
+ * @param module The instrumented class's Java module in its wrapped form or {@code null} if the current VM does not support modules.
+ * @param classLoader The instrumented class's class loader.
+ * @param internalTypeName The internal name of the instrumented class.
+ * @param classBeingRedefined The loaded {@link Class} being redefined or {@code null} if no such class exists.
+ * @param protectionDomain The instrumented type's protection domain.
+ * @param binaryRepresentation The class file of the instrumented class in its current state.
+ * @return The transformed class file or an empty byte array if this transformer does not apply an instrumentation.
+ */
private byte[] transform(JavaModule module,
ClassLoader classLoader,
String internalTypeName,
@@ -5445,8 +5567,26 @@ public String toString() {
'}';
}
+ /**
+ * A factory for creating a {@link ClassFileTransformer} for the current VM.
+ */
protected interface Factory {
+ /**
+ * Creates a new class file transformer for the current VM.
+ *
+ * @param byteBuddy The Byte Buddy instance to be used.
+ * @param typeLocator The type locator to use.
+ * @param typeStrategy The definition handler to use.
+ * @param listener The listener to notify on transformations.
+ * @param nativeMethodStrategy The native method strategy to apply.
+ * @param accessControlContext The access control context to use for loading classes.
+ * @param initializationStrategy The initialization strategy to use for transformed types.
+ * @param bootstrapInjectionStrategy The injection strategy for injecting classes into the bootstrap class loader.
+ * @param ignoredTypeMatcher Identifies types that should not be instrumented.
+ * @param transformation The transformation object for handling type transformations.
+ * @return A class file transformer for the current VM that supports the API of the current VM.
+ */
ClassFileTransformer make(ByteBuddy byteBuddy,
TypeLocator typeLocator,
TypeStrategy typeStrategy,
@@ -5458,10 +5598,24 @@ ClassFileTransformer make(ByteBuddy byteBuddy,
RawMatcher ignoredTypeMatcher,
Transformation transformation);
+ /**
+ * A factory for a class file transformer on a JVM that supports the {@code java.lang.reflect.Module} API to override
+ * the newly added method of the {@link ClassFileTransformer} to capture an instrumented class's module.
+ */
class ForJava9CapableVm implements Factory {
+ /**
+ * A constructor for creating a {@link ClassFileTransformer} that overrides the newly added method for extracting
+ * the {@code java.lang.reflect.Module} of an instrumented class.
+ */
private final Constructor extends ClassFileTransformer> executingTransformer;
+ /**
+ * Creates a class file transformer factory for a Java 9 capable VM.
+ *
+ * @param executingTransformer A constructor for creating a {@link ClassFileTransformer} that overrides the newly added
+ * method for extracting the {@code java.lang.reflect.Module} of an instrumented class.
+ */
protected ForJava9CapableVm(Constructor extends ClassFileTransformer> executingTransformer) {
this.executingTransformer = executingTransformer;
}
@@ -5496,10 +5650,36 @@ public ClassFileTransformer make(ByteBuddy byteBuddy,
throw new IllegalStateException("Cannot invoke " + executingTransformer, exception.getCause());
}
}
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ ForJava9CapableVm that = (ForJava9CapableVm) object;
+ return executingTransformer.equals(that.executingTransformer);
+ }
+
+ @Override
+ public int hashCode() {
+ return executingTransformer.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "AgentBuilder.Default.ExecutingTransformer.Factory.ForJava9CapableVm{" +
+ "executingTransformer=" + executingTransformer +
+ '}';
+ }
}
+ /**
+ * A factory for a {@link ClassFileTransformer} on a VM that does not support the {@code java.lang.reflect.Module} API.
+ */
enum ForLegacyVm implements Factory {
+ /**
+ * The singleton instance.
+ */
INSTANCE;
@Override
@@ -5524,6 +5704,11 @@ public ClassFileTransformer make(ByteBuddy byteBuddy,
ignoredTypeMatcher,
transformation);
}
+
+ @Override
+ public String toString() {
+ return "AgentBuilder.Default.ExecutingTransformer.Factory.ForLegacyVm." + name();
+ }
}
}
}
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java
index 630ed730c81..0eebe78dc83 100644
--- a/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/matcher/ElementMatchers.java
@@ -629,6 +629,12 @@ public static ElementMatcher.Junction nameMatches(St
return new NameMatcher(new StringMatcher(regex, StringMatcher.Mode.MATCHES));
}
+ /**
+ * Matches a {@link NamedElement.WithOptionalName} for having an explicit name.
+ *
+ * @param The type of the matched object.
+ * @return An element matcher that checks if the matched optionally named element has an explicit name.
+ */
public static ElementMatcher.Junction isNamed() {
return new IsNamedMatcher();
}
@@ -1533,7 +1539,7 @@ public static ElementMatcher.Junction isSuperType
* Matches any type description that declares a super type that matches the provided matcher.
*
* @param matcher The type to be checked for being a super type of the matched type.
- * @param The type of the matched object.
+ * @param The type of the matched object.
* @return A matcher that matches any type description that declares a super type that matches the provided matcher.
*/
public static ElementMatcher.Junction hasSuperType(ElementMatcher super TypeDescription> matcher) {
@@ -1544,7 +1550,7 @@ public static ElementMatcher.Junction hasSuperTyp
* Matches any type description that declares a super type that matches the provided matcher.
*
* @param matcher The type to be checked for being a super type of the matched type.
- * @param The type of the matched object.
+ * @param The type of the matched object.
* @return A matcher that matches any type description that declares a super type that matches the provided matcher.
*/
public static ElementMatcher.Junction hasGenericSuperType(ElementMatcher super TypeDescription.Generic> matcher) {
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java
index c1ae80c8d1c..91d0b4df4ca 100644
--- a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaModule.java
@@ -8,12 +8,24 @@
import java.security.AccessController;
import java.security.PrivilegedAction;
+/**
+ * Type-safe representation of a {@code java.lang.reflect.Module}. On platforms that do not support the module API, modules are represented by {@code null}.
+ */
public class JavaModule implements NamedElement.WithOptionalName, PrivilegedAction {
- public static JavaModule UNDEFINED = null;
+ /**
+ * Canonical representation of a Java module on a JVM that does not support the module API.
+ */
+ public static final JavaModule UNSUPPORTED = null;
+ /**
+ * The dispatcher to use for accessing Java modules, if available.
+ */
private static final Dispatcher DISPATCHER;
+ /*
+ * Extracts the dispatcher for Java modules that is supported by the current JVM.
+ */
static {
Dispatcher dispatcher;
try {
@@ -30,16 +42,37 @@ public class JavaModule implements NamedElement.WithOptionalName, PrivilegedActi
DISPATCHER = dispatcher;
}
+ /**
+ * The {@code java.lang.reflect.Module} instance this wrapper represents.
+ */
private final Object module;
+ /**
+ * Creates a new Java module representation.
+ *
+ * @param module The {@code java.lang.reflect.Module} instance this wrapper represents.
+ */
protected JavaModule(Object module) {
this.module = module;
}
+ /**
+ * Returns a representation of the supplied type's {@code java.lang.reflect.Module} or {@code null} if the current VM does not support modules.
+ *
+ * @param type The type for which to describe the module.
+ * @return A representation of the type's module or {@code null} if the current VM does not support modules.
+ */
public static JavaModule ofType(Class> type) {
return DISPATCHER.moduleOf(type);
}
+ /**
+ * Represents the supplied {@code java.lang.reflect.Module} as an instance of this class and validates that the
+ * supplied instance really represents a Java {@code Module}.
+ *
+ * @param module The module to represent.
+ * @return A representation of the supplied Java module.
+ */
public static JavaModule of(Object module) {
if (!JavaType.MODULE.getTypeStub().isInstance(module)) {
throw new IllegalArgumentException("Not a Java module: " + module);
@@ -57,6 +90,21 @@ public String getActualName() {
return DISPATCHER.getName(module);
}
+ /**
+ * Returns the class loader of this module.
+ *
+ * @param accessControlContext The access control context to use for using extracting the class loader.
+ * @return The class loader of the represented module.
+ */
+ public ClassLoader getClassLoader(AccessControlContext accessControlContext) {
+ return AccessController.doPrivileged(this, accessControlContext);
+ }
+
+ /**
+ * Unwraps this instance to a {@code java.lang.reflect.Module}.
+ *
+ * @return The represented {@code java.lang.reflect.Module}.
+ */
public Object unwrap() {
return module;
}
@@ -79,35 +127,81 @@ public String toString() {
return module.toString();
}
- public ClassLoader getClassLoader(AccessControlContext accessControlContext) {
- return AccessController.doPrivileged(this, accessControlContext);
- }
-
@Override
public ClassLoader run() {
return DISPATCHER.getClassLoader(module);
}
+ /**
+ * A dispatcher for accessing the {@code java.lang.reflect.Module} API if it is available on the current VM.
+ */
protected interface Dispatcher {
+ /**
+ * Extracts the Java {@code Module} for the provided class or returns {@code null} if the current VM does not support modules.
+ *
+ * @param type The type for which to extract the module.
+ * @return The class's {@code Module} or {@code null} if the current VM does not support modules.
+ */
JavaModule moduleOf(Class> type);
+ /**
+ * Returns {@code true} if the supplied module is named.
+ *
+ * @param module The {@code java.lang.reflect.Module} to check for the existence of a name.
+ * @return {@code true} if the supplied module is named.
+ */
boolean isNamed(Object module);
+ /**
+ * Returns the module's name.
+ *
+ * @param module The {@code java.lang.reflect.Module} to check for its name.
+ * @return The module's (implicit or explicit) name.
+ */
String getName(Object module);
+ /**
+ * Returns the module's class loader.
+ *
+ * @param module The {@code java.lang.reflect.Module}
+ * @return The module's class loader.
+ */
ClassLoader getClassLoader(Object module);
+ /**
+ * A dispatcher for a VM that does support the {@code java.lang.reflect.Module} API.
+ */
class Enabled implements Dispatcher {
+ /**
+ * The {@code java.lang.Class#getModule()} method.
+ */
private final Method getModule;
+ /**
+ * The {@code java.lang.reflect.Module#getClassLoader()} method.
+ */
private final Method getClassLoader;
+ /**
+ * The {@code java.lang.reflect.Module#isNamed()} method.
+ */
private final Method isNamed;
+ /**
+ * The {@code java.lang.reflect.Module#getName()} method.
+ */
private final Method getName;
+ /**
+ * Creates a new enabled dispatcher.
+ *
+ * @param getModule The {@code java.lang.Class#getModule()} method.
+ * @param getClassLoader The {@code java.lang.reflect.Module#getClassLoader()} method.
+ * @param isNamed The {@code java.lang.reflect.Module#isNamed()} method.
+ * @param getName The {@code java.lang.reflect.Module#getName()} method.
+ */
protected Enabled(Method getModule, Method getClassLoader, Method isNamed, Method getName) {
this.getModule = getModule;
this.getClassLoader = getClassLoader;
@@ -158,15 +252,51 @@ public String getName(Object module) {
throw new IllegalStateException("Cannot invoke " + getName, exception.getCause());
}
}
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ Enabled enabled = (Enabled) object;
+ if (!getModule.equals(enabled.getModule)) return false;
+ if (!getClassLoader.equals(enabled.getClassLoader)) return false;
+ if (!isNamed.equals(enabled.isNamed)) return false;
+ return getName.equals(enabled.getName);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = getModule.hashCode();
+ result = 31 * result + getClassLoader.hashCode();
+ result = 31 * result + isNamed.hashCode();
+ result = 31 * result + getName.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaModule.Dispatcher.Enabled{" +
+ "getModule=" + getModule +
+ ", getClassLoader=" + getClassLoader +
+ ", isNamed=" + isNamed +
+ ", getName=" + getName +
+ '}';
+ }
}
+ /**
+ * A disabled dispatcher for a VM that does not support the {@code java.lang.reflect.Module} API.
+ */
enum Disabled implements Dispatcher {
+ /**
+ * The singleton instance.
+ */
INSTANCE;
@Override
public JavaModule moduleOf(Class> type) {
- return UNDEFINED;
+ return UNSUPPORTED;
}
@Override
diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java
index b37745f129c..ea709dccf0e 100644
--- a/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java
+++ b/byte-buddy-dep/src/main/java/net/bytebuddy/utility/JavaType.java
@@ -39,6 +39,9 @@ public enum JavaType {
*/
EXECUTABLE("java.lang.reflect.Executable", Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, AccessibleObject.class, Member.class, GenericDeclaration.class),
+ /**
+ * The {@code java.lang.reflect.Module} type.
+ */
MODULE("java.lang.reflect.Module", Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, Object.class);
/**
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java
index 8fdba4db870..593e3476e97 100644
--- a/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/agent/builder/AgentBuilderDefaultTest.java
@@ -1058,6 +1058,8 @@ public AccessControlContext create() {
return new AccessControlContext(new ProtectionDomain[]{mock(ProtectionDomain.class)});
}
}).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.Factory.ForJava9CapableVm.class).apply();
+ ObjectPropertyAssertion.of(AgentBuilder.Default.ExecutingTransformer.Factory.ForLegacyVm.class).apply();
}
public static class Foo {
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java
index 25b9a2b7bc7..2c052d3edd5 100644
--- a/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/matcher/ElementMatchersTest.java
@@ -2,6 +2,7 @@
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.ModifierReviewable;
+import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
@@ -466,6 +467,14 @@ public void testNameMatches() throws Exception {
assertThat(ElementMatchers.nameMatches(BAR).matches(byteCodeElement), is(false));
}
+ @Test
+ public void testIsNamed() throws Exception {
+ NamedElement.WithOptionalName namedElement = mock(NamedElement.WithOptionalName.class);
+ assertThat(ElementMatchers.isNamed().matches(namedElement), is(false));
+ when(namedElement.isNamed()).thenReturn(true);
+ assertThat(ElementMatchers.isNamed().matches(namedElement), is(true));
+ }
+
@Test
public void testHasDescriptor() throws Exception {
ByteCodeElement byteCodeElement = mock(ByteCodeElement.class);
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaModuleTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaModuleTest.java
new file mode 100644
index 00000000000..515f4c7add6
--- /dev/null
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaModuleTest.java
@@ -0,0 +1,65 @@
+package net.bytebuddy.utility;
+
+import net.bytebuddy.test.utility.ObjectPropertyAssertion;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class JavaModuleTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testExtractModule() throws Exception {
+ JavaModule.of(mock(Object.class));
+ }
+
+ @Test
+ public void testUnwrap() throws Exception {
+ Object object = new Object();
+ JavaModule module = new JavaModule(object);
+ assertThat(module.unwrap(), sameInstance(object));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testIsNamedDisabledThrowException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.isNamed(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetNameDisabledThrowException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.getName(mock(Object.class));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetClassLoaderDisabledThrowException() throws Exception {
+ JavaModule.Dispatcher.Disabled.INSTANCE.getClassLoader(mock(Object.class));
+ }
+
+ @Test
+ public void testDisabledModuleIsNull() throws Exception {
+ assertThat(JavaModule.Dispatcher.Disabled.INSTANCE.moduleOf(Object.class), nullValue(JavaModule.class));
+ }
+
+ @Test
+ public void testObjectProperties() throws Exception {
+ ObjectPropertyAssertion.of(JavaModule.class).skipToString().apply();
+ Object object = new Object();
+ assertThat(new JavaModule(object).hashCode(), is(object.hashCode()));
+ assertThat(new JavaModule(object).toString(), is(object.toString()));
+ final Iterator iterator = Arrays.asList(Object.class.getDeclaredMethods()).iterator();
+ ObjectPropertyAssertion.of(JavaModule.Dispatcher.Enabled.class).create(new ObjectPropertyAssertion.Creator() {
+ @Override
+ public Method create() {
+ return iterator.next();
+ }
+ }).apply();
+ ObjectPropertyAssertion.of(JavaModule.Dispatcher.Disabled.class).apply();
+ }
+}
\ No newline at end of file
diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java
index 80973e02a7a..6993ded4ed6 100644
--- a/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java
+++ b/byte-buddy-dep/src/test/java/net/bytebuddy/utility/JavaTypeTest.java
@@ -66,6 +66,14 @@ public void testExecutable() throws Exception {
assertThat(JavaType.EXECUTABLE.getTypeStub().getInterfaces().contains(new TypeDescription.Generic.OfNonGenericType.ForLoadedType(GenericDeclaration.class)), is(true));
}
+ @Test
+ public void testModule() throws Exception {
+ assertThat(JavaType.MODULE.getTypeStub().getName(), is("java.lang.reflect.Module"));
+ assertThat(JavaType.MODULE.getTypeStub().getModifiers(), is(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL));
+ assertThat(JavaType.MODULE.getTypeStub().getSuperClass(), is(TypeDescription.Generic.OBJECT));
+ assertThat(JavaType.MODULE.getTypeStub().getInterfaces().size(), is(0));
+ }
+
@Test
@JavaVersionRule.Enforce(7)
public void testJava7Types() throws Exception {