Wed, 19 Dec 2012 11:29:56 +0000
8004833: Integrate doclint support into javac
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.Iterator;
37 import java.util.LinkedHashSet;
38 import java.util.ServiceLoader;
39 import java.util.Set;
41 import javax.annotation.processing.Processor;
42 import javax.tools.JavaFileManager;
43 import javax.tools.JavaFileObject;
45 import com.sun.source.util.JavacTask;
46 import com.sun.source.util.Plugin;
47 import com.sun.tools.doclint.DocLint;
48 import com.sun.tools.javac.api.BasicJavacTask;
49 import com.sun.tools.javac.code.Source;
50 import com.sun.tools.javac.file.CacheFSInfo;
51 import com.sun.tools.javac.file.JavacFileManager;
52 import com.sun.tools.javac.jvm.Target;
53 import com.sun.tools.javac.processing.AnnotationProcessingError;
54 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
55 import com.sun.tools.javac.util.*;
56 import com.sun.tools.javac.util.Log.PrefixKind;
57 import com.sun.tools.javac.util.Log.WriterKind;
58 import static com.sun.tools.javac.main.Option.*;
60 /** This class provides a command line interface to the javac compiler.
61 *
62 * <p><b>This is NOT part of any supported API.
63 * If you write code that depends on this, you do so at your own risk.
64 * This code and its internal interfaces are subject to change or
65 * deletion without notice.</b>
66 */
67 public class Main {
69 /** The name of the compiler, for use in diagnostics.
70 */
71 String ownName;
73 /** The writer to use for diagnostic output.
74 */
75 PrintWriter out;
77 /** The log to use for diagnostic output.
78 */
79 Log log;
81 /**
82 * If true, certain errors will cause an exception, such as command line
83 * arg errors, or exceptions in user provided code.
84 */
85 boolean apiMode;
88 /** Result codes.
89 */
90 public enum Result {
91 OK(0), // Compilation completed with no errors.
92 ERROR(1), // Completed but reported errors.
93 CMDERR(2), // Bad command-line arguments
94 SYSERR(3), // System error or resource exhaustion.
95 ABNORMAL(4); // Compiler terminated abnormally
97 Result(int exitCode) {
98 this.exitCode = exitCode;
99 }
101 public boolean isOK() {
102 return (exitCode == 0);
103 }
105 public final int exitCode;
106 }
108 private Option[] recognizedOptions =
109 Option.getJavaCompilerOptions().toArray(new Option[0]);
111 private OptionHelper optionHelper = new OptionHelper() {
112 @Override
113 public String get(Option option) {
114 return options.get(option);
115 }
117 @Override
118 public void put(String name, String value) {
119 options.put(name, value);
120 }
122 @Override
123 public void remove(String name) {
124 options.remove(name);
125 }
127 @Override
128 public Log getLog() {
129 return log;
130 }
132 @Override
133 public String getOwnName() {
134 return ownName;
135 }
137 @Override
138 public void error(String key, Object... args) {
139 Main.this.error(key, args);
140 }
142 @Override
143 public void addFile(File f) {
144 filenames.add(f);
145 }
147 @Override
148 public void addClassName(String s) {
149 classnames.append(s);
150 }
152 };
154 /**
155 * Construct a compiler instance.
156 */
157 public Main(String name) {
158 this(name, new PrintWriter(System.err, true));
159 }
161 /**
162 * Construct a compiler instance.
163 */
164 public Main(String name, PrintWriter out) {
165 this.ownName = name;
166 this.out = out;
167 }
168 /** A table of all options that's passed to the JavaCompiler constructor. */
169 private Options options = null;
171 /** The list of source files to process
172 */
173 public Set<File> filenames = null; // XXX sb protected
175 /** List of class files names passed on the command line
176 */
177 public ListBuffer<String> classnames = null; // XXX sb protected
179 /** Report a usage error.
180 */
181 void error(String key, Object... args) {
182 if (apiMode) {
183 String msg = log.localize(PrefixKind.JAVAC, key, args);
184 throw new PropagatedException(new IllegalStateException(msg));
185 }
186 warning(key, args);
187 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName);
188 }
190 /** Report a warning.
191 */
192 void warning(String key, Object... args) {
193 log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args));
194 }
196 public Option getOption(String flag) {
197 for (Option option : recognizedOptions) {
198 if (option.matches(flag))
199 return option;
200 }
201 return null;
202 }
204 public void setOptions(Options options) {
205 if (options == null)
206 throw new NullPointerException();
207 this.options = options;
208 }
210 public void setAPIMode(boolean apiMode) {
211 this.apiMode = apiMode;
212 }
214 /** Process command line arguments: store all command line options
215 * in `options' table and return all source filenames.
216 * @param flags The array of command line arguments.
217 */
218 public Collection<File> processArgs(String[] flags) { // XXX sb protected
219 return processArgs(flags, null);
220 }
222 public Collection<File> processArgs(String[] flags, String[] classNames) { // XXX sb protected
223 int ac = 0;
224 while (ac < flags.length) {
225 String flag = flags[ac];
226 ac++;
228 Option option = null;
230 if (flag.length() > 0) {
231 // quick hack to speed up file processing:
232 // if the option does not begin with '-', there is no need to check
233 // most of the compiler options.
234 int firstOptionToCheck = flag.charAt(0) == '-' ? 0 : recognizedOptions.length-1;
235 for (int j=firstOptionToCheck; j<recognizedOptions.length; j++) {
236 if (recognizedOptions[j].matches(flag)) {
237 option = recognizedOptions[j];
238 break;
239 }
240 }
241 }
243 if (option == null) {
244 error("err.invalid.flag", flag);
245 return null;
246 }
248 if (option.hasArg()) {
249 if (ac == flags.length) {
250 error("err.req.arg", flag);
251 return null;
252 }
253 String operand = flags[ac];
254 ac++;
255 if (option.process(optionHelper, flag, operand))
256 return null;
257 } else {
258 if (option.process(optionHelper, flag))
259 return null;
260 }
261 }
263 if (this.classnames != null && classNames != null) {
264 this.classnames.addAll(Arrays.asList(classNames));
265 }
267 if (!checkDirectory(D))
268 return null;
269 if (!checkDirectory(S))
270 return null;
272 String sourceString = options.get(SOURCE);
273 Source source = (sourceString != null)
274 ? Source.lookup(sourceString)
275 : Source.DEFAULT;
276 String targetString = options.get(TARGET);
277 Target target = (targetString != null)
278 ? Target.lookup(targetString)
279 : Target.DEFAULT;
280 // We don't check source/target consistency for CLDC, as J2ME
281 // profiles are not aligned with J2SE targets; moreover, a
282 // single CLDC target may have many profiles. In addition,
283 // this is needed for the continued functioning of the JSR14
284 // prototype.
285 if (Character.isDigit(target.name.charAt(0))) {
286 if (target.compareTo(source.requiredTarget()) < 0) {
287 if (targetString != null) {
288 if (sourceString == null) {
289 warning("warn.target.default.source.conflict",
290 targetString,
291 source.requiredTarget().name);
292 } else {
293 warning("warn.source.target.conflict",
294 sourceString,
295 source.requiredTarget().name);
296 }
297 return null;
298 } else {
299 target = source.requiredTarget();
300 options.put("-target", target.name);
301 }
302 } else {
303 if (targetString == null && !source.allowGenerics()) {
304 target = Target.JDK1_4;
305 options.put("-target", target.name);
306 }
307 }
308 }
310 // handle this here so it works even if no other options given
311 String showClass = options.get("showClass");
312 if (showClass != null) {
313 if (showClass.equals("showClass")) // no value given for option
314 showClass = "com.sun.tools.javac.Main";
315 showClass(showClass);
316 }
318 options.notifyListeners();
320 return filenames;
321 }
322 // where
323 private boolean checkDirectory(Option option) {
324 String value = options.get(option);
325 if (value == null)
326 return true;
327 File file = new File(value);
328 if (!file.exists()) {
329 error("err.dir.not.found", value);
330 return false;
331 }
332 if (!file.isDirectory()) {
333 error("err.file.not.directory", value);
334 return false;
335 }
336 return true;
337 }
339 /** Programmatic interface for main function.
340 * @param args The command line parameters.
341 */
342 public Result compile(String[] args) {
343 Context context = new Context();
344 JavacFileManager.preRegister(context); // can't create it until Log has been set up
345 Result result = compile(args, context);
346 if (fileManager instanceof JavacFileManager) {
347 // A fresh context was created above, so jfm must be a JavacFileManager
348 ((JavacFileManager)fileManager).close();
349 }
350 return result;
351 }
353 public Result compile(String[] args, Context context) {
354 return compile(args, context, List.<JavaFileObject>nil(), null);
355 }
357 /** Programmatic interface for main function.
358 * @param args The command line parameters.
359 */
360 public Result compile(String[] args,
361 Context context,
362 List<JavaFileObject> fileObjects,
363 Iterable<? extends Processor> processors)
364 {
365 return compile(args, null, context, fileObjects, processors);
366 }
368 public Result compile(String[] args,
369 String[] classNames,
370 Context context,
371 List<JavaFileObject> fileObjects,
372 Iterable<? extends Processor> processors)
373 {
374 context.put(Log.outKey, out);
375 log = Log.instance(context);
377 if (options == null)
378 options = Options.instance(context); // creates a new one
380 filenames = new LinkedHashSet<File>();
381 classnames = new ListBuffer<String>();
382 JavaCompiler comp = null;
383 /*
384 * TODO: Logic below about what is an acceptable command line
385 * should be updated to take annotation processing semantics
386 * into account.
387 */
388 try {
389 if (args.length == 0
390 && (classNames == null || classNames.length == 0)
391 && fileObjects.isEmpty()) {
392 Option.HELP.process(optionHelper, "-help");
393 return Result.CMDERR;
394 }
396 Collection<File> files;
397 try {
398 files = processArgs(CommandLine.parse(args), classNames);
399 if (files == null) {
400 // null signals an error in options, abort
401 return Result.CMDERR;
402 } else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) {
403 // it is allowed to compile nothing if just asking for help or version info
404 if (options.isSet(HELP)
405 || options.isSet(X)
406 || options.isSet(VERSION)
407 || options.isSet(FULLVERSION))
408 return Result.OK;
409 if (JavaCompiler.explicitAnnotationProcessingRequested(options)) {
410 error("err.no.source.files.classes");
411 } else {
412 error("err.no.source.files");
413 }
414 return Result.CMDERR;
415 }
416 } catch (java.io.FileNotFoundException e) {
417 warning("err.file.not.found", e.getMessage());
418 return Result.SYSERR;
419 }
421 boolean forceStdOut = options.isSet("stdout");
422 if (forceStdOut) {
423 log.flush();
424 log.setWriters(new PrintWriter(System.out, true));
425 }
427 // allow System property in following line as a Mustang legacy
428 boolean batchMode = (options.isUnset("nonBatchMode")
429 && System.getProperty("nonBatchMode") == null);
430 if (batchMode)
431 CacheFSInfo.preRegister(context);
433 // FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate
434 // invoke any available plugins
435 String plugins = options.get(PLUGIN);
436 if (plugins != null) {
437 JavacProcessingEnvironment pEnv = JavacProcessingEnvironment.instance(context);
438 ClassLoader cl = pEnv.getProcessorClassLoader();
439 ServiceLoader<Plugin> sl = ServiceLoader.load(Plugin.class, cl);
440 Set<List<String>> pluginsToCall = new LinkedHashSet<List<String>>();
441 for (String plugin: plugins.split("\\x00")) {
442 pluginsToCall.add(List.from(plugin.split("\\s+")));
443 }
444 JavacTask task = null;
445 Iterator<Plugin> iter = sl.iterator();
446 while (iter.hasNext()) {
447 Plugin plugin = iter.next();
448 for (List<String> p: pluginsToCall) {
449 if (plugin.getName().equals(p.head)) {
450 pluginsToCall.remove(p);
451 try {
452 if (task == null)
453 task = JavacTask.instance(pEnv);
454 plugin.init(task, p.tail.toArray(new String[p.tail.size()]));
455 } catch (Throwable ex) {
456 if (apiMode)
457 throw new RuntimeException(ex);
458 pluginMessage(ex);
459 return Result.SYSERR;
460 }
462 }
463 }
464 }
465 for (List<String> p: pluginsToCall) {
466 log.printLines(PrefixKind.JAVAC, "msg.plugin.not.found", p.head);
467 }
468 }
470 comp = JavaCompiler.instance(context);
472 // FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate
473 String xdoclint = options.get(XDOCLINT);
474 String xdoclintCustom = options.get(XDOCLINT_CUSTOM);
475 if (xdoclint != null || xdoclintCustom != null) {
476 Set<String> doclintOpts = new LinkedHashSet<String>();
477 if (xdoclint != null)
478 doclintOpts.add(DocLint.XMSGS_OPTION);
479 if (xdoclintCustom != null) {
480 for (String s: xdoclintCustom.split("\\s+")) {
481 if (s.isEmpty())
482 continue;
483 doclintOpts.add(s.replace(XDOCLINT_CUSTOM.text, DocLint.XMSGS_CUSTOM_PREFIX));
484 }
485 }
486 if (!(doclintOpts.size() == 1
487 && doclintOpts.iterator().next().equals(DocLint.XMSGS_CUSTOM_PREFIX + "none"))) {
488 JavacTask t = BasicJavacTask.instance(context);
489 new DocLint().init(t, doclintOpts.toArray(new String[doclintOpts.size()]));
490 comp.keepComments = true;
491 }
492 }
494 fileManager = context.get(JavaFileManager.class);
496 if (!files.isEmpty()) {
497 // add filenames to fileObjects
498 comp = JavaCompiler.instance(context);
499 List<JavaFileObject> otherFiles = List.nil();
500 JavacFileManager dfm = (JavacFileManager)fileManager;
501 for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files))
502 otherFiles = otherFiles.prepend(fo);
503 for (JavaFileObject fo : otherFiles)
504 fileObjects = fileObjects.prepend(fo);
505 }
506 comp.compile(fileObjects,
507 classnames.toList(),
508 processors);
510 if (log.expectDiagKeys != null) {
511 if (log.expectDiagKeys.isEmpty()) {
512 log.printRawLines("all expected diagnostics found");
513 return Result.OK;
514 } else {
515 log.printRawLines("expected diagnostic keys not found: " + log.expectDiagKeys);
516 return Result.ERROR;
517 }
518 }
520 if (comp.errorCount() != 0)
521 return Result.ERROR;
522 } catch (IOException ex) {
523 ioMessage(ex);
524 return Result.SYSERR;
525 } catch (OutOfMemoryError ex) {
526 resourceMessage(ex);
527 return Result.SYSERR;
528 } catch (StackOverflowError ex) {
529 resourceMessage(ex);
530 return Result.SYSERR;
531 } catch (FatalError ex) {
532 feMessage(ex);
533 return Result.SYSERR;
534 } catch (AnnotationProcessingError ex) {
535 if (apiMode)
536 throw new RuntimeException(ex.getCause());
537 apMessage(ex);
538 return Result.SYSERR;
539 } catch (ClientCodeException ex) {
540 // as specified by javax.tools.JavaCompiler#getTask
541 // and javax.tools.JavaCompiler.CompilationTask#call
542 throw new RuntimeException(ex.getCause());
543 } catch (PropagatedException ex) {
544 throw ex.getCause();
545 } catch (Throwable ex) {
546 // Nasty. If we've already reported an error, compensate
547 // for buggy compiler error recovery by swallowing thrown
548 // exceptions.
549 if (comp == null || comp.errorCount() == 0 ||
550 options == null || options.isSet("dev"))
551 bugMessage(ex);
552 return Result.ABNORMAL;
553 } finally {
554 if (comp != null) {
555 try {
556 comp.close();
557 } catch (ClientCodeException ex) {
558 throw new RuntimeException(ex.getCause());
559 }
560 }
561 filenames = null;
562 options = null;
563 }
564 return Result.OK;
565 }
567 /** Print a message reporting an internal error.
568 */
569 void bugMessage(Throwable ex) {
570 log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version());
571 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
572 }
574 /** Print a message reporting a fatal error.
575 */
576 void feMessage(Throwable ex) {
577 log.printRawLines(ex.getMessage());
578 if (ex.getCause() != null && options.isSet("dev")) {
579 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
580 }
581 }
583 /** Print a message reporting an input/output error.
584 */
585 void ioMessage(Throwable ex) {
586 log.printLines(PrefixKind.JAVAC, "msg.io");
587 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
588 }
590 /** Print a message reporting an out-of-resources error.
591 */
592 void resourceMessage(Throwable ex) {
593 log.printLines(PrefixKind.JAVAC, "msg.resource");
594 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
595 }
597 /** Print a message reporting an uncaught exception from an
598 * annotation processor.
599 */
600 void apMessage(AnnotationProcessingError ex) {
601 log.printLines(PrefixKind.JAVAC, "msg.proc.annotation.uncaught.exception");
602 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
603 }
605 /** Print a message reporting an uncaught exception from an
606 * annotation processor.
607 */
608 void pluginMessage(Throwable ex) {
609 log.printLines(PrefixKind.JAVAC, "msg.plugin.uncaught.exception");
610 ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
611 }
613 /** Display the location and checksum of a class. */
614 void showClass(String className) {
615 PrintWriter pw = log.getWriter(WriterKind.NOTICE);
616 pw.println("javac: show class: " + className);
617 URL url = getClass().getResource('/' + className.replace('.', '/') + ".class");
618 if (url == null)
619 pw.println(" class not found");
620 else {
621 pw.println(" " + url);
622 try {
623 final String algorithm = "MD5";
624 byte[] digest;
625 MessageDigest md = MessageDigest.getInstance(algorithm);
626 DigestInputStream in = new DigestInputStream(url.openStream(), md);
627 try {
628 byte[] buf = new byte[8192];
629 int n;
630 do { n = in.read(buf); } while (n > 0);
631 digest = md.digest();
632 } finally {
633 in.close();
634 }
635 StringBuilder sb = new StringBuilder();
636 for (byte b: digest)
637 sb.append(String.format("%02x", b));
638 pw.println(" " + algorithm + " checksum: " + sb);
639 } catch (Exception e) {
640 pw.println(" cannot compute digest: " + e);
641 }
642 }
643 }
645 private JavaFileManager fileManager;
647 /* ************************************************************************
648 * Internationalization
649 *************************************************************************/
651 // /** Find a localized string in the resource bundle.
652 // * @param key The key for the localized string.
653 // */
654 // public static String getLocalizedString(String key, Object... args) { // FIXME sb private
655 // try {
656 // if (messages == null)
657 // messages = new JavacMessages(javacBundleName);
658 // return messages.getLocalizedString("javac." + key, args);
659 // }
660 // catch (MissingResourceException e) {
661 // throw new Error("Fatal Error: Resource for javac is missing", e);
662 // }
663 // }
664 //
665 // public static void useRawMessages(boolean enable) {
666 // if (enable) {
667 // messages = new JavacMessages(javacBundleName) {
668 // @Override
669 // public String getLocalizedString(String key, Object... args) {
670 // return key;
671 // }
672 // };
673 // } else {
674 // messages = new JavacMessages(javacBundleName);
675 // }
676 // }
678 public static final String javacBundleName =
679 "com.sun.tools.javac.resources.javac";
680 //
681 // private static JavacMessages messages;
682 }