1 /*
   2  * Copyright (c) 2013, 2017, 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 package toolbox;
  25 
  26 import java.io.File;
  27 import java.io.IOException;
  28 import java.io.PrintWriter;
  29 import java.nio.file.Path;
  30 import java.nio.file.Paths;
  31 import java.util.ArrayList;
  32 import java.util.Arrays;
  33 import java.util.Collections;
  34 import java.util.HashMap;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.stream.Collectors;
  38 import java.util.stream.Stream;
  39 import javax.tools.JavaCompiler;
  40 import javax.tools.JavaFileManager;
  41 import javax.tools.JavaFileObject;
  42 import javax.tools.StandardJavaFileManager;
  43 import javax.tools.StandardLocation;
  44 
  45 import com.sun.tools.javac.api.JavacTaskImpl;
  46 import com.sun.tools.javac.api.JavacTool;
  47 
  48 /**
  49  * A task to configure and run the Java compiler, javac.
  50  */
  51 public class JavacTask extends AbstractTask<JavacTask> {
  52     private boolean includeStandardOptions;
  53     private List<Path> classpath;
  54     private List<Path> sourcepath;
  55     private Path outdir;
  56     private List<String> options;
  57     private List<String> classes;
  58     private List<String> files;
  59     private List<JavaFileObject> fileObjects;
  60     private JavaFileManager fileManager;
  61 
  62     private JavaCompiler compiler;
  63     private StandardJavaFileManager internalFileManager;
  64 
  65     /**
  66      * Creates a task to execute {@code javac} using API mode.
  67      * @param toolBox the {@code ToolBox} to use
  68      */
  69     public JavacTask(ToolBox toolBox) {
  70         super(toolBox, Task.Mode.API);
  71     }
  72 
  73     /**
  74      * Creates a task to execute {@code javac} in a specified mode.
  75      * @param toolBox the {@code ToolBox} to use
  76      * @param mode the mode to be used
  77      */
  78     public JavacTask(ToolBox toolBox, Task.Mode mode) {
  79         super(toolBox, mode);
  80     }
  81 
  82     /**
  83      * Sets the classpath.
  84      * @param classpath the classpath
  85      * @return this task object
  86      */
  87     public JavacTask classpath(String classpath) {
  88         this.classpath = Stream.of(classpath.split(File.pathSeparator))
  89                 .filter(s -> !s.isEmpty())
  90                 .map(s -> Paths.get(s))
  91                 .collect(Collectors.toList());
  92         return this;
  93     }
  94 
  95     /**
  96      * Sets the classpath.
  97      * @param classpath the classpath
  98      * @return this task object
  99      */
 100     public JavacTask classpath(Path... classpath) {
 101         this.classpath = Arrays.asList(classpath);
 102         return this;
 103     }
 104 
 105     /**
 106      * Sets the classpath.
 107      * @param classpath the classpath
 108      * @return this task object
 109      */
 110     public JavacTask classpath(List<Path> classpath) {
 111         this.classpath = classpath;
 112         return this;
 113     }
 114 
 115     /**
 116      * Sets the sourcepath.
 117      * @param sourcepath the sourcepath
 118      * @return this task object
 119      */
 120     public JavacTask sourcepath(String sourcepath) {
 121         this.sourcepath = Stream.of(sourcepath.split(File.pathSeparator))
 122                 .filter(s -> !s.isEmpty())
 123                 .map(s -> Paths.get(s))
 124                 .collect(Collectors.toList());
 125         return this;
 126     }
 127 
 128     /**
 129      * Sets the sourcepath.
 130      * @param sourcepath the sourcepath
 131      * @return this task object
 132      */
 133     public JavacTask sourcepath(Path... sourcepath) {
 134         this.sourcepath = Arrays.asList(sourcepath);
 135         return this;
 136     }
 137 
 138     /**
 139      * Sets the sourcepath.
 140      * @param sourcepath the sourcepath
 141      * @return this task object
 142      */
 143     public JavacTask sourcepath(List<Path> sourcepath) {
 144         this.sourcepath = sourcepath;
 145         return this;
 146     }
 147 
 148     /**
 149      * Sets the output directory.
 150      * @param outdir the output directory
 151      * @return this task object
 152      */
 153     public JavacTask outdir(String outdir) {
 154         this.outdir = Paths.get(outdir);
 155         return this;
 156     }
 157 
 158     /**
 159      * Sets the output directory.
 160      * @param outdir the output directory
 161      * @return this task object
 162      */
 163     public JavacTask outdir(Path outdir) {
 164         this.outdir = outdir;
 165         return this;
 166     }
 167 
 168     /**
 169      * Sets the options.
 170      * @param options the options
 171      * @return this task object
 172      */
 173     public JavacTask options(String... options) {
 174         this.options = Arrays.asList(options);
 175         return this;
 176     }
 177 
 178     /**
 179      * Sets the options.
 180      * @param options the options
 181      * @return this task object
 182      */
 183     public JavacTask options(List<String> options) {
 184         this.options = options;
 185         return this;
 186     }
 187 
 188     /**
 189      * Sets the classes to be analyzed.
 190      * @param classes the classes
 191      * @return this task object
 192      */
 193     public JavacTask classes(String... classes) {
 194         this.classes = Arrays.asList(classes);
 195         return this;
 196     }
 197 
 198     /**
 199      * Sets the files to be compiled or analyzed.
 200      * @param files the files
 201      * @return this task object
 202      */
 203     public JavacTask files(String... files) {
 204         this.files = Arrays.asList(files);
 205         return this;
 206     }
 207 
 208     /**
 209      * Sets the files to be compiled or analyzed.
 210      * @param files the files
 211      * @return this task object
 212      */
 213     public JavacTask files(Path... files) {
 214         this.files = Stream.of(files)
 215                 .map(Path::toString)
 216                 .collect(Collectors.toList());
 217         return this;
 218     }
 219 
 220     /**
 221      * Sets the files to be compiled or analyzed.
 222      * @param files the files
 223      * @return this task object
 224      */
 225     public JavacTask files(List<Path> files) {
 226         this.files = files.stream()
 227                 .map(Path::toString)
 228                 .collect(Collectors.toList());
 229         return this;
 230     }
 231 
 232     /**
 233      * Sets the sources to be compiled or analyzed.
 234      * Each source string is converted into an in-memory object that
 235      * can be passed directly to the compiler.
 236      * @param sources the sources
 237      * @return this task object
 238      */
 239     public JavacTask sources(String... sources) {
 240         fileObjects = Stream.of(sources)
 241                 .map(s -> new ToolBox.JavaSource(s))
 242                 .collect(Collectors.toList());
 243         return this;
 244     }
 245 
 246     /**
 247      * Sets the file manager to be used by this task.
 248      * @param fileManager the file manager
 249      * @return this task object
 250      */
 251     public JavacTask fileManager(JavaFileManager fileManager) {
 252         this.fileManager = fileManager;
 253         return this;
 254     }
 255 
 256     /**
 257      * {@inheritDoc}
 258      * @return the name "javac"
 259      */
 260     @Override
 261     public String name() {
 262         return "javac";
 263     }
 264 
 265     /**
 266      * Calls the compiler with the arguments as currently configured.
 267      * @return a Result object indicating the outcome of the compilation
 268      * and the content of any output written to stdout, stderr, or the
 269      * main stream by the compiler.
 270      * @throws TaskError if the outcome of the task is not as expected.
 271      */
 272     @Override
 273     public Task.Result run() {
 274         if (mode == Task.Mode.EXEC)
 275             return runExec();
 276 
 277         AbstractTask.WriterOutput direct = new AbstractTask.WriterOutput();
 278         // The following are to catch output to System.out and System.err,
 279         // in case these are used instead of the primary (main) stream
 280         AbstractTask.StreamOutput sysOut = new AbstractTask.StreamOutput(System.out, System::setOut);
 281         AbstractTask.StreamOutput sysErr = new AbstractTask.StreamOutput(System.err, System::setErr);
 282         int rc;
 283         Map<Task.OutputKind, String> outputMap = new HashMap<>();
 284         try {
 285             switch (mode == null ? Task.Mode.API : mode) {
 286                 case API:
 287                     rc = runAPI(direct.pw);
 288                     break;
 289                 case CMDLINE:
 290                     if (fileManager != null) {
 291                         throw new IllegalStateException("file manager set in CMDLINE mode");
 292                     }
 293                     rc = runCommand(direct.pw);
 294                     break;
 295                 default:
 296                     throw new IllegalStateException("unknown mode " + mode);
 297             }
 298         } catch (IOException e) {
 299             toolBox.out.println("Exception occurred: " + e);
 300             rc = 99;
 301         } finally {
 302             outputMap.put(Task.OutputKind.STDOUT, sysOut.close());
 303             outputMap.put(Task.OutputKind.STDERR, sysErr.close());
 304             outputMap.put(Task.OutputKind.DIRECT, direct.close());
 305         }
 306         return checkExit(new Task.Result(toolBox, this, rc, outputMap));
 307     }
 308 
 309     private int runAPI(PrintWriter pw) throws IOException {
 310         try {
 311 //                if (compiler == null) {
 312                 // TODO: allow this to be set externally
 313 //                    compiler = ToolProvider.getSystemJavaCompiler();
 314                 compiler = JavacTool.create();
 315 //                }
 316 
 317             if (fileManager == null)
 318                 fileManager = internalFileManager = compiler.getStandardFileManager(null, null, null);
 319             if (outdir != null)
 320                 setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singletonList(outdir));
 321             if (classpath != null)
 322                 setLocationFromPaths(StandardLocation.CLASS_PATH, classpath);
 323             if (sourcepath != null)
 324                 setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath);
 325             List<String> allOpts = new ArrayList<>();
 326             if (options != null)
 327                 allOpts.addAll(options);
 328 
 329             Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects);
 330             JavaCompiler.CompilationTask task = compiler.getTask(pw,
 331                     fileManager,
 332                     null,  // diagnostic listener; should optionally collect diags
 333                     allOpts,
 334                     classes,
 335                     allFiles);
 336             return ((JavacTaskImpl) task).doCall().exitCode;
 337         } finally {
 338             if (internalFileManager != null)
 339                 internalFileManager.close();
 340         }
 341     }
 342 
 343     private void setLocationFromPaths(StandardLocation location, List<Path> files) throws IOException {
 344         if (!(fileManager instanceof StandardJavaFileManager))
 345             throw new IllegalStateException("not a StandardJavaFileManager");
 346         ((StandardJavaFileManager) fileManager).setLocationFromPaths(location, files);
 347     }
 348 
 349     private int runCommand(PrintWriter pw) {
 350         List<String> args = getAllArgs();
 351         String[] argsArray = args.toArray(new String[args.size()]);
 352         return com.sun.tools.javac.Main.compile(argsArray, pw);
 353     }
 354 
 355     private Task.Result runExec() {
 356         List<String> args = new ArrayList<>();
 357         Path javac = toolBox.getJDKTool("javac");
 358         args.add(javac.toString());
 359         if (includeStandardOptions) {
 360             args.addAll(toolBox.split(System.getProperty("test.tool.vm.opts"), " +"));
 361             args.addAll(toolBox.split(System.getProperty("test.compiler.opts"), " +"));
 362         }
 363         args.addAll(getAllArgs());
 364 
 365         String[] argsArray = args.toArray(new String[args.size()]);
 366         ProcessBuilder pb = getProcessBuilder();
 367         pb.command(argsArray);
 368         try {
 369             return runProcess(toolBox, this, pb.start());
 370         } catch (IOException | InterruptedException e) {
 371             throw new Error(e);
 372         }
 373     }
 374 
 375     private List<String> getAllArgs() {
 376         List<String> args = new ArrayList<>();
 377         if (options != null)
 378             args.addAll(options);
 379         if (outdir != null) {
 380             args.add("-d");
 381             args.add(outdir.toString());
 382         }
 383         if (classpath != null) {
 384             args.add("-classpath");
 385             args.add(toSearchPath(classpath));
 386         }
 387         if (sourcepath != null) {
 388             args.add("-sourcepath");
 389             args.add(toSearchPath(sourcepath));
 390         }
 391         if (classes != null)
 392             args.addAll(classes);
 393         if (files != null)
 394             args.addAll(files);
 395 
 396         return args;
 397     }
 398 
 399     private String toSearchPath(List<Path> files) {
 400         return files.stream()
 401             .map(Path::toString)
 402             .collect(Collectors.joining(File.pathSeparator));
 403     }
 404 
 405     private Iterable<? extends JavaFileObject> joinFiles(
 406             List<String> files, List<JavaFileObject> fileObjects) {
 407         if (files == null)
 408             return fileObjects;
 409         if (internalFileManager == null)
 410             internalFileManager = compiler.getStandardFileManager(null, null, null);
 411         Iterable<? extends JavaFileObject> filesAsFileObjects =
 412                 internalFileManager.getJavaFileObjectsFromStrings(files);
 413         if (fileObjects == null)
 414             return filesAsFileObjects;
 415         List<JavaFileObject> combinedList = new ArrayList<>();
 416         for (JavaFileObject o : filesAsFileObjects)
 417             combinedList.add(o);
 418         combinedList.addAll(fileObjects);
 419         return combinedList;
 420     }
 421 }