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