/*
 * Copyright (c) 2008, 2013, 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 jdk.internal.vm.annotation.Stable;

public class ValueConversions {
    private static final Class<?> THIS_CLASS = ValueConversions.class;
    private static final Lookup IMPL_LOOKUP = MethodHandles.lookup();

    
Thread-safe canonicalized mapping from Wrapper to MethodHandle with unsynchronized reads and synchronized writes. It's safe to publish MethodHandles by data race because they are immutable.
/** * Thread-safe canonicalized mapping from Wrapper to MethodHandle * with unsynchronized reads and synchronized writes. * It's safe to publish MethodHandles by data race because they are immutable. */
private static class WrapperCache { @Stable private final MethodHandle[] map = new MethodHandle[Wrapper.COUNT]; public MethodHandle get(Wrapper w) { return map[w.ordinal()]; } public synchronized MethodHandle put(final Wrapper w, final MethodHandle mh) { MethodHandle prev = map[w.ordinal()]; if (prev != null) { return prev; } else { map[w.ordinal()] = mh; return mh; } } } private static WrapperCache[] newWrapperCaches(int n) { WrapperCache[] caches = new WrapperCache[n]; for (int i = 0; i < n; i++) caches[i] = new WrapperCache(); return caches; } /// Converting references to values. // There are several levels of this unboxing conversions: // no conversions: exactly Integer.valueOf, etc. // implicit conversions sanctioned by JLS 5.1.2, etc. // explicit conversions as allowed by explicitCastArguments static int unboxInteger(Integer x) { return x; } static int unboxInteger(Object x, boolean cast) { if (x instanceof Integer) return (Integer) x; return primitiveConversion(Wrapper.INT, x, cast).intValue(); } static byte unboxByte(Byte x) { return x; } static byte unboxByte(Object x, boolean cast) { if (x instanceof Byte) return (Byte) x; return primitiveConversion(Wrapper.BYTE, x, cast).byteValue(); } static short unboxShort(Short x) { return x; } static short unboxShort(Object x, boolean cast) { if (x instanceof Short) return (Short) x; return primitiveConversion(Wrapper.SHORT, x, cast).shortValue(); } static boolean unboxBoolean(Boolean x) { return x; } static boolean unboxBoolean(Object x, boolean cast) { if (x instanceof Boolean) return (Boolean) x; return (primitiveConversion(Wrapper.BOOLEAN, x, cast).intValue() & 1) != 0; } static char unboxCharacter(Character x) { return x; } static char unboxCharacter(Object x, boolean cast) { if (x instanceof Character) return (Character) x; return (char) primitiveConversion(Wrapper.CHAR, x, cast).intValue(); } static long unboxLong(Long x) { return x; } static long unboxLong(Object x, boolean cast) { if (x instanceof Long) return (Long) x; return primitiveConversion(Wrapper.LONG, x, cast).longValue(); } static float unboxFloat(Float x) { return x; } static float unboxFloat(Object x, boolean cast) { if (x instanceof Float) return (Float) x; return primitiveConversion(Wrapper.FLOAT, x, cast).floatValue(); } static double unboxDouble(Double x) { return x; } static double unboxDouble(Object x, boolean cast) { if (x instanceof Double) return (Double) x; return primitiveConversion(Wrapper.DOUBLE, x, cast).doubleValue(); } private static MethodType unboxType(Wrapper wrap, int kind) { if (kind == 0) return MethodType.methodType(wrap.primitiveType(), wrap.wrapperType()); return MethodType.methodType(wrap.primitiveType(), Object.class, boolean.class); } private static final WrapperCache[] UNBOX_CONVERSIONS = newWrapperCaches(4); private static MethodHandle unbox(Wrapper wrap, int kind) { // kind 0 -> strongly typed with NPE // kind 1 -> strongly typed but zero for null, // kind 2 -> asType rules: accept multiple box types but only widening conversions with NPE // kind 3 -> explicitCastArguments rules: allow narrowing conversions, zero for null WrapperCache cache = UNBOX_CONVERSIONS[kind]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // slow path switch (wrap) { case OBJECT: case VOID: throw new IllegalArgumentException("unbox "+wrap); } // look up the method String name = "unbox" + wrap.wrapperSimpleName(); MethodType type = unboxType(wrap, kind); try { mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); } catch (ReflectiveOperationException ex) { mh = null; } if (mh != null) { if (kind > 0) { boolean cast = (kind != 2); mh = MethodHandles.insertArguments(mh, 1, cast); } if (kind == 1) { // casting but exact (null -> zero) mh = mh.asType(unboxType(wrap, 0)); } return cache.put(wrap, mh); } throw new IllegalArgumentException("cannot find unbox adapter for " + wrap + (kind <= 1 ? " (exact)" : kind == 3 ? " (cast)" : "")); }
Return an exact unboxer for the given primitive type.
/** Return an exact unboxer for the given primitive type. */
public static MethodHandle unboxExact(Wrapper type) { return unbox(type, 0); }
Return an exact unboxer for the given primitive type, with optional null-to-zero conversion. The boolean says whether to throw an NPE on a null value (false means unbox a zero). The type of the unboxer is of a form like (Integer)int.
/** Return an exact unboxer for the given primitive type, with optional null-to-zero conversion. * The boolean says whether to throw an NPE on a null value (false means unbox a zero). * The type of the unboxer is of a form like (Integer)int. */
public static MethodHandle unboxExact(Wrapper type, boolean throwNPE) { return unbox(type, throwNPE ? 0 : 1); }
Return a widening unboxer for the given primitive type. Widen narrower primitive boxes to the given type. Do not narrow any primitive values or convert null to zero. The type of the unboxer is of a form like (Object)int.
/** Return a widening unboxer for the given primitive type. * Widen narrower primitive boxes to the given type. * Do not narrow any primitive values or convert null to zero. * The type of the unboxer is of a form like (Object)int. */
public static MethodHandle unboxWiden(Wrapper type) { return unbox(type, 2); }
Return a casting unboxer for the given primitive type. Widen or narrow primitive values to the given type, or convert null to zero, as needed. The type of the unboxer is of a form like (Object)int.
/** Return a casting unboxer for the given primitive type. * Widen or narrow primitive values to the given type, or convert null to zero, as needed. * The type of the unboxer is of a form like (Object)int. */
public static MethodHandle unboxCast(Wrapper type) { return unbox(type, 3); } private static final Integer ZERO_INT = 0, ONE_INT = 1; /// Primitive conversions
Produce a Number which represents the given value x according to the primitive type of the given wrapper wrap. Caller must invoke intValue, byteValue, longValue (etc.) on the result to retrieve the desired primitive value.
/** * Produce a Number which represents the given value {@code x} * according to the primitive type of the given wrapper {@code wrap}. * Caller must invoke intValue, byteValue, longValue (etc.) on the result * to retrieve the desired primitive value. */
public static Number primitiveConversion(Wrapper wrap, Object x, boolean cast) { // Maybe merge this code with Wrapper.convert/cast. Number res; if (x == null) { if (!cast) return null; return ZERO_INT; } if (x instanceof Number) { res = (Number) x; } else if (x instanceof Boolean) { res = ((boolean)x ? ONE_INT : ZERO_INT); } else if (x instanceof Character) { res = (int)(char)x; } else { // this will fail with the required ClassCastException: res = (Number) x; } Wrapper xwrap = Wrapper.findWrapperType(x.getClass()); if (xwrap == null || !cast && !wrap.isConvertibleFrom(xwrap)) // this will fail with the required ClassCastException: return (Number) wrap.wrapperType().cast(x); return res; }
The JVM verifier allows boolean, byte, short, or char to widen to int. Support exactly this conversion, from a boxed value type Boolean, Byte, Short, Character, or Integer.
/** * The JVM verifier allows boolean, byte, short, or char to widen to int. * Support exactly this conversion, from a boxed value type Boolean, * Byte, Short, Character, or Integer. */
public static int widenSubword(Object x) { if (x instanceof Integer) return (int) x; else if (x instanceof Boolean) return fromBoolean((boolean) x); else if (x instanceof Character) return (char) x; else if (x instanceof Short) return (short) x; else if (x instanceof Byte) return (byte) x; else // Fail with a ClassCastException. return (int) x; } /// 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; } private static MethodType boxType(Wrapper wrap) { // be exact, since return casts are hard to compose Class<?> boxType = wrap.wrapperType(); return MethodType.methodType(boxType, wrap.primitiveType()); } private static final WrapperCache[] BOX_CONVERSIONS = newWrapperCaches(1); public static MethodHandle boxExact(Wrapper wrap) { WrapperCache cache = BOX_CONVERSIONS[0]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; } // look up the method String name = "box" + wrap.wrapperSimpleName(); MethodType type = boxType(wrap); try { mh = IMPL_LOOKUP.findStatic(THIS_CLASS, name, type); } catch (ReflectiveOperationException ex) { mh = null; } if (mh != null) { return cache.put(wrap, mh); } throw new IllegalArgumentException("cannot find box adapter for " + wrap); } /// Constant functions static void ignore(Object x) { // no value to return; this is an unbox of null } static void empty() { } 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 WrapperCache[] CONSTANT_FUNCTIONS = newWrapperCaches(2); public static MethodHandle zeroConstantFunction(Wrapper wrap) { WrapperCache 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 OBJECT: case INT: case LONG: case FLOAT: case DOUBLE: try { mh = IMPL_LOOKUP.findStatic(THIS_CLASS, "zero"+wrap.wrapperSimpleName(), type); } catch (ReflectiveOperationException ex) { mh = null; } break; } if (mh != null) { return cache.put(wrap, mh); } // use zeroInt and cast the result if (wrap.isSubwordOrInt() && wrap != Wrapper.INT) { mh = MethodHandles.explicitCastArguments(zeroConstantFunction(Wrapper.INT), type); return cache.put(wrap, mh); } throw new IllegalArgumentException("cannot find zero constant for " + wrap); } private static final MethodHandle CAST_REFERENCE, IGNORE, EMPTY; static { try { MethodType idType = MethodType.genericMethodType(1); MethodType ignoreType = idType.changeReturnType(void.class); CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); IGNORE = IMPL_LOOKUP.findStatic(THIS_CLASS, "ignore", ignoreType); EMPTY = IMPL_LOOKUP.findStatic(THIS_CLASS, "empty", ignoreType.dropParameterTypes(0, 1)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw newInternalError("uncaught exception", ex); } } public static MethodHandle ignore() { return IGNORE; }
Return a method that casts its second argument (an Object) to the given type (a Class).
/** Return a method that casts its second argument (an Object) to the given type (a Class). */
public static MethodHandle cast() { return CAST_REFERENCE; } /// Primitive conversions. // These are supported directly by the JVM, usually by a single instruction. // In the case of narrowing to a subword, there may be a pair of instructions. // In the case of booleans, there may be a helper routine to manage a 1-bit value. // This is the full 8x8 matrix (minus the diagonal). // narrow double to all other types: static float doubleToFloat(double x) { // bytecode: d2f return (float) x; } static long doubleToLong(double x) { // bytecode: d2l return (long) x; } static int doubleToInt(double x) { // bytecode: d2i return (int) x; } static short doubleToShort(double x) { // bytecodes: d2i, i2s return (short) x; } static char doubleToChar(double x) { // bytecodes: d2i, i2c return (char) x; } static byte doubleToByte(double x) { // bytecodes: d2i, i2b return (byte) x; } static boolean doubleToBoolean(double x) { return toBoolean((byte) x); } // widen float: static double floatToDouble(float x) { // bytecode: f2d return x; } // narrow float: static long floatToLong(float x) { // bytecode: f2l return (long) x; } static int floatToInt(float x) { // bytecode: f2i return (int) x; } static short floatToShort(float x) { // bytecodes: f2i, i2s return (short) x; } static char floatToChar(float x) { // bytecodes: f2i, i2c return (char) x; } static byte floatToByte(float x) { // bytecodes: f2i, i2b return (byte) x; } static boolean floatToBoolean(float x) { return toBoolean((byte) x); } // widen long: static double longToDouble(long x) { // bytecode: l2d return x; } static float longToFloat(long x) { // bytecode: l2f return x; } // narrow long: static int longToInt(long x) { // bytecode: l2i return (int) x; } static short longToShort(long x) { // bytecodes: f2i, i2s return (short) x; } static char longToChar(long x) { // bytecodes: f2i, i2c return (char) x; } static byte longToByte(long x) { // bytecodes: f2i, i2b return (byte) x; } static boolean longToBoolean(long x) { return toBoolean((byte) x); } // widen int: static double intToDouble(int x) { // bytecode: i2d return x; } static float intToFloat(int x) { // bytecode: i2f return x; } static long intToLong(int x) { // bytecode: i2l return x; } // narrow int: static short intToShort(int x) { // bytecode: i2s return (short) x; } static char intToChar(int x) { // bytecode: i2c return (char) x; } static byte intToByte(int x) { // bytecode: i2b return (byte) x; } static boolean intToBoolean(int x) { return toBoolean((byte) x); } // widen short: static double shortToDouble(short x) { // bytecode: i2d (implicit 's2i') return x; } static float shortToFloat(short x) { // bytecode: i2f (implicit 's2i') return x; } static long shortToLong(short x) { // bytecode: i2l (implicit 's2i') return x; } static int shortToInt(short x) { // (implicit 's2i') return x; } // narrow short: static char shortToChar(short x) { // bytecode: i2c (implicit 's2i') return (char)x; } static byte shortToByte(short x) { // bytecode: i2b (implicit 's2i') return (byte)x; } static boolean shortToBoolean(short x) { return toBoolean((byte) x); } // widen char: static double charToDouble(char x) { // bytecode: i2d (implicit 'c2i') return x; } static float charToFloat(char x) { // bytecode: i2f (implicit 'c2i') return x; } static long charToLong(char x) { // bytecode: i2l (implicit 'c2i') return x; } static int charToInt(char x) { // (implicit 'c2i') return x; } // narrow char: static short charToShort(char x) { // bytecode: i2s (implicit 'c2i') return (short)x; } static byte charToByte(char x) { // bytecode: i2b (implicit 'c2i') return (byte)x; } static boolean charToBoolean(char x) { return toBoolean((byte) x); } // widen byte: static double byteToDouble(byte x) { // bytecode: i2d (implicit 'b2i') return x; } static float byteToFloat(byte x) { // bytecode: i2f (implicit 'b2i') return x; } static long byteToLong(byte x) { // bytecode: i2l (implicit 'b2i') return x; } static int byteToInt(byte x) { // (implicit 'b2i') return x; } static short byteToShort(byte x) { // bytecode: i2s (implicit 'b2i') return (short)x; } static char byteToChar(byte x) { // bytecode: i2b (implicit 'b2i') return (char)x; } // narrow byte to boolean: static boolean byteToBoolean(byte x) { return toBoolean(x); } // widen boolean to all types: static double booleanToDouble(boolean x) { return fromBoolean(x); } static float booleanToFloat(boolean x) { return fromBoolean(x); } static long booleanToLong(boolean x) { return fromBoolean(x); } static int booleanToInt(boolean x) { return fromBoolean(x); } static short booleanToShort(boolean x) { return fromBoolean(x); } static char booleanToChar(boolean x) { return (char)fromBoolean(x); } static byte booleanToByte(boolean x) { return fromBoolean(x); } // helpers to force boolean into the conversion scheme: static boolean toBoolean(byte x) { // see javadoc for MethodHandles.explicitCastArguments return ((x & 1) != 0); } static byte fromBoolean(boolean x) { // see javadoc for MethodHandles.explicitCastArguments return (x ? (byte)1 : (byte)0); } private static final WrapperCache[] CONVERT_PRIMITIVE_FUNCTIONS = newWrapperCaches(Wrapper.COUNT); public static MethodHandle convertPrimitive(Wrapper wsrc, Wrapper wdst) { WrapperCache cache = CONVERT_PRIMITIVE_FUNCTIONS[wsrc.ordinal()]; MethodHandle mh = cache.get(wdst); if (mh != null) { return mh; } // slow path Class<?> src = wsrc.primitiveType(); Class<?> dst = wdst.primitiveType(); MethodType type = MethodType.methodType(dst, src); if (wsrc == wdst) { mh = MethodHandles.identity(src); } else { assert(src.isPrimitive() && dst.isPrimitive()); try { mh = IMPL_LOOKUP.findStatic(THIS_CLASS, src.getSimpleName()+"To"+capitalize(dst.getSimpleName()), type); } catch (ReflectiveOperationException ex) { mh = null; } } if (mh != null) { assert(mh.type() == type) : mh; return cache.put(wdst, mh); } throw new IllegalArgumentException("cannot find primitive conversion function for " + src.getSimpleName()+" -> "+dst.getSimpleName()); } public static MethodHandle convertPrimitive(Class<?> src, Class<?> dst) { return convertPrimitive(Wrapper.forPrimitiveType(src), Wrapper.forPrimitiveType(dst)); } private static String capitalize(String x) { return Character.toUpperCase(x.charAt(0))+x.substring(1); } // handy shared exception makers (they simplify the common case code) private static InternalError newInternalError(String message, Throwable cause) { return new InternalError(message, cause); } private static InternalError newInternalError(Throwable cause) { return new InternalError(cause); } }