blob: d7ed1ce9b0c3d6bfc2cfa0a6a883c359b4c427e0 [file] [log] [blame]
package aQute.lib.osgi;
import java.io.*;
import java.lang.annotation.*;
import java.nio.*;
import java.util.*;
import java.util.regex.*;
import aQute.bnd.annotation.*;
import aQute.libg.generics.*;
public class Clazz {
public class ClassConstant {
int cname;
public ClassConstant(int class_index) {
this.cname = class_index;
}
public String getName() {
return (String) pool[cname];
}
}
public static enum JAVA {
UNKNOWN(Integer.MAX_VALUE), OpenJDK7(51), J2S6(50), J2SE5(49), JDK1_4(48), JDK1_3(47), JDK1_2(
46), JDK1_1(45);
final int major;
JAVA(int major) {
this.major = major;
}
static JAVA format(int n) {
for (JAVA e : JAVA.values())
if (e.major == n)
return e;
return UNKNOWN;
}
public int getMajor() {
return major;
}
public boolean hasAnnotations() {
return major >= J2SE5.major;
}
public boolean hasGenerics() {
return major >= J2SE5.major;
}
public boolean hasEnums() {
return major >= J2SE5.major;
}
};
public static enum QUERY {
IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION, CONCRETE, ABSTRACT, PUBLIC, ANNOTATION, RUNTIMEANNOTATIONS, CLASSANNOTATIONS
};
public static EnumSet<QUERY> HAS_ARGUMENT = EnumSet.of(QUERY.IMPLEMENTS, QUERY.EXTENDS,
QUERY.IMPORTS, QUERY.NAMED,
QUERY.VERSION, QUERY.ANNOTATION);
/**
* <pre>
* ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its
* package.
* ACC_FINAL 0x0010 Declared final; no subclasses allowed.
* ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the
* invokespecial instruction.
* ACC_INTERFACE 0x0200 Is an interface, not a
* class.
* ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated.
* </pre>
*
* @param mod
*/
final static int ACC_PUBLIC = 0x0001; // Declared
// public;
// may
// be
// accessed
// from outside its package.
final static int ACC_FINAL = 0x0010; // Declared
// final;
// no
// subclasses
// allowed.
final static int ACC_SUPER = 0x0020; // Treat
// superclass
// methods
// specially when invoked by the
// invokespecial instruction.
final static int ACC_INTERFACE = 0x0200; // Is
// an
// interface,
// not
// a
// classs
final static int ACC_ABSTRACT = 0x0400; // Declared
// abstract;
// may
// not
// be
// instantiated.
final static int ACC_ENUM = 0x04000;
static protected class Assoc {
Assoc(byte tag, int a, int b) {
this.tag = tag;
this.a = a;
this.b = b;
}
byte tag;
int a;
int b;
}
static public class FieldDef implements Comparable<FieldDef> {
public FieldDef(int access, String clazz, String name, String descriptor) {
this.access = access;
this.clazz = clazz.replace('/', '.');
this.name = name;
this.descriptor = descriptor;
}
final public int access;
final public String clazz;
final public String name;
final public String descriptor;
public String signature;
public Object constant;
public boolean equals(Object other) {
if (!(other instanceof MethodDef))
return false;
FieldDef m = (FieldDef) other;
return clazz.equals(m.clazz) && name.equals(m.name) && descriptor.equals(m.descriptor);
}
public int hashCode() {
return clazz.hashCode() ^ name.hashCode() ^ descriptor.hashCode();
}
public int compareTo(FieldDef o) {
int result = clazz.compareTo(o.clazz);
if (result == 0) {
result = name.compareTo(o.name);
if (result == 0) {
result = descriptor.compareTo(o.descriptor);
}
}
return result;
}
public String getPretty() {
return name;
}
public String toString() {
return getPretty();
}
public boolean isEnum() {
return (access & ACC_ENUM) != 0;
}
}
static public class MethodDef extends FieldDef {
Pattern METHOD_DESCRIPTOR = Pattern.compile("\\((.*)\\)(.+)");
public MethodDef(int access, String clazz, String method, String descriptor) {
super(access, clazz, method, descriptor);
}
public boolean isConstructor() {
return name.equals("<init>") || name.equals("<clinit>");
}
public String getReturnType() {
String use = descriptor;
if (signature != null)
use = signature;
Matcher m = METHOD_DESCRIPTOR.matcher(use);
if (!m.matches())
throw new IllegalArgumentException("Not a valid method descriptor: " + descriptor);
String returnType = m.group(2);
return objectDescriptorToFQN(returnType);
}
public String getPretty() {
StringBuilder sb = new StringBuilder();
sb.append(descriptor.charAt(0));
int index = 1;
String del = "";
while (index < descriptor.length() && descriptor.charAt(index) != ')') {
sb.append(del);
index = printParameter(sb, descriptor, index);
del = ",";
}
sb.append(descriptor.charAt(index++));
StringBuilder sb2 = new StringBuilder();
if (isConstructor()) {
sb2.append(getShortName(clazz));
index++; // skip the V
} else {
printParameter(sb2, descriptor, index);
sb2.append(" ");
sb2.append(getShortName(clazz));
sb2.append(".");
sb2.append(name);
}
sb2.append(sb);
return sb2.toString();
}
private int printParameter(StringBuilder sb, CharSequence descriptor, int index) {
char c = descriptor.charAt(index++);
switch (c) {
case 'B':
sb.append("byte");
break;
case 'C':
sb.append("char");
break;
case 'D':
sb.append("double");
break;
case 'F':
sb.append("float");
break;
case 'I':
sb.append("int");
break;
case 'J':
sb.append("long");
break;
case 'S':
sb.append("short");
break;
case 'Z':
sb.append("boolean");
break;
case 'V':
sb.append("void");
break;
case 'L':
index = reference(sb, descriptor, index);
break;
case '[':
index = array(sb, descriptor, index);
break;
}
return index;
}
private int reference(StringBuilder sb, CharSequence descriptor, int index) {
int n = sb.length();
int lastSlash = n;
while (index < descriptor.length() && descriptor.charAt(index) != ';') {
char c = descriptor.charAt(index++);
if (c == '/') {
c = '.';
lastSlash = sb.length() + 1;
}
sb.append(c);
}
if (lastSlash != n) {
sb.delete(n, lastSlash);
}
return ++index;
}
private int array(StringBuilder sb, CharSequence descriptor, int index) {
int n = 1;
while (index < descriptor.length() && descriptor.charAt(index) == '[') {
index++;
}
index = printParameter(sb, descriptor, index);
while (n-- > 0) {
sb.append("[]");
}
return index;
}
}
final static byte SkipTable[] = { 0, // 0 non existent
-1, // 1 CONSTANT_utf8 UTF 8, handled in
// method
-1, // 2
4, // 3 CONSTANT_Integer
4, // 4 CONSTANT_Float
8, // 5 CONSTANT_Long (index +=2!)
8, // 6 CONSTANT_Double (index +=2!)
-1, // 7 CONSTANT_Class
2, // 8 CONSTANT_String
4, // 9 CONSTANT_FieldRef
4, // 10 CONSTANT_MethodRef
4, // 11 CONSTANT_InterfaceMethodRef
4, // 12 CONSTANT_NameAndType
};
boolean isAbstract;
boolean isPublic;
boolean isEnum;
boolean hasRuntimeAnnotations;
boolean hasClassAnnotations;
String className;
Object pool[];
int intPool[];
Set<String> imports = Create.set();
String path;
int minor = 0;
int major = 0;
int access = 0;
String sourceFile;
Set<String> xref;
Set<Integer> classes;
Set<Integer> descriptors;
Set<String> annotations;
int forName = 0;
int class$ = 0;
String[] interfaces;
String zuper;
ClassDataCollector cd = null;
Resource resource;
FieldDef last = null;
public Clazz(String path, Resource resource) {
this.path = path;
this.resource = resource;
}
public Set<String> parseClassFile() throws Exception {
return parseClassFileWithCollector(null);
}
public Set<String> parseClassFile(InputStream in) throws IOException {
return parseClassFile(in, null);
}
public Set<String> parseClassFileWithCollector(ClassDataCollector cd) throws Exception {
InputStream in = resource.openInputStream();
try {
return parseClassFile(in, cd);
} finally {
in.close();
}
}
public Set<String> parseClassFile(InputStream in, ClassDataCollector cd) throws IOException {
DataInputStream din = new DataInputStream(in);
try {
this.cd = cd;
return parseClassFile(din);
} finally {
cd = null;
din.close();
}
}
Set<String> parseClassFile(DataInputStream in) throws IOException {
xref = new HashSet<String>();
classes = new HashSet<Integer>();
descriptors = new HashSet<Integer>();
boolean crawl = cd != null; // Crawl the byte code if we have a
// collector
int magic = in.readInt();
if (magic != 0xCAFEBABE)
throw new IOException("Not a valid class file (no CAFEBABE header)");
minor = in.readUnsignedShort(); // minor version
major = in.readUnsignedShort(); // major version
int count = in.readUnsignedShort();
pool = new Object[count];
intPool = new int[count];
process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
byte tag = in.readByte();
switch (tag) {
case 0:
break process;
case 1:
constantUtf8(in, poolIndex);
break;
case 3:
constantInteger(in, poolIndex);
break;
case 4:
constantFloat(in, poolIndex);
break;
// For some insane optimization reason are
// the long and the double two entries in the
// constant pool. See 4.4.5
case 5:
constantLong(in, poolIndex);
poolIndex++;
break;
case 6:
constantDouble(in, poolIndex);
poolIndex++;
break;
case 7:
constantClass(in, poolIndex);
break;
case 8:
constantString(in, poolIndex);
break;
case 10: // Method ref
case 11: // Interface Method ref
methodRef(in, poolIndex);
break;
// Name and Type
case 12:
nameAndType(in, poolIndex, tag);
break;
// We get the skip count for each record type
// from the SkipTable. This will also automatically
// abort when
default:
if (tag == 2)
throw new IOException("Invalid tag " + tag);
in.skipBytes(SkipTable[tag]);
break;
}
}
pool(pool, intPool);
/*
* Parse after the constant pool, code thanks to Hans Christian
* Falkenberg
*/
int access_flags = in.readUnsignedShort(); // access
isAbstract = (access_flags & ACC_ABSTRACT) != 0;
isPublic = (access_flags & ACC_PUBLIC) != 0;
isEnum = (access_flags & ACC_ENUM) != 0;
int this_class = in.readUnsignedShort();
className = (String) pool[intPool[this_class]];
try {
if (cd != null) {
if (!cd.classStart(access_flags, className))
return null;
}
int super_class = in.readUnsignedShort();
zuper = (String) pool[intPool[super_class]];
if (zuper != null) {
String pack = getPackage(zuper);
packageReference(pack);
if (cd != null)
cd.extendsClass(zuper);
}
int interfacesCount = in.readUnsignedShort();
if (interfacesCount > 0) {
interfaces = new String[interfacesCount];
for (int i = 0; i < interfacesCount; i++)
interfaces[i] = (String) pool[intPool[in.readUnsignedShort()]];
if (cd != null)
cd.implementsInterfaces(interfaces);
}
int fieldsCount = in.readUnsignedShort();
for (int i = 0; i < fieldsCount; i++) {
access_flags = in.readUnsignedShort(); // skip access flags
int name_index = in.readUnsignedShort();
int descriptor_index = in.readUnsignedShort();
// Java prior to 1.5 used a weird
// static variable to hold the com.X.class
// result construct. If it did not find it
// it would create a variable class$com$X
// that would be used to hold the class
// object gotten with Class.forName ...
// Stupidly, they did not actively use the
// class name for the field type, so bnd
// would not see a reference. We detect
// this case and add an artificial descriptor
String name = pool[name_index].toString(); // name_index
if (name.startsWith("class$")) {
crawl = true;
}
if (cd != null)
cd.field(last = new FieldDef(access_flags, className, name,
pool[descriptor_index].toString()));
descriptors.add(new Integer(descriptor_index));
doAttributes(in, ElementType.FIELD, false);
}
//
// Check if we have to crawl the code to find
// the ldc(_w) <string constant> invokestatic Class.forName
// if so, calculate the method ref index so we
// can do this efficiently
//
if (crawl) {
forName = findMethodReference("java/lang/Class", "forName",
"(Ljava/lang/String;)Ljava/lang/Class;");
class$ = findMethodReference(className, "class$",
"(Ljava/lang/String;)Ljava/lang/Class;");
} else if (major == 48) {
forName = findMethodReference("java/lang/Class", "forName",
"(Ljava/lang/String;)Ljava/lang/Class;");
if (forName > 0) {
crawl = true;
class$ = findMethodReference(className, "class$",
"(Ljava/lang/String;)Ljava/lang/Class;");
}
}
//
// Handle the methods
//
int methodCount = in.readUnsignedShort();
for (int i = 0; i < methodCount; i++) {
access_flags = in.readUnsignedShort();
int name_index = in.readUnsignedShort();
int descriptor_index = in.readUnsignedShort();
descriptors.add(new Integer(descriptor_index));
String name = pool[name_index].toString();
String descriptor = pool[descriptor_index].toString();
if (cd != null) {
MethodDef mdef = new MethodDef(access_flags, className, name, descriptor);
last = mdef;
cd.method(mdef);
}
if ("<init>".equals(name)) {
doAttributes(in, ElementType.CONSTRUCTOR, crawl);
} else {
doAttributes(in, ElementType.METHOD, crawl);
}
}
doAttributes(in, ElementType.TYPE, false);
//
// Now iterate over all classes we found and
// parse those as well. We skip duplicates
//
for (int n : classes) {
String clazz = (String) pool[n];
if (clazz.endsWith(";") || clazz.startsWith("["))
parseReference(clazz, 0);
else {
String pack = getPackage(clazz);
packageReference(pack);
}
}
//
// Parse all the descriptors we found
//
for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
Integer index = e.next();
String prototype = (String) pool[index.intValue()];
if (prototype != null)
parseDescriptor(prototype);
else
System.err.println("Unrecognized descriptor: " + index);
}
Set<String> xref = this.xref;
reset();
return xref;
} finally {
if (cd != null)
cd.classEnd();
}
}
private void constantFloat(DataInputStream in, int poolIndex) throws IOException {
if (cd != null)
pool[poolIndex] = in.readFloat(); // ALU
else
in.skipBytes(4);
}
private void constantInteger(DataInputStream in, int poolIndex) throws IOException {
intPool[poolIndex] = in.readInt();
if (cd != null)
pool[poolIndex] = intPool[poolIndex];
}
protected void pool(Object[] pool, int[] intPool) {
}
/**
* @param in
* @param poolIndex
* @param tag
* @throws IOException
*/
protected void nameAndType(DataInputStream in, int poolIndex, byte tag) throws IOException {
int name_index = in.readUnsignedShort();
int descriptor_index = in.readUnsignedShort();
descriptors.add(new Integer(descriptor_index));
pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
}
/**
* @param in
* @param poolIndex
* @param tag
* @throws IOException
*/
private void methodRef(DataInputStream in, int poolIndex) throws IOException {
int class_index = in.readUnsignedShort();
int name_and_type_index = in.readUnsignedShort();
pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
}
/**
* @param in
* @param poolIndex
* @throws IOException
*/
private void constantString(DataInputStream in, int poolIndex) throws IOException {
int string_index = in.readUnsignedShort();
intPool[poolIndex] = string_index;
}
/**
* @param in
* @param poolIndex
* @throws IOException
*/
protected void constantClass(DataInputStream in, int poolIndex) throws IOException {
int class_index = in.readUnsignedShort();
classes.add(new Integer(class_index));
intPool[poolIndex] = class_index;
ClassConstant c = new ClassConstant(class_index);
pool[poolIndex] = c;
}
/**
* @param in
* @throws IOException
*/
protected void constantDouble(DataInputStream in, int poolIndex) throws IOException {
if (cd != null)
pool[poolIndex] = in.readDouble();
else
in.skipBytes(8);
}
/**
* @param in
* @throws IOException
*/
protected void constantLong(DataInputStream in, int poolIndex) throws IOException {
if (cd != null) {
pool[poolIndex] = in.readLong();
} else
in.skipBytes(8);
}
/**
* @param in
* @param poolIndex
* @throws IOException
*/
protected void constantUtf8(DataInputStream in, int poolIndex) throws IOException {
// CONSTANT_Utf8
String name = in.readUTF();
xref.add(name);
pool[poolIndex] = name;
}
/**
* Find a method reference in the pool that points to the given class,
* methodname and descriptor.
*
* @param clazz
* @param methodname
* @param descriptor
* @return index in constant pool
*/
private int findMethodReference(String clazz, String methodname, String descriptor) {
for (int i = 1; i < pool.length; i++) {
if (pool[i] instanceof Assoc) {
Assoc methodref = (Assoc) pool[i];
if (methodref.tag == 10) {
// Method ref
int class_index = methodref.a;
int class_name_index = intPool[class_index];
if (clazz.equals(pool[class_name_index])) {
int name_and_type_index = methodref.b;
Assoc name_and_type = (Assoc) pool[name_and_type_index];
if (name_and_type.tag == 12) {
// Name and Type
int name_index = name_and_type.a;
int type_index = name_and_type.b;
if (methodname.equals(pool[name_index])) {
if (descriptor.equals(pool[type_index])) {
return i;
}
}
}
}
}
}
}
return -1;
}
/**
* Called for each attribute in the class, field, or method.
*
* @param in
* The stream
* @throws IOException
*/
private void doAttributes(DataInputStream in, ElementType member, boolean crawl)
throws IOException {
int attributesCount = in.readUnsignedShort();
for (int j = 0; j < attributesCount; j++) {
// skip name CONSTANT_Utf8 pointer
doAttribute(in, member, crawl);
}
}
/**
* Process a single attribute, if not recognized, skip it.
*
* @param in
* the data stream
* @throws IOException
*/
private void doAttribute(DataInputStream in, ElementType member, boolean crawl)
throws IOException {
int attribute_name_index = in.readUnsignedShort();
String attributeName = (String) pool[attribute_name_index];
long attribute_length = in.readInt();
attribute_length &= 0xFFFFFFFF;
if ("RuntimeVisibleAnnotations".equals(attributeName))
doAnnotations(in, member, RetentionPolicy.RUNTIME);
else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
doParameterAnnotations(in, member, RetentionPolicy.RUNTIME);
else if ("RuntimeInvisibleAnnotations".equals(attributeName))
doAnnotations(in, member, RetentionPolicy.CLASS);
else if ("RuntimeInvisibleParameterAnnotations".equals(attributeName))
doParameterAnnotations(in, member, RetentionPolicy.CLASS);
else if ("InnerClasses".equals(attributeName))
doInnerClasses(in);
else if ("EnclosingMethod".equals(attributeName))
doEnclosingMethod(in);
else if ("SourceFile".equals(attributeName))
doSourceFile(in);
else if ("Code".equals(attributeName) && crawl)
doCode(in);
else if ("Signature".equals(attributeName))
doSignature(in, member);
else if ("ConstantValue".equals(attributeName))
doConstantValue(in);
else {
if (attribute_length > 0x7FFFFFFF) {
throw new IllegalArgumentException("Attribute > 2Gb");
}
in.skipBytes((int) attribute_length);
}
}
/**
* <pre>
* EnclosingMethod_attribute {
* u2 attribute_name_index;
* u4 attribute_length;
* u2 class_index
* u2 method_index;
* }
* </pre>
*
*
* @param in
* @throws IOException
*/
private void doEnclosingMethod(DataInputStream in) throws IOException {
int cIndex = in.readShort();
int mIndex = in.readShort();
if (cd != null) {
int nameIndex = intPool[cIndex];
String cName = (String) pool[nameIndex];
String mName = null;
String mDescriptor = null;
if (mIndex != 0) {
Assoc nameAndType = (Assoc) pool[mIndex];
mName = (String) pool[nameAndType.a];
mDescriptor = (String) pool[nameAndType.b];
}
cd.enclosingMethod(cName, mName, mDescriptor);
}
}
/**
* <pre>
* InnerClasses_attribute {
* u2 attribute_name_index;
* u4 attribute_length;
* u2 number_of_classes; {
* u2 inner_class_info_index;
* u2 outer_class_info_index;
* u2 inner_name_index;
* u2 inner_class_access_flags;
* } classes[number_of_classes];
* }
* </pre>
*
* @param in
* @throws IOException
*/
private void doInnerClasses(DataInputStream in) throws IOException {
int number_of_classes = in.readShort();
for (int i = 0; i < number_of_classes; i++) {
int inner_class_info_index = in.readShort();
int outer_class_info_index = in.readShort();
int inner_name_index = in.readShort();
int inner_class_access_flags = in.readShort() & 0xFFFF;
if (cd != null) {
String innerClass = null;
String outerClass = null;
String innerName = null;
if (inner_class_info_index != 0) {
int nameIndex = intPool[inner_class_info_index];
innerClass = (String) pool[nameIndex];
}
if (outer_class_info_index != 0) {
int nameIndex = intPool[outer_class_info_index];
outerClass = (String) pool[nameIndex];
}
if (inner_name_index != 0)
innerName = (String) pool[inner_name_index];
cd.innerClass(innerClass, outerClass, innerName, inner_class_access_flags);
}
}
}
/**
* Handle a signature
*
* <pre>
* Signature_attribute {
* u2 attribute_name_index;
* u4 attribute_length;
* u2 signature_index;
* }
* </pre>
*
* @param member
*/
void doSignature(DataInputStream in, ElementType member) throws IOException {
int signature_index = in.readUnsignedShort();
String signature = (String) pool[signature_index];
// System.out.println("Signature " + signature );
// The type signature is kind of weird,
// lets skip it for now. Seems to be some kind of
// type variable name index but it does not seem to
// conform to the language specification.
if (member != ElementType.TYPE)
parseDescriptor(signature);
if (last != null)
last.signature = signature;
if (cd != null)
cd.signature(signature);
}
/**
* Handle a constant value call the data collector with it
*/
void doConstantValue(DataInputStream in) throws IOException {
int constantValue_index = in.readUnsignedShort();
if (cd == null)
return;
Object object = pool[constantValue_index];
if (object == null)
object = pool[intPool[constantValue_index]];
last.constant = object;
cd.constant(object);
}
/**
* <pre>
* Code_attribute {
* u2 attribute_name_index;
* u4 attribute_length;
* u2 max_stack;
* u2 max_locals;
* u4 code_length;
* u1 code[code_length];
* u2 exception_table_length;
* { u2 start_pc;
* u2 end_pc;
* u2 handler_pc;
* u2 catch_type;
* } exception_table[exception_table_length];
* u2 attributes_count;
* attribute_info attributes[attributes_count];
* }
* </pre>
*
* @param in
* @param pool
* @throws IOException
*/
private void doCode(DataInputStream in) throws IOException {
/* int max_stack = */in.readUnsignedShort();
/* int max_locals = */in.readUnsignedShort();
int code_length = in.readInt();
byte code[] = new byte[code_length];
in.readFully(code);
crawl(code);
int exception_table_length = in.readUnsignedShort();
in.skipBytes(exception_table_length * 8);
doAttributes(in, ElementType.METHOD, false);
}
/**
* We must find Class.forName references ...
*
* @param code
*/
protected void crawl(byte[] code) {
ByteBuffer bb = ByteBuffer.wrap(code);
bb.order(ByteOrder.BIG_ENDIAN);
int lastReference = -1;
while (bb.remaining() > 0) {
int instruction = 0xFF & bb.get();
switch (instruction) {
case OpCodes.ldc:
lastReference = 0xFF & bb.get();
break;
case OpCodes.ldc_w:
lastReference = 0xFFFF & bb.getShort();
break;
case OpCodes.invokespecial: {
int mref = 0xFFFF & bb.getShort();
if (cd != null)
cd.reference(getMethodDef(0, mref));
break;
}
case OpCodes.invokevirtual: {
int mref = 0xFFFF & bb.getShort();
if (cd != null)
cd.reference(getMethodDef(0, mref));
break;
}
case OpCodes.invokeinterface: {
int mref = 0xFFFF & bb.getShort();
if (cd != null)
cd.reference(getMethodDef(0, mref));
break;
}
case OpCodes.invokestatic: {
int methodref = 0xFFFF & bb.getShort();
if (cd != null)
cd.reference(getMethodDef(0, methodref));
if ((methodref == forName || methodref == class$) && lastReference != -1
&& pool[intPool[lastReference]] instanceof String) {
String clazz = (String) pool[intPool[lastReference]];
if (clazz.startsWith("[") || clazz.endsWith(";"))
parseReference(clazz, 0);
else {
int n = clazz.lastIndexOf('.');
if (n > 0)
packageReference(clazz.substring(0, n));
}
}
break;
}
case OpCodes.tableswitch:
// Skip to place divisible by 4
while ((bb.position() & 0x3) != 0)
bb.get();
/* int deflt = */
bb.getInt();
int low = bb.getInt();
int high = bb.getInt();
bb.position(bb.position() + (high - low + 1) * 4);
lastReference = -1;
break;
case OpCodes.lookupswitch:
// Skip to place divisible by 4
while ((bb.position() & 0x3) != 0)
bb.get();
/* deflt = */
bb.getInt();
int npairs = bb.getInt();
bb.position(bb.position() + npairs * 8);
lastReference = -1;
break;
default:
lastReference = -1;
bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
}
}
}
private void doSourceFile(DataInputStream in) throws IOException {
int sourcefile_index = in.readUnsignedShort();
this.sourceFile = pool[sourcefile_index].toString();
}
private void doParameterAnnotations(DataInputStream in, ElementType member,
RetentionPolicy policy) throws IOException {
int num_parameters = in.readUnsignedByte();
for (int p = 0; p < num_parameters; p++) {
if (cd != null)
cd.parameter(p);
doAnnotations(in, member, policy);
}
}
private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy)
throws IOException {
int num_annotations = in.readUnsignedShort(); // # of annotations
for (int a = 0; a < num_annotations; a++) {
if (cd == null)
doAnnotation(in, member, policy, false);
else {
Annotation annotion = doAnnotation(in, member, policy, true);
cd.annotation(annotion);
}
}
}
private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy,
boolean collect) throws IOException {
int type_index = in.readUnsignedShort();
if (annotations == null)
annotations = new HashSet<String>();
annotations.add(pool[type_index].toString());
if (policy == RetentionPolicy.RUNTIME) {
descriptors.add(new Integer(type_index));
hasRuntimeAnnotations = true;
} else {
hasClassAnnotations = true;
}
String name = (String) pool[type_index];
int num_element_value_pairs = in.readUnsignedShort();
Map<String, Object> elements = null;
for (int v = 0; v < num_element_value_pairs; v++) {
int element_name_index = in.readUnsignedShort();
String element = (String) pool[element_name_index];
Object value = doElementValue(in, member, policy, collect);
if (collect) {
if (elements == null)
elements = new LinkedHashMap<String, Object>();
elements.put(element, value);
}
}
if (collect)
return new Annotation(name, elements, member, policy);
else
return null;
}
private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy,
boolean collect) throws IOException {
char tag = (char) in.readUnsignedByte();
switch (tag) {
case 'B': // Byte
case 'C': // Character
case 'I': // Integer
case 'S': // Short
int const_value_index = in.readUnsignedShort();
return intPool[const_value_index];
case 'D': // Double
case 'F': // Float
case 's': // String
case 'J': // Long
const_value_index = in.readUnsignedShort();
return pool[const_value_index];
case 'Z': // Boolean
const_value_index = in.readUnsignedShort();
return pool[const_value_index] == null || pool[const_value_index].equals(0) ? false : true;
case 'e': // enum constant
int type_name_index = in.readUnsignedShort();
if (policy == RetentionPolicy.RUNTIME)
descriptors.add(new Integer(type_name_index));
int const_name_index = in.readUnsignedShort();
return pool[const_name_index];
case 'c': // Class
int class_info_index = in.readUnsignedShort();
if (policy == RetentionPolicy.RUNTIME)
descriptors.add(new Integer(class_info_index));
return pool[class_info_index];
case '@': // Annotation type
return doAnnotation(in, member, policy, collect);
case '[': // Array
int num_values = in.readUnsignedShort();
Object[] result = new Object[num_values];
for (int i = 0; i < num_values; i++) {
result[i] = doElementValue(in, member, policy, collect);
}
return result;
default:
throw new IllegalArgumentException("Invalid value for Annotation ElementValue tag "
+ tag);
}
}
/**
* Add a new package reference.
*
* @param pack
* A '.' delimited package name
*/
void packageReference(String pack) {
imports.add(pack);
}
/**
* This method parses a descriptor and adds the package of the descriptor to
* the referenced packages.
*
* The syntax of the descriptor is:
*
* <pre>
* descriptor ::= ( '(' reference * ')' )? reference
* reference ::= 'L' classname ( '&lt;' references '&gt;' )? ';' | 'B' | 'Z' | ... | '+' | '-' | '['
* </pre>
*
* This methods uses heavy recursion to parse the descriptor and a roving
* pointer to limit the creation of string objects.
*
* @param descriptor
* The to be parsed descriptor
* @param rover
* The pointer to start at
*/
public void parseDescriptor(String descriptor) {
// Some descriptors are weird, they start with a generic
// declaration that contains ':', not sure what they mean ...
if (descriptor.charAt(0) == '<')
return;
int rover = 0;
if (descriptor.charAt(rover) == '(') {
rover = parseReferences(descriptor, rover + 1, ')');
rover++;
}
parseReferences(descriptor, rover, (char) 0);
}
/**
* Parse a sequence of references. A sequence ends with a given character or
* when the string ends.
*
* @param descriptor
* The whole descriptor.
* @param rover
* The index in the descriptor
* @param delimiter
* The end character or 0
* @return the last index processed, one character after the delimeter
*/
int parseReferences(String descriptor, int rover, char delimiter) {
while (rover < descriptor.length() && descriptor.charAt(rover) != delimiter) {
rover = parseReference(descriptor, rover);
}
return rover;
}
/**
* Parse a single reference. This can be a single character or an object
* reference when it starts with 'L'.
*
* @param descriptor
* The descriptor
* @param rover
* The place to start
* @return The return index after the reference
*/
int parseReference(String descriptor, int rover) {
char c = descriptor.charAt(rover);
while (c == '[')
c = descriptor.charAt(++rover);
if (c == '<') {
rover = parseReferences(descriptor, rover + 1, '>');
} else if (c == 'T') {
// Type variable name
rover++;
while (descriptor.charAt(rover) != ';')
rover++;
} else if (c == 'L') {
StringBuilder sb = new StringBuilder();
rover++;
int lastSlash = -1;
while ((c = descriptor.charAt(rover)) != ';') {
if (c == '<') {
rover = parseReferences(descriptor, rover + 1, '>');
} else if (c == '/') {
lastSlash = sb.length();
sb.append('.');
} else
sb.append(c);
rover++;
}
if (cd != null)
cd.addReference(sb.toString());
if (lastSlash > 0)
packageReference(sb.substring(0, lastSlash));
} else {
if ("+-*BCDFIJSZV".indexOf(c) < 0)
;// System.out.println("Should not skip: " + c);
}
// this skips a lot of characters
// [, *, +, -, B, etc.
return rover + 1;
}
public static String getPackage(String clazz) {
int n = clazz.lastIndexOf('/');
if (n < 0) {
n = clazz.lastIndexOf('.');
if (n < 0)
return ".";
}
return clazz.substring(0, n).replace('/', '.');
}
public Set<String> getReferred() {
return imports;
}
String getClassName() {
if (className == null)
return "NOCLASSNAME";
return className;
}
public String getPath() {
return path;
}
public String getSourceFile() {
return sourceFile;
}
/**
* .class construct for different compilers
*
* sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
* 1.5 ldc_w (class) 1.6 "
*
* eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
* 1.5 ldc (class) 1.6 "
*
* 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
* variable that decodes the class name. For eclipse, the class$0 gives away
* we have a reference encoded in a string.
* compilerversions/compilerversions.jar contains test versions of all
* versions/compilers.
*/
public void reset() {
pool = null;
intPool = null;
xref = null;
classes = null;
descriptors = null;
}
public boolean is(QUERY query, Instruction instr, Analyzer analyzer) throws Exception {
switch (query) {
case ANY:
return true;
case NAMED:
if (instr.matches(getClassName()))
return !instr.isNegated();
return false;
case VERSION:
String v = major + "/" + minor;
if (instr.matches(v))
return !instr.isNegated();
return false;
case IMPLEMENTS:
for (int i = 0; interfaces != null && i < interfaces.length; i++) {
if (instr.matches(interfaces[i]))
return !instr.isNegated();
}
break;
case EXTENDS:
if (zuper == null)
return false;
if (instr.matches(zuper))
return !instr.isNegated();
break;
case PUBLIC:
return !isPublic;
case CONCRETE:
return !isAbstract;
case ANNOTATION:
if (annotations == null)
return false;
if (annotations.contains(instr.getPattern()))
return true;
for (String annotation : annotations) {
if (instr.matches(annotation))
return !instr.isNegated();
}
return false;
case RUNTIMEANNOTATIONS:
return hasClassAnnotations;
case CLASSANNOTATIONS:
return hasClassAnnotations;
case ABSTRACT:
return isAbstract;
case IMPORTS:
for (String imp : imports) {
if (instr.matches(imp.replace('.', '/')))
return !instr.isNegated();
}
}
if (zuper == null)
return false;
Clazz clazz = analyzer.findClass(zuper + ".class");
if (clazz == null)
return false;
return clazz.is(query, instr, analyzer);
}
public String toString() {
return getFQN();
}
public String getFQN() {
String s = getClassName().replace('/', '.');
return s;
}
/**
* Return a list of packages implemented by this class.
*
* @param implemented
* @param classspace
* @param clazz
* @throws Exception
*/
@SuppressWarnings("deprecation") final static String USEPOLICY = toDescriptor(UsePolicy.class);
final static String PROVIDERPOLICY = toDescriptor(ProviderType.class);
public static void getImplementedPackages(Set<String> implemented, Analyzer analyzer,
Clazz clazz) throws Exception {
if (clazz.interfaces != null) {
for (String interf : clazz.interfaces) {
interf = interf + ".class";
Clazz c = analyzer.getClassspace().get(interf);
// If not found, actually parse the imported
// class file to check for implementation policy.
if (c == null)
c = analyzer.findClass(interf);
if (c != null) {
boolean consumer = false;
Set<String> annotations = c.annotations;
if (annotations != null)
// Override if we marked the interface as a consumer
// interface
consumer = annotations.contains(USEPOLICY)
|| annotations.contains(PROVIDERPOLICY);
if (!consumer)
implemented.add(getPackage(interf));
getImplementedPackages(implemented, analyzer, c);
} else
implemented.add(getPackage(interf));
}
}
if (clazz.zuper != null) {
Clazz c = analyzer.getClassspace().get(clazz.zuper);
if (c != null) {
getImplementedPackages(implemented, analyzer, c);
}
}
}
// String RNAME = "LaQute/bnd/annotation/UsePolicy;";
public static String toDescriptor(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
sb.append('L');
sb.append(clazz.getName().replace('.', '/'));
sb.append(';');
return sb.toString();
}
MethodDef getMethodDef(int access, int methodRefPoolIndex) {
Object o = pool[methodRefPoolIndex];
if (o != null && o instanceof Assoc) {
Assoc assoc = (Assoc) o;
if (assoc.tag == 10) {
int string_index = intPool[assoc.a];
String className = (String) pool[string_index];
int name_and_type_index = assoc.b;
Assoc name_and_type = (Assoc) pool[name_and_type_index];
if (name_and_type.tag == 12) {
// Name and Type
int name_index = name_and_type.a;
int type_index = name_and_type.b;
String method = (String) pool[name_index];
String descriptor = (String) pool[type_index];
return new MethodDef(access, className, method, descriptor);
} else
throw new IllegalArgumentException(
"Invalid class file (or parsing is wrong), assoc is not type + name (12)");
} else
throw new IllegalArgumentException(
"Invalid class file (or parsing is wrong), Assoc is not method ref! (10)");
} else
throw new IllegalArgumentException(
"Invalid class file (or parsing is wrong), Not an assoc at a method ref");
}
public static String getShortName(String cname) {
int n = cname.lastIndexOf('.');
if (n < 0)
return cname;
return cname.substring(n + 1, cname.length());
}
public static String fqnToPath(String dotted) {
return dotted.replace('.', '/') + ".class";
}
public static String fqnToBinary(String dotted) {
return "L" + dotted.replace('.', '/') + ";";
}
public static String pathToFqn(String path) {
return path.replace('/', '.').substring(0, path.length() - 6);
}
public boolean isPublic() {
return isPublic;
}
public boolean isEnum() {
return isEnum;
}
public JAVA getFormat() {
return JAVA.format(major);
}
public static String objectDescriptorToFQN(String string) {
if (string.startsWith("L") && string.endsWith(";"))
return string.substring(1, string.length() - 1).replace('/', '.');
switch (string.charAt(0)) {
case 'V':
return "void";
case 'B':
return "byte";
case 'C':
return "char";
case 'I':
return "int";
case 'S':
return "short";
case 'D':
return "double";
case 'F':
return "float";
case 'J':
return "long";
case 'Z':
return "boolean";
case '[': // Array
return objectDescriptorToFQN(string.substring(1)) + "[]";
}
throw new IllegalArgumentException("Invalid type character in descriptor " + string);
}
public static String internalToFqn(String string) {
return string.replace('/', '.');
}
public static String unCamel(String id) {
StringBuilder out = new StringBuilder();
for (int i = 0; i < id.length(); i++) {
char c = id.charAt(i);
if (c == '_' || c == '$' || c == '.') {
if (out.length() > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
out.append(' ');
continue;
}
int n = i;
while (n < id.length() && Character.isUpperCase(id.charAt(n))) {
n++;
}
if (n == i)
out.append(id.charAt(i));
else {
boolean tolower = (n - i) == 1;
if (i > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
out.append(' ');
for (; i < n;) {
if (tolower)
out.append(Character.toLowerCase(id.charAt(i)));
else
out.append(id.charAt(i));
i++;
}
i--;
}
}
if (id.startsWith("."))
out.append(" *");
out.replace(0, 1, Character.toUpperCase(out.charAt(0)) + "");
return out.toString();
}
}