Tue, 25 Sep 2012 13:11:05 -0700
7196464: upgrade JavaCompiler.shouldStopPolicy to accomodate policies in face of error and no error
Reviewed-by: mcimadamore
1 /*
2 * Copyright (c) 1999, 2012, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.tools.javac.main;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.net.URL;
32 import java.security.DigestInputStream;
33 import java.security.MessageDigest;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.LinkedHashSet;
37 import java.util.Set;
38 import javax.tools.JavaFileManager;
39 import javax.tools.JavaFileObject;
40 import javax.annotation.processing.Processor;
42 import com.sun.tools.javac.code.Source;
43 import com.sun.tools.javac.file.CacheFSInfo;
44 import com.sun.tools.javac.file.JavacFileManager;
45 import com.sun.tools.javac.jvm.Target;
46 import com.sun.tools.javac.util.*;
47 import com.sun.tools.javac.util.Log.WriterKind;
48 import com.sun.tools.javac.util.Log.PrefixKind;
49 import com.sun.tools.javac.processing.AnnotationProcessingError;
51 import static com.sun.tools.javac.main.Option.*;
53 /** This class provides a commandline interface to the GJC compiler.
54 *
55 * <p><b>This is NOT part of any supported API.
56 * If you write code that depends on this, you do so at your own risk.
57 * This code and its internal interfaces are subject to change or
58 * deletion without notice.</b>
59 */
60 public class Main {
62 /** The name of the compiler, for use in diagnostics.
63 */
64 String ownName;
66 /** The writer to use for diagnostic output.
67 */
68 PrintWriter out;
70 /** The log to use for diagnostic output.
71 */
72 Log log;
74 /**
75 * If true, certain errors will cause an exception, such as command line
76 * arg errors, or exceptions in user provided code.
77 */
78 boolean apiMode;
81 /** Result codes.
82 */
83 public enum Result {
84 OK(0), // Compilation completed with no errors.
85 ERROR(1), // Completed but reported errors.
86 CMDERR(2), // Bad command-line arguments
87 SYSERR(3), // System error or resource exhaustion.
88 ABNORMAL(4); // Compiler terminated abnormally
90 Result(int exitCode) {
91 this.exitCode = exitCode;
92 }
94 public boolean isOK() {
95 return (exitCode == 0);
96 }
98 public final int exitCode;
99 }
101 private Option[] recognizedOptions =
102 Option.getJavaCompilerOptions().toArray(new Option[0]);
104 private OptionHelper optionHelper = new OptionHelper() {
105 @Override
106 public String get(Option option) {
107 return options.get(option);
108 }
110 @Override
111 public void put(String name, String value) {
112 options.put(name, value);
113 }
115 @Override
116 public void remove(String name) {
117 options.remove(name);
118 }
120 @Override
121 public Log getLog() {
122 return log;
123 }
125 @Override
126 public String getOwnName() {
127 return ownName;
128 }
130 @Override
131 public void error(String key, Object... args) {
132 Main.this.error(key, args);
133 }
135 @Override
136 public void addFile(File f) {
137 filenames.add(f);
138 }
140 @Override
141 public void addClassName(String s) {
142 classnames.append(s);
143 }
145 };
147 /**
148 * Construct a compiler instance.
149 */
150 public Main(String name) {
151 this(name, new PrintWriter(System.err, true));
152 }
154 /**
155 * Construct a compiler instance.
156 */
157 public Main(String name, PrintWriter out) {
158 this.ownName = name;
159 this.out = out;
160 }
161 /** A table of all options that's passed to the JavaCompiler constructor. */
162 private Options options = null;
164 /** The list of source files to process
165 */
166 public Set<File> filenames = null; // XXX sb protected
168 /** List of class files names passed on the command line
169 */
170 public ListBuffer<String> classnames = null; // XXX sb protected
172 /** Report a usage error.
173 */
174 void error(String key, Object... args) {
175 if (apiMode) {
176 String msg = log.localize(PrefixKind.JAVAC, key, args);
177 throw new PropagatedException(new IllegalStateException(msg));
178 }
179 warning(key, args);
180 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName);
181 }
183 /** Report a warning.
184 */
185 void warning(String key, Object... args) {
186 log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args));
187 }
189 public Option getOption(String flag) {
190 for (Option option : recognizedOptions) {
191 if (option.matches(flag))
192 return option;
193 }
194 return null;
195 }
197 public void setOptions(Options options) {
198 if (options == null)
199 throw new NullPointerException();
200 this.options = options;
201 }
203 public void setAPIMode(boolean apiMode) {
204 this.apiMode = apiMode;
205 }
207 /** Process command line arguments: store all command line options
208 * in `options' table and return all source filenames.
209 * @param flags The array of command line arguments.
210 */
211 public Collection<File> processArgs(String[] flags) { // XXX sb protected
212 return processArgs(flags, null);
213 }
215 public Collection<File> processArgs(String[] flags, String[] classNames) { // XXX sb protected
216 int ac = 0;
217 while (ac < flags.length) {
218 String flag = flags[ac];
219 ac++;
221 Option option = null;
223 if (flag.length() > 0) {
224 // quick hack to speed up file processing:
225 // if the option does not begin with '-', there is no need to check
226 // most of the compiler options.
227 int firstOptionToCheck = flag.charAt(0) == '-' ? 0 : recognizedOptions.length-1;
228 for (int j=firstOptionToCheck; j<recognizedOptions.length; j++) {
229 if (recognizedOptions[j].matches(flag)) {
230 option = recognizedOptions[j];
231 break;
232 }
233 }
234 }
236 if (option == null) {
237 error("err.invalid.flag", flag);
238 return null;
239 }
241 if (option.hasArg()) {
242 if (ac == flags.length) {
243 error("err.req.arg", flag);
244 return null;
245 }
246 String operand = flags[ac];
247 ac++;
248 if (option.process(optionHelper, flag, operand))
249 return null;
250 } else {
251 if (option.process(optionHelper, flag))
252 return null;
253 }
254 }
256 if (this.classnames != null && classNames != null) {
257 this.classnames.addAll(Arrays.asList(classNames));
258 }
260 if (!checkDirectory(D))
261 return null;
262 if (!checkDirectory(S))
263 return null;
265 String sourceString = options.get(SOURCE);
266 Source source = (sourceString != null)
267 ? Source.lookup(sourceString)
268 : Source.DEFAULT;
269 String targetString = options.get(TARGET);
270 Target target = (targetString != null)
271 ? Target.lookup(targetString)
272 : Target.DEFAULT;
273 // We don't check source/target consistency for CLDC, as J2ME
274 // profiles are not aligned with J2SE targets; moreover, a
275 // single CLDC target may have many profiles. In addition,
276 // this is needed for the continued functioning of the JSR14
277 // prototype.
278 if (Character.isDigit(target.name.charAt(0))) {
279 if (target.compareTo(source.requiredTarget()) < 0) {
280 if (targetString != null) {
281 if (sourceString == null) {
282 warning("warn.target.default.source.conflict",
283 targetString,
284 source.requiredTarget().name);
285 } else {
286 warning("warn.source.target.conflict",
287 sourceString,
288 source.requiredTarget().name);
289 }
290 return null;
291 } else {
292 target = source.requiredTarget();
293 options.put("-target", target.name);
294 }
295 } else {
296 if (targetString == null && !source.allowGenerics()) {
297 target = Target.JDK1_4;
298 options.put("-target", target.name);
299 }
300 }
301 }
303 // handle this here so it works even if no other options given
304 String showClass = options.get("showClass");
305 if (showClass != null) {
306 if (showClass.equals("showClass")) // no value given for option
307 showClass = "com.sun.tools.javac.Main";
308 showClass(showClass);
309 }
311 options.notifyListeners();
313 return filenames;
314 }
315 // where
316 private boolean checkDirectory(Option option) {
317 String value = options.get(option);
318 if (value == null)
319 return true;
320 File file = new File(value);
321 if (!file.exists()) {
322 error("err.dir.not.found", value);
323 return false;
324 }
325 if (!file.isDirectory()) {
326 error("err.file.not.directory", value);
327 return false;
328 }
329 return true;
330 }
332 /** Programmatic interface for main function.
333 * @param args The command line parameters.
334 */
335 public Result compile(String[] args) {
336 Context context = new Context();
337 JavacFileManager.preRegister(context); // can't create it until Log has been set up
338 Result result = compile(args, context);
339 if (fileManager instanceof JavacFileManager) {
340 // A fresh context was created above, so jfm must be a JavacFileManager
341 ((JavacFileManager)fileManager).close();
342 }
343 return result;
344 }
346 public Result compile(String[] args, Context context) {
347 return compile(args, context, List.<JavaFileObject>nil(), null);
348 }
350 /** Programmatic interface for main function.
351 * @param args The command line parameters.
352 */
353 public Result compile(String[] args,
354 Context context,
355 List<JavaFileObject> fileObjects,
356 Iterable<? extends Processor> processors)
357 {
358 return compile(args, null, context, fileObjects, processors);
359 }
361 public Result compile(String[] args,
362 String[] classNames,
363 Context context,
364 List<JavaFileObject> fileObjects,
365 Iterable<? extends Processor> processors)
366 {
367 context.put(Log.outKey, out);
368 log = Log.instance(context);
370 if (options == null)
371 options = Options.instance(context); // creates a new one
373 filenames = new LinkedHashSet<File>();
374 classnames = new ListBuffer<String>();
375 JavaCompiler comp = null;
376 /*
377 * TODO: Logic below about what is an acceptable command line
378 * should be updated to take annotation processing semantics
379 * into account.
380 */
381 try {
382 if (args.length == 0
383 && (classNames == null || classNames.length == 0)
384 && fileObjects.isEmpty()) {
385 Option.HELP.process(optionHelper, "-help");
386 return Result.CMDERR;
387 }
389 Collection<File> files;
390 try {
391 files = processArgs(CommandLine.parse(args), classNames);
392 if (files == null) {
393 // null signals an error in options, abort
394 return Result.CMDERR;
395 } else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) {
396 // it is allowed to compile nothing if just asking for help or version info
397 if (options.isSet(HELP)
398 || options.isSet(X)
399 || options.isSet(VERSION)
400 || options.isSet(FULLVERSION))
401 return Result.OK;
402 if (JavaCompiler.explicitAnnotationProcessingRequested(options)) {
403 error("err.no.source.files.classes");
404 } else {
405 error("err.no.source.files");
406 }
407 return Result.CMDERR;
408 }
409 } catch (java.io.FileNotFoundException e) {
410 warning("err.file.not.found", e.getMessage());
411 return Result.SYSERR;
412 }
414 boolean forceStdOut = options.isSet("stdout");
415 if (forceStdOut) {
416 log.flush();
417 log.setWriters(new PrintWriter(System.out, true));
418 }
420 // allow System property in following line as a Mustang legacy
421 boolean batchMode = (options.isUnset("nonBatchMode")
422 && System.getProperty("nonBatchMode") == null);
423 if (batchMode)
424 CacheFSInfo.preRegister(context);
426 fileManager = context.get(JavaFileManager.class);
428 comp = JavaCompiler.instance(context);
429 if (comp == null) return Result.SYSERR;
431 if (!files.isEmpty()) {
432 // add filenames to fileObjects
433 comp = JavaCompiler.instance(context);
434 List<JavaFileObject> otherFiles = List.nil();
435 JavacFileManager dfm = (JavacFileManager)fileManager;
436 for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files))
437 otherFiles = otherFiles.prepend(fo);
438 for (JavaFileObject fo : otherFiles)
439 fileObjects = fileObjects.prepend(fo);
440 }
441 comp.compile(fileObjects,
442 classnames.toList(),
443 processors);
445 if (log.expectDiagKeys != null) {
446 if (log.expectDiagKeys.isEmpty()) {
447 log.printRawLines("all expected diagnostics found");
448 return Result.OK;
449 } else {
450 log.printRawLines("expected diagnostic keys not found: " + log.expectDiagKeys);
451 return Result.ERROR;
452 }
453 }
455 if (comp.errorCount() != 0)
456 return Result.ERROR;
457 } catch (IOException ex) {
458 ioMessage(ex);
459 return Result.SYSERR;
460 } catch (OutOfMemoryError ex) {
461 resourceMessage(ex);
462 return Result.SYSERR;
463 } catch (StackOverflowError ex) {
464 resourceMessage(ex);
465 return Result.SYSERR;
466 } catch (FatalError ex) {
467 feMessage(ex);
468 return Result.SYSERR;
469 } catch (AnnotationProcessingError ex) {
470 if (apiMode)
471 throw new RuntimeException(ex.getCause());
472 apMessage(ex);
473 return Result.SYSERR;
474 } catch (ClientCodeException ex) {
475 // as specified by javax.tools.JavaCompiler#getTask
476 // and javax.tools.JavaCompiler.CompilationTask#call
477 throw new RuntimeException(ex.getCause());
478 } catch (PropagatedException ex) {
479 throw ex.getCause();
480 } catch (Throwable ex) {
481 // Nasty. If we've already reported an error, compensate
482 // for buggy compiler error recovery by swallowing thrown
483 // exceptions.
484 if (comp == null || comp.errorCount() == 0 ||
485 options == null || options.isSet("dev"))
486 bugMessage(ex);
487 return Result.ABNORMAL;
488 } finally {
489 if (comp != null) {
490 try {
491 comp.close();
492 } catch (ClientCodeException ex) {
493 throw new RuntimeException(ex.getCause());
494 }
495 }
496 filenames = null;
497 options = null;
498 }
499 return Result.OK;
500 }
502 /** Print a message reporting an internal error.
503 */
504 void bugMessage(Throwable ex) {
505 log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version());
506 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
507 }
509 /** Print a message reporting a fatal error.
510 */
511 void feMessage(Throwable ex) {
512 log.printRawLines(ex.getMessage());
513 if (ex.getCause() != null && options.isSet("dev")) {
514 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
515 }
516 }
518 /** Print a message reporting an input/output error.
519 */
520 void ioMessage(Throwable ex) {
521 log.printLines(PrefixKind.JAVAC, "msg.io");
522 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
523 }
525 /** Print a message reporting an out-of-resources error.
526 */
527 void resourceMessage(Throwable ex) {
528 log.printLines(PrefixKind.JAVAC, "msg.resource");
529 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
530 }
532 /** Print a message reporting an uncaught exception from an
533 * annotation processor.
534 */
535 void apMessage(AnnotationProcessingError ex) {
536 log.printLines("msg.proc.annotation.uncaught.exception");
537 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
538 }
540 /** Display the location and checksum of a class. */
541 void showClass(String className) {
542 PrintWriter pw = log.getWriter(WriterKind.NOTICE);
543 pw.println("javac: show class: " + className);
544 URL url = getClass().getResource('/' + className.replace('.', '/') + ".class");
545 if (url == null)
546 pw.println(" class not found");
547 else {
548 pw.println(" " + url);
549 try {
550 final String algorithm = "MD5";
551 byte[] digest;
552 MessageDigest md = MessageDigest.getInstance(algorithm);
553 DigestInputStream in = new DigestInputStream(url.openStream(), md);
554 try {
555 byte[] buf = new byte[8192];
556 int n;
557 do { n = in.read(buf); } while (n > 0);
558 digest = md.digest();
559 } finally {
560 in.close();
561 }
562 StringBuilder sb = new StringBuilder();
563 for (byte b: digest)
564 sb.append(String.format("%02x", b));
565 pw.println(" " + algorithm + " checksum: " + sb);
566 } catch (Exception e) {
567 pw.println(" cannot compute digest: " + e);
568 }
569 }
570 }
572 private JavaFileManager fileManager;
574 /* ************************************************************************
575 * Internationalization
576 *************************************************************************/
578 // /** Find a localized string in the resource bundle.
579 // * @param key The key for the localized string.
580 // */
581 // public static String getLocalizedString(String key, Object... args) { // FIXME sb private
582 // try {
583 // if (messages == null)
584 // messages = new JavacMessages(javacBundleName);
585 // return messages.getLocalizedString("javac." + key, args);
586 // }
587 // catch (MissingResourceException e) {
588 // throw new Error("Fatal Error: Resource for javac is missing", e);
589 // }
590 // }
591 //
592 // public static void useRawMessages(boolean enable) {
593 // if (enable) {
594 // messages = new JavacMessages(javacBundleName) {
595 // @Override
596 // public String getLocalizedString(String key, Object... args) {
597 // return key;
598 // }
599 // };
600 // } else {
601 // messages = new JavacMessages(javacBundleName);
602 // }
603 // }
605 public static final String javacBundleName =
606 "com.sun.tools.javac.resources.javac";
607 //
608 // private static JavacMessages messages;
609 }