com.fitbur.mockito.bytebuddy.implementation.Implementation Maven / Gradle / Ivy
package com.fitbur.mockito.bytebuddy.implementation;
import com.fitbur.mockito.bytebuddy.ClassFileVersion;
import com.fitbur.mockito.bytebuddy.description.annotation.AnnotationList;
import com.fitbur.mockito.bytebuddy.description.field.FieldDescription;
import com.fitbur.mockito.bytebuddy.description.method.MethodDescription;
import com.fitbur.mockito.bytebuddy.description.method.ParameterDescription;
import com.fitbur.mockito.bytebuddy.description.method.ParameterList;
import com.fitbur.mockito.bytebuddy.description.type.TypeDefinition;
import com.fitbur.mockito.bytebuddy.description.type.TypeDescription;
import com.fitbur.mockito.bytebuddy.description.type.TypeList;
import com.fitbur.mockito.bytebuddy.dynamic.DynamicType;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.InstrumentedType;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.MethodGraph;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.TypeInitializer;
import com.fitbur.mockito.bytebuddy.dynamic.scaffold.TypeWriter;
import com.fitbur.mockito.bytebuddy.implementation.attribute.AnnotationValueFilter;
import com.fitbur.mockito.bytebuddy.implementation.auxiliary.AuxiliaryType;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.ByteCodeAppender;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.StackManipulation;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.FieldAccess;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.MethodInvocation;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.MethodReturn;
import com.fitbur.mockito.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import com.fitbur.mockito.bytebuddy.utility.RandomString;
import com.fitbur.mockito.bytebuddy.jar.asm.ClassVisitor;
import com.fitbur.mockito.bytebuddy.jar.asm.MethodVisitor;
import com.fitbur.mockito.bytebuddy.jar.asm.Opcodes;
import java.util.*;
/**
* An implementation is responsible for implementing methods of a dynamically created type as byte code. An
* implementation is applied in two stages:
*
* - The implementation is able to prepare an instrumented type by adding fields and/or helper methods that are
* required for the methods implemented by this implementation. Furthermore,
* {@link LoadedTypeInitializer}s and byte code for the type initializer can be registered for the instrumented
* type.
* - Any implementation is required to supply a byte code appender that is responsible for providing the byte code
* to the instrumented methods that were delegated to this implementation. This byte code appender is also
* be responsible for providing implementations for the methods added in step 1.
*
*
* An implementation should provide meaningful implementations of both {@link java.lang.Object#equals(Object)}
* and {@link Object#hashCode()} if it wants to avoid to be used twice within the creation of a dynamic type. For two
* equal implementations only one will be applied on the creation of a dynamic type.
*/
public interface Implementation extends InstrumentedType.Prepareable {
/**
* Creates a byte code appender that determines the implementation of the instrumented type's methods.
*
* @param implementationTarget The target of the current implementation.
* @return A byte code appender for implementing methods delegated to this implementation. This byte code appender
* is also responsible for handling methods that were added by this implementation on the call to
* {@link Implementation#prepare(InstrumentedType)}.
*/
ByteCodeAppender appender(Target implementationTarget);
/**
* Represents an implementation that can be chained together with another implementation.
*/
interface Composable extends Implementation {
/**
* Appends the supplied implementation to this implementation.
*
* @param implementation The subsequent implementation.
* @return An implementation that combines this implementation with the provided one.
*/
Implementation andThen(Implementation implementation);
}
/**
* Represents an type-specific method invocation on the current instrumented type which is not legal from outside
* the type such as a super method or default method invocation. Legal instances of special method invocations must
* be equal to one another if they represent the same invocation target.
*/
interface SpecialMethodInvocation extends StackManipulation {
/**
* Returns the method that represents this special method invocation. This method can be different even for
* equal special method invocations, dependant on the method that was used to request such an invocation by the
* means of a {@link Implementation.Target}.
*
* @return The method description that describes this instances invocation target.
*/
MethodDescription getMethodDescription();
/**
* Returns the target type the represented method is invoked on.
*
* @return The type the represented method is invoked on.
*/
TypeDescription getTypeDescription();
/**
* A canonical implementation of an illegal {@link Implementation.SpecialMethodInvocation}.
*/
enum Illegal implements SpecialMethodInvocation {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public boolean isValid() {
return false;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
throw new IllegalStateException("Cannot implement an undefined method");
}
@Override
public MethodDescription getMethodDescription() {
throw new IllegalStateException("An illegal special method invocation must not be applied");
}
@Override
public TypeDescription getTypeDescription() {
throw new IllegalStateException("An illegal special method invocation must not be applied");
}
@Override
public String toString() {
return "Implementation.SpecialMethodInvocation.Illegal." + name();
}
}
/**
* An abstract base implementation of a valid special method invocation.
*/
abstract class AbstractBase implements SpecialMethodInvocation {
@Override
public boolean isValid() {
return true;
}
@Override
public int hashCode() {
return 31 * getMethodDescription().asSignatureToken().hashCode() + getTypeDescription().hashCode();
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof SpecialMethodInvocation)) return false;
SpecialMethodInvocation specialMethodInvocation = (SpecialMethodInvocation) other;
return getMethodDescription().asSignatureToken().equals(specialMethodInvocation.getMethodDescription().asSignatureToken())
&& getTypeDescription().equals(((SpecialMethodInvocation) other).getTypeDescription());
}
}
/**
* A canonical implementation of a {@link Implementation.SpecialMethodInvocation}.
*/
class Simple extends SpecialMethodInvocation.AbstractBase {
/**
* The method description that is represented by this legal special method invocation.
*/
private final MethodDescription methodDescription;
/**
* The type description that is represented by this legal special method invocation.
*/
private final TypeDescription typeDescription;
/**
* A stack manipulation representing the method's invocation on the type description.
*/
private final StackManipulation stackManipulation;
/**
* Creates a new legal special method invocation.
*
* @param methodDescription The method that represents the special method invocation.
* @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL}
* invocation.
* @param stackManipulation The stack manipulation that represents this special method invocation.
*/
protected Simple(MethodDescription methodDescription, TypeDescription typeDescription, StackManipulation stackManipulation) {
this.methodDescription = methodDescription;
this.typeDescription = typeDescription;
this.stackManipulation = stackManipulation;
}
/**
* Creates a special method invocation for a given invocation target.
*
* @param methodDescription The method that represents the special method invocation.
* @param typeDescription The type on which the method should be invoked on by an {@code INVOKESPECIAL}
* invocation.
* @return A special method invocation representing a legal invocation if the method can be invoked
* specially on the target type or an illegal invocation if this is not possible.
*/
public static SpecialMethodInvocation of(MethodDescription methodDescription, TypeDescription typeDescription) {
StackManipulation stackManipulation = MethodInvocation.invoke(methodDescription).special(typeDescription);
return stackManipulation.isValid()
? new Simple(methodDescription, typeDescription, stackManipulation)
: SpecialMethodInvocation.Illegal.INSTANCE;
}
@Override
public MethodDescription getMethodDescription() {
return methodDescription;
}
@Override
public TypeDescription getTypeDescription() {
return typeDescription;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
return stackManipulation.apply(methodVisitor, implementationContext);
}
@Override
public String toString() {
return "Implementation.SpecialMethodInvocation.Simple{" +
"typeDescription=" + typeDescription +
", methodDescription=" + methodDescription +
", stackManipulation=" + stackManipulation +
'}';
}
}
}
/**
* The target of an implementation. Implementation targets must be immutable and can be queried without altering
* the implementation result. An implementation target provides information on the type that is to be created
* where it is the implementation's responsibility to cache expensive computations, especially such computations
* that require reflective look-up.
*/
interface Target {
/**
* Returns a description of the instrumented type.
*
* @return A description of the instrumented type.
*/
TypeDescription getInstrumentedType();
/**
* Identifies the origin type of an implementation. The origin type describes the type that is subject to
* any form of enhancement. If a subclass of a given type is generated, the base type of this subclass
* describes the origin type. If a given type is redefined or rebased, the origin type is described by the
* instrumented type itself.
*
* @return The origin type of this implementation.
*/
TypeDefinition getOriginType();
/**
* Creates a special method invocation for invoking the super method of the given method.
*
* @param token A token of the method that is to be invoked as a super method.
* @return The corresponding special method invocation which might be illegal if the requested invocation is
* not legal.
*/
SpecialMethodInvocation invokeSuper(MethodDescription.SignatureToken token);
/**
* Creates a special method invocation for invoking a default method.
*
* @param targetType The interface on which the default method is to be invoked.
* @param token A token that uniquely describes the method to invoke.
* @return The corresponding special method invocation which might be illegal if the requested invocation is
* not legal.
*/
SpecialMethodInvocation invokeDefault(TypeDescription targetType, MethodDescription.SignatureToken token);
/**
* Invokes a dominant method, i.e. if the method token can be invoked as a super method invocation, this invocation is considered dominant.
* Alternatively, a method invocation is attempted on an interface type as a default method invocation only if this invocation is not ambiguous
* for several interfaces.
*
* @param token The method token representing the method to be invoked.
* @return A special method invocation for a method representing the method token.
*/
SpecialMethodInvocation invokeDominant(MethodDescription.SignatureToken token);
/**
* A factory for creating an {@link Implementation.Target}.
*/
interface Factory {
/**
* Creates an implementation target.
*
* @param instrumentedType The instrumented type.
* @param methodGraph A method graph of the instrumented type.
* @return An implementation target for the instrumented type.
*/
Target make(TypeDescription instrumentedType, MethodGraph.Linked methodGraph);
}
/**
* An abstract base implementation for an {@link Implementation.Target}.
*/
abstract class AbstractBase implements Target {
/**
* The instrumented type.
*/
protected final TypeDescription instrumentedType;
/**
* The instrumented type's method graph.
*/
protected final MethodGraph.Linked methodGraph;
/**
* Creates a new implementation target.
*
* @param instrumentedType The instrumented type.
* @param methodGraph The instrumented type's method graph.
*/
protected AbstractBase(TypeDescription instrumentedType, MethodGraph.Linked methodGraph) {
this.instrumentedType = instrumentedType;
this.methodGraph = methodGraph;
}
@Override
public TypeDescription getInstrumentedType() {
return instrumentedType;
}
@Override
public Implementation.SpecialMethodInvocation invokeDefault(TypeDescription targetType, MethodDescription.SignatureToken token) {
MethodGraph.Node node = methodGraph.getInterfaceGraph(targetType).locate(token);
return node.getSort().isUnique()
? SpecialMethodInvocation.Simple.of(node.getRepresentative(), targetType)
: Implementation.SpecialMethodInvocation.Illegal.INSTANCE;
}
@Override
public SpecialMethodInvocation invokeDominant(MethodDescription.SignatureToken token) {
SpecialMethodInvocation specialMethodInvocation = invokeSuper(token);
if (!specialMethodInvocation.isValid()) {
Iterator iterator = instrumentedType.getInterfaces().asErasures().iterator();
while (!specialMethodInvocation.isValid() && iterator.hasNext()) {
specialMethodInvocation = invokeDefault(iterator.next(), token);
}
while (iterator.hasNext()) {
if (invokeDefault(iterator.next(), token).isValid()) {
return SpecialMethodInvocation.Illegal.INSTANCE;
}
}
}
return specialMethodInvocation;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
AbstractBase that = (AbstractBase) other;
return instrumentedType.equals(that.instrumentedType)
&& methodGraph.equals(that.methodGraph);
}
@Override
public int hashCode() {
int result = instrumentedType.hashCode();
result = 31 * result + methodGraph.hashCode();
return result;
}
}
}
/**
* The context for an implementation application. An implementation context represents a mutable data structure
* where any registration is irrevocable. Calling methods on an implementation context should be considered equally
* sensitive as calling a {@link com.fitbur.mockito.bytebuddy.jar.asm.MethodVisitor}. As such, an implementation context and a
* {@link com.fitbur.mockito.bytebuddy.jar.asm.MethodVisitor} are complementary for creating an new Java type.
*/
interface Context {
/**
* Registers an auxiliary type as required for the current implementation. Registering a type will cause the
* creation of this type even if this type is not effectively used for the current implementation.
*
* @param auxiliaryType The auxiliary type that is required for the current implementation.
* @return A description of the registered auxiliary type.
*/
TypeDescription register(AuxiliaryType auxiliaryType);
/**
* Caches a single value by storing it in form of a {@code private}, {@code final} and {@code static} field.
* By caching values, expensive instance creations can be avoided and object identity can be preserved.
* The field is initiated in a generated class's static initializer.
*
* @param fieldValue A stack manipulation for creating the value that is to be cached in a {@code static} field.
* After executing the stack manipulation, exactly one value must be put onto the operand
* stack which is assignable to the given {@code fieldType}.
* @param fieldType The type of the field for storing the cached value. This field's type determines the value
* that is put onto the operand stack by this method's returned stack manipulation.
* @return A description of a field that was defined on the instrumented type which contains the given value.
*/
FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType);
/**
* Returns the instrumented type of the current implementation. The instrumented type is exposed with the intend of allowing optimal
* byte code generation and not for implementing checks or changing the behavior of a {@link StackManipulation}.
*
* @return The instrumented type of the current implementation.
*/
TypeDescription getInstrumentedType();
/**
* Returns the class file version of the current implementation.
*
* @return The class file version of the current implementation.
*/
ClassFileVersion getClassFileVersion();
/**
* Represents an extractable view of an {@link Implementation.Context} which
* allows the retrieval of any registered auxiliary type.
*/
interface ExtractableView extends Context {
/**
* Sets the class file version this implementation context should represent.
*
* @param classFileVersion The class file version to represent.
*/
void setClassFileVersion(ClassFileVersion classFileVersion);
/**
* Determines if this implementation context allows for the retention of a static type initializer.
*
* @return {@code true} if the original type initializer can be retained. {@code false} if the original type
* initializer needs to be copied to another method for allowing code injection into the initializer.
*/
boolean isRetainTypeInitializer();
/**
* Returns any {@link com.fitbur.mockito.bytebuddy.implementation.auxiliary.AuxiliaryType} that was registered
* with this {@link Implementation.Context}.
*
* @return A list of all manifested registered auxiliary types.
*/
List getRegisteredAuxiliaryTypes();
/**
* Writes any information that was registered with an {@link Implementation.Context}
* to the provided class visitor. This contains any fields for value caching, any accessor method and it
* writes the type initializer. The type initializer must therefore never be written manually.
*
* @param classVisitor The class visitor to which the extractable view is to be written.
* @param methodPool A method pool which is queried for any user code to add to the type initializer.
* @param injectedCode Potential code that is to be injected into the type initializer.
* @param annotationValueFilterFactory The annotation value filter factory to apply when writing annotation.
*/
void drain(ClassVisitor classVisitor,
TypeWriter.MethodPool methodPool,
InjectedCode injectedCode,
AnnotationValueFilter.Factory annotationValueFilterFactory);
/**
* Prohibits any instrumentation of an instrumented class's type initializer.
*/
void prohibitTypeInitializer();
/**
* When draining an implementation context, a type initializer might be written to the created class
* file. If any code must be explicitly invoked from within the type initializer, this can be achieved
* by providing a code injection by this instance. The injected code is added after the class is set up but
* before any user code is run from within the type initializer.
*/
interface InjectedCode {
/**
* Returns a byte code appender for appending the injected code.
*
* @return A byte code appender for appending the injected code.
*/
ByteCodeAppender getByteCodeAppender();
/**
* Checks if there is actually code defined to be injected.
*
* @return {@code true} if code is to be injected.
*/
boolean isDefined();
/**
* A canonical implementation of non-applicable injected code.
*/
enum None implements InjectedCode {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ByteCodeAppender getByteCodeAppender() {
throw new IllegalStateException();
}
@Override
public boolean isDefined() {
return false;
}
@Override
public String toString() {
return "Implementation.Context.ExtractableView.InjectedCode.None." + name();
}
}
}
/**
* An abstract base implementation of an extractable view of an implementation context.
*/
abstract class AbstractBase implements ExtractableView {
/**
* The instrumented type.
*/
protected final TypeDescription instrumentedType;
/**
* The class file version of the instrumented type.
*/
private ClassFileVersion classFileVersion;
/**
* Create a new extractable view.
*
* @param instrumentedType The instrumented type.
*/
protected AbstractBase(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
@Override
public void setClassFileVersion(ClassFileVersion classFileVersion) {
this.classFileVersion = classFileVersion;
}
@Override
public TypeDescription getInstrumentedType() {
return instrumentedType;
}
@Override
public ClassFileVersion getClassFileVersion() {
if (classFileVersion == null) {
throw new IllegalStateException("Cannot read class file version before it was set");
}
return classFileVersion;
}
}
}
/**
* A factory for creating a new implementation context.
*/
interface Factory {
/**
* Creates a new implementation context.
*
* @param instrumentedType The description of the type that is currently subject of creation.
* @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type.
* @param typeInitializer The type initializer of the created instrumented type.
* @param classFileVersion The class file version of the created class.
* @return An implementation context in its extractable view.
*/
ExtractableView make(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion);
}
/**
* An implementation context that does not allow for any injections into the static initializer block. This can be useful when
* redefining a class when it is not allowed to add methods to a class what is an implicit requirement when copying the static
* initializer block into another method.
*/
class Disabled extends ExtractableView.AbstractBase {
/**
* Creates a new disabled implementation context.
*
* @param instrumentedType The instrumented type.
*/
protected Disabled(TypeDescription instrumentedType) {
super(instrumentedType);
}
@Override
public boolean isRetainTypeInitializer() {
return true;
}
@Override
public List getRegisteredAuxiliaryTypes() {
return Collections.emptyList();
}
@Override
public void drain(ClassVisitor classVisitor,
TypeWriter.MethodPool methodPool,
InjectedCode injectedCode,
AnnotationValueFilter.Factory annotationValueFilterFactory) {
if (injectedCode.isDefined() || methodPool.target(new MethodDescription.Latent.TypeInitializer(instrumentedType)).getSort().isDefined()) {
throw new IllegalStateException("Type initializer interception is impossible or was disabled for " + instrumentedType);
}
}
@Override
public TypeDescription register(AuxiliaryType auxiliaryType) {
throw new IllegalStateException("Registration of auxiliary types was disabled: " + auxiliaryType);
}
@Override
public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
throw new IllegalStateException("Field values caching was disabled: " + fieldType);
}
@Override
public void prohibitTypeInitializer() {
/* do nothing */
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& instrumentedType.equals(((Disabled) other).instrumentedType);
}
@Override
public int hashCode() {
return instrumentedType.hashCode();
}
@Override
public String toString() {
return "Implementation.Context.Disabled{" +
"instrumentedType=" + instrumentedType +
'}';
}
/**
* A factory for creating a {@link com.fitbur.mockito.bytebuddy.implementation.Implementation.Context.Disabled}.
*/
public enum Factory implements Context.Factory {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ExtractableView make(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion) {
if (typeInitializer.isDefined()) {
throw new IllegalStateException("Cannot define type initializer which was explicitly disabled: " + typeInitializer);
}
return new Disabled(instrumentedType);
}
@Override
public String toString() {
return "Implementation.Context.Disabled.Factory." + name();
}
}
}
/**
* A default implementation of an {@link Implementation.Context.ExtractableView}
* which serves as its own {@link com.fitbur.mockito.bytebuddy.implementation.auxiliary.AuxiliaryType.MethodAccessorFactory}.
*/
class Default extends ExtractableView.AbstractBase implements AuxiliaryType.MethodAccessorFactory {
/**
* The name suffix to be appended to an accessor method.
*/
public static final String ACCESSOR_METHOD_SUFFIX = "accessor";
/**
* The name prefix to be prepended to a field storing a cached value.
*/
public static final String FIELD_CACHE_PREFIX = "cachedValue";
/**
* The type initializer of the created instrumented type.
*/
private final TypeInitializer typeInitializer;
/**
* The class file version that the instrumented type is written in.
*/
private final ClassFileVersion classFileVersion;
/**
* The naming strategy for naming auxiliary types that are registered.
*/
private final AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy;
/**
* A mapping of special method invocations to their accessor methods that each invoke their mapped invocation.
*/
private final Map registeredAccessorMethods;
/**
* The registered getters.
*/
private final Map registeredGetters;
/**
* The registered setters.
*/
private final Map registeredSetters;
/**
* A map of accessor methods to a method pool entry that represents their implementation.
*/
private final List accessorMethods;
/**
* A map of registered auxiliary types to their dynamic type representation.
*/
private final Map auxiliaryTypes;
/**
* A map of already registered field caches to their field representation.
*/
private final Map registeredFieldCacheEntries;
/**
* A random suffix to append to the names of accessor methods.
*/
private final String suffix;
/**
* If {@code false}, the type initializer for this instance was already drained what prohibits the registration of additional cached field values.
*/
private boolean fieldCacheCanAppendEntries;
/**
* If {@code true}, this instance suggests the retention of the original type initializer and prohibits the definition of a custom initializer.
* This property is required for interfaces before the Java 8 byte code level where type initializers are not allowed.
*/
private boolean prohibitTypeInitiailzer;
/**
* Creates a new default implementation context.
*
* @param instrumentedType The description of the type that is currently subject of creation.
* @param auxiliaryTypeNamingStrategy The naming strategy for naming an auxiliary type.
* @param typeInitializer The type initializer of the created instrumented type.
* @param classFileVersion The class file version of the created class.
*/
protected Default(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion) {
super(instrumentedType);
this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
this.typeInitializer = typeInitializer;
this.classFileVersion = classFileVersion;
registeredAccessorMethods = new HashMap();
registeredGetters = new HashMap();
registeredSetters = new HashMap();
accessorMethods = new ArrayList();
auxiliaryTypes = new HashMap();
registeredFieldCacheEntries = new HashMap();
suffix = RandomString.make();
fieldCacheCanAppendEntries = true;
prohibitTypeInitiailzer = false;
}
@Override
public MethodDescription.InDefinedShape registerAccessorFor(Implementation.SpecialMethodInvocation specialMethodInvocation) {
MethodDescription.InDefinedShape accessorMethod = registeredAccessorMethods.get(specialMethodInvocation);
if (accessorMethod == null) {
accessorMethod = new AccessorMethod(instrumentedType, specialMethodInvocation.getMethodDescription(), suffix);
registeredAccessorMethods.put(specialMethodInvocation, accessorMethod);
accessorMethods.add(new AccessorMethodDelegation(accessorMethod, specialMethodInvocation));
}
return accessorMethod;
}
@Override
public MethodDescription.InDefinedShape registerGetterFor(FieldDescription fieldDescription) {
MethodDescription.InDefinedShape accessorMethod = registeredGetters.get(fieldDescription);
if (accessorMethod == null) {
accessorMethod = new FieldGetter(instrumentedType, fieldDescription, suffix);
registeredGetters.put(fieldDescription, accessorMethod);
accessorMethods.add(new FieldGetterDelegation(accessorMethod, fieldDescription));
}
return accessorMethod;
}
@Override
public MethodDescription.InDefinedShape registerSetterFor(FieldDescription fieldDescription) {
MethodDescription.InDefinedShape accessorMethod = registeredSetters.get(fieldDescription);
if (accessorMethod == null) {
accessorMethod = new FieldSetter(instrumentedType, fieldDescription, suffix);
registeredSetters.put(fieldDescription, accessorMethod);
accessorMethods.add(new FieldSetterDelegation(accessorMethod, fieldDescription));
}
return accessorMethod;
}
@Override
public TypeDescription register(AuxiliaryType auxiliaryType) {
DynamicType dynamicType = auxiliaryTypes.get(auxiliaryType);
if (dynamicType == null) {
dynamicType = auxiliaryType.make(auxiliaryTypeNamingStrategy.name(instrumentedType), classFileVersion, this);
auxiliaryTypes.put(auxiliaryType, dynamicType);
}
return dynamicType.getTypeDescription();
}
@Override
public boolean isRetainTypeInitializer() {
return prohibitTypeInitiailzer;
}
@Override
public List getRegisteredAuxiliaryTypes() {
return new ArrayList(auxiliaryTypes.values());
}
@Override
public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
FieldCacheEntry fieldCacheEntry = new FieldCacheEntry(fieldValue, fieldType);
FieldDescription.InDefinedShape fieldCache = registeredFieldCacheEntries.get(fieldCacheEntry);
if (fieldCache != null) {
return fieldCache;
}
if (!fieldCacheCanAppendEntries) {
throw new IllegalStateException("Cached values cannot be registered after defining the type initializer for " + instrumentedType);
}
fieldCache = new CacheValueField(instrumentedType, fieldType.asGenericType(), suffix, fieldValue.hashCode());
registeredFieldCacheEntries.put(fieldCacheEntry, fieldCache);
return fieldCache;
}
@Override
public void drain(ClassVisitor classVisitor,
TypeWriter.MethodPool methodPool,
InjectedCode injectedCode,
AnnotationValueFilter.Factory annotationValueFilterFactory) {
fieldCacheCanAppendEntries = false;
TypeInitializer typeInitializer = this.typeInitializer;
for (Map.Entry entry : registeredFieldCacheEntries.entrySet()) {
classVisitor.visitField(entry.getValue().getModifiers(),
entry.getValue().getInternalName(),
entry.getValue().getDescriptor(),
entry.getValue().getGenericSignature(),
FieldDescription.NO_DEFAULT_VALUE).visitEnd();
typeInitializer = typeInitializer.expandWith(new ByteCodeAppender.Simple(entry.getKey().storeIn(entry.getValue())));
}
if (injectedCode.isDefined()) {
typeInitializer = typeInitializer.expandWith(injectedCode.getByteCodeAppender());
}
MethodDescription typeInitializerMethod = new MethodDescription.Latent.TypeInitializer(instrumentedType);
TypeWriter.MethodPool.Record initializerRecord = methodPool.target(typeInitializerMethod);
if (initializerRecord.getSort().isImplemented() && typeInitializer.isDefined()) {
initializerRecord = initializerRecord.prepend(typeInitializer);
} else if (typeInitializer.isDefined()) {
initializerRecord = new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(typeInitializerMethod, typeInitializer.withReturn());
}
if (prohibitTypeInitiailzer && initializerRecord.getSort().isDefined()) {
throw new IllegalStateException("It is impossible to define a class initializer or cached values for " + instrumentedType);
}
initializerRecord.apply(classVisitor, this, annotationValueFilterFactory);
for (TypeWriter.MethodPool.Record record : accessorMethods) {
record.apply(classVisitor, this, annotationValueFilterFactory);
}
}
@Override
public void prohibitTypeInitializer() {
prohibitTypeInitiailzer = true;
}
@Override
public String toString() {
return "Implementation.Context.Default{" +
"instrumentedType=" + instrumentedType +
", typeInitializer=" + typeInitializer +
", classFileVersion=" + classFileVersion +
", auxiliaryTypeNamingStrategy=" + auxiliaryTypeNamingStrategy +
", registeredAccessorMethods=" + registeredAccessorMethods +
", registeredGetters=" + registeredGetters +
", registeredSetters=" + registeredSetters +
", accessorMethods=" + accessorMethods +
", auxiliaryTypes=" + auxiliaryTypes +
", registeredFieldCacheEntries=" + registeredFieldCacheEntries +
", suffix=" + suffix +
", fieldCacheCanAppendEntries=" + fieldCacheCanAppendEntries +
", prohibitTypeInitiailzer=" + prohibitTypeInitiailzer +
'}';
}
/**
* A description of a field that stores a cached value.
*/
protected static class CacheValueField extends FieldDescription.InDefinedShape.AbstractBase {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The type of the cache's field.
*/
private final TypeDescription.Generic fieldType;
/**
* The suffix to use for the cache field's name.
*/
private final String suffix;
/**
* The hash value of the field's value for creating a unique field name.
*/
private final int valueHashCode;
/**
* Creates a new cache value field.
*
* @param instrumentedType The instrumented type.
* @param fieldType The type of the cache's field.
* @param suffix The suffix to use for the cache field's name.
* @param valueHashCode The hash value of the field's value for creating a unique field name.
*/
protected CacheValueField(TypeDescription instrumentedType, TypeDescription.Generic fieldType, String suffix, int valueHashCode) {
this.instrumentedType = instrumentedType;
this.fieldType = fieldType;
this.suffix = suffix;
this.valueHashCode = valueHashCode;
}
@Override
public TypeDescription.Generic getType() {
return fieldType;
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public int getModifiers() {
return Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | (instrumentedType.isInterface()
? Opcodes.ACC_PUBLIC
: Opcodes.ACC_PRIVATE);
}
@Override
public String getName() {
return String.format("%s$%s$%d", FIELD_CACHE_PREFIX, suffix, Math.abs(valueHashCode % Integer.MAX_VALUE));
}
}
/**
* A field cache entry for uniquely identifying a cached field. A cached field is described by the stack
* manipulation that loads the field's value onto the operand stack and the type of the field.
*/
protected static class FieldCacheEntry implements StackManipulation {
/**
* The field value that is represented by this field cache entry.
*/
private final StackManipulation fieldValue;
/**
* The field type that is represented by this field cache entry.
*/
private final TypeDescription fieldType;
/**
* Creates a new field cache entry.
*
* @param fieldValue The field value that is represented by this field cache entry.
* @param fieldType The field type that is represented by this field cache entry.
*/
protected FieldCacheEntry(StackManipulation fieldValue, TypeDescription fieldType) {
this.fieldValue = fieldValue;
this.fieldType = fieldType;
}
/**
* Returns a stack manipulation where the represented value is stored in the given field.
*
* @param fieldDescription A static field in which the value is to be stored.
* @return A stack manipulation that represents this storage.
*/
public StackManipulation storeIn(FieldDescription fieldDescription) {
return new Compound(this, FieldAccess.forField(fieldDescription).putter());
}
/**
* Returns the field type that is represented by this field cache entry.
*
* @return The field type that is represented by this field cache entry.
*/
public TypeDescription getFieldType() {
return fieldType;
}
@Override
public boolean isValid() {
return fieldValue.isValid();
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
return fieldValue.apply(methodVisitor, implementationContext);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& fieldType.equals(((FieldCacheEntry) other).fieldType)
&& fieldValue.equals(((FieldCacheEntry) other).fieldValue);
}
@Override
public int hashCode() {
return 31 * fieldValue.hashCode() + fieldType.hashCode();
}
@Override
public String toString() {
return "Implementation.Context.Default.FieldCacheEntry{" +
"fieldValue=" + fieldValue +
", fieldType=" + fieldType +
'}';
}
}
/**
* A base implementation of a method that accesses a property of an instrumented type.
*/
protected abstract static class AbstractPropertyAccessorMethod extends MethodDescription.InDefinedShape.AbstractBase {
@Override
public int getModifiers() {
return Opcodes.ACC_SYNTHETIC | getBaseModifiers() | (getDeclaringType().isInterface()
? Opcodes.ACC_PUBLIC
: Opcodes.ACC_FINAL);
}
/**
* Returns the base modifiers, i.e. the modifiers that define the accessed property's features.
*
* @return Returns the base modifiers of the represented methods.
*/
protected abstract int getBaseModifiers();
}
/**
* A description of an accessor method to access another method from outside the instrumented type.
*/
protected static class AccessorMethod extends AbstractPropertyAccessorMethod {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The method that is being accessed.
*/
private final MethodDescription methodDescription;
/**
* The suffix to append to the accessor method's name.
*/
private final String suffix;
/**
* Creates a new accessor method.
*
* @param instrumentedType The instrumented type.
* @param methodDescription The method that is being accessed.
* @param suffix The suffix to append to the accessor method's name.
*/
protected AccessorMethod(TypeDescription instrumentedType, MethodDescription methodDescription, String suffix) {
this.instrumentedType = instrumentedType;
this.methodDescription = methodDescription;
this.suffix = suffix;
}
@Override
public TypeDescription.Generic getReturnType() {
return methodDescription.getReturnType().asRawType();
}
@Override
public ParameterList getParameters() {
return new ParameterList.Explicit.ForTypes(this, methodDescription.getParameters().asTypeList().asRawTypes());
}
@Override
public TypeList.Generic getExceptionTypes() {
return methodDescription.getExceptionTypes().asRawTypes();
}
@Override
public Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@Override
public TypeList.Generic getTypeVariables() {
return new TypeList.Generic.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
public int getBaseModifiers() {
return methodDescription.isStatic()
? Opcodes.ACC_STATIC
: EMPTY_MASK;
}
@Override
public String getInternalName() {
return String.format("%s$%s$%s", methodDescription.getInternalName(), ACCESSOR_METHOD_SUFFIX, suffix);
}
}
/**
* A description of a field getter method.
*/
protected static class FieldGetter extends AbstractPropertyAccessorMethod {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The field for which a getter is described.
*/
private final FieldDescription fieldDescription;
/**
* The name suffix for the field getter method.
*/
private final String suffix;
/**
* Creates a new field getter.
*
* @param instrumentedType The instrumented type.
* @param fieldDescription The field for which a getter is described.
* @param suffix The name suffix for the field getter method.
*/
protected FieldGetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) {
this.instrumentedType = instrumentedType;
this.fieldDescription = fieldDescription;
this.suffix = suffix;
}
@Override
public TypeDescription.Generic getReturnType() {
return fieldDescription.getType().asRawType();
}
@Override
public ParameterList getParameters() {
return new ParameterList.Empty();
}
@Override
public TypeList.Generic getExceptionTypes() {
return new TypeList.Generic.Empty();
}
@Override
public Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@Override
public TypeList.Generic getTypeVariables() {
return new TypeList.Generic.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
protected int getBaseModifiers() {
return fieldDescription.isStatic()
? Opcodes.ACC_STATIC
: EMPTY_MASK;
}
@Override
public String getInternalName() {
return String.format("%s$%s$%s", fieldDescription.getName(), ACCESSOR_METHOD_SUFFIX, suffix);
}
}
/**
* A description of a field setter method.
*/
protected static class FieldSetter extends AbstractPropertyAccessorMethod {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The field for which a setter is described.
*/
private final FieldDescription fieldDescription;
/**
* The name suffix for the field setter method.
*/
private final String suffix;
/**
* Creates a new field setter.
*
* @param instrumentedType The instrumented type.
* @param fieldDescription The field for which a setter is described.
* @param suffix The name suffix for the field setter method.
*/
protected FieldSetter(TypeDescription instrumentedType, FieldDescription fieldDescription, String suffix) {
this.instrumentedType = instrumentedType;
this.fieldDescription = fieldDescription;
this.suffix = suffix;
}
@Override
public TypeDescription.Generic getReturnType() {
return TypeDescription.Generic.VOID;
}
@Override
public ParameterList getParameters() {
return new ParameterList.Explicit.ForTypes(this, Collections.singletonList(fieldDescription.getType().asRawType()));
}
@Override
public TypeList.Generic getExceptionTypes() {
return new TypeList.Generic.Empty();
}
@Override
public Object getDefaultValue() {
return MethodDescription.NO_DEFAULT_VALUE;
}
@Override
public TypeList.Generic getTypeVariables() {
return new TypeList.Generic.Empty();
}
@Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
@Override
public TypeDescription getDeclaringType() {
return instrumentedType;
}
@Override
protected int getBaseModifiers() {
return fieldDescription.isStatic()
? Opcodes.ACC_STATIC
: EMPTY_MASK;
}
@Override
public String getInternalName() {
return String.format("%s$%s$%s", fieldDescription.getName(), ACCESSOR_METHOD_SUFFIX, suffix);
}
}
/**
* An abstract method pool entry that delegates the implementation of a method to itself.
*/
protected abstract static class AbstractDelegationRecord extends TypeWriter.MethodPool.Record.ForDefinedMethod implements ByteCodeAppender {
/**
* The delegation method.
*/
protected final MethodDescription methodDescription;
/**
* Creates a new delegation record.
*
* @param methodDescription The delegation method.
*/
protected AbstractDelegationRecord(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
@Override
public MethodDescription getMethod() {
return methodDescription;
}
@Override
public Sort getSort() {
return Sort.IMPLEMENTED;
}
@Override
public void applyHead(MethodVisitor methodVisitor) {
/* do nothing */
}
@Override
public void applyBody(MethodVisitor methodVisitor, Context implementationContext, AnnotationValueFilter.Factory annotationValueFilterFactory) {
methodVisitor.visitCode();
Size size = apply(methodVisitor, implementationContext, getMethod());
methodVisitor.visitMaxs(size.getOperandStackSize(), size.getLocalVariableSize());
}
@Override
public TypeWriter.MethodPool.Record prepend(ByteCodeAppender byteCodeAppender) {
throw new UnsupportedOperationException("Cannot prepend code to a delegation");
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& methodDescription.equals(((AbstractDelegationRecord) other).methodDescription);
}
@Override
public int hashCode() {
return methodDescription.hashCode();
}
}
/**
* An implementation of a {@link TypeWriter.MethodPool.Record} for implementing
* an accessor method.
*/
protected static class AccessorMethodDelegation extends AbstractDelegationRecord {
/**
* The stack manipulation that represents the requested special method invocation.
*/
private final StackManipulation accessorMethodInvocation;
/**
* Creates a new accessor method delegation.
*
* @param methodDescription The accessor method.
* @param accessorMethodInvocation The stack manipulation that represents the requested special method invocation.
*/
protected AccessorMethodDelegation(MethodDescription methodDescription, StackManipulation accessorMethodInvocation) {
super(methodDescription);
this.accessorMethodInvocation = accessorMethodInvocation;
}
@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
StackManipulation.Size stackSize = new StackManipulation.Compound(
MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
accessorMethodInvocation,
MethodReturn.returning(instrumentedMethod.getReturnType().asErasure())
).apply(methodVisitor, implementationContext);
return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& super.equals(other)
&& accessorMethodInvocation.equals(((AccessorMethodDelegation) other).accessorMethodInvocation);
}
@Override
public int hashCode() {
return accessorMethodInvocation.hashCode() + 31 * super.hashCode();
}
@Override
public String toString() {
return "Implementation.Context.Default.AccessorMethodDelegation{" +
"accessorMethodInvocation=" + accessorMethodInvocation +
", methodDescription=" + methodDescription +
'}';
}
}
/**
* An implementation for a field getter.
*/
protected static class FieldGetterDelegation extends AbstractDelegationRecord {
/**
* The field to read from.
*/
private final FieldDescription fieldDescription;
/**
* Creates a new field getter implementation.
*
* @param methodDescription The delegation method.
* @param fieldDescription The field to read.
*/
protected FieldGetterDelegation(MethodDescription methodDescription, FieldDescription fieldDescription) {
super(methodDescription);
this.fieldDescription = fieldDescription;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
StackManipulation.Size stackSize = new StackManipulation.Compound(
fieldDescription.isStatic()
? StackManipulation.Trivial.INSTANCE
: MethodVariableAccess.REFERENCE.loadOffset(0),
FieldAccess.forField(fieldDescription).getter(),
MethodReturn.returning(fieldDescription.getType().asErasure())
).apply(methodVisitor, implementationContext);
return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& super.equals(other)
&& fieldDescription.equals(((FieldGetterDelegation) other).fieldDescription);
}
@Override
public int hashCode() {
return fieldDescription.hashCode() + 31 * super.hashCode();
}
@Override
public String toString() {
return "Implementation.Context.Default.FieldGetterDelegation{" +
"fieldDescription=" + fieldDescription +
", methodDescription=" + methodDescription +
'}';
}
}
/**
* An implementation for a field setter.
*/
protected static class FieldSetterDelegation extends AbstractDelegationRecord {
/**
* The field to write to.
*/
private final FieldDescription fieldDescription;
/**
* Creates a new field setter.
*
* @param methodDescription The field accessor method.
* @param fieldDescription The field to write to.
*/
protected FieldSetterDelegation(MethodDescription methodDescription, FieldDescription fieldDescription) {
super(methodDescription);
this.fieldDescription = fieldDescription;
}
@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
StackManipulation.Size stackSize = new StackManipulation.Compound(
MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
FieldAccess.forField(fieldDescription).putter(),
MethodReturn.VOID
).apply(methodVisitor, implementationContext);
return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& super.equals(other)
&& fieldDescription.equals(((FieldSetterDelegation) other).fieldDescription);
}
@Override
public int hashCode() {
return fieldDescription.hashCode() + 31 * super.hashCode();
}
@Override
public String toString() {
return "Implementation.Context.Default.FieldSetterDelegation{" +
"fieldDescription=" + fieldDescription +
", methodDescription=" + methodDescription +
'}';
}
}
/**
* A factory for creating a {@link com.fitbur.mockito.bytebuddy.implementation.Implementation.Context.Default}.
*/
public enum Factory implements ExtractableView.Factory {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public ExtractableView make(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion) {
return new Default(instrumentedType, auxiliaryTypeNamingStrategy, typeInitializer, classFileVersion);
}
@Override
public String toString() {
return "Implementation.Context.Default.Factory." + name();
}
}
}
}
/**
* A compound implementation that allows to combine several implementations.
*
* Note that the combination of two implementation might break the contract for implementing
* {@link java.lang.Object#equals(Object)} and {@link Object#hashCode()} as described for
* {@link Implementation}.
*
* @see Implementation
*/
class Compound implements Implementation {
/**
* All implementation that are represented by this compound implementation.
*/
private final Implementation[] implementation;
/**
* Creates a new immutable compound implementation.
*
* @param implementation The implementations to combine in their order.
*/
public Compound(Implementation... implementation) {
this.implementation = implementation;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
for (Implementation implementation : this.implementation) {
instrumentedType = implementation.prepare(instrumentedType);
}
return instrumentedType;
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
ByteCodeAppender[] byteCodeAppender = new ByteCodeAppender[implementation.length];
int index = 0;
for (Implementation implementation : this.implementation) {
byteCodeAppender[index++] = implementation.appender(implementationTarget);
}
return new ByteCodeAppender.Compound(byteCodeAppender);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& Arrays.equals(implementation, ((Compound) other).implementation);
}
@Override
public int hashCode() {
return Arrays.hashCode(implementation);
}
@Override
public String toString() {
return "Implementation.Compound{implementation=" + Arrays.toString(implementation) + '}';
}
}
/**
* A simple implementation that does not register any members with the instrumented type.
*/
class Simple implements Implementation {
/**
* The byte code appender to emmit.
*/
private final ByteCodeAppender byteCodeAppender;
/**
* Creates a new simple implementation for the given byte code appenders.
*
* @param byteCodeAppender The byte code appenders to apply in their order of application.
*/
public Simple(ByteCodeAppender... byteCodeAppender) {
this.byteCodeAppender = new ByteCodeAppender.Compound(byteCodeAppender);
}
/**
* Creates a new simple instrumentation for the given stack manipulations which are summarized in a
* byte code appender that defines any requested method by these manipulations.
*
* @param stackManipulation The stack manipulation to apply in their order of application.
*/
public Simple(StackManipulation... stackManipulation) {
byteCodeAppender = new ByteCodeAppender.Simple(stackManipulation);
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public ByteCodeAppender appender(Target implementationTarget) {
return byteCodeAppender;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& byteCodeAppender.equals(((Simple) other).byteCodeAppender);
}
@Override
public int hashCode() {
return byteCodeAppender.hashCode();
}
@Override
public String toString() {
return "Implementation.Simple{byteCodeAppender=" + byteCodeAppender + '}';
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy