1 /*
2  * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 package vm.mlvm.meth.share;
25 
26 import java.util.HashMap;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 
32 import nsk.share.test.TestUtils;
33 import vm.mlvm.share.Env;
34 
35 public class TestTypes {
36 
37     public static final Class<?>[] TYPES = {
38         // void.class
39         boolean.class, byte.class, char.class, short.class, int.class, long.class,
40         float.class, double.class, Object.class, String.class
41     };
42 
43     public static final Map<Class<?>, Class<?>> BOX_MAP = new HashMap<Class<?>, Class<?>>();
44     public static final Map<Class<?>, Class<?>> UNBOX_MAP = new HashMap<Class<?>, Class<?>>();
45     static {
BOX_MAP.put(boolean.class, Boolean.class)46         BOX_MAP.put(boolean.class, Boolean.class);
BOX_MAP.put(byte.class, Byte.class)47         BOX_MAP.put(byte.class, Byte.class);
BOX_MAP.put(char.class, Character.class)48         BOX_MAP.put(char.class, Character.class);
BOX_MAP.put(short.class, Short.class)49         BOX_MAP.put(short.class, Short.class);
BOX_MAP.put(int.class, Integer.class)50         BOX_MAP.put(int.class, Integer.class);
BOX_MAP.put(long.class, Long.class)51         BOX_MAP.put(long.class, Long.class);
BOX_MAP.put(float.class, Float.class)52         BOX_MAP.put(float.class, Float.class);
BOX_MAP.put(double.class, Double.class)53         BOX_MAP.put(double.class, Double.class);
54 
55         for ( Entry<Class<?>, Class<?>> e : BOX_MAP.entrySet() ) {
e.getValue()56             UNBOX_MAP.put(e.getValue(), e.getKey());
57         }
58     }
59 
60     public static final Class<?>[] PRIMITIVE_WIDENING_HIERARCHY = {
61         byte.class, short.class, char.class, int.class, long.class, float.class, double.class
62     };
63 
isBoxedType(Class<?> type)64     public static boolean isBoxedType(Class<?> type) {
65         return BOX_MAP.values().contains(type);
66     }
67 
addPrimitiveAndBoxed(List<Class<?>> list, Class<?> type)68     private static void addPrimitiveAndBoxed(List<Class<?>> list, Class<?> type) {
69         list.add(type);
70         list.add(BOX_MAP.get(type));
71     }
72 
73     /**
74      * WPC = JLS 5.1.2 Widening Primitive Conversions
75      * @param type
76      * @return
77      */
addWPCAssignableTypesFor(List<Class<?>> result, Class<?> type)78     private static void addWPCAssignableTypesFor(List<Class<?>> result, Class<?> type) {
79         if ( type.equals(short.class) ) {
80             addPrimitiveAndBoxed(result, byte.class);
81         }
82 
83         if ( type.equals(int.class) || type.equals(long.class) || type.equals(float.class) || type.equals(double.class) ) {
84             for ( int p = 0; p < PRIMITIVE_WIDENING_HIERARCHY.length; p++ ) {
85                 Class<?> c = PRIMITIVE_WIDENING_HIERARCHY[p];
86                 addPrimitiveAndBoxed(result, c);
87 
88                 if ( c.equals(type) )
89                     break;
90             }
91         }
92     }
93 
94     /**
95      * NPC = JLS 5.1.3 Narrowing Primitive Conversions
96      *     + JLS 5.1.4 Widening and Narrowing Primitive Conversions
97      */
addNPCAssignableTypesFor(List<Class<?>> result, Class<?> type)98     private static void addNPCAssignableTypesFor(List<Class<?>> result, Class<?> type) {
99         // JLS 5.1.4
100         if ( type.equals(char.class) ) {
101             addPrimitiveAndBoxed(result, byte.class);
102         }
103 
104         // JLS 5.1.3
105         int p = 0;
106         for ( ; p < PRIMITIVE_WIDENING_HIERARCHY.length; p++ ) {
107             if ( PRIMITIVE_WIDENING_HIERARCHY[p].equals(type) )
108                 break;
109         }
110 
111         for ( ; p < PRIMITIVE_WIDENING_HIERARCHY.length; p++ ) {
112             addPrimitiveAndBoxed(result, PRIMITIVE_WIDENING_HIERARCHY[p]);
113         }
114     }
115 
getAssignableTypesFor(Class<?> type)116     public static Class<?>[] getAssignableTypesFor(Class<?> type) {
117         if ( type.equals(void.class) )
118             return new Class<?>[0];
119 
120         if ( type.isPrimitive() ) {
121             List<Class<?>> result = new LinkedList<Class<?>>();
122             addPrimitiveAndBoxed(result, type);
123             addWPCAssignableTypesFor(result, type);
124             return (Class<?>[]) result.toArray();
125         }
126 
127         if ( type.equals(Object.class) )
128             return new Class<?>[] { Object.class, String.class };
129 
130         if ( type.equals(String.class) )
131             return new Class<?>[] { String.class };
132 
133         throw new IllegalArgumentException("Don't know how to handle type " + type);
134     }
135 
getExplicitlyCastTypesFor(Class<?> type)136     public static Class<?>[] getExplicitlyCastTypesFor(Class<?> type) {
137         return TYPES;
138     }
139 
canConvertType(Class<?> from, Class<?> to, boolean isRetType)140     public static boolean canConvertType(Class<?> from, Class<?> to, boolean isRetType) {
141         return (Boolean) convert(from, null, to, isRetType, true);
142     }
143 
canExplicitlyCastType(Class<?> from, Class<?> to, boolean isRetType)144     public static boolean canExplicitlyCastType(Class<?> from, Class<?> to, boolean isRetType) {
145         return true; // TODO: can use explicitCaseArguments() to convert "from" to "to"
146     }
147 
148     /** convert an argument according to the rules defined in MethodHandles.convertArguments() */
convertArgument(Argument from, Class<?> toType, boolean isRetType)149     public static Argument convertArgument(Argument from, Class<?> toType, boolean isRetType) throws ClassCastException {
150         Class<?> fromType = from.getType();
151 
152         if ( fromType.equals(toType) )
153             return from;
154 
155         Object toValue = convert(fromType, from.getValue(), toType, isRetType, false);
156         return new Argument(toType, toValue, from.isPreserved(), from.getTag());
157     }
158 
159     /** convert an argument according to the rules defined in MethodHandles.convertArguments() */
convert(Class<?> fromType, Object fromValue, Class<?> toType, boolean isRetType, boolean dryRun)160     private static Object convert(Class<?> fromType, Object fromValue, Class<?> toType, boolean isRetType, boolean dryRun) {
161         if ( ! dryRun ) {
162             if ( ! fromType.isPrimitive() )
163                 TestUtils.assertTrue(fromType.isAssignableFrom(fromValue.getClass()), "fromType " + fromType + " is not assignable from the type of fromValue " + fromValue);
164             else
165                 TestUtils.assertTrue(BOX_MAP.get(fromType).isAssignableFrom(fromValue.getClass()), "Boxed fromType " + fromType + " is not assignable from the type of fromValue " + fromValue);
166         }
167 
168         // JLS 5.1.1 Identity conversion
169         if ( fromType.equals(toType) )
170             return dryRun ? Boolean.TRUE : fromValue;
171 
172         Class<?> exactFromType = fromValue.getClass();
173 
174         Throwable cause = null;
175 
176         try {
177             if ( isRetType ) {
178                 // If the return type T1 is void, any returned value is discarded
179                 if ( toType.equals(void.class) )
180                     return dryRun ? true : null;
181 
182                 // If the return type T0 is void and T1 a reference, a null value is introduced.
183                 if ( fromType.equals(void.class) && ! toType.isPrimitive() )
184                     return dryRun ? true : null;
185 
186                 // If the return type T0 is void and T1 a primitive, a zero value is introduced.
187                 if ( fromType.equals(void.class) && toType.isPrimitive() ) {
188                     return dryRun ? true : BOX_MAP.get(toType).newInstance();
189                 }
190             }
191 
192             // If T0 and T1 are references, then a cast to T1 is applied.
193             // (The types do not need to be related in any particular way.)
194             if ( ! fromType.isPrimitive() && ! toType.isPrimitive() ) {
195                 return dryRun ? toType.isAssignableFrom(fromType)
196                               : toType.cast(fromValue);
197             }
198 
199             // If T0 and T1 are primitives, then a Java method invocation conversion
200             // (JLS 5.3) is applied, if one exists.
201             if ( fromType.isPrimitive() && toType.isPrimitive() ) {
202                 if ( dryRun ) {
203                     for ( Class<?> tt : getAssignableTypesFor(toType) ) {
204                         if ( tt.equals(fromType) )
205                             return true;
206                     }
207 
208                     return false;
209                 } else {
210                     return PrimitiveTypeConverter.convert(fromValue, toType);
211                 }
212             }
213 
214             // If T0 is a primitive and T1 a reference, a boxing conversion is applied
215             // if one exists, possibly followed by a reference conversion to a superclass.
216             // T1 must be a wrapper class or a supertype of one.
217             if ( fromType.isPrimitive() && ! toType.isPrimitive() ) {
218                 return dryRun ? toType.isAssignableFrom(BOX_MAP.get(fromType))
219                               : toType.cast(fromType.cast(fromValue));
220             }
221 
222             // If T0 is a reference and T1 a primitive, an unboxing conversion will be applied
223             // at runtime, possibly followed by a Java method invocation conversion (JLS 5.3)
224             // on the primitive value. (These are the widening conversions.) T0 must be
225             // a wrapper class or a supertype of one. (In the case where T0 is Object,
226             // these are the conversions allowed by java.lang.reflect.Method.invoke.)
227             if ( ! fromType.isPrimitive() && toType.isPrimitive() ) {
228                 if ( dryRun ) {
229                     if ( ! BOX_MAP.values().contains(exactFromType) )
230                         return false;
231 
232 
233                 }
234 
235                 return dryRun ? toType.isAssignableFrom(BOX_MAP.get(fromType))
236                               : toType.cast(fromType.cast(fromValue));
237             }
238 
239         } catch ( Throwable t ) {
240             cause = t;
241         }
242 
243         if ( dryRun )
244             return Boolean.FALSE;
245         else
246             throw (ClassCastException) (new ClassCastException("Can't convert value [" + fromValue + "] from type [" + fromType + "] to type [" + toType + "]")).initCause(cause);
247     }
248 
explicitCastArgument(Argument from, Class<?> toType, boolean isRetType)249     public static Argument explicitCastArgument(Argument from, Class<?> toType, boolean isRetType) {
250         return from; // TODO
251     }
252 
nextRandomValueForType(Class<?> type)253     public static Object nextRandomValueForType(Class<?> type) throws InstantiationException, IllegalAccessException {
254         if (type.equals(void.class))
255             return null;
256 
257         if (type.equals(boolean.class) || type.equals(Boolean.class))
258             return new Boolean(Env.getRNG().nextInt(2) == 0);
259 
260         if (type.equals(byte.class) || type.equals(Byte.class))
261             return new Byte((byte) Env.getRNG().nextInt(1 << Byte.SIZE));
262 
263         if (type.equals(int.class) || type.equals(Integer.class))
264             return new Integer(Env.getRNG().nextInt());
265 
266         if (type.equals(short.class) || type.equals(Short.class))
267             return new Short((short) Env.getRNG().nextInt(1 << Short.SIZE));
268 
269         if (type.equals(long.class) || type.equals(Long.class))
270             return new Long(Env.getRNG().nextLong());
271 
272         if (type.equals(float.class) || type.equals(Float.class))
273             return new Float(Env.getRNG().nextFloat());
274 
275         if (type.equals(double.class) || type.equals(Double.class))
276             return new Double(Env.getRNG().nextDouble());
277 
278         if (type.equals(char.class) || type.equals(Character.class))
279             return new Character((char) (32 + Env.getRNG().nextInt(96)));
280 
281         if (type.equals(Object.class))
282             return new Object();
283 
284         if (type.equals(String.class)) {
285             StringBuilder sb = new StringBuilder();
286             for (int i = Env.getRNG().nextInt(100); i > 0; i--)
287                 sb.append(nextRandomValueForType(char.class));
288             return sb.toString();
289         }
290 
291         throw new IllegalArgumentException("Don't know how to handle type " + type);
292     }
293 
getSlotsCount(Class<?> type)294     public static int getSlotsCount(Class<?> type) {
295         if (type.equals(void.class))
296             return 0;
297 
298         if ( type.equals(boolean.class) || type.equals(Boolean.class)
299           || type.equals(byte.class) || type.equals(Byte.class)
300           || type.equals(int.class) || type.equals(Integer.class)
301           || type.equals(short.class) || type.equals(Short.class)
302           || type.equals(float.class) || type.equals(Float.class)
303           || type.equals(char.class) || type.equals(Character.class)
304           || Object.class.isAssignableFrom(type) )
305             return 1;
306 
307         if ( type.equals(long.class) || type.equals(Long.class)
308           || type.equals(double.class) || type.equals(Double.class))
309             return 2;
310 
311         throw new IllegalArgumentException("Don't know how to handle type " + type);
312     }
313 }
314