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 classes to be analyzed.
 180      * @param classes the classes
 181      * @return this task object
 182      */
 183     public JavacTask classes(String... classes) {
 184         this.classes = Arrays.asList(classes);
 185         return this;
 186     }
 187 
 188     /**
 189      * Sets the files to be compiled or analyzed.
 190      * @param files the files
 191      * @return this task object
 192      */
 193     public JavacTask files(String... files) {
 194         this.files = Arrays.asList(files);
 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(Path... files) {
 204         this.files = Stream.of(files)
 205                 .map(Path::toString)
 206                 .collect(Collectors.toList());
 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(List<Path> files) {
 216         this.files = files.stream()
 217                 .map(Path::toString)
 218                 .collect(Collectors.toList());
 219         return this;
 220     }
 221 
 222     /**
 223      * Sets the sources to be compiled or analyzed.
 224      * Each source string is converted into an in-memory object that
 225      * can be passed directly to the compiler.
 226      * @param sources the sources
 227      * @return this task object
 228      */
 229     public JavacTask sources(String... sources) {
 230         fileObjects = Stream.of(sources)
 231                 .map(s -> new ToolBox.JavaSource(s))
 232                 .collect(Collectors.toList());
 233         return this;
 234     }
 235 
 236     /**
 237      * Sets the file manager to be used by this task.
 238      * @param fileManager the file manager
 239      * @return this task object
 240      */
 241     public JavacTask fileManager(JavaFileManager fileManager) {
 242         this.fileManager = fileManager;
 243         return this;
 244     }
 245 
 246     /**
 247      * {@inheritDoc}
 248      * @return the name "javac"
 249      */
 250     @Override
 251     public String name() {
 252         return "javac";
 253     }
 254 
 255     /**
 256      * Calls the compiler with the arguments as currently configured.
 257      * @return a Result object indicating the outcome of the compilation
 258      * and the content of any output written to stdout, stderr, or the
 259      * main stream by the compiler.
 260      * @throws TaskError if the outcome of the task is not as expected.
 261      */
 262     @Override
 263     public Task.Result run() {
 264         if (mode == Task.Mode.EXEC)
 265             return runExec();
 266 
 267         AbstractTask.WriterOutput direct = new AbstractTask.WriterOutput();
 268         // The following are to catch output to System.out and System.err,
 269         // in case these are used instead of the primary (main) stream
 270         AbstractTask.StreamOutput sysOut = new AbstractTask.StreamOutput(System.out, System::setOut);
 271         AbstractTask.StreamOutput sysErr = new AbstractTask.StreamOutput(System.err, System::setErr);
 272         int rc;
 273         Map<Task.OutputKind, String> outputMap = new HashMap<>();
 274         try {
 275             switch (mode == null ? Task.Mode.API : mode) {
 276                 case API:
 277                     rc = runAPI(direct.pw);
 278                     break;
 279                 case CMDLINE:
 280                     rc = runCommand(direct.pw);
 281                     break;
 282                 default:
 283                     throw new IllegalStateException();
 284             }
 285         } catch (IOException e) {
 286             toolBox.out.println("Exception occurred: " + e);
 287             rc = 99;
 288         } finally {
 289             outputMap.put(Task.OutputKind.STDOUT, sysOut.close());
 290             outputMap.put(Task.OutputKind.STDERR, sysErr.close());
 291             outputMap.put(Task.OutputKind.DIRECT, direct.close());
 292         }
 293         return checkExit(new Task.Result(toolBox, this, rc, outputMap));
 294     }
 295 
 296     private int runAPI(PrintWriter pw) throws IOException {
 297         try {
 298 //                if (compiler == null) {
 299                 // TODO: allow this to be set externally
 300 //                    compiler = ToolProvider.getSystemJavaCompiler();
 301                 compiler = JavacTool.create();
 302 //                }
 303 
 304             if (fileManager == null)
 305                 fileManager = internalFileManager = compiler.getStandardFileManager(null, null, null);
 306             if (outdir != null)
 307                 setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singletonList(outdir));
 308             if (classpath != null)
 309                 setLocationFromPaths(StandardLocation.CLASS_PATH, classpath);
 310             if (sourcepath != null)
 311                 setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath);
 312             List<String> allOpts = new ArrayList<>();
 313             if (options != null)
 314                 allOpts.addAll(options);
 315 
 316             Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects);
 317             JavaCompiler.CompilationTask task = compiler.getTask(pw,
 318                     fileManager,
 319                     null,  // diagnostic listener; should optionally collect diags
 320                     allOpts,
 321                     classes,
 322                     allFiles);
 323             return ((JavacTaskImpl) task).doCall().exitCode;
 324         } finally {
 325             if (internalFileManager != null)
 326                 internalFileManager.close();
 327         }
 328     }
 329 
 330     private void setLocationFromPaths(StandardLocation location, List<Path> files) throws IOException {
 331         if (!(fileManager instanceof StandardJavaFileManager))
 332             throw new IllegalStateException("not a StandardJavaFileManager");
 333         ((StandardJavaFileManager) fileManager).setLocationFromPaths(location, files);
 334     }
 335 
 336     private int runCommand(PrintWriter pw) {
 337         List<String> args = getAllArgs();
 338         String[] argsArray = args.toArray(new String[args.size()]);
 339         return com.sun.tools.javac.Main.compile(argsArray, pw);
 340     }
 341 
 342     private Task.Result runExec() {
 343         List<String> args = new ArrayList<>();
 344         Path javac = toolBox.getJDKTool("javac");
 345         args.add(javac.toString());
 346         if (includeStandardOptions) {
 347             args.addAll(toolBox.split(System.getProperty("test.tool.vm.opts"), " +"));
 348             args.addAll(toolBox.split(System.getProperty("test.compiler.opts"), " +"));
 349         }
 350         args.addAll(getAllArgs());
 351 
 352         String[] argsArray = args.toArray(new String[args.size()]);
 353         ProcessBuilder pb = getProcessBuilder();
 354         pb.command(argsArray);
 355         try {
 356             return runProcess(toolBox, this, pb.start());
 357         } catch (IOException | InterruptedException e) {
 358             throw new Error(e);
 359         }
 360     }
 361 
 362     private List<String> getAllArgs() {
 363         List<String> args = new ArrayList<>();
 364         if (options != null)
 365             args.addAll(options);
 366         if (outdir != null) {
 367             args.add("-d");
 368             args.add(outdir.toString());
 369         }
 370         if (classpath != null) {
 371             args.add("-classpath");
 372             args.add(toSearchPath(classpath));
 373         }
 374         if (sourcepath != null) {
 375             args.add("-sourcepath");
 376             args.add(toSearchPath(sourcepath));
 377         }
 378         if (classes != null)
 379             args.addAll(classes);
 380         if (files != null)
 381             args.addAll(files);
 382 
 383         return args;
 384     }
 385 
 386     private String toSearchPath(List<Path> files) {
 387         return files.stream()
 388             .map(Path::toString)
 389             .collect(Collectors.joining(File.pathSeparator));
 390     }
 391 
 392     private Iterable<? extends JavaFileObject> joinFiles(
 393             List<String> files, List<JavaFileObject> fileObjects) {
 394         if (files == null)
 395             return fileObjects;
 396         if (internalFileManager == null)
 397             internalFileManager = compiler.getStandardFileManager(null, null, null);
 398         Iterable<? extends JavaFileObject> filesAsFileObjects =
 399                 internalFileManager.getJavaFileObjectsFromStrings(files);
 400         if (fileObjects == null)
 401             return filesAsFileObjects;
 402         List<JavaFileObject> combinedList = new ArrayList<>();
 403         for (JavaFileObject o : filesAsFileObjects)
 404             combinedList.add(o);
 405         combinedList.addAll(fileObjects);
 406         return combinedList;
 407     }
 408 }