jjg@1521: /* darcy@1534: * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. jjg@1521: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@1521: * jjg@1521: * This code is free software; you can redistribute it and/or modify it jjg@1521: * under the terms of the GNU General Public License version 2 only, as jjg@1521: * published by the Free Software Foundation. jjg@1521: * jjg@1521: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@1521: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@1521: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@1521: * version 2 for more details (a copy is included in the LICENSE file that jjg@1521: * accompanied this code). jjg@1521: * jjg@1521: * You should have received a copy of the GNU General Public License version jjg@1521: * 2 along with this work; if not, write to the Free Software Foundation, jjg@1521: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@1521: * jjg@1521: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@1521: * or visit www.oracle.com if you need additional information or have any jjg@1521: * questions. jjg@1521: */ jjg@1521: jjg@1521: import java.io.BufferedWriter; jjg@1521: import java.io.File; jjg@1521: import java.io.FileWriter; jjg@1521: import java.io.IOException; jjg@1521: import java.io.PrintStream; jjg@1521: import java.io.PrintWriter; jjg@1521: import java.lang.annotation.*; jjg@1521: import java.lang.reflect.*; jjg@1521: import java.util.ArrayList; jjg@1521: import java.util.Collections; jjg@1521: import java.util.HashMap; jjg@1521: import java.util.List; jjg@1521: import java.util.Map; jjg@1521: jjg@1521: import com.sun.tools.classfile.ClassFile; jjg@1521: import com.sun.tools.classfile.TypeAnnotation; jjg@1521: import com.sun.tools.classfile.TypeAnnotation.TargetType; jjg@1521: jjg@1521: public class Driver { jjg@1521: jjg@1521: private static final PrintStream out = System.out; jjg@1521: jjg@1521: public static void main(String[] args) throws Exception { jjg@1521: if (args.length == 0 || args.length > 1) jjg@1521: throw new IllegalArgumentException("Usage: java Driver "); jjg@1521: String name = args[0]; jjg@1521: Class clazz = Class.forName(name); jjg@1521: new Driver().runDriver(clazz.newInstance()); jjg@1521: } jjg@1521: jjg@1521: protected void runDriver(Object object) throws Exception { jjg@1521: int passed = 0, failed = 0; jjg@1521: Class clazz = object.getClass(); jjg@1521: out.println("Tests for " + clazz.getName()); jjg@1521: jjg@1521: // Find methods jjg@1521: for (Method method : clazz.getMethods()) { jjg@1521: Map expected = expectedOf(method); jjg@1521: if (expected == null) jjg@1521: continue; jjg@1521: if (method.getReturnType() != String.class) jjg@1521: throw new IllegalArgumentException("Test method needs to return a string: " + method); jjg@1521: String testClass = testClassOf(method); jjg@1521: jjg@1521: try { jjg@1521: String compact = (String)method.invoke(object); jjg@1521: String fullFile = wrap(compact); jjg@1521: ClassFile cf = compileAndReturn(fullFile, testClass); jjg@1521: List actual = ReferenceInfoUtil.extendedAnnotationsOf(cf); jjg@1521: ReferenceInfoUtil.compare(expected, actual, cf); jjg@1521: out.println("PASSED: " + method.getName()); jjg@1521: ++passed; jjg@1521: } catch (Throwable e) { jjg@1521: out.println("FAILED: " + method.getName()); jjg@1521: out.println(" " + e.toString()); jjg@1521: ++failed; jjg@1521: } jjg@1521: } jjg@1521: jjg@1521: out.println(); jjg@1521: int total = passed + failed; jjg@1521: out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED"); jjg@1521: jjg@1521: out.flush(); jjg@1521: jjg@1521: if (failed != 0) jjg@1521: throw new RuntimeException(failed + " tests failed"); jjg@1521: } jjg@1521: jjg@1521: private Map expectedOf(Method m) { jjg@1521: TADescription ta = m.getAnnotation(TADescription.class); jjg@1521: TADescriptions tas = m.getAnnotation(TADescriptions.class); jjg@1521: jjg@1521: if (ta == null && tas == null) jjg@1521: return null; jjg@1521: jjg@1521: Map result = jjg@1521: new HashMap(); jjg@1521: jjg@1521: if (ta != null) jjg@1521: result.putAll(expectedOf(ta)); jjg@1521: jjg@1521: if (tas != null) { jjg@1521: for (TADescription a : tas.value()) { jjg@1521: result.putAll(expectedOf(a)); jjg@1521: } jjg@1521: } jjg@1521: jjg@1521: return result; jjg@1521: } jjg@1521: jjg@1521: private Map expectedOf(TADescription d) { jjg@1521: String annoName = d.annotation(); jjg@1521: jjg@1521: TypeAnnotation.Position p = new TypeAnnotation.Position(); jjg@1521: p.type = d.type(); jjg@1521: if (d.offset() != NOT_SET) jjg@1521: p.offset = d.offset(); jjg@1521: if (d.lvarOffset().length != 0) jjg@1521: p.lvarOffset = d.lvarOffset(); jjg@1521: if (d.lvarLength().length != 0) jjg@1521: p.lvarLength = d.lvarLength(); jjg@1521: if (d.lvarIndex().length != 0) jjg@1521: p.lvarIndex = d.lvarIndex(); jjg@1521: if (d.boundIndex() != NOT_SET) jjg@1521: p.bound_index = d.boundIndex(); jjg@1521: if (d.paramIndex() != NOT_SET) jjg@1521: p.parameter_index = d.paramIndex(); jjg@1521: if (d.typeIndex() != NOT_SET) jjg@1521: p.type_index = d.typeIndex(); jjg@1521: if (d.exceptionIndex() != NOT_SET) jjg@1521: p.exception_index = d.exceptionIndex(); jjg@1521: if (d.genericLocation().length != 0) { jjg@1521: p.location = TypeAnnotation.Position.getTypePathFromBinary(wrapIntArray(d.genericLocation())); jjg@1521: } jjg@1521: jjg@1521: return Collections.singletonMap(annoName, p); jjg@1521: } jjg@1521: jjg@1521: private List wrapIntArray(int[] ints) { jjg@1521: List list = new ArrayList(ints.length); jjg@1521: for (int i : ints) jjg@1521: list.add(i); jjg@1521: return list; jjg@1521: } jjg@1521: jjg@1521: private String testClassOf(Method m) { jjg@1521: TestClass tc = m.getAnnotation(TestClass.class); jjg@1521: if (tc != null) { jjg@1521: return tc.value(); jjg@1521: } else { jjg@1521: return "Test"; jjg@1521: } jjg@1521: } jjg@1521: jjg@1521: private ClassFile compileAndReturn(String fullFile, String testClass) throws Exception { jjg@1521: File source = writeTestFile(fullFile); jjg@1521: File clazzFile = compileTestFile(source, testClass); jjg@1521: return ClassFile.read(clazzFile); jjg@1521: } jjg@1521: jjg@1521: protected File writeTestFile(String fullFile) throws IOException { jjg@1521: File f = new File("Test.java"); jjg@1521: PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f))); jjg@1521: out.println(fullFile); jjg@1521: out.close(); jjg@1521: return f; jjg@1521: } jjg@1521: jjg@1521: protected File compileTestFile(File f, String testClass) { jjg@1521: int rc = com.sun.tools.javac.Main.compile(new String[] { "-source", "1.8", "-g", f.getPath() }); jjg@1521: if (rc != 0) jjg@1521: throw new Error("compilation failed. rc=" + rc); jjg@1521: String path; jjg@1521: if (f.getParent() != null) { jjg@1521: path = f.getParent(); jjg@1521: } else { jjg@1521: path = ""; jjg@1521: } jjg@1521: jjg@1521: return new File(path + testClass + ".class"); jjg@1521: } jjg@1521: jjg@1521: private String wrap(String compact) { jjg@1521: StringBuilder sb = new StringBuilder(); jjg@1521: jjg@1521: // Automatically import java.util jjg@1521: sb.append("\nimport java.util.*;"); jjg@1521: sb.append("\nimport java.lang.annotation.*;"); jjg@1521: jjg@1521: sb.append("\n\n"); jjg@1521: boolean isSnippet = !(compact.startsWith("class") jjg@1521: || compact.contains(" class")) jjg@1521: && !compact.contains("interface") jjg@1521: && !compact.contains("enum"); jjg@1521: if (isSnippet) jjg@1521: sb.append("class Test {\n"); jjg@1521: jjg@1521: sb.append(compact); jjg@1521: sb.append("\n"); jjg@1521: jjg@1521: if (isSnippet) jjg@1521: sb.append("}\n\n"); jjg@1521: jjg@1521: if (isSnippet) { jjg@1521: // Have a few common nested types for testing jjg@1521: sb.append("class Outer { class Inner {} }"); jjg@1521: sb.append("class SOuter { static class SInner {} }"); jjg@1521: sb.append("class GOuter { class GInner {} }"); jjg@1521: } jjg@1521: jjg@1521: // create A ... F annotation declarations jjg@1521: sb.append("\n@interface A {}"); jjg@1521: sb.append("\n@interface B {}"); jjg@1521: sb.append("\n@interface C {}"); jjg@1521: sb.append("\n@interface D {}"); jjg@1521: sb.append("\n@interface E {}"); jjg@1521: sb.append("\n@interface F {}"); jjg@1521: jjg@1521: // create TA ... TF proper type annotations jjg@1521: sb.append("\n"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TA {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TB {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TC {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TD {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TE {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TF {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TG {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TH {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TI {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TJ {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TK {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TL {}"); jjg@1521: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TM {}"); jjg@1521: jjg@1521: // create RTA, RTAs, RTB, RTBs for repeating type annotations jjg@1521: sb.append("\n"); jjg@1521: sb.append("\n@Repeatable(RTAs.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTA {}"); jjg@1521: sb.append("\n@Repeatable(RTBs.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTB {}"); jjg@1521: darcy@1531: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTAs { RTA[] value(); }"); darcy@1531: sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTBs { RTB[] value(); }"); jjg@1521: jjg@1521: sb.append("\n@Target(value={ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})"); jjg@1521: sb.append("\n@interface Decl {}"); jjg@1521: jjg@1521: return sb.toString(); jjg@1521: } jjg@1521: jjg@1521: public static final int NOT_SET = -888; jjg@1521: jjg@1521: } jjg@1521: jjg@1521: @Retention(RetentionPolicy.RUNTIME) jjg@1521: @Target(ElementType.METHOD) jjg@1521: @interface TADescription { jjg@1521: String annotation(); jjg@1521: jjg@1521: TargetType type(); jjg@1521: int offset() default Driver.NOT_SET; jjg@1521: int[] lvarOffset() default { }; jjg@1521: int[] lvarLength() default { }; jjg@1521: int[] lvarIndex() default { }; jjg@1521: int boundIndex() default Driver.NOT_SET; jjg@1521: int paramIndex() default Driver.NOT_SET; jjg@1521: int typeIndex() default Driver.NOT_SET; jjg@1521: int exceptionIndex() default Driver.NOT_SET; jjg@1521: jjg@1521: int[] genericLocation() default {}; jjg@1521: } jjg@1521: jjg@1521: @Retention(RetentionPolicy.RUNTIME) jjg@1521: @Target(ElementType.METHOD) jjg@1521: @interface TADescriptions { jjg@1521: TADescription[] value() default {}; jjg@1521: } jjg@1521: jjg@1521: /** jjg@1521: * The name of the class that should be analyzed. jjg@1521: * Should only need to be provided when analyzing inner classes. jjg@1521: */ jjg@1521: @Retention(RetentionPolicy.RUNTIME) jjg@1521: @Target(ElementType.METHOD) jjg@1521: @interface TestClass { jjg@1521: String value() default "Test"; jjg@1521: }