1 /*
   2  * Copyright (c) 2012, 2018, 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 /*
  25  * @test
  26  * @bug 7150368 8003412 8000407
  27  * @summary javac should include basic ability to generate native headers
  28  * @modules jdk.compiler/com.sun.tools.javac.api
  29  *          jdk.compiler/com.sun.tools.javac.file
  30  */
  31 
  32 import java.io.File;
  33 import java.io.FileWriter;
  34 import java.io.IOException;
  35 import java.lang.annotation.Annotation;
  36 import java.lang.annotation.Retention;
  37 import java.lang.annotation.RetentionPolicy;
  38 import java.lang.reflect.InvocationTargetException;
  39 import java.lang.reflect.Method;
  40 import java.util.ArrayList;
  41 import java.util.Arrays;
  42 import java.util.HashSet;
  43 import java.util.List;
  44 import java.util.Set;
  45 
  46 import javax.tools.StandardJavaFileManager;
  47 import javax.tools.StandardLocation;
  48 
  49 import com.sun.source.util.JavacTask;
  50 import com.sun.tools.javac.api.JavacTool;
  51 
  52 public class NativeHeaderTest {
  53     public static void main(String... args) throws Exception {
  54         new NativeHeaderTest().run();
  55     }
  56 
  57     /** How to invoke javac. */
  58     enum RunKind {
  59         /** Use the command line entry point. */
  60         CMD,
  61         /** Use the JavaCompiler API. */
  62         API
  63     };
  64 
  65     /** Which classes for which to generate headers. */
  66     enum GenKind {
  67         /** Just classes with native methods or the marker annotation. */
  68         SIMPLE,
  69         /** All appropriate classes within the top level class. */
  70         FULL
  71     };
  72 
  73     // ---------- Test cases, invoked reflectively via run. ----------
  74 
  75     @Test
  76     void simpleTest(RunKind rk, GenKind gk) throws Exception {
  77         List<File> files = new ArrayList<File>();
  78         files.add(createFile("p/C.java",
  79                 "class C { native void m(); }"));
  80 
  81         Set<String> expect = createSet("C.h");
  82 
  83         test(rk, gk, files, expect);
  84     }
  85 
  86     @Test
  87     void nestedClassTest(RunKind rk, GenKind gk) throws Exception {
  88         List<File> files = new ArrayList<File>();
  89         files.add(createFile("p/C.java",
  90                 "class C { static class Inner { native void m(); } }"));
  91 
  92         Set<String> expect = createSet("C_Inner.h");
  93         if (gk == GenKind.FULL) expect.add("C.h");
  94 
  95         test(rk, gk, files, expect);
  96     }
  97 
  98     @Test
  99     void localClassTest(RunKind rk, GenKind gk) throws Exception {
 100         List<File> files = new ArrayList<File>();
 101         files.add(createFile("p/C.java",
 102                 "class C { native void m(); void m2() { class Local { } } }"));
 103 
 104         Set<String> expect = createSet("C.h");
 105 
 106         test(rk, gk, files, expect);
 107     }
 108 
 109     @Test
 110     void syntheticClassTest(RunKind rk, GenKind gk) throws Exception {
 111         List<File> files = new ArrayList<File>();
 112         files.add(createFile("p/C.java",
 113                 "class C {\n"
 114                 + "    private C() { }\n"
 115                 + "    class Inner extends C { native void m(); }\n"
 116                 + "}"));
 117 
 118         Set<String> expect = createSet("C_Inner.h");
 119         if (gk == GenKind.FULL) expect.add("C.h");
 120 
 121         test(rk, gk, files, expect);
 122 
 123         // double check the synthetic class was generated
 124         checkEqual("generatedClasses",
 125                 createSet("C.class", "C$Inner.class"),
 126                 createSet(classesDir.list()));
 127     }
 128 
 129     @Test
 130     void annoTest(RunKind rk, GenKind gk) throws Exception {
 131         List<File> files = new ArrayList<File>();
 132         files.add(createFile("p/C.java",
 133                 "class C { @java.lang.annotation.Native public static final int i = 1907; }"));
 134 
 135         Set<String> expect = createSet("C.h");
 136 
 137         test(rk, gk, files, expect);
 138     }
 139 
 140     @Test
 141     void annoNestedClassTest(RunKind rk, GenKind gk) throws Exception {
 142         List<File> files = new ArrayList<File>();
 143         files.add(createFile("p/C.java",
 144                 "class C { class Inner { @java.lang.annotation.Native public static final int i = 1907; } }"));
 145 
 146         Set<String> expect = createSet("C_Inner.h");
 147         if (gk == GenKind.FULL) expect.add("C.h");
 148 
 149         test(rk, gk, files, expect);
 150     }
 151 
 152     /**
 153      * The worker method for each test case.
 154      * Compile the files and verify that exactly the expected set of header files
 155      * is generated.
 156      */
 157     void test(RunKind rk, GenKind gk, List<File> files, Set<String> expect) throws Exception {
 158         List<String> args = new ArrayList<String>();
 159         if (gk == GenKind.FULL)
 160             args.add("-XDjavah:full");
 161 
 162         switch (rk) {
 163             case CMD:
 164                 args.add("-d");
 165                 args.add(classesDir.getPath());
 166                 args.add("-h");
 167                 args.add(headersDir.getPath());
 168                 for (File f: files)
 169                     args.add(f.getPath());
 170                 int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]));
 171                 if (rc != 0)
 172                     throw new Exception("compilation failed, rc=" + rc);
 173                 break;
 174 
 175             case API:
 176                 fm.setLocation(StandardLocation.SOURCE_PATH, Arrays.asList(srcDir));
 177                 fm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(classesDir));
 178                 fm.setLocation(StandardLocation.NATIVE_HEADER_OUTPUT, Arrays.asList(headersDir));
 179                 JavacTask task = javac.getTask(null, fm, null, args, null,
 180                         fm.getJavaFileObjectsFromFiles(files));
 181                 if (!task.call())
 182                     throw new Exception("compilation failed");
 183                 break;
 184         }
 185 
 186         Set<String> found = createSet(headersDir.list());
 187         checkEqual("header files", expect, found);
 188     }
 189 
 190     /** Marker annotation for test cases. */
 191     @Retention(RetentionPolicy.RUNTIME)
 192     @interface Test { }
 193 
 194     /** Combo test to run all test cases in all modes. */
 195     void run() throws Exception {
 196         javac = JavacTool.create();
 197         fm = javac.getStandardFileManager(null, null, null);
 198         try {
 199             for (RunKind rk: RunKind.values()) {
 200                 for (GenKind gk: GenKind.values()) {
 201                     for (Method m: getClass().getDeclaredMethods()) {
 202                         Annotation a = m.getAnnotation(Test.class);
 203                         if (a != null) {
 204                             init(rk, gk, m.getName());
 205                             try {
 206                                 m.invoke(this, new Object[] { rk, gk });
 207                             } catch (InvocationTargetException e) {
 208                                 Throwable cause = e.getCause();
 209                                 throw (cause instanceof Exception) ? ((Exception) cause) : e;
 210                             }
 211                             System.err.println();
 212                         }
 213                     }
 214                 }
 215             }
 216             System.err.println(testCount + " tests" + ((errorCount == 0) ? "" : ", " + errorCount + " errors"));
 217             if (errorCount > 0)
 218                 throw new Exception(errorCount + " errors found");
 219         } finally {
 220             fm.close();
 221         }
 222     }
 223 
 224     /**
 225      * Init directories for a test case.
 226      */
 227     void init(RunKind rk, GenKind gk, String name) throws IOException {
 228         System.err.println("Test " + rk + " " + gk + " " + name);
 229         testCount++;
 230 
 231         testDir = new File(rk.toString().toLowerCase() + "_" + gk.toString().toLowerCase() + "-" + name);
 232         srcDir = new File(testDir, "src");
 233         srcDir.mkdirs();
 234         classesDir = new File(testDir, "classes");
 235         classesDir.mkdirs();
 236         headersDir = new File(testDir, "headers");
 237         headersDir.mkdirs();
 238     }
 239 
 240     /** Create a source file with given body text. */
 241     File createFile(String path, final String body) throws IOException {
 242         File f = new File(srcDir, path);
 243         f.getParentFile().mkdirs();
 244         try (FileWriter out = new FileWriter(f)) {
 245             out.write(body);
 246         }
 247         return f;
 248     }
 249 
 250     /** Convenience method to create a set of items. */
 251     <T> Set<T> createSet(T... items) {
 252         return new HashSet<T>(Arrays.asList(items));
 253     }
 254 
 255     /** Convenience method to check two values are equal, and report an error if not. */
 256     <T> void checkEqual(String label, T expect, T found) {
 257         if ((found == null) ? (expect == null) : found.equals(expect))
 258             return;
 259         System.err.println("Error: mismatch");
 260         System.err.println("  expected: " + expect);
 261         System.err.println("     found: " + found);
 262         errorCount++;
 263     }
 264 
 265     // Shared across API test cases
 266     JavacTool javac;
 267     StandardJavaFileManager fm;
 268 
 269     // Directories set up by init
 270     File testDir;
 271     File srcDir;
 272     File classesDir;
 273     File headersDir;
 274 
 275     // Statistics
 276     int testCount;
 277     int errorCount;
 278 }
 279