/*
* 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.bind.annotation;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.constant.*;
import net.bytebuddy.utility.JavaConstant;
import net.bytebuddy.utility.JavaType;
import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
The origin annotation provides some meta information about the source method that is bound to this method where
the binding is dependant of the parameter's type:
- If the annotated parameter is of type
Method
, Constructor
or java.lang.reflect.Executable
, the parameter is assigned a reference to the method or constructor it instruments. If the reference is not assignable to the sort of the intercepted source, the target is not considered for binding.
- If the annotated parameter is of type
Class
, the parameter is assigned a reference of the type of the instrumented type.
- If the annotated parameter is of type
String
, the parameter is assigned a string with the value that would be returned by the Method.toString()
method.
- If the annotated parameter is a
int
type, it is assigned the intercepted method's modifiers.
- If the annotated type is
java.lang.invoke.MethodHandle
, a handle of the intercepted method is injected. A java.lang.invoke.MethodHandle
is stored in a class's constant pool and does therefore not face the same runtime performance limitations as a (non-cached) Method
reference. Method handles are only supported for byte code versions starting from Java 7.
- If the annotated type is
java.lang.invoke.MethodType
, a description of the intercepted method's type is injected. Method type descriptions are only supported for byte code versions starting from Java 7.
Any other parameter type will cause an IllegalStateException
.
Important: A method handle or method type reference can only be used if the referenced method's types are all visible to the instrumented type or an IllegalAccessError
will be thrown at runtime.
See Also:
/**
* <p>
* The origin annotation provides some meta information about the source method that is bound to this method where
* the binding is dependant of the parameter's type:
* </p>
* <ol>
* <li>If the annotated parameter is of type {@link java.lang.reflect.Method}, {@link java.lang.reflect.Constructor} or
* {@code java.lang.reflect.Executable}, the parameter is assigned a reference to the method or constructor it
* instruments. If the reference is not assignable to the sort of the intercepted source, the target is not considered
* for binding.</li>
* <li>If the annotated parameter is of type {@link java.lang.Class}, the parameter is assigned a reference of the
* type of the instrumented type.</li>
* <li>If the annotated parameter is of type {@link java.lang.String}, the parameter is assigned a string with
* the value that would be returned by the {@link Method#toString()} method.
* </li>
* <li>If the annotated parameter is a {@code int} type, it is assigned the intercepted method's modifiers.</li>
* <li>If the annotated type is {@code java.lang.invoke.MethodHandle}, a handle of the intercepted method is injected.
* A {@code java.lang.invoke.MethodHandle} is stored in a class's constant pool and does therefore not face the same
* runtime performance limitations as a (non-cached) {@link java.lang.reflect.Method} reference. Method handles are
* only supported for byte code versions starting from Java 7.</li>
* <li>If the annotated type is {@code java.lang.invoke.MethodType}, a description of the intercepted method's type
* is injected. Method type descriptions are only supported for byte code versions starting from Java 7.</li>
* </ol>
* <p>
* Any other parameter type will cause an {@link java.lang.IllegalStateException}.
* </p>
* <p>
* <b>Important:</b> A method handle or method type reference can only be used if the referenced method's types are all visible
* to the instrumented type or an {@link IllegalAccessError} will be thrown at runtime.
* </p>
*
* @see net.bytebuddy.implementation.MethodDelegation
* @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Origin {
Determines if the value that is assigned by this annotation is cached. For values that can be stored in the constant pool, this value is ignored as such values are cached implicitly. As a result, this value currently only affects caching of Method
instances. Returns: true
if the value for this parameter should be cached in a static
field inside the instrumented class.
/**
* Determines if the value that is assigned by this annotation is cached. For values that can be stored in the constant pool,
* this value is ignored as such values are cached implicitly. As a result, this value currently only affects caching of
* {@link Method} instances.
*
* @return {@code true} if the value for this parameter should be cached in a {@code static} field inside the instrumented class.
*/
boolean cache() default true;
Determines if the method should be resolved by using an AccessController
using the privileges of the generated class. Doing so requires the generation of an auxiliary class that implements PrivilegedExceptionAction
. Returns: true
if the class should be looked up using an AccessController
.
/**
* Determines if the method should be resolved by using an {@link java.security.AccessController} using the privileges of the generated class.
* Doing so requires the generation of an auxiliary class that implements {@link java.security.PrivilegedExceptionAction}.
*
* @return {@code true} if the class should be looked up using an {@link java.security.AccessController}.
*/
boolean privileged() default false;
A binder for binding parameters that are annotated with Origin
. See Also:
/**
* A binder for binding parameters that are annotated with {@link net.bytebuddy.implementation.bind.annotation.Origin}.
*
* @see TargetMethodAnnotationDrivenBinder
*/
enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Origin> {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
Loads a method constant onto the operand stack.
Params: - origin – The origin annotation.
- methodDescription – The method description to load.
Returns: An appropriate stack manipulation.
/**
* Loads a method constant onto the operand stack.
*
* @param origin The origin annotation.
* @param methodDescription The method description to load.
* @return An appropriate stack manipulation.
*/
private static StackManipulation methodConstant(Origin origin, MethodDescription.InDefinedShape methodDescription) {
MethodConstant.CanCache methodConstant = origin.privileged()
? MethodConstant.ofPrivileged(methodDescription)
: MethodConstant.of(methodDescription);
return origin.cache() ? methodConstant.cached() : methodConstant;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Class<Origin> getHandledType() {
return Origin.class;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Origin> annotation,
MethodDescription source,
ParameterDescription target,
Implementation.Target implementationTarget,
Assigner assigner,
Assigner.Typing typing) {
TypeDescription parameterType = target.getType().asErasure();
if (parameterType.represents(Class.class)) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(ClassConstant.of(implementationTarget.getOriginType().asErasure()));
} else if (parameterType.represents(Method.class)) {
return source.isMethod()
? new MethodDelegationBinder.ParameterBinding.Anonymous(methodConstant(annotation.load(), source.asDefined()))
: MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
} else if (parameterType.represents(Constructor.class)) {
return source.isConstructor()
? new MethodDelegationBinder.ParameterBinding.Anonymous(methodConstant(annotation.load(), source.asDefined()))
: MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
} else if (JavaType.EXECUTABLE.getTypeStub().equals(parameterType)) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(methodConstant(annotation.load(), source.asDefined()));
} else if (parameterType.represents(String.class)) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(new TextConstant(source.toString()));
} else if (parameterType.represents(int.class)) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(IntegerConstant.forValue(source.getModifiers()));
} else if (parameterType.equals(JavaType.METHOD_HANDLE.getTypeStub())) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(new JavaConstantValue(JavaConstant.MethodHandle.of(source.asDefined())));
} else if (parameterType.equals(JavaType.METHOD_TYPE.getTypeStub())) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(new JavaConstantValue(JavaConstant.MethodType.of(source.asDefined())));
} else {
throw new IllegalStateException("The " + target + " method's " + target.getIndex() +
" parameter is annotated with a Origin annotation with an argument not representing a Class," +
" Method, Constructor, String, int, MethodType or MethodHandle type");
}
}
}
}