/*
* Copyright 2014 - 2020 Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.implementation;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import org.objectweb.asm.MethodVisitor;
import java.util.*;
This Implementation
invokes a default method for the methods it instruments. A default method is potentially ambiguous if a method of identical signature is defined in several interfaces. For this reason, this implementation allows for the specification of prioritized interfaces whose default
methods, if a method of equivalent signature is defined for a specific interface. All prioritized interfaces are
searched for default methods in the order of their specification. If no prioritized interface defines a default method
of equivalent signature to the given instrumented method, any non-prioritized interface is searched for a suitable
default method. If exactly one of those interfaces defines a suitable default method, this method is invoked.
If no method or more than one method is identified as a suitable default method, an exception is thrown.
When it comes to default methods, the Java programming language specifies stronger requirements for the
legitimacy of invoking a default method than the Java runtime. The Java compiler requires a method to be
the most specific method in its defining type's type hierarchy, i.e. the method must not be overridden by another
interface or class type. Additionally, the method must be invokable from an interface type which is directly
implemented by the instrumented type. The Java runtime only requires the second condition to be fulfilled which
is why this implementation only checks the later condition, as well.
/**
* This {@link Implementation} invokes a default method for the methods it instruments.
* A default method is potentially ambiguous if a method of identical signature is defined in several interfaces.
* For this reason, this implementation allows for the specification of <i>prioritized interfaces</i> whose default
* methods, if a method of equivalent signature is defined for a specific interface. All prioritized interfaces are
* searched for default methods in the order of their specification. If no prioritized interface defines a default method
* of equivalent signature to the given instrumented method, any non-prioritized interface is searched for a suitable
* default method. If exactly one of those interfaces defines a suitable default method, this method is invoked.
* If no method or more than one method is identified as a suitable default method, an exception is thrown.
* <p> </p>
* When it comes to default methods, the Java programming language specifies stronger requirements for the
* legitimacy of invoking a default method than the Java runtime. The Java compiler requires a method to be
* the most specific method in its defining type's type hierarchy, i.e. the method must not be overridden by another
* interface or class type. Additionally, the method must be invokable from an interface type which is directly
* implemented by the instrumented type. The Java runtime only requires the second condition to be fulfilled which
* is why this implementation only checks the later condition, as well.
*/
@HashCodeAndEqualsPlugin.Enhance
public class DefaultMethodCall implements Implementation {
A list of prioritized interfaces in the order in which a method should be attempted to be called.
/**
* A list of prioritized interfaces in the order in which a method should be attempted to be called.
*/
private final List<TypeDescription> prioritizedInterfaces;
Creates a new DefaultMethodCall
implementation for a given list of prioritized interfaces. Params: - prioritizedInterfaces – A list of prioritized interfaces in the order in which a method should be attempted to
be called.
/**
* Creates a new {@link net.bytebuddy.implementation.DefaultMethodCall} implementation for a given list of
* prioritized interfaces.
*
* @param prioritizedInterfaces A list of prioritized interfaces in the order in which a method should be attempted to
* be called.
*/
protected DefaultMethodCall(List<TypeDescription> prioritizedInterfaces) {
this.prioritizedInterfaces = prioritizedInterfaces;
}
Creates a DefaultMethodCall
implementation which searches the given list of interface types for a suitable default method in their order. If no such prioritized interface is suitable, because it is either not defined on the instrumented type or because it does not define a suitable default method, any remaining interface is searched for a suitable default method. If no or more than one method defines a suitable default method, an exception is thrown. Params: - prioritizedInterface – A list of prioritized default method interfaces in their prioritization order.
Returns: An implementation which calls an instrumented method's compatible default method that considers the given
interfaces to be prioritized in their order.
/**
* Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
* of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
* because it is either not defined on the instrumented type or because it does not define a suitable default method,
* any remaining interface is searched for a suitable default method. If no or more than one method defines a
* suitable default method, an exception is thrown.
*
* @param prioritizedInterface A list of prioritized default method interfaces in their prioritization order.
* @return An implementation which calls an instrumented method's compatible default method that considers the given
* interfaces to be prioritized in their order.
*/
public static Implementation prioritize(Class<?>... prioritizedInterface) {
return prioritize(new TypeList.ForLoadedTypes(prioritizedInterface));
}
Creates a DefaultMethodCall
implementation which searches the given list of interface types for a suitable default method in their order. If no such prioritized interface is suitable, because it is either not defined on the instrumented type or because it does not define a suitable default method, any remaining interface is searched for a suitable default method. If no or more than one method defines a suitable default method, an exception is thrown. Params: - prioritizedInterfaces – A list of prioritized default method interfaces in their prioritization order.
Returns: An implementation which calls an instrumented method's compatible default method that considers the given
interfaces to be prioritized in their order.
/**
* Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
* of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
* because it is either not defined on the instrumented type or because it does not define a suitable default method,
* any remaining interface is searched for a suitable default method. If no or more than one method defines a
* suitable default method, an exception is thrown.
*
* @param prioritizedInterfaces A list of prioritized default method interfaces in their prioritization order.
* @return An implementation which calls an instrumented method's compatible default method that considers the given
* interfaces to be prioritized in their order.
*/
public static Implementation prioritize(Iterable<? extends Class<?>> prioritizedInterfaces) {
List<Class<?>> list = new ArrayList<Class<?>>();
for (Class<?> prioritizedInterface : prioritizedInterfaces) {
list.add(prioritizedInterface);
}
return prioritize(new TypeList.ForLoadedTypes(list));
}
Creates a DefaultMethodCall
implementation which searches the given list of interface types for a suitable default method in their order. If no such prioritized interface is suitable, because it is either not defined on the instrumented type or because it does not define a suitable default method, any remaining interface is searched for a suitable default method. If no or more than one method defines a suitable default method, an exception is thrown. Params: - prioritizedInterface – A list of prioritized default method interfaces in their prioritization order.
Returns: An implementation which calls an instrumented method's compatible default method that considers the given
interfaces to be prioritized in their order.
/**
* Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
* of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
* because it is either not defined on the instrumented type or because it does not define a suitable default method,
* any remaining interface is searched for a suitable default method. If no or more than one method defines a
* suitable default method, an exception is thrown.
*
* @param prioritizedInterface A list of prioritized default method interfaces in their prioritization order.
* @return An implementation which calls an instrumented method's compatible default method that considers the given
* interfaces to be prioritized in their order.
*/
public static Implementation prioritize(TypeDescription... prioritizedInterface) {
return prioritize(Arrays.asList(prioritizedInterface));
}
Creates a DefaultMethodCall
implementation which searches the given list of interface types for a suitable default method in their order. If no such prioritized interface is suitable, because it is either not defined on the instrumented type or because it does not define a suitable default method, any remaining interface is searched for a suitable default method. If no or more than one method defines a suitable default method, an exception is thrown. Params: - prioritizedInterfaces – A collection of prioritized default method interfaces in their prioritization order.
Returns: An implementation which calls an instrumented method's compatible default method that considers the given
interfaces to be prioritized in their order.
/**
* Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation which searches the given list
* of interface types for a suitable default method in their order. If no such prioritized interface is suitable,
* because it is either not defined on the instrumented type or because it does not define a suitable default method,
* any remaining interface is searched for a suitable default method. If no or more than one method defines a
* suitable default method, an exception is thrown.
*
* @param prioritizedInterfaces A collection of prioritized default method interfaces in their prioritization order.
* @return An implementation which calls an instrumented method's compatible default method that considers the given
* interfaces to be prioritized in their order.
*/
public static Implementation prioritize(Collection<? extends TypeDescription> prioritizedInterfaces) {
return new DefaultMethodCall(new ArrayList<TypeDescription>(prioritizedInterfaces));
}
Creates a DefaultMethodCall
implementation without prioritizing any interface. Instead, any interface that is defined for a given type is searched for a suitable default method. If exactly one interface defines a suitable default method, this method is invoked from the instrumented method. Otherwise, an exception is thrown. Returns: An implementation which calls an instrumented method's compatible default method if such a method
is unambiguous.
/**
* Creates a {@link net.bytebuddy.implementation.DefaultMethodCall} implementation without prioritizing any
* interface. Instead, any interface that is defined for a given type is searched for a suitable default method. If
* exactly one interface defines a suitable default method, this method is invoked from the instrumented method.
* Otherwise, an exception is thrown.
*
* @return An implementation which calls an instrumented method's compatible default method if such a method
* is unambiguous.
*/
public static Implementation unambiguousOnly() {
return new DefaultMethodCall(Collections.<TypeDescription>emptyList());
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget, filterRelevant(implementationTarget.getInstrumentedType()));
}
Filters the relevant prioritized interfaces for a given type, i.e. finds the types that are
directly declared on a given instrumented type.
Params: - typeDescription – The instrumented type for which the prioritized interfaces are to be filtered.
Returns: A list of prioritized interfaces that are additionally implemented by the given type.
/**
* Filters the relevant prioritized interfaces for a given type, i.e. finds the types that are
* directly declared on a given instrumented type.
*
* @param typeDescription The instrumented type for which the prioritized interfaces are to be filtered.
* @return A list of prioritized interfaces that are additionally implemented by the given type.
*/
private List<TypeDescription> filterRelevant(TypeDescription typeDescription) {
List<TypeDescription> filtered = new ArrayList<TypeDescription>(prioritizedInterfaces.size());
Set<TypeDescription> relevant = new HashSet<TypeDescription>(typeDescription.getInterfaces().asErasures());
for (TypeDescription prioritizedInterface : prioritizedInterfaces) {
if (relevant.remove(prioritizedInterface)) {
filtered.add(prioritizedInterface);
}
}
return filtered;
}
The appender for implementing a DefaultMethodCall
. /**
* The appender for implementing a {@link net.bytebuddy.implementation.DefaultMethodCall}.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Appender implements ByteCodeAppender {
The implementation target of this appender.
/**
* The implementation target of this appender.
*/
private final Target implementationTarget;
The relevant prioritized interfaces to be considered by this appender.
/**
* The relevant prioritized interfaces to be considered by this appender.
*/
private final List<TypeDescription> prioritizedInterfaces;
The relevant non-prioritized interfaces to be considered by this appender.
/**
* The relevant non-prioritized interfaces to be considered by this appender.
*/
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE)
private final Set<TypeDescription> nonPrioritizedInterfaces;
Creates a new appender for implementing a DefaultMethodCall
. Params: - implementationTarget – The implementation target of this appender.
- prioritizedInterfaces – The relevant prioritized interfaces to be considered by this appender.
/**
* Creates a new appender for implementing a {@link net.bytebuddy.implementation.DefaultMethodCall}.
*
* @param implementationTarget The implementation target of this appender.
* @param prioritizedInterfaces The relevant prioritized interfaces to be considered by this appender.
*/
protected Appender(Target implementationTarget, List<TypeDescription> prioritizedInterfaces) {
this.implementationTarget = implementationTarget;
this.prioritizedInterfaces = prioritizedInterfaces;
this.nonPrioritizedInterfaces = new HashSet<TypeDescription>(implementationTarget.getInstrumentedType().getInterfaces().asErasures());
nonPrioritizedInterfaces.removeAll(prioritizedInterfaces);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
StackManipulation defaultMethodInvocation = locateDefault(instrumentedMethod);
if (!defaultMethodInvocation.isValid()) {
throw new IllegalStateException("Cannot invoke default method on " + instrumentedMethod);
}
StackManipulation.Size stackSize = new StackManipulation.Compound(
MethodVariableAccess.allArgumentsOf(instrumentedMethod).prependThisReference(),
defaultMethodInvocation,
MethodReturn.of(instrumentedMethod.getReturnType())
).apply(methodVisitor, implementationContext);
return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
}
Locates a special method invocation to be invoked from a given method.
Params: - methodDescription – The method that is currently instrumented.
Returns: A potentially illegal stack manipulation representing the default method invocation for the
given method.
/**
* Locates a special method invocation to be invoked from a given method.
*
* @param methodDescription The method that is currently instrumented.
* @return A potentially illegal stack manipulation representing the default method invocation for the
* given method.
*/
private StackManipulation locateDefault(MethodDescription methodDescription) {
MethodDescription.SignatureToken methodToken = methodDescription.asSignatureToken();
SpecialMethodInvocation specialMethodInvocation = SpecialMethodInvocation.Illegal.INSTANCE;
for (TypeDescription typeDescription : prioritizedInterfaces) {
specialMethodInvocation = implementationTarget
.invokeDefault(methodToken, typeDescription)
.withCheckedCompatibilityTo(methodDescription.asTypeToken());
if (specialMethodInvocation.isValid()) {
return specialMethodInvocation;
}
}
for (TypeDescription typeDescription : nonPrioritizedInterfaces) {
SpecialMethodInvocation other = implementationTarget
.invokeDefault(methodToken, typeDescription)
.withCheckedCompatibilityTo(methodDescription.asTypeToken());
if (specialMethodInvocation.isValid() && other.isValid()) {
throw new IllegalStateException(methodDescription + " has an ambiguous default method with "
+ other.getMethodDescription() + " and " + specialMethodInvocation.getMethodDescription());
}
specialMethodInvocation = other;
}
return specialMethodInvocation;
}
}
}