From 80b0f3b8789921d46103d9af14a52e75406b92af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Tue, 28 Sep 2021 21:26:16 +0800 Subject: [PATCH] do not introduce sun package dependence --- .../agent/handler/SourceClassHandler.java | 141 +++------ .../testable/agent/model/BasicType.java | 61 ++++ .../testable/agent/model/WrapperType.java | 276 ++++++++++++++++++ 3 files changed, 371 insertions(+), 107 deletions(-) create mode 100644 testable-agent/src/main/java/com/alibaba/testable/agent/model/BasicType.java create mode 100644 testable-agent/src/main/java/com/alibaba/testable/agent/model/WrapperType.java diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java index 8ecfdb6..4f3ba6b 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java @@ -1,5 +1,6 @@ package com.alibaba.testable.agent.handler; +import com.alibaba.testable.agent.model.BasicType; import com.alibaba.testable.agent.model.MethodInfo; import com.alibaba.testable.agent.model.TravelStatus; import com.alibaba.testable.agent.util.BytecodeUtil; @@ -11,11 +12,13 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; -import sun.invoke.util.Wrapper; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR; @@ -25,7 +28,7 @@ import static com.alibaba.testable.core.constant.ConstPool.CONSTRUCTOR; */ public class SourceClassHandler extends BaseClassHandler { - private AtomicInteger atomicInteger = new AtomicInteger(); + private final AtomicInteger atomicInteger = new AtomicInteger(); private final String mockClassName; private final List injectMethods; private final Set invokeOps = new HashSet() {{ @@ -42,6 +45,7 @@ public class SourceClassHandler extends BaseClassHandler { /** * Handle bytecode of source class + * * @param cn original class node */ @Override @@ -79,11 +83,11 @@ public class SourceClassHandler extends BaseClassHandler { int i = 0; do { if (invokeOps.contains(instructions[i].getOpcode())) { - MethodInsnNode node = (MethodInsnNode)instructions[i]; + MethodInsnNode node = (MethodInsnNode) instructions[i]; if (CONSTRUCTOR.equals(node.name)) { if (LogUtil.isVerboseEnabled()) { LogUtil.verbose(" Line %d, constructing \"%s\"", getLineNum(instructions, i), - MethodUtil.toJavaMethodDesc(node.owner, node.desc)); + MethodUtil.toJavaMethodDesc(node.owner, node.desc)); } MethodInfo newOperatorInjectMethod = getNewOperatorInjectMethod(newOperatorInjectMethods, node); if (newOperatorInjectMethod != null) { @@ -100,7 +104,7 @@ public class SourceClassHandler extends BaseClassHandler { } else { if (LogUtil.isVerboseEnabled()) { LogUtil.verbose(" Line %d, invoking \"%s\"", getLineNum(instructions, i), - MethodUtil.toJavaMethodDesc(node.owner, node.name, node.desc)); + MethodUtil.toJavaMethodDesc(node.owner, node.name, node.desc)); } MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node); if (mockMethod != null) { @@ -111,7 +115,7 @@ public class SourceClassHandler extends BaseClassHandler { handleFrameStackChange(mn, mockMethod, rangeStart, i); } instructions = replaceMemberCallOps(mn, mockMethod, - instructions, node.owner, node.getOpcode(), rangeStart, i); + instructions, node.owner, node.getOpcode(), rangeStart, i); i = rangeStart; } else { LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i)); @@ -126,8 +130,9 @@ public class SourceClassHandler extends BaseClassHandler { /** * find the mock method fit for specified method node + * * @param memberInjectMethods mock methods available - * @param node method node to match for + * @param node method node to match for * @return mock method info */ private MethodInfo getMemberInjectMethodName(Set memberInjectMethods, MethodInsnNode node) { @@ -154,12 +159,12 @@ public class SourceClassHandler extends BaseClassHandler { private String getConstructorInjectDesc(MethodInsnNode constructorNode) { return constructorNode.desc.substring(0, constructorNode.desc.length() - 1) + - ClassUtil.toByteCodeClassName(constructorNode.owner); + ClassUtil.toByteCodeClassName(constructorNode.owner); } private int getConstructorStart(AbstractInsnNode[] instructions, String target, int rangeEnd) { for (int i = rangeEnd - 1; i >= 0; i--) { - if (instructions[i].getOpcode() == Opcodes.NEW && ((TypeInsnNode)instructions[i]).desc.equals(target)) { + if (instructions[i].getOpcode() == Opcodes.NEW && ((TypeInsnNode) instructions[i]).desc.equals(target)) { return i; } } @@ -167,7 +172,7 @@ public class SourceClassHandler extends BaseClassHandler { } private int getMemberMethodStart(AbstractInsnNode[] instructions, int rangeEnd) { - int stackLevel = getInitialStackLevel((MethodInsnNode)instructions[rangeEnd]); + int stackLevel = getInitialStackLevel((MethodInsnNode) instructions[rangeEnd]); if (stackLevel < 0) { return rangeEnd; } @@ -187,13 +192,13 @@ public class SourceClassHandler extends BaseClassHandler { break; case LookingForLabel: if (instructions[i] instanceof LabelNode) { - labelToJump = ((LabelNode)instructions[i]).getLabel(); + labelToJump = ((LabelNode) instructions[i]).getLabel(); status = TravelStatus.LookingForJump; } break; case LookingForJump: if (instructions[i] instanceof JumpInsnNode && - ((JumpInsnNode)instructions[i]).label.getLabel().equals(labelToJump)) { + ((JumpInsnNode) instructions[i]).label.getLabel().equals(labelToJump)) { stackLevel += getStackLevelChange(instructions[i]); labelToJump = null; status = TravelStatus.Normal; @@ -226,11 +231,11 @@ public class SourceClassHandler extends BaseClassHandler { case Opcodes.INVOKESPECIAL: case Opcodes.INVOKEVIRTUAL: case Opcodes.INVOKEINTERFACE: - return stackEffectOfInvocation(((MethodInsnNode)instruction).desc) + 1; + return stackEffectOfInvocation(((MethodInsnNode) instruction).desc) + 1; case Opcodes.INVOKESTATIC: - return stackEffectOfInvocation(((MethodInsnNode)instruction).desc); + return stackEffectOfInvocation(((MethodInsnNode) instruction).desc); case Opcodes.INVOKEDYNAMIC: - return stackEffectOfInvocation(((InvokeDynamicInsnNode)instruction).desc); + return stackEffectOfInvocation(((InvokeDynamicInsnNode) instruction).desc); case -1: // either LabelNode or LineNumberNode return 0; @@ -244,7 +249,7 @@ public class SourceClassHandler extends BaseClassHandler { } private AbstractInsnNode[] replaceNewOps(MethodNode mn, MethodInfo newOperatorInjectMethod, - AbstractInsnNode[] instructions, int start, int end) { + AbstractInsnNode[] instructions, int start, int end) { String mockMethodName = newOperatorInjectMethod.getMockName(); int invokeOpcode = newOperatorInjectMethod.isStatic() ? INVOKESTATIC : INVOKEVIRTUAL; String log = String.format("Line %d, mock method \"%s\" used", getLineNum(instructions, start), mockMethodName); @@ -253,14 +258,14 @@ public class SourceClassHandler extends BaseClassHandler { } else { LogUtil.diagnose(2, log); } - String classType = ((TypeInsnNode)instructions[start]).desc; - String constructorDesc = ((MethodInsnNode)instructions[end]).desc; + String classType = ((TypeInsnNode) instructions[start]).desc; + String constructorDesc = ((MethodInsnNode) instructions[end]).desc; if (!newOperatorInjectMethod.isStatic()) { mn.instructions.insertBefore(instructions[start], new MethodInsnNode(INVOKESTATIC, mockClassName, - GET_TESTABLE_REF, VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), false)); + GET_TESTABLE_REF, VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), false)); } mn.instructions.insertBefore(instructions[end], new MethodInsnNode(invokeOpcode, mockClassName, - mockMethodName, getConstructorInjectDesc(constructorDesc, classType), false)); + mockMethodName, getConstructorInjectDesc(constructorDesc, classType), false)); mn.instructions.remove(instructions[start]); mn.instructions.remove(instructions[start + 1]); mn.instructions.remove(instructions[end]); @@ -270,7 +275,7 @@ public class SourceClassHandler extends BaseClassHandler { private int getLineNum(AbstractInsnNode[] instructions, int start) { for (int i = start - 1; i >= 0; i--) { if (instructions[i] instanceof LineNumberNode) { - return ((LineNumberNode)instructions[i]).line; + return ((LineNumberNode) instructions[i]).line; } } return 0; @@ -278,13 +283,13 @@ public class SourceClassHandler extends BaseClassHandler { private String getConstructorInjectDesc(String constructorDesc, String classType) { return constructorDesc.substring(0, constructorDesc.length() - 1) + - ClassUtil.toByteCodeClassName(classType); + ClassUtil.toByteCodeClassName(classType); } private AbstractInsnNode[] replaceMemberCallOps(MethodNode mn, MethodInfo mockMethod, AbstractInsnNode[] instructions, - String ownerClass, int opcode, int start, int end) { + String ownerClass, int opcode, int start, int end) { String log = String.format("Line %d, mock method \"%s\" used", getLineNum(instructions, start), - mockMethod.getMockName()); + mockMethod.getMockName()); if (LogUtil.isVerboseEnabled()) { LogUtil.verbose(5, log); } else { @@ -292,7 +297,7 @@ public class SourceClassHandler extends BaseClassHandler { } if (!mockMethod.isStatic()) { mn.instructions.insertBefore(instructions[start], new MethodInsnNode(INVOKESTATIC, mockClassName, - GET_TESTABLE_REF, VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), false)); + GET_TESTABLE_REF, VOID_ARGS + ClassUtil.toByteCodeClassName(mockClassName), false)); } if (Opcodes.INVOKESTATIC == opcode || isCompanionMethod(ownerClass, opcode)) { // append a null value if it was a static invoke or in kotlin companion class @@ -306,7 +311,7 @@ public class SourceClassHandler extends BaseClassHandler { // method with @MockMethod will be modified as public access int invokeOpcode = mockMethod.isStatic() ? INVOKESTATIC : INVOKEVIRTUAL; mn.instructions.insertBefore(instructions[end], new MethodInsnNode(invokeOpcode, mockClassName, - mockMethod.getMockName(), mockMethod.getMockDesc(), false)); + mockMethod.getMockName(), mockMethod.getMockDesc(), false)); mn.instructions.remove(instructions[end]); mn.maxStack++; return mn.instructions.toArray(); @@ -317,7 +322,7 @@ public class SourceClassHandler extends BaseClassHandler { AbstractInsnNode endInsn = mn.instructions.get(end); do { if (curInsn instanceof FrameNode) { - FrameNode fn = (FrameNode)curInsn; + FrameNode fn = (FrameNode) curInsn; if (fn.type == F_FULL) { fn.stack.add(0, mockMethod.getMockClass()); // remove label reference in stack of frame node @@ -348,7 +353,7 @@ public class SourceClassHandler extends BaseClassHandler { List handleList = new ArrayList(); for (AbstractInsnNode instruction : mn.instructions) { if (instruction.getOpcode() == Opcodes.INVOKEDYNAMIC) { - InvokeDynamicInsnNode invokeDynamicInsnNode = (InvokeDynamicInsnNode)instruction; + InvokeDynamicInsnNode invokeDynamicInsnNode = (InvokeDynamicInsnNode) instruction; handleList.add((Handle) invokeDynamicInsnNode.bsmArgs[1]); } } @@ -417,7 +422,6 @@ public class SourceClassHandler extends BaseClassHandler { Label l1 = new Label(); mv.visitLabel(l1); - String localVarOwner = handle.getOwner(); if (isStatic) { @@ -489,81 +493,4 @@ public class SourceClassHandler extends BaseClassHandler { return BasicType.basicType(arg.charAt(0)).loadVarInsn; } - /** - * copy from java.lang.invoke.LambdaForm.BasicType - */ - enum BasicType { - /** - * all reference types - */ - L_TYPE('L', Object.class, Wrapper.OBJECT, ALOAD, ARETURN), - /** - * all primitive types - */ - I_TYPE('I', int.class, Wrapper.INT, ILOAD, IRETURN), - J_TYPE('J', long.class, Wrapper.LONG, LLOAD, LRETURN), - F_TYPE('F', float.class, Wrapper.FLOAT, FLOAD, FRETURN), - D_TYPE('D', double.class, Wrapper.DOUBLE, DLOAD, DRETURN), - V_TYPE('V', void.class, Wrapper.VOID, null, RETURN), - A_TYPE('[', Object[].class, Wrapper.OBJECT, ALOAD, ARETURN); - - private final char btChar; - private final Class btClass; - private final Wrapper btWrapper; - private final Integer loadVarInsn; - private final Integer returnInsn; - - BasicType(char btChar, Class btClass, Wrapper btWrapper, Integer loadVarInsn, Integer returnInsn) { - this.btChar = btChar; - this.btClass = btClass; - this.btWrapper = btWrapper; - this.loadVarInsn = loadVarInsn; - this.returnInsn = returnInsn; - } - - public char getBtChar() { - return btChar; - } - - public Class getBtClass() { - return btClass; - } - - public Integer getLoadVarInsn() { - return loadVarInsn; - } - - public Integer getReturnInsn() { - return returnInsn; - } - - public Wrapper getBtWrapper() { - return btWrapper; - } - - public boolean isPrimitive() { - return this != L_TYPE && this != A_TYPE; - } - - static BasicType basicType(char type) { - switch (type) { - case 'L': return L_TYPE; - case 'I': return I_TYPE; - case 'J': return J_TYPE; - case 'F': return F_TYPE; - case 'D': return D_TYPE; - case 'V': return V_TYPE; - case '[': return A_TYPE; - // all subword types are represented as ints - case 'Z': - case 'B': - case 'S': - case 'C': - return I_TYPE; - default: - throw new InternalError("Unknown type char: '"+type+"'"); - } - } - } - } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/model/BasicType.java b/testable-agent/src/main/java/com/alibaba/testable/agent/model/BasicType.java new file mode 100644 index 0000000..8a6592d --- /dev/null +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/model/BasicType.java @@ -0,0 +1,61 @@ +package com.alibaba.testable.agent.model; + +import static org.objectweb.asm.Opcodes.*; + +/** + * simplified of java.lang.invoke.LambdaForm.BasicType + */ +public enum BasicType { + + /** + * all reference types + */ + L_TYPE('L', Object.class, WrapperType.OBJECT, ALOAD, ARETURN), + /** + * all primitive types + */ + I_TYPE('I', int.class, WrapperType.INT, ILOAD, IRETURN), + J_TYPE('J', long.class, WrapperType.LONG, LLOAD, LRETURN), + F_TYPE('F', float.class, WrapperType.FLOAT, FLOAD, FRETURN), + D_TYPE('D', double.class, WrapperType.DOUBLE, DLOAD, DRETURN), + V_TYPE('V', void.class, WrapperType.VOID, null, RETURN), + A_TYPE('[', Object[].class, WrapperType.OBJECT, ALOAD, ARETURN); + + public final char btChar; + public final Class btClass; + public final WrapperType btWrapper; + public final Integer loadVarInsn; + public final Integer returnInsn; + + BasicType(char btChar, Class btClass, WrapperType btWrapper, Integer loadVarInsn, Integer returnInsn) { + this.btChar = btChar; + this.btClass = btClass; + this.btWrapper = btWrapper; + this.loadVarInsn = loadVarInsn; + this.returnInsn = returnInsn; + } + + public boolean isPrimitive() { + return this != L_TYPE && this != A_TYPE; + } + + public static BasicType basicType(char type) { + switch (type) { + case 'L': return L_TYPE; + case 'I': return I_TYPE; + case 'J': return J_TYPE; + case 'F': return F_TYPE; + case 'D': return D_TYPE; + case 'V': return V_TYPE; + case '[': return A_TYPE; + // all subword types are represented as ints + case 'Z': + case 'B': + case 'S': + case 'C': + return I_TYPE; + default: + throw new InternalError("Unknown type char: '"+type+"'"); + } + } +} diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/model/WrapperType.java b/testable-agent/src/main/java/com/alibaba/testable/agent/model/WrapperType.java new file mode 100644 index 0000000..dbfb642 --- /dev/null +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/model/WrapperType.java @@ -0,0 +1,276 @@ +package com.alibaba.testable.agent.model; + +/** + * simplified of sun.invoke.util.Wrapper + */ +public enum WrapperType { + + // wrapperType primitiveType char format + BOOLEAN( Boolean.class, boolean.class, 'Z', Format.unsigned( 1)), + // These must be in the order defined for widening primitive conversions in JLS 5.1.2 + BYTE ( Byte.class, byte.class, 'B', Format.signed( 8)), + SHORT ( Short.class, short.class, 'S', Format.signed( 16)), + CHAR (Character.class, char.class, 'C', Format.unsigned(16)), + INT ( Integer.class, int.class, 'I', Format.signed( 32)), + LONG ( Long.class, long.class, 'J', Format.signed( 64)), + FLOAT ( Float.class, float.class, 'F', Format.floating(32)), + DOUBLE ( Double.class, double.class, 'D', Format.floating(64)), + OBJECT ( Object.class, Object.class, 'L', Format.other( 1)), + // VOID must be the last type, since it is "assignable" from any other type: + VOID ( Void.class, void.class, 'V', Format.other( 0)), + ; + + private final Class wrapperType; + private final Class primitiveType; + private final char basicTypeChar; + private final int format; + + WrapperType(Class wtype, Class ptype, char tchar, int format) { + this.wrapperType = wtype; + this.primitiveType = ptype; + this.basicTypeChar = tchar; + this.format = format; + } + + private static abstract class Format { + static final int SLOT_SHIFT = 0, SIZE_SHIFT = 2, KIND_SHIFT = 12; + static final int + SIGNED = (-1) << KIND_SHIFT, + UNSIGNED = 0 << KIND_SHIFT, + FLOATING = 1 << KIND_SHIFT; + static final int + SLOT_MASK = ((1<<(SIZE_SHIFT-SLOT_SHIFT))-1), + SIZE_MASK = ((1<<(KIND_SHIFT-SIZE_SHIFT))-1); + static int format(int kind, int size, int slots) { + assert(((kind >> KIND_SHIFT) << KIND_SHIFT) == kind); + assert((size & (size-1)) == 0); // power of two + assert((kind == SIGNED) ? (size > 0) : (kind == UNSIGNED) ? (size > 0) : (kind == FLOATING) ? (size == 32 || size == 64) : false); + assert((slots == 2) ? (size == 64) : (slots == 1) ? (size <= 32) : false); + return kind | (size << SIZE_SHIFT) | (slots << SLOT_SHIFT); + } + static final int + INT = SIGNED | (32 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + SHORT = SIGNED | (16 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + BOOLEAN = UNSIGNED | (1 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + CHAR = UNSIGNED | (16 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + FLOAT = FLOATING | (32 << SIZE_SHIFT) | (1 << SLOT_SHIFT), + VOID = UNSIGNED | (0 << SIZE_SHIFT) | (0 << SLOT_SHIFT), + NUM_MASK = (-1) << SIZE_SHIFT; + static int signed(int size) { return format(SIGNED, size, (size > 32 ? 2 : 1)); } + static int unsigned(int size) { return format(UNSIGNED, size, (size > 32 ? 2 : 1)); } + static int floating(int size) { return format(FLOATING, size, (size > 32 ? 2 : 1)); } + static int other(int slots) { return slots << SLOT_SHIFT; } + } + + /// format queries: + + /** How many bits are in the wrapped value? Returns 0 for OBJECT or VOID. */ + public int bitWidth() { return (format >> Format.SIZE_SHIFT) & Format.SIZE_MASK; } + /** How many JVM stack slots occupied by the wrapped value? Returns 0 for VOID. */ + public int stackSlots() { return (format >> Format.SLOT_SHIFT) & Format.SLOT_MASK; } + /** Does the wrapped value occupy a single JVM stack slot? */ + public boolean isSingleWord() { return (format & (1 << Format.SLOT_SHIFT)) != 0; } + /** Does the wrapped value occupy two JVM stack slots? */ + public boolean isDoubleWord() { return (format & (2 << Format.SLOT_SHIFT)) != 0; } + /** Is the wrapped type numeric (not void or object)? */ + public boolean isNumeric() { return (format & Format.NUM_MASK) != 0; } + /** Is the wrapped type a primitive other than float, double, or void? */ + public boolean isIntegral() { return isNumeric() && format < Format.FLOAT; } + /** Is the wrapped type one of int, boolean, byte, char, or short? */ + public boolean isSubwordOrInt() { return isIntegral() && isSingleWord(); } + /* Is the wrapped value a signed integral type (one of byte, short, int, or long)? */ + public boolean isSigned() { return format < Format.VOID; } + /* Is the wrapped value an unsigned integral type (one of boolean or char)? */ + public boolean isUnsigned() { return format >= Format.BOOLEAN && format < Format.FLOAT; } + /** Is the wrapped type either float or double? */ + public boolean isFloating() { return format >= Format.FLOAT; } + /** Is the wrapped type either void or a reference? */ + public boolean isOther() { return (format & ~Format.SLOT_MASK) == 0; } + + /** Does the JLS 5.1.2 allow a variable of this wrapper's + * primitive type to be assigned from a value of the given wrapper's primitive type? + * Cases: + *
    + *
  • unboxing followed by widening primitive conversion + *
  • any type converted to {@code void} (i.e., dropping a method call's value) + *
  • boxing conversion followed by widening reference conversion to {@code Object} + *
+ * These are the cases allowed by MethodHandle.asType. + */ + public boolean isConvertibleFrom(WrapperType source) { + if (this == source) return true; + if (this.compareTo(source) < 0) { + // At best, this is a narrowing conversion. + return false; + } + // All conversions are allowed in the enum order between floats and signed ints. + // First detect non-signed non-float types (boolean, char, Object, void). + boolean floatOrSigned = (((this.format & source.format) & Format.SIGNED) != 0); + if (!floatOrSigned) { + if (this.isOther()) return true; + // can convert char to int or wider, but nothing else + return source.format == Format.CHAR; + // no other conversions are classified as widening + } + // All signed and float conversions in the enum order are widening. + assert(this.isFloating() || this.isSigned()); + assert(source.isFloating() || source.isSigned()); + return true; + } + + static { assert(checkConvertibleFrom()); } + private static boolean checkConvertibleFrom() { + // Check the matrix for correct classification of widening conversions. + for (WrapperType w : values()) { + assert(w.isConvertibleFrom(w)); + assert(VOID.isConvertibleFrom(w)); + if (w != VOID) { + assert(OBJECT.isConvertibleFrom(w)); + assert(!w.isConvertibleFrom(VOID)); + } + // check relations with unsigned integral types: + if (w != CHAR) { + assert(!CHAR.isConvertibleFrom(w)); + assert w.isConvertibleFrom(INT) || (!w.isConvertibleFrom(CHAR)); + } + if (w != BOOLEAN) { + assert(!BOOLEAN.isConvertibleFrom(w)); + assert w == VOID || w == OBJECT || (!w.isConvertibleFrom(BOOLEAN)); + } + // check relations with signed integral types: + if (w.isSigned()) { + for (WrapperType x : values()) { + if (w == x) continue; + if (x.isFloating()) + assert(!w.isConvertibleFrom(x)); + else if (x.isSigned()) { + if (w.compareTo(x) < 0) + assert(!w.isConvertibleFrom(x)); + else + assert(w.isConvertibleFrom(x)); + } + } + } + // check relations with floating types: + if (w.isFloating()) { + for (WrapperType x : values()) { + if (w == x) continue; + if (x.isSigned()) + assert(w.isConvertibleFrom(x)); + else if (x.isFloating()) { + if (w.compareTo(x) < 0) + assert(!w.isConvertibleFrom(x)); + else + assert(w.isConvertibleFrom(x)); + } + } + } + } + return true; // i.e., assert(true) + } + + // Note on perfect hashes: + // for signature chars c, do (c + (c >> 1)) % 16 + // for primitive type names n, do (n[0] + n[2]) % 16 + // The type name hash works for both primitive and wrapper names. + // You can add "java/lang/Object" to the primitive names. + // But you add the wrapper name Object, use (n[2] + (3*n[1])) % 16. + private static final WrapperType[] FROM_PRIM = new WrapperType[16]; + private static final WrapperType[] FROM_WRAP = new WrapperType[16]; + private static final WrapperType[] FROM_CHAR = new WrapperType[16]; + private static int hashPrim(Class x) { + String xn = x.getName(); + if (xn.length() < 3) return 0; + return (xn.charAt(0) + xn.charAt(2)) % 16; + } + private static int hashWrap(Class x) { + String xn = x.getName(); + final int offset = 10; + if (xn.length() < offset+3) return 0; + return (3*xn.charAt(offset+1) + xn.charAt(offset+2)) % 16; + } + private static int hashChar(char x) { + return (x + (x >> 1)) % 16; + } + static { + for (WrapperType w : values()) { + int pi = hashPrim(w.primitiveType); + int wi = hashWrap(w.wrapperType); + int ci = hashChar(w.basicTypeChar); + assert(FROM_PRIM[pi] == null); + assert(FROM_WRAP[wi] == null); + assert(FROM_CHAR[ci] == null); + FROM_PRIM[pi] = w; + FROM_WRAP[wi] = w; + FROM_CHAR[ci] = w; + } + } + + /** Wrap a value in this wrapper's type. + * Performs standard primitive conversions, including truncation and float conversions. + * Performs returns the unchanged reference for {@code OBJECT}. + * Returns null for {@code VOID}. + * Returns a zero value for a null input. + * @throws ClassCastException if this wrapper is numeric and the operand + * is not a number, character, boolean, or null + */ + public Object wrap(Object x) { + // do non-numeric wrappers first + switch (basicTypeChar) { + case 'L': return x; + case 'V': return null; + } + Number xn = numberValue(x); + switch (basicTypeChar) { + case 'I': return xn.intValue(); + case 'J': return xn.longValue(); + case 'F': return xn.floatValue(); + case 'D': return xn.doubleValue(); + case 'S': return (short) xn.intValue(); + case 'B': return (byte) xn.intValue(); + case 'C': return (char) xn.intValue(); + case 'Z': return boolValue(xn.byteValue()); + } + throw new InternalError("bad wrapper"); + } + + /** Wrap a value (an int or smaller value) in this wrapper's type. + * Performs standard primitive conversions, including truncation and float conversions. + * Produces an {@code Integer} for {@code OBJECT}, although the exact type + * of the operand is not known. + * Returns null for {@code VOID}. + */ + public Object wrap(int x) { + if (basicTypeChar == 'L') return x; + switch (basicTypeChar) { + case 'L': throw new IllegalArgumentException("cannot wrap to object type"); + case 'V': return null; + case 'I': return x; + case 'J': return (long) x; + case 'F': return (float) x; + case 'D': return (double) x; + case 'S': return (short) x; + case 'B': return (byte) x; + case 'C': return (char) x; + case 'Z': return boolValue((byte) x); + } + throw new InternalError("bad wrapper"); + } + + private static Number numberValue(Object x) { + if (x instanceof Number) return (Number)x; + if (x instanceof Character) return (int)(Character)x; + if (x instanceof Boolean) return (Boolean)x ? 1 : 0; + // Remaining allowed case of void: Must be a null reference. + return (Number)x; + } + + // Parameter type of boolValue must be byte, because + // MethodHandles.explicitCastArguments defines boolean + // conversion as first converting to byte. + private static boolean boolValue(byte bits) { + bits &= 1; // simple 31-bit zero extension + return (bits != 0); + } + +}