1 /*
2 * Copyright 2014 - 2020 Rafael Winterhalter
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.bytebuddy.implementation.bind.annotation;
17
18 import net.bytebuddy.build.HashCodeAndEqualsPlugin;
19 import net.bytebuddy.description.annotation.AnnotationDescription;
20 import net.bytebuddy.description.method.MethodDescription;
21 import net.bytebuddy.description.method.MethodList;
22 import net.bytebuddy.description.method.ParameterDescription;
23 import net.bytebuddy.description.type.TypeDescription;
24 import net.bytebuddy.implementation.Implementation;
25 import net.bytebuddy.implementation.auxiliary.MethodCallProxy;
26 import net.bytebuddy.implementation.bind.MethodDelegationBinder;
27 import net.bytebuddy.implementation.bytecode.StackManipulation;
28 import net.bytebuddy.implementation.bytecode.assign.Assigner;
29 import net.bytebuddy.implementation.bytecode.constant.NullConstant;
30
31 import java.lang.annotation.*;
32 import java.util.concurrent.Callable;
33
34 import static net.bytebuddy.matcher.ElementMatchers.named;
35
36 /**
37 * A parameter with this annotation is assigned a proxy for invoking a default method that fits the intercepted method.
38 * If no suitable default method for the intercepted method can be identified, the target method with the annotated
39 * parameter is considered to be unbindable.
40 *
41 * @see net.bytebuddy.implementation.MethodDelegation
42 * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
43 */
44 @Documented
45 @Retention(RetentionPolicy.RUNTIME)
46 @Target(ElementType.PARAMETER)
47 public @interface DefaultCall {
48
49 /**
50 * If this parameter is not explicitly set, a parameter with the
51 * {@link net.bytebuddy.implementation.bind.annotation.DefaultCall} is only bound to a
52 * source method if this source method directly represents an unambiguous, invokable default method. On the other
53 * hand, if a method is not defined unambiguously by an interface, not setting this parameter will exclude
54 * the target method with the annotated parameter from a binding to the source method.
55 * <p> </p>
56 * If this parameter is however set to an explicit interface type, a default method is always invoked on this given
57 * type as long as this type defines a method with a compatible signature. If this is not the case, the target
58 * method with the annotated parameter is not longer considered as a possible binding candidate of a source method.
59 *
60 * @return The target interface that a default method invocation is to be defined upon. If no such explicit target
61 * is set, this parameter should not be defined as the predefined {@code void} type encodes an implicit resolution.
62 */
63 Class<?> targetType() default void.class;
64
65 /**
66 * Determines if the generated proxy should be {@link java.io.Serializable}.
67 *
68 * @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
69 */
70 boolean serializableProxy() default false;
71
72 /**
73 * Assigns {@code null} to the parameter if it is impossible to invoke the super method or a possible dominant default method, if permitted.
74 *
75 * @return {@code true} if a {@code null} constant should be assigned to this parameter in case that a legal binding is impossible.
76 */
77 boolean nullIfImpossible() default false;
78
79 /**
80 * A binder for handling the
81 * {@link net.bytebuddy.implementation.bind.annotation.DefaultCall}
82 * annotation.
83 *
84 * @see TargetMethodAnnotationDrivenBinder
85 */
86 enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<DefaultCall> {
87
88 /**
89 * The singleton instance.
90 */
91 INSTANCE;
92
93 /**
94 * A reference to the target type method of the default call annotation.
95 */
96 private static final MethodDescription.InDefinedShape TARGET_TYPE;
97
98 /**
99 * A reference to the serializable proxy method of the default call annotation.
100 */
101 private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY;
102
103 /**
104 * A reference to the null if possible method of the default call annotation.
105 */
106 private static final MethodDescription.InDefinedShape NULL_IF_IMPOSSIBLE;
107
108 /*
109 * Looks up method constants of the default call annotation.
110 */
111 static {
112 MethodList<MethodDescription.InDefinedShape> annotationProperties = TypeDescription.ForLoadedType.of(DefaultCall.class).getDeclaredMethods();
113 TARGET_TYPE = annotationProperties.filter(named("targetType")).getOnly();
114 SERIALIZABLE_PROXY = annotationProperties.filter(named("serializableProxy")).getOnly();
115 NULL_IF_IMPOSSIBLE = annotationProperties.filter(named("nullIfImpossible")).getOnly();
116 }
117
118 /**
119 * {@inheritDoc}
120 */
121 public Class<DefaultCall> getHandledType() {
122 return DefaultCall.class;
123 }
124
125 /**
126 * {@inheritDoc}
127 */
128 public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<DefaultCall> annotation,
129 MethodDescription source,
130 ParameterDescription target,
131 Implementation.Target implementationTarget,
132 Assigner assigner,
133 Assigner.Typing typing) {
134 TypeDescription targetType = target.getType().asErasure();
135 if (!targetType.represents(Runnable.class) && !targetType.represents(Callable.class) && !targetType.represents(Object.class)) {
136 throw new IllegalStateException("A default method call proxy can only be assigned to Runnable or Callable types: " + target);
137 } else if (source.isConstructor()) {
138 return annotation.getValue(NULL_IF_IMPOSSIBLE).resolve(Boolean.class)
139 ? new MethodDelegationBinder.ParameterBinding.Anonymous(NullConstant.INSTANCE)
140 : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
141 }
142 TypeDescription typeDescription = annotation.getValue(TARGET_TYPE).resolve(TypeDescription.class);
143 Implementation.SpecialMethodInvocation specialMethodInvocation = (typeDescription.represents(void.class)
144 ? DefaultMethodLocator.Implicit.INSTANCE
145 : new DefaultMethodLocator.Explicit(typeDescription)).resolve(implementationTarget, source).withCheckedCompatibilityTo(source.asTypeToken());
146 StackManipulation stackManipulation;
147 if (specialMethodInvocation.isValid()) {
148 stackManipulation = new MethodCallProxy.AssignableSignatureCall(specialMethodInvocation, annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class));
149 } else if (annotation.load().nullIfImpossible()) {
150 stackManipulation = NullConstant.INSTANCE;
151 } else {
152 return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
153 }
154 return new MethodDelegationBinder.ParameterBinding.Anonymous(stackManipulation);
155 }
156
157 /**
158 * A default method locator is responsible for looking up a default method to a given source method.
159 */
160 protected interface DefaultMethodLocator {
161
162 /**
163 * Locates the correct default method to a given source method.
164 *
165 * @param implementationTarget The current implementation target.
166 * @param source The source method for which a default method should be looked up.
167 * @return A special method invocation of the default method or an illegal special method invocation,
168 * if no suitable invocation could be located.
169 */
170 Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source);
171
172 /**
173 * An implicit default method locator that only permits the invocation of a default method if the source
174 * method itself represents a method that was defined on a default method interface.
175 */
176 enum Implicit implements DefaultMethodLocator {
177
178 /**
179 * The singleton instance.
180 */
181 INSTANCE;
182
183 /**
184 * {@inheritDoc}
185 */
186 public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
187 return implementationTarget.invokeDefault(source.asSignatureToken());
188 }
189 }
190
191 /**
192 * An explicit default method locator attempts to look up a default method in the specified interface type.
193 */
194 @HashCodeAndEqualsPlugin.Enhance
195 class Explicit implements DefaultMethodLocator {
196
197 /**
198 * A description of the type on which the default method should be invoked.
199 */
200 private final TypeDescription typeDescription;
201
202 /**
203 * Creates a new explicit default method locator.
204 *
205 * @param typeDescription The actual target interface as explicitly defined by
206 * {@link DefaultCall#targetType()}.
207 */
208 public Explicit(TypeDescription typeDescription) {
209 this.typeDescription = typeDescription;
210 }
211
212 /**
213 * {@inheritDoc}
214 */
215 public Implementation.SpecialMethodInvocation resolve(Implementation.Target implementationTarget, MethodDescription source) {
216 if (!typeDescription.isInterface()) {
217 throw new IllegalStateException(source + " method carries default method call parameter on non-interface type");
218 }
219 return implementationTarget.invokeDefault(source.asSignatureToken(), typeDescription);
220 }
221 }
222 }
223 }
224 }
225