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 typeMatcher, ElementMatcher 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 typeMatcher, ElementMatcher classLoaderMatcher, ElementMatcher moduleMatcher); @@ -344,6 +375,31 @@ Identified.Narrowable type(ElementMatcher typeMatcher, */ Ignored ignore(ElementMatcher typeMatcher, ElementMatcher 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 typeMatcher, ElementMatcher classLoaderMatcher, ElementMatcher moduleMatcher); @@ -423,6 +479,14 @@ interface Matchable> { */ T and(ElementMatcher typeMatcher, ElementMatcher 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 typeMatcher, ElementMatcher classLoaderMatcher, ElementMatcher moduleMatcher); @@ -453,6 +517,14 @@ T and(ElementMatcher typeMatcher, */ T or(ElementMatcher typeMatcher, ElementMatcher 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 typeMatcher, ElementMatcher classLoaderMatcher, ElementMatcher 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 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 classLoaderMatcher; + /** + * A module matcher to apply to a {@code java.lang.reflect.Module}. + */ private final ElementMatcher 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 typeMatcher, ElementMatcher 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 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 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 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 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 {