package lombok.bytecode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ClassFileMetaData {
private static final byte UTF8 = 1;
private static final byte INTEGER = 3;
private static final byte FLOAT = 4;
private static final byte LONG = 5;
private static final byte DOUBLE = 6;
private static final byte CLASS = 7;
private static final byte STRING = 8;
private static final byte FIELD = 9;
private static final byte METHOD = 10;
private static final byte INTERFACE_METHOD = 11;
private static final byte NAME_TYPE = 12;
private static final byte METHOD_HANDLE = 15;
private static final byte METHOD_TYPE = 16;
private static final byte INVOKE_DYNAMIC = 18;
private static final byte MODULE = 19;
private static final byte PACKAGE = 20;
private static final int NOT_FOUND = -1;
private static final int START_OF_CONSTANT_POOL = 8;
private final byte[] byteCode;
private final int maxPoolSize;
private final int[] offsets;
private final byte[] types;
private final String[] utf8s;
private final int endOfPool;
public ClassFileMetaData(byte[] byteCode) {
this.byteCode = byteCode;
maxPoolSize = readValue(START_OF_CONSTANT_POOL);
offsets = new int[maxPoolSize];
types = new byte[maxPoolSize];
utf8s = new String[maxPoolSize];
int position = 10;
for (int i = 1; i < maxPoolSize; i++) {
byte type = byteCode[position];
types[i] = type;
position++;
offsets[i] = position;
switch (type) {
case UTF8:
int length = readValue(position);
position += 2;
utf8s[i] = decodeString(position, length);
position += length;
break;
case CLASS:
case STRING:
case METHOD_TYPE:
case MODULE:
case PACKAGE:
position += 2;
break;
case METHOD_HANDLE:
position += 3;
break;
case INTEGER:
case FLOAT:
case FIELD:
case METHOD:
case INTERFACE_METHOD:
case NAME_TYPE:
case INVOKE_DYNAMIC:
position += 4;
break;
case LONG:
case DOUBLE:
position += 8;
i++;
break;
case 0:
break;
default:
throw new AssertionError("Unknown constant pool type " + type);
}
}
endOfPool = position;
}
private String decodeString(int pos, int size) {
int end = pos + size;
char[] result = new char[size];
int length = 0;
while (pos < end) {
int first = (byteCode[pos++] & 0xFF);
if (first < 0x80) {
result[length++] = (char)first;
} else if ((first & 0xE0) == 0xC0) {
int x = (first & 0x1F) << 6;
int y = (byteCode[pos++] & 0x3F);
result[length++] = (char)(x | y);
} else {
int x = (first & 0x0F) << 12;
int y = (byteCode[pos++] & 0x3F) << 6;
int z = (byteCode[pos++] & 0x3F);
result[length++] = (char)(x | y | z);
}
}
return new String(result, 0, length);
}
public boolean containsUtf8(String value) {
return findUtf8(value) != NOT_FOUND;
}
public boolean usesClass(String className) {
return findClass(className) != NOT_FOUND;
}
public boolean usesField(String className, String fieldName) {
int classIndex = findClass(className);
if (classIndex == NOT_FOUND) return false;
int fieldNameIndex = findUtf8(fieldName);
if (fieldNameIndex == NOT_FOUND) return false;
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == FIELD && readValue(offsets[i]) == classIndex) {
int nameAndTypeIndex = readValue(offsets[i] + 2);
if (readValue(offsets[nameAndTypeIndex]) == fieldNameIndex) return true;
}
}
return false;
}
public boolean usesMethod(String className, String methodName) {
int classIndex = findClass(className);
if (classIndex == NOT_FOUND) return false;
int methodNameIndex = findUtf8(methodName);
if (methodNameIndex == NOT_FOUND) return false;
for (int i = 1; i < maxPoolSize; i++) {
if (isMethod(i) && readValue(offsets[i]) == classIndex) {
int nameAndTypeIndex = readValue(offsets[i] + 2);
if (readValue(offsets[nameAndTypeIndex]) == methodNameIndex) return true;
}
}
return false;
}
public boolean usesMethod(String className, String methodName, String descriptor) {
int classIndex = findClass(className);
if (classIndex == NOT_FOUND) return false;
int nameAndTypeIndex = findNameAndType(methodName, descriptor);
if (nameAndTypeIndex == NOT_FOUND) return false;
for (int i = 1; i < maxPoolSize; i++) {
if (isMethod(i) &&
readValue(offsets[i]) == classIndex &&
readValue(offsets[i] + 2) == nameAndTypeIndex) return true;
}
return false;
}
public boolean containsStringConstant(String value) {
int index = findUtf8(value);
if (index == NOT_FOUND) return false;
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == STRING && readValue(offsets[i]) == index) return true;
}
return false;
}
public boolean containsLong(long value) {
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == LONG && readLong(i) == value) return true;
}
return false;
}
public boolean containsDouble(double value) {
boolean isNan = Double.isNaN(value);
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == DOUBLE) {
double d = readDouble(i);
if (d == value || (isNan && Double.isNaN(d))) return true;
}
}
return false;
}
public boolean containsInteger(int value) {
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == INTEGER && readInteger(i) == value) return true;
}
return false;
}
public boolean containsFloat(float value) {
boolean isNan = Float.isNaN(value);
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == FLOAT) {
float f = readFloat(i);
if (f == value || (isNan && Float.isNaN(f))) return true;
}
}
return false;
}
private long readLong(int index) {
int pos = offsets[index];
return ((long)read32(pos)) << 32 | (read32(pos + 4) & 0x00000000FFFFFFFFL);
}
private double readDouble(int index) {
return Double.longBitsToDouble(readLong(index));
}
private int readInteger(int index) {
return read32(offsets[index]);
}
private float readFloat(int index) {
return Float.intBitsToFloat(readInteger(index));
}
private int read32(int pos) {
return (byteCode[pos] & 0xFF) << 24 | (byteCode[pos + 1] & 0xFF) << 16 | (byteCode[pos + 2] & 0xFF) << 8 | (byteCode[pos + 3] & 0xFF);
}
public String getClassName() {
return getClassName(readValue(endOfPool + 2));
}
public String getSuperClassName() {
return getClassName(readValue(endOfPool + 4));
}
public List<String> getInterfaces() {
int size = readValue(endOfPool + 6);
if (size == 0) return Collections.emptyList();
List<String> result = new ArrayList<String>();
for (int i = 0; i < size; i++) {
result.add(getClassName(readValue(endOfPool + 8 + (i * 2))));
}
return result;
}
public String poolContent() {
StringBuilder result = new StringBuilder();
for (int i = 1; i < maxPoolSize; i++) {
result.append(String.format("#%02x: ", i));
int pos = offsets[i];
switch(types[i]) {
case UTF8:
result.append("Utf8 ").append(utf8s[i]);
break;
case CLASS:
result.append("Class ").append(getClassName(i));
break;
case STRING:
result.append("String \"").append(utf8s[readValue(pos)]).append("\"");
break;
case INTEGER:
result.append("int ").append(readInteger(i));
break;
case FLOAT:
result.append("float ").append(readFloat(i));
break;
case FIELD:
appendAccess(result.append("Field "), i);
break;
case METHOD:
case INTERFACE_METHOD:
appendAccess(result.append("Method "), i);
break;
case NAME_TYPE:
appendNameAndType(result.append("Name&Type "), i);
break;
case LONG:
result.append("long ").append(readLong(i));
break;
case DOUBLE:
result.append("double ").append(readDouble(i));
break;
case METHOD_HANDLE:
result.append("MethodHandle...");
break;
case METHOD_TYPE:
result.append("MethodType...");
break;
case INVOKE_DYNAMIC:
result.append("InvokeDynamic...");
break;
case 0:
result.append("(cont.)");
break;
}
result.append("\n");
}
return result.toString();
}
private void appendAccess(StringBuilder result, int index) {
int pos = offsets[index];
result.append(getClassName(readValue(pos))).append(".");
appendNameAndType(result, readValue(pos + 2));
}
private void appendNameAndType(StringBuilder result, int index) {
int pos = offsets[index];
result.append(utf8s[readValue(pos)]).append(":").append(utf8s[readValue(pos + 2)]);
}
private String getClassName(int classIndex) {
if (classIndex < 1) return null;
return utf8s[readValue(offsets[classIndex])];
}
private boolean isMethod(int i) {
byte type = types[i];
return type == METHOD || type == INTERFACE_METHOD;
}
private int findNameAndType(String name, String descriptor) {
int nameIndex = findUtf8(name);
if (nameIndex == NOT_FOUND) return NOT_FOUND;
int descriptorIndex = findUtf8(descriptor);
if (descriptorIndex == NOT_FOUND) return NOT_FOUND;
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == NAME_TYPE &&
readValue(offsets[i]) == nameIndex &&
readValue(offsets[i] + 2) == descriptorIndex) return i;
}
return NOT_FOUND;
}
private int findUtf8(String value) {
for (int i = 1; i < maxPoolSize; i++) {
if (value.equals(utf8s[i])) {
return i;
}
}
return NOT_FOUND;
}
private int findClass(String className) {
int index = findUtf8(className);
if (index == -1) return NOT_FOUND;
for (int i = 1; i < maxPoolSize; i++) {
if (types[i] == CLASS && readValue(offsets[i]) == index) return i;
}
return NOT_FOUND;
}
private int readValue(int position) {
return ((byteCode[position] & 0xFF) << 8) | (byteCode[position + 1] & 0xFF);
}
}