package sun.tools.jar;
import jdk.internal.org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
final class FingerPrint {
private static final MessageDigest MD;
private final byte[] sha1;
private final ClassAttributes attrs;
private final boolean isClassEntry;
private final String entryName;
static {
try {
MD = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException x) {
throw new RuntimeException(x);
}
}
public FingerPrint(String entryName,byte[] bytes) throws IOException {
this.entryName = entryName;
if (entryName.endsWith(".class") && isCafeBabe(bytes)) {
isClassEntry = true;
sha1 = sha1(bytes, 8);
attrs = getClassAttributes(bytes);
} else {
isClassEntry = false;
sha1 = sha1(bytes);
attrs = new ClassAttributes();
}
}
public boolean isClass() {
return isClassEntry;
}
public boolean isNestedClass() {
return attrs.nestedClass;
}
public boolean isPublicClass() {
return attrs.publicClass;
}
public boolean isIdentical(FingerPrint that) {
if (that == null) return false;
if (this == that) return true;
return isEqual(this.sha1, that.sha1);
}
public boolean isCompatibleVersion(FingerPrint that) {
return attrs.version >= that.attrs.version;
}
public boolean isSameAPI(FingerPrint that) {
if (that == null) return false;
return attrs.equals(that.attrs);
}
public String name() {
String name = attrs.name;
return name == null ? entryName : name;
}
public String topLevelName() {
String name = attrs.topLevelName;
return name == null ? name() : name;
}
private byte[] sha1(byte[] entry) {
MD.update(entry);
return MD.digest();
}
private byte[] sha1(byte[] entry, int offset) {
MD.update(entry, offset, entry.length - offset);
return MD.digest();
}
private boolean isEqual(byte[] sha1_1, byte[] sha1_2) {
return MessageDigest.isEqual(sha1_1, sha1_2);
}
private static final byte[] cafeBabe = {(byte)0xca, (byte)0xfe, (byte)0xba, (byte)0xbe};
private boolean isCafeBabe(byte[] bytes) {
if (bytes.length < 4) return false;
for (int i = 0; i < 4; i++) {
if (bytes[i] != cafeBabe[i]) {
return false;
}
}
return true;
}
private ClassAttributes getClassAttributes(byte[] bytes) {
ClassReader rdr = new ClassReader(bytes);
ClassAttributes attrs = new ClassAttributes();
rdr.accept(attrs, 0);
return attrs;
}
private static final class Field {
private final int access;
private final String name;
private final String desc;
Field(int access, String name, String desc) {
this.access = access;
this.name = name;
this.desc = desc;
}
@Override
public boolean equals(Object that) {
if (that == null) return false;
if (this == that) return true;
if (!(that instanceof Field)) return false;
Field field = (Field)that;
return (access == field.access) && name.equals(field.name)
&& desc.equals(field.desc);
}
@Override
public int hashCode() {
int result = 17;
result = 37 * result + access;
result = 37 * result + name.hashCode();
result = 37 * result + desc.hashCode();
return result;
}
}
private static final class Method {
private final int access;
private final String name;
private final String desc;
private final Set<String> exceptions;
Method(int access, String name, String desc, Set<String> exceptions) {
this.access = access;
this.name = name;
this.desc = desc;
this.exceptions = exceptions;
}
@Override
public boolean equals(Object that) {
if (that == null) return false;
if (this == that) return true;
if (!(that instanceof Method)) return false;
Method method = (Method)that;
return (access == method.access) && name.equals(method.name)
&& desc.equals(method.desc)
&& exceptions.equals(method.exceptions);
}
@Override
public int hashCode() {
int result = 17;
result = 37 * result + access;
result = 37 * result + name.hashCode();
result = 37 * result + desc.hashCode();
result = 37 * result + exceptions.hashCode();
return result;
}
}
private static final class ClassAttributes extends ClassVisitor {
private String name;
private String topLevelName;
private String superName;
private int version;
private int access;
private boolean publicClass;
private boolean nestedClass;
private final Set<Field> fields = new HashSet<>();
private final Set<Method> methods = new HashSet<>();
public ClassAttributes() {
super(Opcodes.ASM5);
}
private boolean isPublic(int access) {
return ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC)
|| ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
this.version = version;
this.access = access;
this.name = name;
this.nestedClass = name.contains("$");
this.superName = superName;
this.publicClass = isPublic(access);
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
if (!this.nestedClass) return;
this.topLevelName = owner;
}
@Override
public void visitInnerClass(String name, String outerName, String innerName,
int access) {
if (!this.nestedClass) return;
if (outerName == null) return;
if (!this.name.equals(name)) return;
if (this.topLevelName == null) this.topLevelName = outerName;
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if (isPublic(access)) {
fields.add(new Field(access, name, desc));
}
return null;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (isPublic(access)) {
Set<String> exceptionSet = new HashSet<>();
if (exceptions != null) {
for (String e : exceptions) {
exceptionSet.add(e);
}
}
int n;
if (desc != null && (n = desc.lastIndexOf(')')) != -1) {
desc = desc.substring(0, n + 1);
methods.add(new Method(access, name, desc, exceptionSet));
}
}
return null;
}
@Override
public void visitEnd() {
this.nestedClass = this.topLevelName != null;
}
@Override
public boolean equals(Object that) {
if (that == null) return false;
if (this == that) return true;
if (!(that instanceof ClassAttributes)) return false;
ClassAttributes clsAttrs = (ClassAttributes)that;
boolean superNameOkay = superName != null
? superName.equals(clsAttrs.superName) : true;
return access == clsAttrs.access
&& superNameOkay
&& fields.equals(clsAttrs.fields)
&& methods.equals(clsAttrs.methods);
}
@Override
public int hashCode() {
int result = 17;
result = 37 * result + access;
result = 37 * result + superName != null ? superName.hashCode() : 0;
result = 37 * result + fields.hashCode();
result = 37 * result + methods.hashCode();
return result;
}
}
}