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 }