1 /*
   2  * Copyright (c) 2013, 2016, 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                     rc = runCommand(direct.pw);
 291                     break;
 292                 default:
 293                     throw new IllegalStateException();
 294             }
 295         } catch (IOException e) {
 296             toolBox.out.println("Exception occurred: " + e);
 297             rc = 99;
 298         } finally {
 299             outputMap.put(Task.OutputKind.STDOUT, sysOut.close());
 300             outputMap.put(Task.OutputKind.STDERR, sysErr.close());
 301             outputMap.put(Task.OutputKind.DIRECT, direct.close());
 302         }
 303         return checkExit(new Task.Result(toolBox, this, rc, outputMap));
 304     }
 305 
 306     private int runAPI(PrintWriter pw) throws IOException {
 307         try {
 308 //                if (compiler == null) {
 309                 // TODO: allow this to be set externally
 310 //                    compiler = ToolProvider.getSystemJavaCompiler();
 311                 compiler = JavacTool.create();
 312 //                }
 313 
 314             if (fileManager == null)
 315                 fileManager = internalFileManager = compiler.getStandardFileManager(null, null, null);
 316             if (outdir != null)
 317                 setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singletonList(outdir));
 318             if (classpath != null)
 319                 setLocationFromPaths(StandardLocation.CLASS_PATH, classpath);
 320             if (sourcepath != null)
 321                 setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath);
 322             List<String> allOpts = new ArrayList<>();
 323             if (options != null)
 324                 allOpts.addAll(options);
 325 
 326             Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects);
 327             JavaCompiler.CompilationTask task = compiler.getTask(pw,
 328                     fileManager,
 329                     null,  // diagnostic listener; should optionally collect diags
 330                     allOpts,
 331                     classes,
 332                     allFiles);
 333             return ((JavacTaskImpl) task).doCall().exitCode;
 334         } finally {
 335             if (internalFileManager != null)
 336                 internalFileManager.close();
 337         }
 338     }
 339 
 340     private void setLocationFromPaths(StandardLocation location, List<Path> files) throws IOException {
 341         if (!(fileManager instanceof StandardJavaFileManager))
 342             throw new IllegalStateException("not a StandardJavaFileManager");
 343         ((StandardJavaFileManager) fileManager).setLocationFromPaths(location, files);
 344     }
 345 
 346     private int runCommand(PrintWriter pw) {
 347         List<String> args = getAllArgs();
 348         String[] argsArray = args.toArray(new String[args.size()]);
 349         return com.sun.tools.javac.Main.compile(argsArray, pw);
 350     }
 351 
 352     private Task.Result runExec() {
 353         List<String> args = new ArrayList<>();
 354         Path javac = toolBox.getJDKTool("javac");
 355         args.add(javac.toString());
 356         if (includeStandardOptions) {
 357             args.addAll(toolBox.split(System.getProperty("test.tool.vm.opts"), " +"));
 358             args.addAll(toolBox.split(System.getProperty("test.compiler.opts"), " +"));
 359         }
 360         args.addAll(getAllArgs());
 361 
 362         String[] argsArray = args.toArray(new String[args.size()]);
 363         ProcessBuilder pb = getProcessBuilder();
 364         pb.command(argsArray);
 365         try {
 366             return runProcess(toolBox, this, pb.start());
 367         } catch (IOException | InterruptedException e) {
 368             throw new Error(e);
 369         }
 370     }
 371 
 372     private List<String> getAllArgs() {
 373         List<String> args = new ArrayList<>();
 374         if (options != null)
 375             args.addAll(options);
 376         if (outdir != null) {
 377             args.add("-d");
 378             args.add(outdir.toString());
 379         }
 380         if (classpath != null) {
 381             args.add("-classpath");
 382             args.add(toSearchPath(classpath));
 383         }
 384         if (sourcepath != null) {
 385             args.add("-sourcepath");
 386             args.add(toSearchPath(sourcepath));
 387         }
 388         if (classes != null)
 389             args.addAll(classes);
 390         if (files != null)
 391             args.addAll(files);
 392 
 393         return args;
 394     }
 395 
 396     private String toSearchPath(List<Path> files) {
 397         return files.stream()
 398             .map(Path::toString)
 399             .collect(Collectors.joining(File.pathSeparator));
 400     }
 401 
 402     private Iterable<? extends JavaFileObject> joinFiles(
 403             List<String> files, List<JavaFileObject> fileObjects) {
 404         if (files == null)
 405             return fileObjects;
 406         if (internalFileManager == null)
 407             internalFileManager = compiler.getStandardFileManager(null, null, null);
 408         Iterable<? extends JavaFileObject> filesAsFileObjects =
 409                 internalFileManager.getJavaFileObjectsFromStrings(files);
 410         if (fileObjects == null)
 411             return filesAsFileObjects;
 412         List<JavaFileObject> combinedList = new ArrayList<>();
 413         for (JavaFileObject o : filesAsFileObjects)
 414             combinedList.add(o);
 415         combinedList.addAll(fileObjects);
 416         return combinedList;
 417     }
 418 }