/* * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.invoke.util; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.List; public class ValueConversions { private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); private static EnumMap[] newWrapperCaches(int n) { @SuppressWarnings("unchecked") EnumMap[] caches = (EnumMap[]) new EnumMap[n]; // unchecked warning expected here for (int i = 0; i < n; i++) caches[i] = new EnumMap(Wrapper.class); return caches; } /// Converting references to values. static int unboxInteger(Object x) { if (x == null) return 0; // never NPE return ((Integer) x).intValue(); } static byte unboxByte(Object x) { if (x == null) return 0; // never NPE return ((Byte) x).byteValue(); } static short unboxShort(Object x) { if (x == null) return 0; // never NPE return ((Short) x).shortValue(); } static boolean unboxBoolean(Object x) { if (x == null) return false; // never NPE return ((Boolean) x).booleanValue(); } static char unboxCharacter(Object x) { if (x == null) return 0; // never NPE return ((Character) x).charValue(); } static long unboxLong(Object x) { if (x == null) return 0; // never NPE return ((Long) x).longValue(); } static float unboxFloat(Object x) { if (x == null) return 0; // never NPE return ((Float) x).floatValue(); } static double unboxDouble(Object x) { if (x == null) return 0; // never NPE return ((Double) x).doubleValue(); } /// Converting references to "raw" values. /// A raw primitive value is always an int or long. static int unboxByteRaw(Object x) { return unboxByte(x); } static int unboxShortRaw(Object x) { return unboxShort(x); } static int unboxBooleanRaw(Object x) { return unboxBoolean(x) ? 1 : 0; } static int unboxCharacterRaw(Object x) { return unboxCharacter(x); } static int unboxFloatRaw(Object x) { return Float.floatToIntBits(unboxFloat(x)); } static long unboxDoubleRaw(Object x) { return Double.doubleToRawLongBits(unboxDouble(x)); } private static MethodType unboxType(Wrapper wrap, boolean raw) { return MethodType.methodType(rawWrapper(wrap, raw).primitiveType(), wrap.wrapperType()); } private static final EnumMap[] UNBOX_CONVERSIONS = newWrapperCaches(4); private static MethodHandle unbox(Wrapper wrap, boolean exact, boolean raw) { EnumMap cache = UNBOX_CONVERSIONS[(exact?1:0)+(raw?2:0)]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // slow path switch (wrap) { case OBJECT: mh = IDENTITY; break; case VOID: mh = raw ? ALWAYS_ZERO : IGNORE; break; case INT: case LONG: // these guys don't need separate raw channels if (raw) mh = unbox(wrap, exact, false); break; } if (mh != null) { cache.put(wrap, mh); return mh; } // look up the method String name = "unbox" + wrap.simpleName() + (raw ? "Raw" : ""); MethodType type = unboxType(wrap, raw); if (!exact) { try { // actually, type is wrong; the Java method takes Object mh = IMPL_LOOKUP.findStatic(ValueConversions.class, name, type.erase()); } catch (ReflectiveOperationException ex) { mh = null; } } else { mh = unbox(wrap, !exact, raw).asType(type); } if (mh != null) { cache.put(wrap, mh); return mh; } throw new IllegalArgumentException("cannot find unbox adapter for " + wrap + (raw ? " (raw)" : "")); } public static MethodHandle unbox(Wrapper type, boolean exact) { return unbox(type, exact, false); } public static MethodHandle unboxRaw(Wrapper type, boolean exact) { return unbox(type, exact, true); } public static MethodHandle unbox(Class type, boolean exact) { return unbox(Wrapper.forPrimitiveType(type), exact, false); } public static MethodHandle unboxRaw(Class type, boolean exact) { return unbox(Wrapper.forPrimitiveType(type), exact, true); } /// Converting primitives to references static Integer boxInteger(int x) { return x; } static Byte boxByte(byte x) { return x; } static Short boxShort(short x) { return x; } static Boolean boxBoolean(boolean x) { return x; } static Character boxCharacter(char x) { return x; } static Long boxLong(long x) { return x; } static Float boxFloat(float x) { return x; } static Double boxDouble(double x) { return x; } /// Converting raw primitives to references static Byte boxByteRaw(int x) { return boxByte((byte)x); } static Short boxShortRaw(int x) { return boxShort((short)x); } static Boolean boxBooleanRaw(int x) { return boxBoolean(x != 0); } static Character boxCharacterRaw(int x) { return boxCharacter((char)x); } static Float boxFloatRaw(int x) { return boxFloat(Float.intBitsToFloat(x)); } static Double boxDoubleRaw(long x) { return boxDouble(Double.longBitsToDouble(x)); } // a raw void value is (arbitrarily) a garbage int static Void boxVoidRaw(int x) { return null; } private static MethodType boxType(Wrapper wrap, boolean raw) { // be exact, since return casts are hard to compose Class boxType = wrap.wrapperType(); return MethodType.methodType(boxType, rawWrapper(wrap, raw).primitiveType()); } private static Wrapper rawWrapper(Wrapper wrap, boolean raw) { if (raw) return wrap.isDoubleWord() ? Wrapper.LONG : Wrapper.INT; return wrap; } private static final EnumMap[] BOX_CONVERSIONS = newWrapperCaches(4); private static MethodHandle box(Wrapper wrap, boolean exact, boolean raw) { EnumMap cache = BOX_CONVERSIONS[(exact?1:0)+(raw?2:0)]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // slow path switch (wrap) { case OBJECT: mh = IDENTITY; break; case VOID: if (!raw) mh = ZERO_OBJECT; break; case INT: case LONG: // these guys don't need separate raw channels if (raw) mh = box(wrap, exact, false); break; } if (mh != null) { cache.put(wrap, mh); return mh; } // look up the method String name = "box" + wrap.simpleName() + (raw ? "Raw" : ""); MethodType type = boxType(wrap, raw); if (exact) { try { mh = IMPL_LOOKUP.findStatic(ValueConversions.class, name, type); } catch (ReflectiveOperationException ex) { mh = null; } } else { mh = box(wrap, !exact, raw).asType(type.erase()); } if (mh != null) { cache.put(wrap, mh); return mh; } throw new IllegalArgumentException("cannot find box adapter for " + wrap + (raw ? " (raw)" : "")); } public static MethodHandle box(Class type, boolean exact) { return box(Wrapper.forPrimitiveType(type), exact, false); } public static MethodHandle boxRaw(Class type, boolean exact) { return box(Wrapper.forPrimitiveType(type), exact, true); } public static MethodHandle box(Wrapper type, boolean exact) { return box(type, exact, false); } public static MethodHandle boxRaw(Wrapper type, boolean exact) { return box(type, exact, true); } /// Kludges for when raw values get accidentally boxed. static int unboxRawInteger(Object x) { if (x instanceof Integer) return unboxInteger(x); else return (int) unboxLong(x); } static Integer reboxRawInteger(Object x) { if (x instanceof Integer) return (Integer) x; else return (int) unboxLong(x); } static Byte reboxRawByte(Object x) { if (x instanceof Byte) return (Byte) x; return boxByteRaw(unboxRawInteger(x)); } static Short reboxRawShort(Object x) { if (x instanceof Short) return (Short) x; return boxShortRaw(unboxRawInteger(x)); } static Boolean reboxRawBoolean(Object x) { if (x instanceof Boolean) return (Boolean) x; return boxBooleanRaw(unboxRawInteger(x)); } static Character reboxRawCharacter(Object x) { if (x instanceof Character) return (Character) x; return boxCharacterRaw(unboxRawInteger(x)); } static Float reboxRawFloat(Object x) { if (x instanceof Float) return (Float) x; return boxFloatRaw(unboxRawInteger(x)); } static Long reboxRawLong(Object x) { return (Long) x; //never a rebox } static Double reboxRawDouble(Object x) { if (x instanceof Double) return (Double) x; return boxDoubleRaw(unboxLong(x)); } private static MethodType reboxType(Wrapper wrap) { Class boxType = wrap.wrapperType(); return MethodType.methodType(boxType, Object.class); } private static final EnumMap[] REBOX_CONVERSIONS = newWrapperCaches(2); /** * Because we normalize primitive types to reduce the number of signatures, * primitives are sometimes manipulated under an "erased" type, * either int (for types other than long/double) or long (for all types). * When the erased primitive value is then boxed into an Integer or Long, * the final boxed primitive is sometimes required. This transformation * is called a "rebox". It takes an Integer or Long and produces some * other boxed value. */ public static MethodHandle rebox(Wrapper wrap, boolean exact) { EnumMap cache = REBOX_CONVERSIONS[exact?1:0]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // slow path switch (wrap) { case OBJECT: mh = IDENTITY; break; case VOID: throw new IllegalArgumentException("cannot rebox a void"); } if (mh != null) { cache.put(wrap, mh); return mh; } // look up the method String name = "reboxRaw" + wrap.simpleName(); MethodType type = reboxType(wrap); if (exact) { try { mh = IMPL_LOOKUP.findStatic(ValueConversions.class, name, type); } catch (ReflectiveOperationException ex) { mh = null; } } else { mh = rebox(wrap, !exact).asType(IDENTITY.type()); } if (mh != null) { cache.put(wrap, mh); return mh; } throw new IllegalArgumentException("cannot find rebox adapter for " + wrap); } public static MethodHandle rebox(Class type, boolean exact) { return rebox(Wrapper.forPrimitiveType(type), exact); } /// Width-changing conversions between int and long. static long widenInt(int x) { return (long) x; } static Long widenBoxedInt(Integer x) { return (long)(int)x; } static int narrowLong(long x) { return (int) x; } static Integer narrowBoxedLong(Long x) { return (int)(long) x; } /// Constant functions static void ignore(Object x) { // no value to return; this is an unbox of null return; } static void empty() { return; } static Object zeroObject() { return null; } static int zeroInteger() { return 0; } static long zeroLong() { return 0; } static float zeroFloat() { return 0; } static double zeroDouble() { return 0; } private static final EnumMap[] CONSTANT_FUNCTIONS = newWrapperCaches(2); public static MethodHandle zeroConstantFunction(Wrapper wrap) { EnumMap cache = CONSTANT_FUNCTIONS[0]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // slow path MethodType type = MethodType.methodType(wrap.primitiveType()); switch (wrap) { case VOID: mh = EMPTY; break; case INT: case LONG: case FLOAT: case DOUBLE: try { mh = IMPL_LOOKUP.findStatic(ValueConversions.class, "zero"+wrap.simpleName(), type); } catch (ReflectiveOperationException ex) { mh = null; } break; } if (mh != null) { cache.put(wrap, mh); return mh; } // use the raw method Wrapper rawWrap = wrap.rawPrimitive(); if (mh == null && rawWrap != wrap) { mh = MethodHandles.explicitCastArguments(zeroConstantFunction(rawWrap), type); } if (mh != null) { cache.put(wrap, mh); return mh; } throw new IllegalArgumentException("cannot find zero constant for " + wrap); } /// Converting references to references. /** * Value-killing function. * @param x an arbitrary reference value * @return a null */ static Object alwaysNull(Object x) { return null; } /** * Value-killing function. * @param x an arbitrary reference value * @return a zero */ static int alwaysZero(Object x) { return 0; } /** * Identity function. * @param x an arbitrary reference value * @return the same value x */ static T identity(T x) { return x; } /** * Identity function on ints. * @param x an arbitrary int value * @return the same value x */ static int identity(int x) { return x; } static byte identity(byte x) { return x; } static short identity(short x) { return x; } static boolean identity(boolean x) { return x; } static char identity(char x) { return x; } /** * Identity function on longs. * @param x an arbitrary long value * @return the same value x */ static long identity(long x) { return x; } static float identity(float x) { return x; } static double identity(double x) { return x; } /** * Identity function, with reference cast. * @param t an arbitrary reference type * @param x an arbitrary reference value * @return the same value x */ static T castReference(Class t, U x) { return t.cast(x); } private static final MethodHandle IDENTITY, IDENTITY_I, IDENTITY_J, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY; static { try { MethodType idType = MethodType.genericMethodType(1); MethodType castType = idType.insertParameterTypes(0, Class.class); MethodType alwaysZeroType = idType.changeReturnType(int.class); MethodType ignoreType = idType.changeReturnType(void.class); MethodType zeroObjectType = MethodType.genericMethodType(0); IDENTITY = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", idType); IDENTITY_I = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", MethodType.methodType(int.class, int.class)); IDENTITY_J = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", MethodType.methodType(long.class, long.class)); //CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); CAST_REFERENCE = IMPL_LOOKUP.findStatic(ValueConversions.class, "castReference", castType); ALWAYS_NULL = IMPL_LOOKUP.findStatic(ValueConversions.class, "alwaysNull", idType); ALWAYS_ZERO = IMPL_LOOKUP.findStatic(ValueConversions.class, "alwaysZero", alwaysZeroType); ZERO_OBJECT = IMPL_LOOKUP.findStatic(ValueConversions.class, "zeroObject", zeroObjectType); IGNORE = IMPL_LOOKUP.findStatic(ValueConversions.class, "ignore", ignoreType); EMPTY = IMPL_LOOKUP.findStatic(ValueConversions.class, "empty", ignoreType.dropParameterTypes(0, 1)); } catch (Exception ex) { Error err = new InternalError("uncaught exception"); err.initCause(ex); throw err; } } private static final EnumMap WRAPPER_CASTS = new EnumMap(Wrapper.class); private static final EnumMap EXACT_WRAPPER_CASTS = new EnumMap(Wrapper.class); /** Return a method that casts its sole argument (an Object) to the given type * and returns it as the given type (if exact is true), or as plain Object (if erase is true). */ public static MethodHandle cast(Class type, boolean exact) { if (type.isPrimitive()) throw new IllegalArgumentException("cannot cast primitive type "+type); MethodHandle mh = null; Wrapper wrap = null; EnumMap cache = null; if (Wrapper.isWrapperType(type)) { wrap = Wrapper.forWrapperType(type); cache = (exact ? EXACT_WRAPPER_CASTS : WRAPPER_CASTS); mh = cache.get(wrap); if (mh != null) return mh; } if (VerifyType.isNullReferenceConversion(Object.class, type)) mh = IDENTITY; else if (VerifyType.isNullType(type)) mh = ALWAYS_NULL; else mh = MethodHandles.insertArguments(CAST_REFERENCE, 0, type); if (exact) { MethodType xmt = MethodType.methodType(type, Object.class); mh = MethodHandles.explicitCastArguments(mh, xmt); //mh = AdapterMethodHandle.makeRetypeRaw(IMPL_TOKEN, xmt, mh); } if (cache != null) cache.put(wrap, mh); return mh; } public static MethodHandle identity() { return IDENTITY; } public static MethodHandle identity(Class type) { // This stuff has been moved into MethodHandles: return MethodHandles.identity(type); } public static MethodHandle identity(Wrapper wrap) { EnumMap cache = CONSTANT_FUNCTIONS[1]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // slow path MethodType type = MethodType.methodType(wrap.primitiveType()); if (wrap != Wrapper.VOID) type = type.appendParameterTypes(wrap.primitiveType()); try { mh = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", type); } catch (ReflectiveOperationException ex) { mh = null; } if (mh == null && wrap == Wrapper.VOID) { mh = EMPTY; // #(){} : #()void } if (mh != null) { cache.put(wrap, mh); return mh; } if (mh != null) { cache.put(wrap, mh); return mh; } throw new IllegalArgumentException("cannot find identity for " + wrap); } private static final Object[] NO_ARGS_ARRAY = {}; private static Object[] makeArray(Object... args) { return args; } private static Object[] array() { return NO_ARGS_ARRAY; } private static Object[] array(Object a0) { return makeArray(a0); } private static Object[] array(Object a0, Object a1) { return makeArray(a0, a1); } private static Object[] array(Object a0, Object a1, Object a2) { return makeArray(a0, a1, a2); } private static Object[] array(Object a0, Object a1, Object a2, Object a3) { return makeArray(a0, a1, a2, a3); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4) { return makeArray(a0, a1, a2, a3, a4); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5) { return makeArray(a0, a1, a2, a3, a4, a5); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) { return makeArray(a0, a1, a2, a3, a4, a5, a6); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7, Object a8) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7, Object a8, Object a9) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } static MethodHandle[] makeArrays() { ArrayList arrays = new ArrayList(); MethodHandles.Lookup lookup = IMPL_LOOKUP; for (;;) { int nargs = arrays.size(); MethodType type = MethodType.genericMethodType(nargs).changeReturnType(Object[].class); String name = "array"; MethodHandle array = null; try { array = lookup.findStatic(ValueConversions.class, name, type); } catch (ReflectiveOperationException ex) { } if (array == null) break; arrays.add(array); } assert(arrays.size() == 11); // current number of methods return arrays.toArray(new MethodHandle[0]); } static final MethodHandle[] ARRAYS = makeArrays(); /** Return a method handle that takes the indicated number of Object * arguments and returns an Object array of them, as if for varargs. */ public static MethodHandle varargsArray(int nargs) { if (nargs < ARRAYS.length) return ARRAYS[nargs]; // else need to spin bytecode or do something else fancy throw new UnsupportedOperationException("NYI: cannot form a varargs array of length "+nargs); } private static final List NO_ARGS_LIST = Arrays.asList(NO_ARGS_ARRAY); private static List makeList(Object... args) { return Arrays.asList(args); } private static List list() { return NO_ARGS_LIST; } private static List list(Object a0) { return makeList(a0); } private static List list(Object a0, Object a1) { return makeList(a0, a1); } private static List list(Object a0, Object a1, Object a2) { return makeList(a0, a1, a2); } private static List list(Object a0, Object a1, Object a2, Object a3) { return makeList(a0, a1, a2, a3); } private static List list(Object a0, Object a1, Object a2, Object a3, Object a4) { return makeList(a0, a1, a2, a3, a4); } private static List list(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5) { return makeList(a0, a1, a2, a3, a4, a5); } private static List list(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) { return makeList(a0, a1, a2, a3, a4, a5, a6); } private static List list(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) { return makeList(a0, a1, a2, a3, a4, a5, a6, a7); } private static List list(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7, Object a8) { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8); } private static List list(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7, Object a8, Object a9) { return makeList(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } static MethodHandle[] makeLists() { ArrayList arrays = new ArrayList(); MethodHandles.Lookup lookup = IMPL_LOOKUP; for (;;) { int nargs = arrays.size(); MethodType type = MethodType.genericMethodType(nargs).changeReturnType(List.class); String name = "list"; MethodHandle array = null; try { array = lookup.findStatic(ValueConversions.class, name, type); } catch (ReflectiveOperationException ex) { } if (array == null) break; arrays.add(array); } assert(arrays.size() == 11); // current number of methods return arrays.toArray(new MethodHandle[0]); } static final MethodHandle[] LISTS = makeLists(); /** Return a method handle that takes the indicated number of Object * arguments and returns List. */ public static MethodHandle varargsList(int nargs) { if (nargs < LISTS.length) return LISTS[nargs]; // else need to spin bytecode or do something else fancy throw new UnsupportedOperationException("NYI"); } }