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 {
  46         BOX_MAP.put(boolean.class, Boolean.class);
  47         BOX_MAP.put(byte.class, Byte.class);
  48         BOX_MAP.put(char.class, Character.class);
  49         BOX_MAP.put(short.class, Short.class);
  50         BOX_MAP.put(int.class, Integer.class);
  51         BOX_MAP.put(long.class, Long.class);
  52         BOX_MAP.put(float.class, Float.class);
  53         BOX_MAP.put(double.class, Double.class);
  54 
  55         for ( Entry<Class<?>, Class<?>> e : BOX_MAP.entrySet() ) {
  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 
  64     public static boolean isBoxedType(Class<?> type) {
  65         return BOX_MAP.values().contains(type);
  66     }
  67 
  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      */
  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      */
  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 
 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 
 136     public static Class<?>[] getExplicitlyCastTypesFor(Class<?> type) {
 137         return TYPES;
 138     }
 139 
 140     public static boolean canConvertType(Class<?> from, Class<?> to, boolean isRetType) {
 141         return (Boolean) convert(from, null, to, isRetType, true);
 142     }
 143 
 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() */
 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() */
 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 
 249     public static Argument explicitCastArgument(Argument from, Class<?> toType, boolean isRetType) {
 250         return from; // TODO
 251     }
 252 
 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 
 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 }