1 /*
   2  * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.BufferedWriter;
  25 import java.io.File;
  26 import java.io.FileWriter;
  27 import java.io.IOException;
  28 import java.io.PrintStream;
  29 import java.io.PrintWriter;
  30 import java.lang.annotation.*;
  31 import java.lang.reflect.*;
  32 import java.util.ArrayList;
  33 import java.util.Arrays;
  34 import java.util.Collections;
  35 import java.util.HashMap;
  36 import java.util.List;
  37 import java.util.Map;
  38 
  39 import com.sun.tools.classfile.ClassFile;
  40 import com.sun.tools.classfile.TypeAnnotation;
  41 import com.sun.tools.classfile.TypeAnnotation.TargetType;
  42 
  43 public class Driver {
  44 
  45     private static final PrintStream out = System.out;
  46 
  47     public static void main(String[] args) throws Exception {
  48         if (args.length == 0 || args.length > 1)
  49             throw new IllegalArgumentException("Usage: java Driver <test-name>");
  50         String name = args[0];
  51         Class<?> clazz = Class.forName(name);
  52         new Driver().runDriver(clazz.newInstance());
  53     }
  54 
  55     String[][] extraParamsCombinations = new String[][] {
  56         new String[] { },
  57         new String[] { "-g" },
  58     };
  59 
  60     protected void runDriver(Object object) throws Exception {
  61         int passed = 0, failed = 0;
  62         Class<?> clazz = object.getClass();
  63         out.println("Tests for " + clazz.getName());
  64 
  65         // Find methods
  66         for (Method method : clazz.getMethods()) {
  67             Map<String, TypeAnnotation.Position> expected = expectedOf(method);
  68             if (expected == null)
  69                 continue;
  70             if (method.getReturnType() != String.class)
  71                 throw new IllegalArgumentException("Test method needs to return a string: " + method);
  72             String testClass = testClassOf(method);
  73 
  74             for (String[] extraParams : extraParamsCombinations) {
  75                 try {
  76                     String compact = (String)method.invoke(object);
  77                     String fullFile = wrap(compact);
  78                     ClassFile cf = compileAndReturn(fullFile, testClass, extraParams);
  79                     List<TypeAnnotation> actual = ReferenceInfoUtil.extendedAnnotationsOf(cf);
  80                     ReferenceInfoUtil.compare(expected, actual, cf);
  81                     out.println("PASSED:  " + method.getName());
  82                     ++passed;
  83                 } catch (Throwable e) {
  84                     out.println("FAILED:  " + method.getName());
  85                     out.println("    " + e.toString());
  86                     ++failed;
  87                 }
  88             }
  89         }
  90 
  91         out.println();
  92         int total = passed + failed;
  93         out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED");
  94 
  95         out.flush();
  96 
  97         if (failed != 0)
  98             throw new RuntimeException(failed + " tests failed");
  99     }
 100 
 101     private Map<String, TypeAnnotation.Position> expectedOf(Method m) {
 102         TADescription ta = m.getAnnotation(TADescription.class);
 103         TADescriptions tas = m.getAnnotation(TADescriptions.class);
 104 
 105         if (ta == null && tas == null)
 106             return null;
 107 
 108         Map<String, TypeAnnotation.Position> result =
 109             new HashMap<String, TypeAnnotation.Position>();
 110 
 111         if (ta != null)
 112             result.putAll(expectedOf(ta));
 113 
 114         if (tas != null) {
 115             for (TADescription a : tas.value()) {
 116                 result.putAll(expectedOf(a));
 117             }
 118         }
 119 
 120         return result;
 121     }
 122 
 123     private Map<String, TypeAnnotation.Position> expectedOf(TADescription d) {
 124         String annoName = d.annotation();
 125 
 126         TypeAnnotation.Position p = new TypeAnnotation.Position();
 127         p.type = d.type();
 128         if (d.offset() != NOT_SET)
 129             p.offset = d.offset();
 130         if (d.lvarOffset().length != 0)
 131             p.lvarOffset = d.lvarOffset();
 132         if (d.lvarLength().length != 0)
 133             p.lvarLength = d.lvarLength();
 134         if (d.lvarIndex().length != 0)
 135             p.lvarIndex = d.lvarIndex();
 136         if (d.boundIndex() != NOT_SET)
 137             p.bound_index = d.boundIndex();
 138         if (d.paramIndex() != NOT_SET)
 139             p.parameter_index = d.paramIndex();
 140         if (d.typeIndex() != NOT_SET)
 141             p.type_index = d.typeIndex();
 142         if (d.exceptionIndex() != NOT_SET)
 143             p.exception_index = d.exceptionIndex();
 144         if (d.genericLocation().length != 0) {
 145             p.location = TypeAnnotation.Position.getTypePathFromBinary(wrapIntArray(d.genericLocation()));
 146         }
 147 
 148         return Collections.singletonMap(annoName, p);
 149     }
 150 
 151     private List<Integer> wrapIntArray(int[] ints) {
 152         List<Integer> list = new ArrayList<Integer>(ints.length);
 153         for (int i : ints)
 154             list.add(i);
 155         return list;
 156     }
 157 
 158     private String testClassOf(Method m) {
 159         TestClass tc = m.getAnnotation(TestClass.class);
 160         if (tc != null) {
 161             return tc.value();
 162         } else {
 163             return "Test";
 164         }
 165     }
 166 
 167     private ClassFile compileAndReturn(String fullFile, String testClass, String... extraParams) throws Exception {
 168         File source = writeTestFile(fullFile);
 169         File clazzFile = compileTestFile(source, testClass);
 170         return ClassFile.read(clazzFile);
 171     }
 172 
 173     protected File writeTestFile(String fullFile) throws IOException {
 174         File f = new File("Test.java");
 175         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
 176         out.println(fullFile);
 177         out.close();
 178         return f;
 179     }
 180 
 181     protected File compileTestFile(File f, String testClass, String... extraParams) {
 182         List<String> options = new ArrayList<>();
 183         options.addAll(Arrays.asList("-source", "1.8"));
 184         options.addAll(Arrays.asList(extraParams));
 185         options.add(f.getPath());
 186         int rc = com.sun.tools.javac.Main.compile(options.toArray(new String[options.size()]));
 187         if (rc != 0)
 188             throw new Error("compilation failed. rc=" + rc);
 189         String path;
 190         if (f.getParent() != null) {
 191             path = f.getParent();
 192         } else {
 193             path = "";
 194         }
 195 
 196         return new File(path + testClass + ".class");
 197     }
 198 
 199     private String wrap(String compact) {
 200         StringBuilder sb = new StringBuilder();
 201 
 202         // Automatically import java.util
 203         sb.append("\nimport java.util.*;");
 204         sb.append("\nimport java.lang.annotation.*;");
 205 
 206         sb.append("\n\n");
 207         boolean isSnippet = !(compact.startsWith("class")
 208                               || compact.contains(" class"))
 209                             && !compact.contains("interface")
 210                             && !compact.contains("enum");
 211         if (isSnippet)
 212             sb.append("class Test {\n");
 213 
 214         sb.append(compact);
 215         sb.append("\n");
 216 
 217         if (isSnippet)
 218             sb.append("}\n\n");
 219 
 220         if (isSnippet) {
 221             // Have a few common nested types for testing
 222             sb.append("class Outer { class Inner {} class Middle { class MInner {} } }");
 223             sb.append("class SOuter { static class SInner {} }");
 224             sb.append("class GOuter<X, Y> { class GInner<X, Y> {} }");
 225         }
 226 
 227         // create A ... F annotation declarations
 228         sb.append("\n@interface A {}");
 229         sb.append("\n@interface B {}");
 230         sb.append("\n@interface C {}");
 231         sb.append("\n@interface D {}");
 232         sb.append("\n@interface E {}");
 233         sb.append("\n@interface F {}");
 234 
 235         // create TA ... TF proper type annotations
 236         sb.append("\n");
 237         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TA {}");
 238         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TB {}");
 239         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TC {}");
 240         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TD {}");
 241         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TE {}");
 242         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TF {}");
 243         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TG {}");
 244         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TH {}");
 245         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TI {}");
 246         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TJ {}");
 247         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TK {}");
 248         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TL {}");
 249         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TM {}");
 250 
 251         // create RTA, RTAs, RTB, RTBs for repeating type annotations
 252         sb.append("\n");
 253         sb.append("\n@Repeatable(RTAs.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTA {}");
 254         sb.append("\n@Repeatable(RTBs.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTB {}");
 255 
 256         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTAs { RTA[] value(); }");
 257         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTBs { RTB[] value(); }");
 258 
 259         sb.append("\n@Target(value={ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})");
 260         sb.append("\n@interface Decl {}");
 261 
 262         return sb.toString();
 263     }
 264 
 265     public static final int NOT_SET = -888;
 266 
 267 }
 268 
 269 @Retention(RetentionPolicy.RUNTIME)
 270 @Target(ElementType.METHOD)
 271 @interface TADescription {
 272     String annotation();
 273 
 274     TargetType type();
 275     int offset() default Driver.NOT_SET;
 276     int[] lvarOffset() default { };
 277     int[] lvarLength() default { };
 278     int[] lvarIndex() default { };
 279     int boundIndex() default Driver.NOT_SET;
 280     int paramIndex() default Driver.NOT_SET;
 281     int typeIndex() default Driver.NOT_SET;
 282     int exceptionIndex() default Driver.NOT_SET;
 283 
 284     int[] genericLocation() default {};
 285 }
 286 
 287 @Retention(RetentionPolicy.RUNTIME)
 288 @Target(ElementType.METHOD)
 289 @interface TADescriptions {
 290     TADescription[] value() default {};
 291 }
 292 
 293 /**
 294  * The name of the class that should be analyzed.
 295  * Should only need to be provided when analyzing inner classes.
 296  */
 297 @Retention(RetentionPolicy.RUNTIME)
 298 @Target(ElementType.METHOD)
 299 @interface TestClass {
 300     String value() default "Test";
 301 }