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>&nbsp;</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 trueif 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 trueif 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