do not introduce sun package dependence

This commit is contained in:
金戟
2021-09-28 21:26:16 +08:00
parent 8fbd459280
commit 80b0f3b878
3 changed files with 371 additions and 107 deletions

View File

@@ -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<MethodInfo> injectMethods;
private final Set<Integer> invokeOps = new HashSet<Integer>() {{
@@ -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<MethodInfo> 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<Handle> handleList = new ArrayList<Handle>();
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+"'");
}
}
}
}

View File

@@ -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+"'");
}
}
}

View File

@@ -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:
* <ul>
* <li>unboxing followed by widening primitive conversion
* <li>any type converted to {@code void} (i.e., dropping a method call's value)
* <li>boxing conversion followed by widening reference conversion to {@code Object}
* </ul>
* 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);
}
}