mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-19 09:43:29 +08:00
do not introduce sun package dependence
This commit is contained in:
@@ -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+"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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+"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user