All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.easymock.bytebuddy.implementation.DefaultMethodCall Maven / Gradle / Ivy

/*
 * Copyright 2014 - Present 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 org.easymock.bytebuddy.implementation;

import org.easymock.bytebuddy.build.HashCodeAndEqualsPlugin;
import org.easymock.bytebuddy.description.method.MethodDescription;
import org.easymock.bytebuddy.description.type.TypeDescription;
import org.easymock.bytebuddy.description.type.TypeList;
import org.easymock.bytebuddy.dynamic.scaffold.InstrumentedType;
import org.easymock.bytebuddy.implementation.bytecode.ByteCodeAppender;
import org.easymock.bytebuddy.implementation.bytecode.StackManipulation;
import org.easymock.bytebuddy.implementation.bytecode.member.MethodReturn;
import org.easymock.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import org.easymock.bytebuddy.jar.asm.MethodVisitor;

import java.util.*;

/**
 * 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 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. */ @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. */ private final List prioritizedInterfaces; /** * Creates a new {@link org.easymock.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 prioritizedInterfaces) { this.prioritizedInterfaces = prioritizedInterfaces; } /** * Creates a {@link org.easymock.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 {@link org.easymock.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> prioritizedInterfaces) { List> list = new ArrayList>(); for (Class prioritizedInterface : prioritizedInterfaces) { list.add(prioritizedInterface); } return prioritize(new TypeList.ForLoadedTypes(list)); } /** * Creates a {@link org.easymock.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 {@link org.easymock.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 prioritizedInterfaces) { return new DefaultMethodCall(new ArrayList(prioritizedInterfaces)); } /** * Creates a {@link org.easymock.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.emptyList()); } /** * {@inheritDoc} */ public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } /** * {@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. * * @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 filterRelevant(TypeDescription typeDescription) { List filtered = new ArrayList(prioritizedInterfaces.size()); Set relevant = new HashSet(typeDescription.getInterfaces().asErasures()); for (TypeDescription prioritizedInterface : prioritizedInterfaces) { if (relevant.remove(prioritizedInterface)) { filtered.add(prioritizedInterface); } } return filtered; } /** * The appender for implementing a {@link org.easymock.bytebuddy.implementation.DefaultMethodCall}. */ @HashCodeAndEqualsPlugin.Enhance protected static class Appender implements ByteCodeAppender { /** * The implementation target of this appender. */ private final Target implementationTarget; /** * The relevant prioritized interfaces to be considered by this appender. */ private final List prioritizedInterfaces; /** * The relevant non-prioritized interfaces to be considered by this appender. */ @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE) private final Set nonPrioritizedInterfaces; /** * Creates a new appender for implementing a {@link org.easymock.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 prioritizedInterfaces) { this.implementationTarget = implementationTarget; this.prioritizedInterfaces = prioritizedInterfaces; this.nonPrioritizedInterfaces = new HashSet(implementationTarget.getInstrumentedType().getInterfaces().asErasures()); nonPrioritizedInterfaces.removeAll(prioritizedInterfaces); } /** * {@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. * * @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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy