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.bytecode.assign.primitive;
17
18 import net.bytebuddy.build.HashCodeAndEqualsPlugin;
19 import net.bytebuddy.description.type.TypeDefinition;
20 import net.bytebuddy.description.type.TypeDescription;
21 import net.bytebuddy.implementation.Implementation;
22 import net.bytebuddy.implementation.bytecode.StackManipulation;
23 import net.bytebuddy.implementation.bytecode.StackSize;
24 import net.bytebuddy.implementation.bytecode.assign.Assigner;
25 import net.bytebuddy.jar.asm.MethodVisitor;
26 import net.bytebuddy.jar.asm.Opcodes;
27
28 /**
29  * This delegate is responsible for unboxing a wrapper type to their primitive equivalents.
30  */

31 public enum PrimitiveUnboxingDelegate implements StackManipulation {
32
33     /**
34      * The unboxing delegate for {@code Boolean} types.
35      */

36     BOOLEAN(Boolean.classboolean.class, StackSize.ZERO, "booleanValue""()Z"),
37
38     /**
39      * The unboxing delegate for {@code Byte} types.
40      */

41     BYTE(Byte.classbyte.class, StackSize.ZERO, "byteValue""()B"),
42
43     /**
44      * The unboxing delegate for {@code Short} types.
45      */

46     SHORT(Short.classshort.class, StackSize.ZERO, "shortValue""()S"),
47
48     /**
49      * The unboxing delegate for {@code Character} types.
50      */

51     CHARACTER(Character.classchar.class, StackSize.ZERO, "charValue""()C"),
52
53     /**
54      * The unboxing delegate for {@code Integer} types.
55      */

56     INTEGER(Integer.classint.class, StackSize.ZERO, "intValue""()I"),
57
58     /**
59      * The unboxing delegate for {@code Long} types.
60      */

61     LONG(Long.classlong.class, StackSize.SINGLE, "longValue""()J"),
62
63     /**
64      * The unboxing delegate for {@code Float} types.
65      */

66     FLOAT(Float.classfloat.class, StackSize.ZERO, "floatValue""()F"),
67
68     /**
69      * The unboxing delegate for {@code Double} types.
70      */

71     DOUBLE(Double.classdouble.class, StackSize.SINGLE, "doubleValue""()D");
72
73     /**
74      * The wrapper type of the represented primitive type.
75      */

76     private final TypeDescription wrapperType;
77
78     /**
79      * The represented primitive type.
80      */

81     private final TypeDescription primitiveType;
82
83     /**
84      * The size increase after a wrapper type was unwrapped.
85      */

86     private final Size size;
87
88     /**
89      * The name of the method for unboxing a wrapper value to its primitive value.
90      */

91     private final String unboxingMethodName;
92
93     /**
94      * The descriptor of the method for unboxing a wrapper value to its primitive value.
95      */

96     private final String unboxingMethodDescriptor;
97
98     /**
99      * Creates a new primitive unboxing delegate.
100      *
101      * @param wrapperType              The wrapper type of the represented primitive type.
102      * @param primitiveType            The represented primitive type.
103      * @param sizeDifference           The size difference between the wrapper type and its primitive value.
104      * @param unboxingMethodName       The name of the method for unboxing a wrapper value to its primitive value.
105      * @param unboxingMethodDescriptor The descriptor of the method for unboxing a wrapper value to its primitive value.
106      */

107     PrimitiveUnboxingDelegate(Class<?> wrapperType,
108                               Class<?> primitiveType,
109                               StackSize sizeDifference,
110                               String unboxingMethodName,
111                               String unboxingMethodDescriptor) {
112         this.size = sizeDifference.toIncreasingSize();
113         this.wrapperType = TypeDescription.ForLoadedType.of(wrapperType);
114         this.primitiveType = TypeDescription.ForLoadedType.of(primitiveType);
115         this.unboxingMethodName = unboxingMethodName;
116         this.unboxingMethodDescriptor = unboxingMethodDescriptor;
117     }
118
119     /**
120      * Locates a primitive unboxing delegate for a given primitive type.
121      *
122      * @param typeDefinition A description of the primitive type.
123      * @return A corresponding primitive unboxing delegate.
124      */

125     public static PrimitiveUnboxingDelegate forPrimitive(TypeDefinition typeDefinition) {
126         if (typeDefinition.represents(boolean.class)) {
127             return BOOLEAN;
128         } else if (typeDefinition.represents(byte.class)) {
129             return BYTE;
130         } else if (typeDefinition.represents(short.class)) {
131             return SHORT;
132         } else if (typeDefinition.represents(char.class)) {
133             return CHARACTER;
134         } else if (typeDefinition.represents(int.class)) {
135             return INTEGER;
136         } else if (typeDefinition.represents(long.class)) {
137             return LONG;
138         } else if (typeDefinition.represents(float.class)) {
139             return FLOAT;
140         } else if (typeDefinition.represents(double.class)) {
141             return DOUBLE;
142         } else {
143             throw new IllegalArgumentException("Expected non-void primitive type instead of " + typeDefinition);
144         }
145     }
146
147     /**
148      * Creates an unboxing responsible that is capable of unboxing a wrapper type.
149      * <ol>
150      * <li>If the reference type represents a wrapper type, the wrapper type will simply be unboxed.</li>
151      * <li>If the reference type does not represent a wrapper type, the wrapper type will be inferred by the primitive target
152      * type that is later given to the
153      * {@link net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate.UnboxingResponsible}
154      * in order to then check if the given type is assignable to the inferred wrapper type.</li>
155      * </ol>
156      *
157      * @param typeDefinition A non-primitive type.
158      * @return An unboxing responsible capable of performing an unboxing operation while considering a further assignment
159      * of the unboxed value.
160      */

161     public static UnboxingResponsible forReferenceType(TypeDefinition typeDefinition) {
162         if (typeDefinition.isPrimitive()) {
163             throw new IllegalArgumentException("Expected reference type instead of " + typeDefinition);
164         } else if (typeDefinition.represents(Boolean.class)) {
165             return ExplicitlyTypedUnboxingResponsible.BOOLEAN;
166         } else if (typeDefinition.represents(Byte.class)) {
167             return ExplicitlyTypedUnboxingResponsible.BYTE;
168         } else if (typeDefinition.represents(Short.class)) {
169             return ExplicitlyTypedUnboxingResponsible.SHORT;
170         } else if (typeDefinition.represents(Character.class)) {
171             return ExplicitlyTypedUnboxingResponsible.CHARACTER;
172         } else if (typeDefinition.represents(Integer.class)) {
173             return ExplicitlyTypedUnboxingResponsible.INTEGER;
174         } else if (typeDefinition.represents(Long.class)) {
175             return ExplicitlyTypedUnboxingResponsible.LONG;
176         } else if (typeDefinition.represents(Float.class)) {
177             return ExplicitlyTypedUnboxingResponsible.FLOAT;
178         } else if (typeDefinition.represents(Double.class)) {
179             return ExplicitlyTypedUnboxingResponsible.DOUBLE;
180         } else {
181             return new ImplicitlyTypedUnboxingResponsible(typeDefinition.asGenericType());
182         }
183     }
184
185     /**
186      * Returns the wrapper type that this unboxing delegate represents.
187      *
188      * @return A generic version of this delegate's wrapper type.
189      */

190     protected TypeDescription.Generic getWrapperType() {
191         return wrapperType.asGenericType();
192     }
193
194     /**
195      * {@inheritDoc}
196      */

197     public boolean isValid() {
198         return true;
199     }
200
201     /**
202      * {@inheritDoc}
203      */

204     public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
205         methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
206                 wrapperType.asErasure().getInternalName(),
207                 unboxingMethodName,
208                 unboxingMethodDescriptor,
209                 false);
210         return size;
211     }
212
213     /**
214      * An explicitly types unboxing responsible is applied for directly unboxing a wrapper type.
215      */

