--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java Fri Jul 29 09:58:28 2016 -0700
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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;
+
+/**
+ * A FingerPrint is an abstract representation of a JarFile entry that contains
+ * information to determine if the entry represents a class or a
+ * resource, and whether two entries are identical. If the FingerPrint represents
+ * a class, it also contains information to (1) describe the public API;
+ * (2) compare the public API of this class with another class; (3) determine
+ * whether or not it's a nested class and, if so, the name of the associated
+ * top level class; and (4) for an canonically ordered set of classes determine
+ * if the class versions are compatible. A set of classes is canonically
+ * ordered if the classes in the set have the same name, and the base class
+ * precedes the versioned classes and if each versioned class with version
+ * {@code n} precedes classes with versions {@code > n} for all versions
+ * {@code n}.
+ */
+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) {
+ // log big problem?
+ 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); // skip magic number and major/minor version
+ attrs = getClassAttributes(bytes);
+ } else {
+ isClassEntry = false;
+ sha1 = sha1(bytes);
+ attrs = new ClassAttributes(); // empty class
+ }
+ }
+
+ 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);
+ }
+ }
+ // treat type descriptor as a proxy for signature because signature
+ // is usually null, need to strip off the return type though
+ 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;
+ }
+ }
+}