A Class that creates a string hash out of the public API of a
class file. Useful for caching bytecode.
Review at http://gwt-code-reviews.appspot.com/1359802
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9750 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/BytecodeSignatureMaker.java b/dev/core/src/com/google/gwt/dev/javac/BytecodeSignatureMaker.java
new file mode 100644
index 0000000..8664e0e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/BytecodeSignatureMaker.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.javac;
+
+import com.google.gwt.dev.asm.AnnotationVisitor;
+import com.google.gwt.dev.asm.Attribute;
+import com.google.gwt.dev.asm.ClassReader;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.FieldVisitor;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.util.Util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Creates string hashes for various purposes from walking bytecode.
+ */
+public class BytecodeSignatureMaker {
+
+ /**
+ * This visitor looks at public/protected members and methods to compute a
+ * signature. This is intended for determining if a type needs to be
+ * recompiled if byte code it depends on changes.
+ */
+ private static class CompileDependencyVisitor implements ClassVisitor {
+ /**
+ * Mask to strip access bits we don't care about for computing the
+ * signature.
+ */
+ private static final int ACCESS_FILTER_MASK =
+ ~(Opcodes.ACC_DEPRECATED | Opcodes.ACC_NATIVE | Opcodes.ACC_STRICT
+ | Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_SUPER
+ | Opcodes.ACC_TRANSIENT | Opcodes.ACC_VOLATILE);
+
+ private String header;
+ private Map<String, String> fields = new HashMap<String, String>();
+ private Map<String, String> methods = new HashMap<String, String>();
+
+ public String getSignature() {
+ return Util.computeStrongName(Util.getBytes(getRawString()));
+ }
+
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ StringBuilder headerBuilder = new StringBuilder();
+ // ignoring version
+ headerBuilder.append(access & ACCESS_FILTER_MASK);
+ headerBuilder.append(":");
+ headerBuilder.append(name);
+ if (signature != null) {
+ headerBuilder.append(":");
+ headerBuilder.append(signature);
+ }
+ if (superName != null) {
+ headerBuilder.append(":");
+ headerBuilder.append(superName);
+ }
+ if (interfaces != null) {
+ Arrays.sort(interfaces);
+ for (String iface : interfaces) {
+ headerBuilder.append(":");
+ headerBuilder.append(iface);
+ }
+ }
+ header = headerBuilder.toString();
+ }
+
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // ignore
+ return null;
+ }
+
+ public void visitAttribute(Attribute attr) {
+ // ignore
+ }
+
+ public void visitEnd() {
+ // unused
+ }
+
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ StringBuilder fieldBuilder = new StringBuilder();
+ // We don't care about private or synthetic fields
+ if ((access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC)) == 0) {
+ fieldBuilder.append(access & ACCESS_FILTER_MASK);
+ fieldBuilder.append(":");
+ fieldBuilder.append(name);
+ fieldBuilder.append(":");
+ fieldBuilder.append(desc);
+ if (signature != null) {
+ fieldBuilder.append(":");
+ fieldBuilder.append(signature);
+ }
+ if (value != null) {
+ fieldBuilder.append(":");
+ fieldBuilder.append(value.toString());
+ }
+ fields.put(name, fieldBuilder.toString());
+ }
+
+ // ignoring annotations/attributes on the field.
+ return null;
+ }
+
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ // ignored
+ }
+
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ // We don't care about private or synthetic methods
+ if ((access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC)) == 0) {
+ StringBuilder methodBuilder = new StringBuilder();
+ methodBuilder.append(access & ACCESS_FILTER_MASK);
+ methodBuilder.append(":");
+ methodBuilder.append(name);
+ methodBuilder.append(":");
+ methodBuilder.append(desc);
+ if (signature != null) {
+ methodBuilder.append(":");
+ methodBuilder.append(signature);
+ }
+ if (exceptions != null) {
+ String[] sortedExceptions = exceptions;
+ Arrays.sort(sortedExceptions);
+ for (String exception : sortedExceptions) {
+ methodBuilder.append(":");
+ methodBuilder.append(exception);
+ }
+ }
+ methods.put(name, methodBuilder.toString());
+ }
+ return null;
+ }
+
+ public void visitOuterClass(String owner, String name, String desc) {
+ // ignored
+ }
+
+ public void visitSource(String source, String debug) {
+ // ignore
+ }
+
+ private String getRawString() {
+ StringBuilder signatureBuilder = new StringBuilder();
+ signatureBuilder.append(header);
+ signatureBuilder.append("|");
+
+ // sort all fields and methods for a deterministic signature.
+ String[] sortedFields = fields.values().toArray(new String[0]);
+ Arrays.sort(sortedFields);
+ for (String field : sortedFields) {
+ signatureBuilder.append(field);
+ signatureBuilder.append("|");
+ }
+
+ String[] sortedMethods = methods.values().toArray(new String[0]);
+ Arrays.sort(sortedMethods);
+ for (String method : sortedMethods) {
+ signatureBuilder.append(method);
+ signatureBuilder.append("|");
+ }
+ return signatureBuilder.toString();
+ }
+ }
+
+ /**
+ * Returns a hash computed from the non-private/non-synthetic members and
+ * methods in a class.
+ *
+ * @param byteCode byte code for class to analyze.
+ * @return a hex string representing an MD5 digest.
+ */
+ public static String getCompileDependencySignature(byte[] byteCode) {
+ ClassReader reader = new ClassReader(byteCode);
+ CompileDependencyVisitor v = new CompileDependencyVisitor();
+ reader.accept(v, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
+ | ClassReader.SKIP_FRAMES);
+ return v.getSignature();
+ }
+
+ /**
+ * Returns a raw string used to compute the hash from the
+ * non-private/non-synthetic members and methods in a class.
+ *
+ * @param byteCode byte code for class to analyze.
+ * @return a human readable string of all public API fields
+ */
+ static String getCompileDependencyRawSignature(byte[] byteCode) {
+ ClassReader reader = new ClassReader(byteCode);
+ CompileDependencyVisitor v = new CompileDependencyVisitor();
+ reader.accept(v, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
+ | ClassReader.SKIP_FRAMES);
+ return v.getRawString();
+ }
+
+ private BytecodeSignatureMaker() {
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/BytecodeSignatureMakerTest.java b/dev/core/test/com/google/gwt/dev/javac/BytecodeSignatureMakerTest.java
new file mode 100644
index 0000000..6737d34
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/BytecodeSignatureMakerTest.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.javac;
+
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+
+/**
+ * Tests for {@link BytecodeSignatureMaker}
+ */
+public class BytecodeSignatureMakerTest extends CompilationStateTestBase {
+ static final String CLASS_DEP_TYPE_NAME = "test.ClassDependency";
+
+ public void testClassDependencySignature() {
+ final MockJavaResource CLASS_DEP_ORIG =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ // A verbatim copy of CLASS_DEP_ORIG
+ final MockJavaResource CLASS_DEP_NO_CHANGE =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_NO_PRIVATE =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ // Missing fieldPrivate
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ // Missing methodPrivate
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_NO_PROTECTED_FIELD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ // missing fieldProtected
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_NO_DEFAULT_FIELD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ // missing fieldDefault
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_NO_PUBLIC_FIELD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ // missing public field
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_FIELD_VALUE_CHANGE =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ // Value was 100
+ code.append(" static public final int fieldPublicStatic = 99;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_ORDER =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ // re-ordered this field
+ code.append(" public int fieldPublic;\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ // re-ordered this method
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_INNER =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ // Added an inner class definition
+ code.append(" public static class IgnoreMe {\n");
+ code.append(" private int ignoreThisMember;\n");
+ code.append(" }\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_DEPRECATED_FIELD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" @Deprecated\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_DEPRECATED_METHOD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" @Deprecated\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+
+ final MockJavaResource CLASS_DEP_ANNOTATED_FIELD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" @TestAnnotation(\"Foo\")\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_ANNOTATED_METHOD =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" @TestAnnotation(\"Foo\")\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ final MockJavaResource CLASS_DEP_JAVADOC =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" /** a static field */\n");
+ code.append(" static public final int fieldPublicStatic = 100;\n");
+ code.append(" /** a public field */\n");
+ code.append(" public int fieldPublic;\n");
+ code.append(" protected int fieldProtected;\n");
+ code.append(" int fieldDefault;\n");
+ code.append(" private int fieldPrivate;\n");
+ code.append(" /** a public method */\n");
+ code.append(" public int methodPublic() {return 1;};\n");
+ code.append(" protected int methodProtected(String arg) {return 1;};\n");
+ code.append(" int methodDefault() {return 1;};\n");
+ code.append(" private int methodPrivate(){return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+
+ final MockJavaResource TEST_ANNOTATION =
+ new MockJavaResource("test.TestAnnotation") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public @interface TestAnnotation {\n");
+ code.append(" String value();");
+ code.append("}\n");
+ return code;
+ }
+ };
+ CompiledClass originalClass = buildClass(CLASS_DEP_ORIG);
+ assertNotNull(originalClass);
+
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_NO_CHANGE));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_NO_PRIVATE));
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_NO_PUBLIC_FIELD));
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_NO_PROTECTED_FIELD));
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_NO_DEFAULT_FIELD));
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_FIELD_VALUE_CHANGE));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_ORDER));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_INNER));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_DEPRECATED_FIELD));
+ assertSignaturesEqual(originalClass,
+ buildClass(CLASS_DEP_DEPRECATED_METHOD));
+
+ oracle.add(TEST_ANNOTATION);
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_ANNOTATED_FIELD));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_ANNOTATED_METHOD));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_JAVADOC));
+ }
+
+ public void testClassDependencySignatureWithExceptions() {
+ MockJavaResource ILLEGAL_STATE_EXCEPTION =
+ new MockJavaResource("java.lang.IllegalStateException") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package java.lang;\n");
+ code.append("public class IllegalStateException extends Throwable {}\n");
+ return code;
+ }
+ };
+ MockJavaResource NUMBER_FORMAT_EXCEPTION =
+ new MockJavaResource("java.lang.NumberFormatException") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package java.lang;\n");
+ code.append("public class NumberFormatException extends Throwable {}\n");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_EXCEPTION_ORIG =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" public int methodPublic(String arg) ");
+ code.append(" throws IllegalStateException, NumberFormatException {");
+ code.append(" return 1;\n");
+ code.append(" }\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_EXCEPTION_MOD1 =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ // no exceptions declared
+ code.append(" public int methodPublic(String arg) {return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_EXCEPTION_MOD2 =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ // one exception declared
+ code.append(" public int methodPublic(String arg)");
+ code.append(" throws IllegalStateException {");
+ code.append(" return 1;\n");
+ code.append(" }\n");
+ code.append("}");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_EXCEPTION_MOD3 =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency {\n");
+ code.append(" public int methodPublic(String arg)");
+ // order of declared exceptions is flipped
+ code.append(" throws NumberFormatException, IllegalStateException {");
+ code.append(" return 1;\n");
+ code.append(" }\n");
+ code.append("}");
+ return code;
+ }
+ };
+
+ oracle.add(ILLEGAL_STATE_EXCEPTION);
+ oracle.add(NUMBER_FORMAT_EXCEPTION);
+ CompiledClass originalClass = buildClass(CLASS_DEP_EXCEPTION_ORIG);
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_EXCEPTION_MOD1));
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_EXCEPTION_MOD2));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_EXCEPTION_MOD3));
+ }
+
+ public void testClassDependencySignatureWithGenerics() {
+ MockJavaResource CLASS_DEP_GENERIC_ORIG =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class ClassDependency<T> {\n");
+ code.append(" public int methodPublic(T arg) {return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_GENERIC_PARAMETERIZED =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("import java.util.Map;");
+ code.append("public class ClassDependency<T extends Map> {\n");
+ code.append(" public int methodPublic(T arg) {return 1;};\n");
+ code.append("}");
+ return code;
+ }
+ };
+ CompiledClass originalClass = buildClass(CLASS_DEP_GENERIC_ORIG);
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_GENERIC_PARAMETERIZED));
+ }
+
+ public void testClassDependencySignatureWithInterfaces() {
+ MockJavaResource CLASS_DEP_INTERFACE_ORIG =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("import java.util.Map;");
+ code.append("import java.util.Collection;");
+ code.append("public class ClassDependency implements Map, Collection {\n");
+ code.append(" public int methodPublic(String arg) { return 1;}\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_INTERFACE_MOD1 =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("import java.util.Map;");
+ code.append("import java.util.Collection;");
+ // no interfaces
+ code.append("public class ClassDependency {\n");
+ code.append(" public int methodPublic(String arg) { return 1;}\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_INTERFACE_MOD2 =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("import java.util.Map;");
+ code.append("import java.util.Collection;");
+ // only one interface
+ code.append("public class ClassDependency implements Map {\n");
+ code.append(" public int methodPublic(String arg) { return 1;}\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+ MockJavaResource CLASS_DEP_INTERFACE_MOD3 =
+ new MockJavaResource(CLASS_DEP_TYPE_NAME) {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("import java.util.Map;");
+ code.append("import java.util.Collection;");
+ // flipped order of interface decls
+ code.append("public class ClassDependency implements Collection, Map {\n");
+ code.append(" public int methodPublic(String arg) { return 1;}\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+ CompiledClass originalClass = buildClass(CLASS_DEP_INTERFACE_ORIG);
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_INTERFACE_MOD1));
+ assertSignaturesNotEqual(originalClass,
+ buildClass(CLASS_DEP_INTERFACE_MOD2));
+ assertSignaturesEqual(originalClass, buildClass(CLASS_DEP_INTERFACE_MOD3));
+ }
+
+ private void assertSignaturesEqual(CompiledClass original,
+ CompiledClass updated) {
+ String originalSignature =
+ BytecodeSignatureMaker.getCompileDependencySignature(original.getBytes());
+ String updatedSignature =
+ BytecodeSignatureMaker.getCompileDependencySignature(updated.getBytes());
+ if (!originalSignature.equals(updatedSignature)) {
+ String originalRaw =
+ BytecodeSignatureMaker.getCompileDependencyRawSignature(original.getBytes());
+ String updatedRaw =
+ BytecodeSignatureMaker.getCompileDependencyRawSignature(updated.getBytes());
+ fail("Signatures don't match. raw data expected=<" + originalRaw
+ + "> actual=<" + updatedRaw + ">");
+ }
+ }
+
+ private void assertSignaturesNotEqual(CompiledClass original,
+ CompiledClass updated) {
+ String originalSignature =
+ BytecodeSignatureMaker.getCompileDependencySignature(original.getBytes());
+ String updatedSignature =
+ BytecodeSignatureMaker.getCompileDependencySignature(updated.getBytes());
+ if (originalSignature.equals(updatedSignature)) {
+ String originalRaw =
+ BytecodeSignatureMaker.getCompileDependencyRawSignature(original.getBytes());
+ String updatedRaw =
+ BytecodeSignatureMaker.getCompileDependencyRawSignature(updated.getBytes());
+ fail("Signatures should not match. raw data expected=<" + originalRaw
+ + "> actual=<" + updatedRaw + ">");
+ }
+ }
+
+ private CompiledClass buildClass(MockJavaResource resource) {
+ oracle.addOrReplace(resource);
+ this.rebuildCompilationState();
+ CompilationUnit unit =
+ state.getCompilationUnitMap().get(resource.getTypeName());
+ assertNotNull(unit);
+ String internalName = resource.getTypeName().replace(".", "/");
+ CategorizedProblem[] problems = unit.getProblems();
+ if (problems != null && problems.length != 0) {
+ fail(problems[0].toString());
+ }
+ for (CompiledClass cc : unit.getCompiledClasses()) {
+ if (cc.getInternalName().equals(internalName)) {
+ return cc;
+ }
+ }
+ fail("Couldn't find class " + internalName + " after compiling.");
+ return null;
+ }
+}