216     protected enum ExplicitlyTypedUnboxingResponsible implements UnboxingResponsible {
217
218         /**
219          * An unboxing responsible for unboxing a {@link java.lang.Boolean} type.
220          */

221         BOOLEAN(PrimitiveUnboxingDelegate.BOOLEAN),
222
223         /**
224          * An unboxing responsible for unboxing a {@link java.lang.Byte} type.
225          */

226         BYTE(PrimitiveUnboxingDelegate.BYTE),
227
228         /**
229          * An unboxing responsible for unboxing a {@link java.lang.Short} type.
230          */

231         SHORT(PrimitiveUnboxingDelegate.SHORT),
232
233         /**
234          * An unboxing responsible for unboxing a {@link java.lang.Character} type.
235          */

236         CHARACTER(PrimitiveUnboxingDelegate.CHARACTER),
237
238         /**
239          * An unboxing responsible for unboxing a {@link java.lang.Integer} type.
240          */

241         INTEGER(PrimitiveUnboxingDelegate.INTEGER),
242
243         /**
244          * An unboxing responsible for unboxing a {@link java.lang.Long} type.
245          */

246         LONG(PrimitiveUnboxingDelegate.LONG),
247
248         /**
249          * An unboxing responsible for unboxing a {@link java.lang.Float} type.
250          */

251         FLOAT(PrimitiveUnboxingDelegate.FLOAT),
252
253         /**
254          * An unboxing responsible for unboxing a {@link java.lang.Double} type.
255          */

256         DOUBLE(PrimitiveUnboxingDelegate.DOUBLE);
257
258         /**
259          * The primitive unboxing delegate for handling the given wrapper type.
260          */

261         private final PrimitiveUnboxingDelegate primitiveUnboxingDelegate;
262
263         /**
264          * Creates a new explicitly typed unboxing responsible.
265          *
266          * @param primitiveUnboxingDelegate The primitive unboxing delegate for handling the given wrapper type.
267          */

268         ExplicitlyTypedUnboxingResponsible(PrimitiveUnboxingDelegate primitiveUnboxingDelegate) {
269             this.primitiveUnboxingDelegate = primitiveUnboxingDelegate;
270         }
271
272         /**
273          * {@inheritDoc}
274          */

275         public StackManipulation assignUnboxedTo(TypeDescription.Generic targetType, Assigner assigner, Assigner.Typing typing) {
276             return new Compound(
277                     primitiveUnboxingDelegate,
278                     PrimitiveWideningDelegate.forPrimitive(primitiveUnboxingDelegate.primitiveType).widenTo(targetType));
279         }
280     }
281
282     /**
283      * Implementations represent an unboxing delegate that is able to perform the unboxing operation.
284      */

