org.zkoss.bind.paranamer.BytecodeReadingParanamer Maven / Gradle / Ivy
/***
*
* Portions Copyright (c) 2007 Paul Hammant
* Portions copyright (c) 2000-2007 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.zkoss.bind.paranamer;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
/**
* An ASM-based implementation of Paranamer. It relies on debug information compiled
* with the "-g" javac option to retrieve parameter names.
*
* Portions of this source file are a fork of ASM.
*
* @author Guilherme Silveira
* @author Paul Hammant
*/
public class BytecodeReadingParanamer implements Paranamer {
private static final Map _primitives = new HashMap() {
{
put("int", "I");
put("boolean", "Z");
put("byte", "B");
put("char", "C");
put("short", "S");
put("float", "F");
put("long", "J");
put("double", "D");
}
};
public String[] lookupParameterNames(AccessibleObject methodOrConstructor) {
return lookupParameterNames(methodOrConstructor, true);
}
public String[] lookupParameterNames(AccessibleObject methodOrCtor, boolean throwExceptionIfMissing) {
Class>[] types = null;
Class> declaringClass = null;
String name = null;
if (methodOrCtor instanceof Method) {
Method method = (Method) methodOrCtor;
types = method.getParameterTypes();
name = method.getName();
declaringClass = method.getDeclaringClass();
} else {
Constructor> constructor = (Constructor>) methodOrCtor;
types = constructor.getParameterTypes();
declaringClass = constructor.getDeclaringClass();
name = "";
}
if (types.length == 0) {
return EMPTY_NAMES;
}
InputStream byteCodeStream = getClassAsStream(declaringClass);
if (byteCodeStream == null) {
if (throwExceptionIfMissing) {
throw new ParameterNamesNotFoundException("Unable to get class bytes");
} else {
return Paranamer.EMPTY_NAMES;
}
}
try {
ClassReader reader = new ClassReader(byteCodeStream);
TypeCollector visitor = new TypeCollector(name, types, throwExceptionIfMissing);
reader.accept(visitor);
String[] parameterNamesForMethod = visitor.getParameterNamesForMethod();
return parameterNamesForMethod;
} catch (IOException e) {
if (throwExceptionIfMissing) {
throw new ParameterNamesNotFoundException("IoException while reading class bytes", e);
} else {
return Paranamer.EMPTY_NAMES;
}
} finally {
try {
byteCodeStream.close();
} catch (IOException ignored) {
}
}
}
private InputStream getClassAsStream(Class> clazz) {
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
return getClassAsStream(classLoader, clazz.getName());
}
private InputStream getClassAsStream(ClassLoader classLoader, String className) {
String name = className.replace('.', '/') + ".class";
// better pre-cache all methods otherwise this content will be loaded
// multiple times
InputStream asStream = classLoader.getResourceAsStream(name);
if (asStream == null) {
asStream = BytecodeReadingParanamer.class.getResourceAsStream(name);
}
return asStream;
}
/**
* The type collector waits for an specific method in order to start a method
* collector.
*
* @author Guilherme Silveira
*/
private static class TypeCollector {
private static final String COMMA = ",";
private final String methodName;
private final Class>[] parameterTypes;
private final boolean throwExceptionIfMissing;
private MethodCollector collector;
private TypeCollector(String methodName, Class>[] parameterTypes, boolean throwExceptionIfMissing) {
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.throwExceptionIfMissing = throwExceptionIfMissing;
this.collector = null;
}
private MethodCollector visitMethod(int access, String name, String desc) {
// already found the method, skip any processing
if (collector != null) {
return null;
}
// not the same name
if (!name.equals(methodName)) {
return null;
}
Type[] argumentTypes = Type.getArgumentTypes(desc);
int longOrDoubleQuantity = 0;
for (Type t : argumentTypes) {
if (t.getClassName().equals("long")
|| t.getClassName().equals("double")) {
longOrDoubleQuantity++;
}
}
int paramCount = argumentTypes.length;
// not the same quantity of parameters
if (paramCount != this.parameterTypes.length) {
return null;
}
for (int i = 0; i < argumentTypes.length; i++) {
if (!correctTypeName(argumentTypes, i).equals(
this.parameterTypes[i].getName())) {
return null;
}
}
this.collector = new MethodCollector((Modifier.isStatic(access) ? 0 : 1),
argumentTypes.length + longOrDoubleQuantity);
return collector;
}
private String correctTypeName(Type[] argumentTypes, int i) {
String s = argumentTypes[i].getClassName();
// array notation needs cleanup.
String braces = "";
while (s.endsWith("[]")) {
braces = braces + "[";
s = s.substring(0, s.length() - 2);
}
if (!braces.equals("")) {
if (_primitives.containsKey(s)) {
s = braces + _primitives.get(s);
} else {
s = braces + "L" + s + ";";
}
}
return s;
}
private String[] getParameterNamesForMethod() {
if (collector == null) {
return Paranamer.EMPTY_NAMES;
}
if (!collector.isDebugInfoPresent()) {
if (throwExceptionIfMissing) {
throw new ParameterNamesNotFoundException("Parameter names not found for " + methodName);
} else {
return Paranamer.EMPTY_NAMES;
}
}
return collector.getResult().split(COMMA);
}
}
/**
* Objects of this class collects information from a specific method.
*
* @author Guilherme Silveira
*/
private static class MethodCollector {
private final int paramCount;
private final int ignoreCount;
private int currentParameter;
private final StringBuffer result;
private boolean debugInfoPresent;
private MethodCollector(int ignoreCount, int paramCount) {
this.ignoreCount = ignoreCount;
this.paramCount = paramCount;
this.result = new StringBuffer();
this.currentParameter = 0;
// if there are 0 parameters, there is no need for debug info
this.debugInfoPresent = paramCount == 0;
}
private void visitLocalVariable(String name, int index) {
if (index >= ignoreCount && index < ignoreCount + paramCount) {
if (!name.equals("arg" + currentParameter)) {
debugInfoPresent = true;
}
result.append(',');
result.append(name);
currentParameter++;
}
}
private String getResult() {
return result.length() != 0 ? result.substring(1) : "";
}
private boolean isDebugInfoPresent() {
return debugInfoPresent;
}
}
/***
* Portions Copyright (c) 2007 Paul Hammant
* Portions copyright (c) 2000-2007 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* A Java class parser to make a Class Visitor visit an existing class.
* This class parses a byte array conforming to the Java class file format and
* calls the appropriate visit methods of a given class visitor for each field,
* method and bytecode instruction encountered.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
private static class ClassReader {
/**
* The class to be parsed. The content of this array must not be
* modified. This field is intended for Attribute sub classes, and
* is normally not needed by class generators or adapters.
*/
public final byte[] b;
/**
* The start index of each constant pool item in {@link #b b}, plus one.
* The one byte offset skips the constant pool item tag that indicates its
* type.
*/
private final int[] items;
/**
* The String objects corresponding to the CONSTANT_Utf8 items. This cache
* avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
* which GREATLY improves performances (by a factor 2 to 3). This caching
* strategy could be extended to all constant pool items, but its benefit
* would not be so great for these items (because they are much less
* expensive to parse than CONSTANT_Utf8 items).
*/
private final String[] strings;
/**
* Maximum length of the strings contained in the constant pool of the
* class.
*/
private final int maxStringLength;
/**
* Start index of the class header information (access, name...) in
* {@link #b b}.
*/
public final int header;
/**
* The type of CONSTANT_Fieldref constant pool items.
*/
static final int FIELD = 9;
/**
* The type of CONSTANT_Methodref constant pool items.
*/
static final int METH = 10;
/**
* The type of CONSTANT_InterfaceMethodref constant pool items.
*/
static final int IMETH = 11;
/**
* The type of CONSTANT_Integer constant pool items.
*/
static final int INT = 3;
/**
* The type of CONSTANT_Float constant pool items.
*/
static final int FLOAT = 4;
/**
* The type of CONSTANT_Long constant pool items.
*/
static final int LONG = 5;
/**
* The type of CONSTANT_Double constant pool items.
*/
static final int DOUBLE = 6;
/**
* The type of CONSTANT_NameAndType constant pool items.
*/
static final int NAME_TYPE = 12;
/**
* The type of CONSTANT_MethodHandle constant pool items.
*/
static final int MHANDLE = 15;
/**
* The type of CONSTANT_InvokeDynamic constant pool items.
*/
static final int INVOKEDYN = 18;
/**
* The type of CONSTANT_Utf8 constant pool items.
*/
static final int UTF8 = 1;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Constructs a new {@link ClassReader} object.
*
* @param b the bytecode of the class to be read.
*/
private ClassReader(final byte[] b) {
this(b, 0);
}
/**
* Constructs a new {@link ClassReader} object.
*
* @param b the bytecode of the class to be read.
* @param off the start offset of the class data.
*/
private ClassReader(final byte[] b, final int off) {
this.b = b;
// parses the constant pool
items = new int[readUnsignedShort(off + 8)];
int n = items.length;
strings = new String[n];
int max = 0;
int index = off + 10;
for (int i = 1; i < n; ++i) {
items[i] = index + 1;
int size;
switch (b[index]) {
case FIELD:
case METH:
case IMETH:
case INT:
case FLOAT:
case INVOKEDYN:
case NAME_TYPE:
size = 5;
break;
case LONG:
case DOUBLE:
size = 9;
++i;
break;
case MHANDLE:
size = 4;
break;
case UTF8:
size = 3 + readUnsignedShort(index + 1);
if (size > max) {
max = size;
}
break;
// case HamConstants.CLASS:
// case HamConstants.STR:
default:
size = 3;
break;
}
index += size;
}
maxStringLength = max;
// the class header information starts just after the constant pool
header = index;
}
/**
* Constructs a new {@link ClassReader} object.
*
* @param is an input stream from which to read the class.
* @throws IOException if a problem occurs during reading.
*/
private ClassReader(final InputStream is) throws IOException {
this(readClass(is));
}
/**
* Reads the bytecode of a class.
*
* @param is an input stream from which to read the class.
* @return the bytecode read from the given input stream.
* @throws IOException if a problem occurs during reading.
*/
private static byte[] readClass(final InputStream is) throws IOException {
if (is == null) {
throw new IOException("Class not found");
}
try {
byte[] b = new byte[is.available()];
int len = 0;
while (true) {
int n = is.read(b, len, b.length - len);
if (n == -1) {
if (len < b.length) {
byte[] c = new byte[len];
System.arraycopy(b, 0, c, 0, len);
b = c;
}
return b;
}
len += n;
if (len == b.length) {
int last = is.read();
if (last < 0) {
return b;
}
byte[] c = new byte[b.length + 1000];
System.arraycopy(b, 0, c, 0, len);
c[len++] = (byte) last;
b = c;
}
}
} finally {
try {
is.close();
} catch (IOException ex) {
//ignore
}
}
}
// ------------------------------------------------------------------------
// Public methods
// ------------------------------------------------------------------------
/**
* Makes the given visitor visit the Java class of this {@link ClassReader}.
* This class is the one specified in the constructor (see
* {@link #ClassReader(byte[]) ClassReader}).
*
* @param classVisitor the visitor that must visit this class.
*/
private void accept(final TypeCollector classVisitor) {
char[] c = new char[maxStringLength]; // buffer used to read strings
int i, j, k; // loop variables
int u, v, w; // indexes in b
String attrName;
int anns = 0;
int ianns = 0;
// visits the header
u = header;
v = items[readUnsignedShort(u + 4)];
int len = readUnsignedShort(u + 6);
w = 0;
u += 8;
for (i = 0; i < len; ++i) {
u += 2;
}
v = u;
i = readUnsignedShort(v);
v += 2;
for (; i > 0; --i) {
j = readUnsignedShort(v + 6);
v += 8;
for (; j > 0; --j) {
v += 6 + readInt(v + 2);
}
}
i = readUnsignedShort(v);
v += 2;
for (; i > 0; --i) {
j = readUnsignedShort(v + 6);
v += 8;
for (; j > 0; --j) {
v += 6 + readInt(v + 2);
}
}
i = readUnsignedShort(v);
v += 2;
for (; i > 0; --i) {
v += 6 + readInt(v + 2);
}
//annotations not needed.
// visits the fields
i = readUnsignedShort(u);
u += 2;
for (; i > 0; --i) {
j = readUnsignedShort(u + 6);
u += 8;
for (; j > 0; --j) {
u += 6 + readInt(u + 2);
}
}
// visits the methods
i = readUnsignedShort(u);
u += 2;
for (; i > 0; --i) {
// inlined in original ASM source, now a method call
u = readMethod(classVisitor, c, u);
}
}
private int readMethod(TypeCollector classVisitor, char[] c, int u) {
int v;
int w;
int j;
String attrName;
int k;
int access = readUnsignedShort(u);
String name = readUTF8(u + 2, c);
String desc = readUTF8(u + 4, c);
v = 0;
w = 0;
// looks for Code and Exceptions attributes
j = readUnsignedShort(u + 6);
u += 8;
for (; j > 0; --j) {
attrName = readUTF8(u, c);
int attrSize = readInt(u + 2);
u += 6;
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if (attrName.equals("Code")) {
v = u;
}
u += attrSize;
}
// reads declared exceptions
if (w == 0) {
// noop
} else {
w += 2;
for (j = 0; j < readUnsignedShort(w); ++j) {
w += 2;
}
}
// visits the method's code, if any
MethodCollector mv = classVisitor.visitMethod(access, name, desc);
if (mv != null && v != 0) {
int codeLength = readInt(v + 4);
v += 8;
int codeStart = v;
int codeEnd = v + codeLength;
v = codeEnd;
j = readUnsignedShort(v);
v += 2;
for (; j > 0; --j) {
v += 8;
}
// parses the local variable, line number tables, and code
// attributes
int varTable = 0;
int varTypeTable = 0;
j = readUnsignedShort(v);
v += 2;
for (; j > 0; --j) {
attrName = readUTF8(v, c);
if (attrName.equals("LocalVariableTable")) {
varTable = v + 6;
} else if (attrName.equals("LocalVariableTypeTable")) {
varTypeTable = v + 6;
}
v += 6 + readInt(v + 2);
}
v = codeStart;
// visits the local variable tables
if (varTable != 0) {
if (varTypeTable != 0) {
k = readUnsignedShort(varTypeTable) * 3;
w = varTypeTable + 2;
int[] typeTable = new int[k];
while (k > 0) {
typeTable[--k] = w + 6; // signature
typeTable[--k] = readUnsignedShort(w + 8); // index
typeTable[--k] = readUnsignedShort(w); // start
w += 10;
}
}
k = readUnsignedShort(varTable);
w = varTable + 2;
for (; k > 0; --k) {
int index = readUnsignedShort(w + 8);
mv.visitLocalVariable(readUTF8(w + 4, c), index);
w += 10;
}
}
}
return u;
}
/**
* Reads an unsigned short value in {@link #b b}. This method is
* intended for Attribute sub classes, and is normally not needed by
* class generators or adapters.
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
private int readUnsignedShort(final int index) {
byte[] b = this.b;
return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
}
/**
* Reads a signed int value in {@link #b b}. This method is intended for
* Attribute sub classes, and is normally not needed by class
* generators or adapters.
*
* @param index the start index of the value to be read in {@link #b b}.
* @return the read value.
*/
private int readInt(final int index) {
byte[] b = this.b;
return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
| ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
}
/**
* Reads an UTF8 string constant pool item in {@link #b b}. This method
* is intended for Attribute sub classes, and is normally not needed
* by class generators or adapters.
*
* @param index the start index of an unsigned short value in {@link #b b},
* whose value is the index of an UTF8 constant pool item.
* @param buf buffer to be used to read the item. This buffer must be
* sufficiently large. It is not automatically resized.
* @return the String corresponding to the specified UTF8 item.
*/
private String readUTF8(int index, final char[] buf) {
int item = readUnsignedShort(index);
String s = strings[item];
if (s != null) {
return s;
}
index = items[item];
return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
}
/**
* Reads UTF8 string in {@link #b b}.
*
* @param index start offset of the UTF8 string to be read.
* @param utfLen length of the UTF8 string to be read.
* @param buf buffer to be used to read the string. This buffer must be
* sufficiently large. It is not automatically resized.
* @return the String corresponding to the specified UTF8 string.
*/
private String readUTF(int index, final int utfLen, final char[] buf) {
int endIndex = index + utfLen;
byte[] b = this.b;
int strLen = 0;
int c;
int st = 0;
char cc = 0;
while (index < endIndex) {
c = b[index++];
switch (st) {
case 0:
c = c & 0xFF;
if (c < 0x80) { // 0xxxxxxx
buf[strLen++] = (char) c;
} else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
cc = (char) (c & 0x1F);
st = 1;
} else { // 1110 xxxx 10xx xxxx 10xx xxxx
cc = (char) (c & 0x0F);
st = 2;
}
break;
case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
st = 0;
break;
case 2: // byte 2 of 3-byte char
cc = (char) ((cc << 6) | (c & 0x3F));
st = 1;
break;
}
}
return new String(buf, 0, strLen);
}
}
/***
* Portions Copyright (c) 2007 Paul Hammant
* Portions copyright (c) 2000-2007 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* A Java type. This class can be used to make it easier to manipulate type and
* method descriptors.
*
* @author Eric Bruneton
* @author Chris Nokleberg
*/
private static class Type {
/**
* The sort of the void type.
*/
private static final int VOID = 0;
/**
* The sort of the boolean type.
*/
private static final int BOOLEAN = 1;
/**
* The sort of the char type.
*/
private static final int CHAR = 2;
/**
* The sort of the byte type.
*/
private static final int BYTE = 3;
/**
* The sort of the short type.
*/
private static final int SHORT = 4;
/**
* The sort of the int type.
*/
private static final int INT = 5;
/**
* The sort of the float type.
*/
private static final int FLOAT = 6;
/**
* The sort of the long type.
*/
private static final int LONG = 7;
/**
* The sort of the double type.
*/
private static final int DOUBLE = 8;
/**
* The sort of array reference types.
*/
private static final int ARRAY = 9;
/**
* The sort of object reference type.
*/
private static final int OBJECT = 10;
/**
* The void type.
*/
private static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24)
| (5 << 16) | (0 << 8) | 0, 1);
/**
* The boolean type.
*/
private static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24)
| (0 << 16) | (5 << 8) | 1, 1);
/**
* The char type.
*/
private static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24)
| (0 << 16) | (6 << 8) | 1, 1);
/**
* The byte type.
*/
private static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24)
| (0 << 16) | (5 << 8) | 1, 1);
/**
* The short type.
*/
private static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24)
| (0 << 16) | (7 << 8) | 1, 1);
/**
* The int type.
*/
private static final Type INT_TYPE = new Type(INT, null, ('I' << 24)
| (0 << 16) | (0 << 8) | 1, 1);
/**
* The float type.
*/
private static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24)
| (2 << 16) | (2 << 8) | 1, 1);
/**
* The long type.
*/
private static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24)
| (1 << 16) | (1 << 8) | 2, 1);
/**
* The double type.
*/
private static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24)
| (3 << 16) | (3 << 8) | 2, 1);
// ------------------------------------------------------------------------
// Fields
// ------------------------------------------------------------------------
/**
* The sort of this Java type.
*/
private final int sort;
/**
* A buffer containing the internal name of this Java type. This field is
* only used for reference types.
*/
private char[] buf;
/**
* The offset of the internal name of this Java type in {@link #buf buf} or,
* for primitive types, the size, descriptor and getOpcode offsets for this
* type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
* for IALOAD or IASTORE, byte 3 the offset for all other instructions).
*/
private int off;
/**
* The length of the internal name of this Java type.
*/
private final int len;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Constructs a primitive type.
*
* @param sort the sort of the primitive type to be constructed.
*/
private Type(final int sort) {
this.sort = sort;
this.len = 1;
}
/**
* Constructs a reference type.
*
* @param sort the sort of the reference type to be constructed.
* @param buf a buffer containing the descriptor of the previous type.
* @param off the offset of this descriptor in the previous buffer.
* @param len the length of this descriptor.
*/
private Type(final int sort, final char[] buf, final int off, final int len) {
this.sort = sort;
this.buf = buf;
this.off = off;
this.len = len;
}
/**
* Returns the Java types corresponding to the argument types of the given
* method descriptor.
*
* @param methodDescriptor a method descriptor.
* @return the Java types corresponding to the argument types of the given
* method descriptor.
*/
private static Type[] getArgumentTypes(final String methodDescriptor) {
char[] buf = methodDescriptor.toCharArray();
int off = 1;
int size = 0;
while (true) {
char car = buf[off++];
if (car == ')') {
break;
} else if (car == 'L') {
while (buf[off++] != ';') {
}
++size;
} else if (car != '[') {
++size;
}
}
Type[] args = new Type[size];
off = 1;
size = 0;
while (buf[off] != ')') {
args[size] = getType(buf, off);
off += args[size].len + (args[size].sort == OBJECT ? 2 : 0);
size += 1;
}
return args;
}
/**
* Returns the Java type corresponding to the given type descriptor.
*
* @param buf a buffer containing a type descriptor.
* @param off the offset of this descriptor in the previous buffer.
* @return the Java type corresponding to the given type descriptor.
*/
private static Type getType(final char[] buf, final int off) {
int len;
switch (buf[off]) {
case 'V':
return VOID_TYPE;
case 'Z':
return BOOLEAN_TYPE;
case 'C':
return CHAR_TYPE;
case 'B':
return BYTE_TYPE;
case 'S':
return SHORT_TYPE;
case 'I':
return INT_TYPE;
case 'F':
return FLOAT_TYPE;
case 'J':
return LONG_TYPE;
case 'D':
return DOUBLE_TYPE;
case '[':
len = 1;
while (buf[off + len] == '[') {
++len;
}
if (buf[off + len] == 'L') {
++len;
while (buf[off + len] != ';') {
++len;
}
}
return new Type(ARRAY, buf, off, len + 1);
// case 'L':
default:
len = 1;
while (buf[off + len] != ';') {
++len;
}
return new Type(OBJECT, buf, off + 1, len - 1);
}
}
// ------------------------------------------------------------------------
// Accessors
// ------------------------------------------------------------------------
/**
* Returns the number of dimensions of this array type. This method should
* only be used for an array type.
*
* @return the number of dimensions of this array type.
*/
private int getDimensions() {
int i = 1;
while (buf[off + i] == '[') {
++i;
}
return i;
}
/**
* Returns the type of the elements of this array type. This method should
* only be used for an array type.
*
* @return Returns the type of the elements of this array type.
*/
private Type getElementType() {
return getType(buf, off + getDimensions());
}
/**
* Returns the name of the class corresponding to this type.
*
* @return the fully qualified name of the class corresponding to this type.
*/
private String getClassName() {
switch (sort) {
case VOID:
return "void";
case BOOLEAN:
return "boolean";
case CHAR:
return "char";
case BYTE:
return "byte";
case SHORT:
return "short";
case INT:
return "int";
case FLOAT:
return "float";
case LONG:
return "long";
case DOUBLE:
return "double";
case ARRAY:
StringBuffer b = new StringBuffer(getElementType().getClassName());
for (int i = getDimensions(); i > 0; --i) {
b.append("[]");
}
return b.toString();
// case OBJECT:
default:
return new String(buf, off, len).replace('/', '.');
}
}
}
}