285     public interface UnboxingResponsible {
286
287         /**
288          * Attempts to unbox the represented type in order to assign the unboxed value to the given target type
289          * while using the assigner that is provided by the method call.
290          *
291          * @param target   The type that is the desired outcome of the assignment.
292          * @param assigner The assigner used to assign the unboxed type to the target type.
293          * @param typing   Determines if a type-casting should be attempted for incompatible types.
294          * @return A stack manipulation representing this assignment if such an assignment is possible. An illegal
295          * assignment otherwise.
296          */

297         StackManipulation assignUnboxedTo(TypeDescription.Generic target, Assigner assigner, Assigner.Typing typing);
298     }
299
300     /**
301      * An unboxing responsible for an implicitly typed value. This implementation is applied for source types that
302      * were not found to be of a given wrapper type. Instead, this unboxing responsible tries to assign the
303      * source type to the primitive target type's wrapper type before performing an unboxing operation.
304      */

305     @HashCodeAndEqualsPlugin.Enhance
306     protected static class ImplicitlyTypedUnboxingResponsible implements UnboxingResponsible {
307
308         /**
309          * The original type which should be unboxed but is not of any known wrapper type.
310          */

311         private final TypeDescription.Generic originalType;
312
313         /**
314          * Creates a new implicitly typed unboxing responsible.
315          *
316          * @param originalType The original type which should be unboxed but is not of any known wrapper type.
317          */

318         protected ImplicitlyTypedUnboxingResponsible(TypeDescription.Generic originalType) {
319             this.originalType = originalType;
320         }
321
322         /**
323          * {@inheritDoc}
324          */

325         public StackManipulation assignUnboxedTo(TypeDescription.Generic target, Assigner assigner, Assigner.Typing typing) {
326             PrimitiveUnboxingDelegate primitiveUnboxingDelegate = PrimitiveUnboxingDelegate.forPrimitive(target);
327             return new Compound(
328                     assigner.assign(originalType, primitiveUnboxingDelegate.getWrapperType(), typing),
329                     primitiveUnboxingDelegate);
330         }
331     }
332 }
333