1 /*
   2  * Copyright 2004-2008 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package com.sun.tools.apt.main;
  27 
  28 import java.io.File;
  29 import java.io.FileWriter;
  30 import java.io.IOException;
  31 import java.io.PrintWriter;
  32 import java.text.MessageFormat;
  33 import java.util.ResourceBundle;
  34 import java.util.MissingResourceException;
  35 import java.util.StringTokenizer;
  36 import java.util.Map;
  37 import java.util.HashMap;
  38 import java.util.Collections;
  39 
  40 import java.net.URLClassLoader;
  41 import java.net.URL;
  42 import java.net.MalformedURLException;
  43 
  44 import javax.tools.JavaFileManager;
  45 import javax.tools.StandardLocation;
  46 
  47 import com.sun.tools.javac.file.JavacFileManager;
  48 import com.sun.tools.javac.code.Source;
  49 import com.sun.tools.javac.code.Symbol;
  50 import com.sun.tools.javac.code.Type;
  51 import com.sun.tools.javac.jvm.Target;
  52 import com.sun.tools.javac.util.*;
  53 
  54 import com.sun.tools.apt.comp.AnnotationProcessingError;
  55 import com.sun.tools.apt.comp.UsageMessageNeededException;
  56 import com.sun.tools.apt.util.Bark;
  57 import com.sun.mirror.apt.AnnotationProcessorFactory;
  58 
  59 /** This class provides a commandline interface to the apt build-time
  60  *  tool.
  61  *
  62  *  <p><b>This is NOT part of any API supported by Sun Microsystems.
  63  *  If you write code that depends on this, you do so at your own
  64  *  risk.  This code and its internal interfaces are subject to change
  65  *  or deletion without notice.</b>
  66  */
  67 @SuppressWarnings("deprecation")
  68 public class Main {
  69 
  70     /** For testing: enter any options you want to be set implicitly
  71      *  here.
  72      */
  73     static String[] forcedOpts = {
  74         // Preserve parameter names from class files if the class was
  75         // compiled with debug enabled
  76         "-XDsave-parameter-names"
  77     };
  78 
  79     /** The name of the compiler, for use in diagnostics.
  80      */
  81     String ownName;
  82 
  83     /** The writer to use for diagnostic output.
  84      */
  85     PrintWriter out;
  86 
  87 
  88     /** Instantiated factory to use in lieu of discovery process.
  89      */
  90     AnnotationProcessorFactory providedFactory = null;
  91 
  92     /** Map representing original command-line arguments.
  93      */
  94     Map<String,String> origOptions = new HashMap<String, String>();
  95 
  96     /** Classloader to use for finding factories.
  97      */
  98     ClassLoader aptCL = null;
  99 
 100     /** Result codes.
 101      */
 102     static final int
 103         EXIT_OK = 0,        // Compilation completed with no errors.
 104         EXIT_ERROR = 1,     // Completed but reported errors.
 105         EXIT_CMDERR = 2,    // Bad command-line arguments
 106         EXIT_SYSERR = 3,    // System error or resource exhaustion.
 107         EXIT_ABNORMAL = 4;  // Compiler terminated abnormally
 108 
 109     /** This class represents an option recognized by the main program
 110      */
 111     private class Option {
 112         /** Whether or not the option is used only aptOnly.
 113          */
 114         boolean aptOnly = false;
 115 
 116         /** Option string.
 117          */
 118         String name;
 119 
 120         /** Documentation key for arguments.
 121          */
 122         String argsNameKey;
 123 
 124         /** Documentation key for description.
 125          */
 126         String descrKey;
 127 
 128         /** Suffix option (-foo=bar or -foo:bar)
 129          */
 130         boolean hasSuffix;
 131 
 132         Option(String name, String argsNameKey, String descrKey) {
 133             this.name = name;
 134             this.argsNameKey = argsNameKey;
 135             this.descrKey = descrKey;
 136             char lastChar = name.charAt(name.length()-1);
 137             hasSuffix = lastChar == ':' || lastChar == '=';
 138         }
 139         Option(String name, String descrKey) {
 140             this(name, null, descrKey);
 141         }
 142 
 143         public String toString() {
 144             return name;
 145         }
 146 
 147         /** Does this option take a (separate) operand?
 148          */
 149         boolean hasArg() {
 150             return argsNameKey != null && !hasSuffix;
 151         }
 152 
 153         /** Does argument string match option pattern?
 154          *  @param arg        The command line argument string.
 155          */
 156         boolean matches(String arg) {
 157             return hasSuffix ? arg.startsWith(name) : arg.equals(name);
 158         }
 159 
 160         /** For javac-only options, print nothing.
 161          */
 162         void help() {
 163         }
 164 
 165         String helpSynopsis() {
 166             return name +
 167                 (argsNameKey == null ? "" :
 168                  ((hasSuffix ? "" : " ") +
 169                   getLocalizedString(argsNameKey)));
 170         }
 171 
 172         /** Print a line of documentation describing this option, if non-standard.
 173          */
 174         void xhelp() {}
 175 
 176         /** Process the option (with arg). Return true if error detected.
 177          */
 178         boolean process(String option, String arg) {
 179             options.put(option, arg);
 180             return false;
 181         }
 182 
 183         /** Process the option (without arg). Return true if error detected.
 184          */
 185         boolean process(String option) {
 186             if (hasSuffix)
 187                 return process(name, option.substring(name.length()));
 188             else
 189                 return process(option, option);
 190         }
 191     };
 192 
 193     private class SharedOption extends Option {
 194         SharedOption(String name, String argsNameKey, String descrKey) {
 195             super(name, argsNameKey, descrKey);
 196         }
 197 
 198         SharedOption(String name, String descrKey) {
 199             super(name, descrKey);
 200         }
 201 
 202         void help() {
 203             String s = "  " + helpSynopsis();
 204             out.print(s);
 205             for (int j = s.length(); j < 29; j++) out.print(" ");
 206             Bark.printLines(out, getLocalizedString(descrKey));
 207         }
 208 
 209     }
 210 
 211     private class AptOption extends Option {
 212         AptOption(String name, String argsNameKey, String descrKey) {
 213             super(name, argsNameKey, descrKey);
 214             aptOnly = true;
 215         }
 216 
 217         AptOption(String name, String descrKey) {
 218             super(name, descrKey);
 219             aptOnly = true;
 220         }
 221 
 222         /** Print a line of documentation describing this option, if standard.
 223          */
 224         void help() {
 225             String s = "  " + helpSynopsis();
 226             out.print(s);
 227             for (int j = s.length(); j < 29; j++) out.print(" ");
 228             Bark.printLines(out, getLocalizedString(descrKey));
 229         }
 230 
 231     }
 232 
 233     /** A nonstandard or extended (-X) option
 234      */
 235     private class XOption extends Option {
 236         XOption(String name, String argsNameKey, String descrKey) {
 237             super(name, argsNameKey, descrKey);
 238         }
 239         XOption(String name, String descrKey) {
 240             this(name, null, descrKey);
 241         }
 242         void help() {}
 243         void xhelp() {}
 244     };
 245 
 246     /** A nonstandard or extended (-X) option
 247      */
 248     private class AptXOption extends Option {
 249         AptXOption(String name, String argsNameKey, String descrKey) {
 250             super(name, argsNameKey, descrKey);
 251             aptOnly = true;
 252         }
 253         AptXOption(String name, String descrKey) {
 254             this(name, null, descrKey);
 255         }
 256         void xhelp() {
 257             String s = "  " + helpSynopsis();
 258             out.print(s);
 259             for (int j = s.length(); j < 29; j++) out.print(" ");
 260             Log.printLines(out, getLocalizedString(descrKey));
 261         }
 262     };
 263 
 264     /** A hidden (implementor) option
 265      */
 266     private class HiddenOption extends Option {
 267         HiddenOption(String name) {
 268             super(name, null, null);
 269         }
 270         HiddenOption(String name, String argsNameKey) {
 271             super(name, argsNameKey, null);
 272         }
 273         void help() {}
 274         void xhelp() {}
 275     };
 276 
 277     private class AptHiddenOption extends HiddenOption {
 278         AptHiddenOption(String name) {
 279             super(name);
 280             aptOnly = true;
 281         }
 282         AptHiddenOption(String name, String argsNameKey) {
 283             super(name, argsNameKey);
 284             aptOnly = true;
 285         }
 286     }
 287 
 288     private Option[] recognizedOptions = {
 289         new Option("-g",                                        "opt.g"),
 290         new Option("-g:none",                                   "opt.g.none") {
 291             boolean process(String option) {
 292                 options.put("-g:", "none");
 293                 return false;
 294             }
 295         },
 296 
 297         new Option("-g:{lines,vars,source}",                    "opt.g.lines.vars.source") {
 298             boolean matches(String s) {
 299                 return s.startsWith("-g:");
 300             }
 301             boolean process(String option) {
 302                 String suboptions = option.substring(3);
 303                 options.put("-g:", suboptions);
 304                 // enter all the -g suboptions as "-g:suboption"
 305                 for (StringTokenizer t = new StringTokenizer(suboptions, ","); t.hasMoreTokens(); ) {
 306                     String tok = t.nextToken();
 307                     String opt = "-g:" + tok;
 308                     options.put(opt, opt);
 309                 }
 310                 return false;
 311             }
 312         },
 313 
 314         new XOption("-Xlint",                                   "opt.Xlint"),
 315         new XOption("-Xlint:{"
 316                     + "all,"
 317                     + "cast,deprecation,divzero,empty,unchecked,fallthrough,path,serial,finally,overrides,"
 318                     + "-cast,-deprecation,-divzero,-empty,-unchecked,-fallthrough,-path,-serial,-finally,-overrides,"
 319                     + "none}",
 320                                                                 "opt.Xlint.suboptlist") {
 321             boolean matches(String s) {
 322                 return s.startsWith("-Xlint:");
 323             }
 324             boolean process(String option) {
 325                 String suboptions = option.substring(7);
 326                 options.put("-Xlint:", suboptions);
 327                 // enter all the -Xlint suboptions as "-Xlint:suboption"
 328                 for (StringTokenizer t = new StringTokenizer(suboptions, ","); t.hasMoreTokens(); ) {
 329                     String tok = t.nextToken();
 330                     String opt = "-Xlint:" + tok;
 331                     options.put(opt, opt);
 332                 }
 333                 return false;
 334             }
 335         },
 336 
 337         new Option("-nowarn",                                   "opt.nowarn"),
 338         new Option("-verbose",                                  "opt.verbose"),
 339 
 340         // -deprecation is retained for command-line backward compatibility
 341         new Option("-deprecation",                              "opt.deprecation") {
 342                 boolean process(String option) {
 343                     options.put("-Xlint:deprecation", option);
 344                     return false;
 345                 }
 346             },
 347 
 348         new SharedOption("-classpath",     "opt.arg.path",      "opt.classpath"),
 349         new SharedOption("-cp",            "opt.arg.path",      "opt.classpath") {
 350             boolean process(String option, String arg) {
 351                 return super.process("-classpath", arg);
 352             }
 353         },
 354         new Option("-sourcepath",          "opt.arg.path",      "opt.sourcepath"),
 355         new Option("-bootclasspath",       "opt.arg.path",      "opt.bootclasspath") {
 356             boolean process(String option, String arg) {
 357                 options.remove("-Xbootclasspath/p:");
 358                 options.remove("-Xbootclasspath/a:");
 359                 return super.process(option, arg);
 360             }
 361         },
 362         new XOption("-Xbootclasspath/p:",  "opt.arg.path", "opt.Xbootclasspath.p"),
 363         new XOption("-Xbootclasspath/a:",  "opt.arg.path", "opt.Xbootclasspath.a"),
 364         new XOption("-Xbootclasspath:",    "opt.arg.path", "opt.bootclasspath") {
 365             boolean process(String option, String arg) {
 366                 options.remove("-Xbootclasspath/p:");
 367                 options.remove("-Xbootclasspath/a:");
 368                 return super.process("-bootclasspath", arg);
 369             }
 370         },
 371         new Option("-extdirs",             "opt.arg.dirs",      "opt.extdirs"),
 372         new XOption("-Djava.ext.dirs=",    "opt.arg.dirs",      "opt.extdirs") {
 373             boolean process(String option, String arg) {
 374                 return super.process("-extdirs", arg);
 375             }
 376         },
 377         new Option("-endorseddirs",        "opt.arg.dirs",      "opt.endorseddirs"),
 378         new XOption("-Djava.endorsed.dirs=","opt.arg.dirs",     "opt.endorseddirs") {
 379             boolean process(String option, String arg) {
 380                 return super.process("-endorseddirs", arg);
 381             }
 382         },
 383         new Option("-proc:{none, only}",                        "opt.proc.none.only") {
 384             public boolean matches(String s) {
 385                 return s.equals("-proc:none") || s.equals("-proc:only");
 386             }
 387         },
 388         new Option("-processor",        "opt.arg.class",        "opt.processor"),
 389         new Option("-processorpath",    "opt.arg.path",         "opt.processorpath"),
 390 
 391         new SharedOption("-d",          "opt.arg.path", "opt.d"),
 392         new SharedOption("-s",          "opt.arg.path", "opt.s"),
 393         new Option("-encoding",         "opt.arg.encoding",     "opt.encoding"),
 394         new SharedOption("-source",             "opt.arg.release",      "opt.source") {
 395             boolean process(String option, String operand) {
 396                 Source source = Source.lookup(operand);
 397                 if (source == null) {
 398                     error("err.invalid.source", operand);
 399                     return true;
 400                 } else if (source.compareTo(Source.JDK1_5) > 0) {
 401                     error("err.unsupported.source.version", operand);
 402                     return true;
 403                 }
 404                 return super.process(option, operand);
 405             }
 406         },
 407         new Option("-target",           "opt.arg.release",      "opt.target") {
 408             boolean process(String option, String operand) {
 409                 Target target = Target.lookup(operand);
 410                 if (target == null) {
 411                     error("err.invalid.target", operand);
 412                     return true;
 413                 } else if (target.compareTo(Target.JDK1_5) > 0) {
 414                     error("err.unsupported.target.version", operand);
 415                     return true;
 416                 }
 417                 return super.process(option, operand);
 418             }
 419         },
 420         new AptOption("-version",               "opt.version") {
 421             boolean process(String option) {
 422                 Bark.printLines(out, ownName + " " + JavaCompiler.version());
 423                 return super.process(option);
 424             }
 425         },
 426         new HiddenOption("-fullversion"),
 427         new AptOption("-help",                                  "opt.help") {
 428             boolean process(String option) {
 429                 Main.this.help();
 430                 return super.process(option);
 431             }
 432         },
 433         new SharedOption("-X",                                  "opt.X") {
 434             boolean process(String option) {
 435                 Main.this.xhelp();
 436                 return super.process(option);
 437             }
 438         },
 439 
 440         // This option exists only for the purpose of documenting itself.
 441         // It's actually implemented by the launcher.
 442         new AptOption("-J",             "opt.arg.flag",         "opt.J") {
 443             String helpSynopsis() {
 444                 hasSuffix = true;
 445                 return super.helpSynopsis();
 446             }
 447             boolean process(String option) {
 448                 throw new AssertionError
 449                     ("the -J flag should be caught by the launcher.");
 450             }
 451         },
 452 
 453 
 454         new SharedOption("-A",          "opt.proc.flag",        "opt.A") {
 455                 String helpSynopsis() {
 456                     hasSuffix = true;
 457                     return super.helpSynopsis();
 458                 }
 459 
 460                 boolean matches(String arg) {
 461                     return arg.startsWith("-A");
 462                 }
 463 
 464                 boolean hasArg() {
 465                     return false;
 466                 }
 467 
 468                 boolean process(String option) {
 469                     return process(option, option);
 470                 }
 471             },
 472 
 473         new AptOption("-nocompile",     "opt.nocompile"),
 474 
 475         new AptOption("-print",         "opt.print"),
 476 
 477         new AptOption("-factorypath", "opt.arg.path", "opt.factorypath"),
 478 
 479         new AptOption("-factory",     "opt.arg.class", "opt.factory"),
 480 
 481         new AptXOption("-XListAnnotationTypes", "opt.XListAnnotationTypes"),
 482 
 483         new AptXOption("-XListDeclarations",    "opt.XListDeclarations"),
 484 
 485         new AptXOption("-XPrintAptRounds",      "opt.XPrintAptRounds"),
 486 
 487         new AptXOption("-XPrintFactoryInfo",    "opt.XPrintFactoryInfo"),
 488 
 489         /*
 490          * Option to treat both classes and source files as
 491          * declarations that can be given on the command line and
 492          * processed as the result of an apt round.
 493          */
 494         new AptXOption("-XclassesAsDecls", "opt.XClassesAsDecls"),
 495 
 496         // new Option("-moreinfo",                                      "opt.moreinfo") {
 497         new HiddenOption("-moreinfo") {
 498             boolean process(String option) {
 499                 Type.moreInfo = true;
 500                 return super.process(option);
 501             }
 502         },
 503 
 504         // treat warnings as errors
 505         new HiddenOption("-Werror"),
 506 
 507         // use complex inference from context in the position of a method call argument
 508         new HiddenOption("-complexinference"),
 509 
 510         // prompt after each error
 511         // new Option("-prompt",                                        "opt.prompt"),
 512         new HiddenOption("-prompt"),
 513 
 514         // dump stack on error
 515         new HiddenOption("-doe"),
 516 
 517         // display warnings for generic unchecked and unsafe operations
 518         new HiddenOption("-warnunchecked") {
 519             boolean process(String option) {
 520                 options.put("-Xlint:unchecked", option);
 521                 return false;
 522             }
 523         },
 524 
 525         new HiddenOption("-Xswitchcheck") {
 526             boolean process(String option) {
 527                 options.put("-Xlint:switchcheck", option);
 528                 return false;
 529             }
 530         },
 531 
 532         // generate trace output for subtyping operations
 533         new HiddenOption("-debugsubtyping"),
 534 
 535         new XOption("-Xmaxerrs",        "opt.arg.number",       "opt.maxerrs"),
 536         new XOption("-Xmaxwarns",       "opt.arg.number",       "opt.maxwarns"),
 537         new XOption("-Xstdout",         "opt.arg.file",         "opt.Xstdout") {
 538             boolean process(String option, String arg) {
 539                 try {
 540                     out = new PrintWriter(new FileWriter(arg), true);
 541                 } catch (java.io.IOException e) {
 542                     error("err.error.writing.file", arg, e);
 543                     return true;
 544                 }
 545                 return super.process(option, arg);
 546             }
 547         },
 548 
 549         new XOption("-Xprint",                                  "opt.print"),
 550 
 551         new XOption("-XprintRounds",                            "opt.printRounds"),
 552 
 553         new XOption("-XprintProcessorInfo",                     "opt.printProcessorInfo"),
 554 
 555 
 556         /* -O is a no-op, accepted for backward compatibility. */
 557         new HiddenOption("-O"),
 558 
 559         /* -Xjcov produces tables to support the code coverage tool jcov. */
 560         new HiddenOption("-Xjcov"),
 561 
 562         /* This is a back door to the compiler's option table.
 563          * -Dx=y sets the option x to the value y.
 564          * -Dx sets the option x to the value x.
 565          */
 566         new HiddenOption("-XD") {
 567             String s;
 568             boolean matches(String s) {
 569                 this.s = s;
 570                 return s.startsWith(name);
 571             }
 572             boolean process(String option) {
 573                 s = s.substring(name.length());
 574                 int eq = s.indexOf('=');
 575                 String key = (eq < 0) ? s : s.substring(0, eq);
 576                 String value = (eq < 0) ? s : s.substring(eq+1);
 577                 options.put(key, value);
 578                 return false;
 579             }
 580         },
 581 
 582         new HiddenOption("sourcefile") {
 583                 String s;
 584                 boolean matches(String s) {
 585                     this.s = s;
 586                     return s.endsWith(".java") ||
 587                         (options.get("-XclassesAsDecls") != null);
 588                 }
 589                 boolean process(String option) {
 590                     if (s.endsWith(".java")) {
 591                         if (!sourceFileNames.contains(s))
 592                             sourceFileNames.add(s);
 593                     } else if (options.get("-XclassesAsDecls") != null) {
 594                         classFileNames.add(s);
 595                     }
 596                     return false;
 597                 }
 598             },
 599     };
 600 
 601     /**
 602      * Construct a compiler instance.
 603      */
 604     public Main(String name) {
 605         this(name, new PrintWriter(System.err, true));
 606     }
 607 
 608     /**
 609      * Construct a compiler instance.
 610      */
 611     public Main(String name, PrintWriter out) {
 612         this.ownName = name;
 613         this.out = out;
 614     }
 615 
 616     /** A table of all options that's passed to the JavaCompiler constructor.  */
 617     private Options options = null;
 618 
 619     /** The list of source files to process
 620      */
 621     java.util.List<String> sourceFileNames = new java.util.LinkedList<String>();
 622 
 623     /** The list of class files to process
 624      */
 625     java.util.List<String> classFileNames = new java.util.LinkedList<String>();
 626 
 627     /** List of top level names of generated source files from most recent apt round.
 628      */
 629     java.util.Set<String> genSourceFileNames = new java.util.LinkedHashSet<String>();
 630 
 631     /** List of names of generated class files from most recent apt round.
 632      */
 633     java.util.Set<String> genClassFileNames  = new java.util.LinkedHashSet<String>();
 634 
 635     /**
 636      * List of all the generated source file names across all apt rounds.
 637      */
 638     java.util.Set<String> aggregateGenSourceFileNames = new java.util.LinkedHashSet<String>();
 639 
 640     /**
 641      * List of all the generated class file names across all apt rounds.
 642      */
 643     java.util.Set<String> aggregateGenClassFileNames  = new java.util.LinkedHashSet<String>();
 644 
 645     /**
 646      * List of all the generated file names across all apt rounds.
 647      */
 648     java.util.Set<java.io.File> aggregateGenFiles = new java.util.LinkedHashSet<java.io.File>();
 649 
 650     /**
 651      * Set of all factories that have provided a processor on some apt round.
 652      */
 653     java.util.Set<Class<? extends AnnotationProcessorFactory> > productiveFactories  =
 654         new java.util.LinkedHashSet<Class<? extends AnnotationProcessorFactory> >();
 655 
 656 
 657 
 658     /** Print a string that explains usage.
 659      */
 660     void help() {
 661         Bark.printLines(out, getLocalizedString("msg.usage.header", ownName));
 662         for (int i=0; i < recognizedOptions.length; i++) {
 663             recognizedOptions[i].help();
 664         }
 665         Bark.printLines(out, getLocalizedString("msg.usage.footer"));
 666         out.println();
 667     }
 668 
 669     /** Print a string that explains usage for X options.
 670      */
 671     void xhelp() {
 672         for (int i=0; i<recognizedOptions.length; i++) {
 673             recognizedOptions[i].xhelp();
 674         }
 675         out.println();
 676         Bark.printLines(out, getLocalizedString("msg.usage.nonstandard.footer"));
 677     }
 678 
 679     /** Report a usage error.
 680      */
 681     void error(String key, Object... args) {
 682         warning(key, args);
 683         help();
 684     }
 685 
 686     /** Report a warning.
 687      */
 688     void warning(String key, Object... args) {
 689         Bark.printLines(out, ownName + ": "
 690                        + getLocalizedString(key, args));
 691     }
 692 
 693     /** Process command line arguments: store all command line options
 694      *  in `options' table and return all source filenames.
 695      *  @param args    The array of command line arguments.
 696      */
 697     protected java.util.List<String> processArgs(String[] flags) {
 698         int ac = 0;
 699         while (ac < flags.length) {
 700             String flag = flags[ac];
 701             ac++;
 702 
 703             int j;
 704             for (j=0; j < recognizedOptions.length; j++)
 705                 if (recognizedOptions[j].matches(flag))
 706                     break;
 707 
 708             if (j == recognizedOptions.length) {
 709                 error("err.invalid.flag", flag);
 710                 return null;
 711             }
 712 
 713             Option option = recognizedOptions[j];
 714             if (option.hasArg()) {
 715                 if (ac == flags.length) {
 716                     error("err.req.arg", flag);
 717                     return null;
 718                 }
 719                 String operand = flags[ac];
 720                 ac++;
 721                 if (option.process(flag, operand))
 722                     return null;
 723             } else {
 724                 if (option.process(flag))
 725                     return null;
 726             }
 727         }
 728 
 729         String sourceString = options.get("-source");
 730         Source source = (sourceString != null)
 731             ? Source.lookup(sourceString)
 732             : Source.JDK1_5; // JDK 5 is the latest supported source version
 733         String targetString = options.get("-target");
 734         Target target = (targetString != null)
 735             ? Target.lookup(targetString)
 736             : Target.JDK1_5; // JDK 5 is the latest supported source version
 737         // We don't check source/target consistency for CLDC, as J2ME
 738         // profiles are not aligned with J2SE targets; moreover, a
 739         // single CLDC target may have many profiles.  In addition,
 740         // this is needed for the continued functioning of the JSR14
 741         // prototype.
 742         if (Character.isDigit(target.name.charAt(0)) &&
 743             target.compareTo(source.requiredTarget()) < 0) {
 744             if (targetString != null) {
 745                 if (sourceString == null) {
 746                     warning("warn.target.default.source.conflict",
 747                             targetString,
 748                             source.requiredTarget().name);
 749                 } else {
 750                     warning("warn.source.target.conflict",
 751                             sourceString,
 752                             source.requiredTarget().name);
 753                 }
 754                 return null;
 755             } else {
 756                 options.put("-target", source.requiredTarget().name);
 757             }
 758         }
 759         return sourceFileNames;
 760     }
 761 
 762     /** Programmatic interface for main function.
 763      * @param args    The command line parameters.
 764      */
 765     public int compile(String[] args, AnnotationProcessorFactory factory) {
 766         int returnCode = 0;
 767         providedFactory = factory;
 768 
 769         Context context = new Context();
 770         JavacFileManager.preRegister(context);
 771         options = Options.instance(context);
 772         Bark bark;
 773 
 774         /*
 775          * Process the command line options to create the intial
 776          * options data.  This processing is at least partially reused
 777          * by any recursive apt calls.
 778          */
 779 
 780         // For testing: assume all arguments in forcedOpts are
 781         // prefixed to command line arguments.
 782         processArgs(forcedOpts);
 783 
 784         /*
 785          * A run of apt only gets passed the most recently generated
 786          * files; the initial run of apt gets passed the files from
 787          * the command line.
 788          */
 789 
 790         java.util.List<String> origFilenames;
 791         try {
 792             // assign args the result of parse to capture results of
 793             // '@file' expansion
 794             origFilenames = processArgs((args=CommandLine.parse(args)));
 795 
 796             if (options.get("suppress-tool-api-removal-message") == null) {
 797                 Bark.printLines(out, getLocalizedString("misc.Deprecation"));
 798             }
 799 
 800             if (origFilenames == null) {
 801                 return EXIT_CMDERR;
 802             } else if (origFilenames.size() == 0) {
 803                 // it is allowed to compile nothing if just asking for help
 804                 if (options.get("-help") != null ||
 805                     options.get("-X") != null)
 806                     return EXIT_OK;
 807             }
 808         } catch (java.io.FileNotFoundException e) {
 809             Bark.printLines(out, ownName + ": " +
 810                            getLocalizedString("err.file.not.found",
 811                                               e.getMessage()));
 812             return EXIT_SYSERR;
 813         } catch (IOException ex) {
 814             ioMessage(ex);
 815             return EXIT_SYSERR;
 816         } catch (OutOfMemoryError ex) {
 817             resourceMessage(ex);
 818             return EXIT_SYSERR;
 819         } catch (StackOverflowError ex) {
 820             resourceMessage(ex);
 821             return EXIT_SYSERR;
 822         } catch (FatalError ex) {
 823             feMessage(ex);
 824             return EXIT_SYSERR;
 825         } catch (sun.misc.ServiceConfigurationError sce) {
 826             sceMessage(sce);
 827             return EXIT_ABNORMAL;
 828         } catch (Throwable ex) {
 829             bugMessage(ex);
 830             return EXIT_ABNORMAL;
 831         }
 832 
 833 
 834         boolean firstRound = true;
 835         boolean needSourcePath = false;
 836         boolean needClassPath  = false;
 837         boolean classesAsDecls = options.get("-XclassesAsDecls") != null;
 838 
 839         /*
 840          * Create augumented classpath and sourcepath values.
 841          *
 842          * If any of the prior apt rounds generated any new source
 843          * files, the n'th apt round (and any javac invocation) has the
 844          * source destination path ("-s path") as the last element of
 845          * the "-sourcepath" to the n'th call.
 846          *
 847          * If any of the prior apt rounds generated any new class files,
 848          * the n'th apt round (and any javac invocation) has the class
 849          * destination path ("-d path") as the last element of the
 850          * "-classpath" to the n'th call.
 851          */
 852         String augmentedSourcePath = "";
 853         String augmentedClassPath = "";
 854         String baseClassPath = "";
 855 
 856         try {
 857             /*
 858              * Record original options for future annotation processor
 859              * invocations.
 860              */
 861             origOptions = new HashMap<String, String>(options.size());
 862             for(String s: options.keySet()) {
 863                 String value;
 864                 if (s.equals(value = options.get(s)))
 865                     origOptions.put(s, (String)null);
 866                 else
 867                     origOptions.put(s, value);
 868             }
 869             origOptions = Collections.unmodifiableMap(origOptions);
 870 
 871             JavacFileManager fm = (JavacFileManager) context.get(JavaFileManager.class);
 872             {
 873                 // Note: it might be necessary to check for an empty
 874                 // component ("") of the source path or class path
 875 
 876                 String sourceDest = options.get("-s");
 877                 if (fm.hasLocation(StandardLocation.SOURCE_PATH)) {
 878                     for(File f: fm.getLocation(StandardLocation.SOURCE_PATH))
 879                         augmentedSourcePath += (f + File.pathSeparator);
 880                     augmentedSourcePath += (sourceDest == null)?".":sourceDest;
 881                 } else {
 882                     augmentedSourcePath = ".";
 883 
 884                     if (sourceDest != null)
 885                         augmentedSourcePath += (File.pathSeparator + sourceDest);
 886                 }
 887 
 888                 String classDest = options.get("-d");
 889                 if (fm.hasLocation(StandardLocation.CLASS_PATH)) {
 890                     for(File f: fm.getLocation(StandardLocation.CLASS_PATH))
 891                         baseClassPath += (f + File.pathSeparator);
 892                     // put baseClassPath into map to handle any
 893                     // value needed for the classloader
 894                     options.put("-classpath", baseClassPath);
 895 
 896                     augmentedClassPath = baseClassPath + ((classDest == null)?".":classDest);
 897                 } else {
 898                     baseClassPath = ".";
 899                     if (classDest != null)
 900                         augmentedClassPath = baseClassPath + (File.pathSeparator + classDest);
 901                 }
 902                 assert options.get("-classpath") != null;
 903             }
 904 
 905             /*
 906              * Create base and augmented class loaders
 907              */
 908             ClassLoader augmentedAptCL = null;
 909             {
 910             /*
 911              * Use a url class loader to look for classes on the
 912              * user-specified class path. Prepend computed bootclass
 913              * path, which includes extdirs, to the URLClassLoader apt
 914              * uses.
 915              */
 916                 String aptclasspath = "";
 917                 String bcp = "";
 918                 Iterable<? extends File> bootclasspath = fm.getLocation(StandardLocation.PLATFORM_CLASS_PATH);
 919 
 920                 if (bootclasspath != null) {
 921                     for(File f: bootclasspath)
 922                         bcp += (f + File.pathSeparator);
 923                 }
 924 
 925                 // If the factory path is set, use that path
 926                 if (providedFactory == null)
 927                     aptclasspath = options.get("-factorypath");
 928                 if (aptclasspath == null)
 929                     aptclasspath = options.get("-classpath");
 930 
 931                 assert aptclasspath != null;
 932                 aptclasspath = (bcp + aptclasspath);
 933                 aptCL = new URLClassLoader(pathToURLs(aptclasspath));
 934 
 935                 if (providedFactory == null &&
 936                     options.get("-factorypath") != null) // same CL even if new class files written
 937                     augmentedAptCL = aptCL;
 938                 else {
 939                     // Create class loader in case new class files are
 940                     // written
 941                     augmentedAptCL = new URLClassLoader(pathToURLs(augmentedClassPath.
 942                                                                    substring(baseClassPath.length())),
 943                                                         aptCL);
 944                 }
 945             }
 946 
 947             int round = 0; // For -XPrintAptRounds
 948             do {
 949                 round++;
 950 
 951                 Context newContext = new Context();
 952                 Options newOptions = Options.instance(newContext); // creates a new context
 953                 newOptions.putAll(options);
 954 
 955                 // populate with old options... don't bother reparsing command line, etc.
 956 
 957                 // if genSource files, must add destination to source path
 958                 if (genSourceFileNames.size() > 0 && !firstRound) {
 959                     newOptions.put("-sourcepath", augmentedSourcePath);
 960                     needSourcePath = true;
 961                 }
 962                 aggregateGenSourceFileNames.addAll(genSourceFileNames);
 963                 sourceFileNames.addAll(genSourceFileNames);
 964                 genSourceFileNames.clear();
 965 
 966                 // Don't really need to track this; just have to add -d
 967                 // "foo" to class path if any class files are generated
 968                 if (genClassFileNames.size() > 0) {
 969                     newOptions.put("-classpath", augmentedClassPath);
 970                     aptCL = augmentedAptCL;
 971                     needClassPath = true;
 972                 }
 973                 aggregateGenClassFileNames.addAll(genClassFileNames);
 974                 classFileNames.addAll(genClassFileNames);
 975                 genClassFileNames.clear();
 976 
 977                 options = newOptions;
 978 
 979                 if (options.get("-XPrintAptRounds") != null) {
 980                     out.println("apt Round : " + round);
 981                     out.println("filenames: " + sourceFileNames);
 982                     if (classesAsDecls)
 983                         out.println("classnames: " + classFileNames);
 984                     out.println("options: " + options);
 985                 }
 986 
 987                 returnCode = compile(args, newContext);
 988                 firstRound = false;
 989 
 990                 // Check for reported errors before continuing
 991                 bark = Bark.instance(newContext);
 992             } while(((genSourceFileNames.size() != 0 ) ||
 993                      (classesAsDecls && genClassFileNames.size() != 0)) &&
 994                     bark.nerrors == 0);
 995         } catch (UsageMessageNeededException umne) {
 996             help();
 997             return EXIT_CMDERR; // will cause usage message to be printed
 998         }
 999 
1000         /*
1001          * Do not compile if a processor has reported an error or if
1002          * there are no source files to process.  A more sophisticated
1003          * test would also fail for syntax errors caught by javac.
1004          */
1005         if (options.get("-nocompile") == null &&
1006             options.get("-print")     == null &&
1007             bark.nerrors == 0 &&
1008             (origFilenames.size() > 0 || aggregateGenSourceFileNames.size() > 0 )) {
1009             /*
1010              * Need to create new argument string for calling javac:
1011              * 1. apt specific arguments (e.g. -factory) must be stripped out
1012              * 2. proper settings for sourcepath and classpath must be used
1013              * 3. generated class names must be added
1014              * 4. class file names as declarations must be removed
1015              */
1016 
1017             int newArgsLength = args.length +
1018                 (needSourcePath?1:0) +
1019                 (needClassPath?1:0) +
1020                 aggregateGenSourceFileNames.size();
1021 
1022             // Null out apt-specific options and don't copy over into
1023             // newArgs. This loop should be a lot faster; the options
1024             // array should be replaced with a better data structure
1025             // which includes a map from strings to options.
1026             //
1027             // If treating classes as declarations, must strip out
1028             // class names from the javac argument list
1029             argLoop:
1030             for(int i = 0; i < args.length; i++) {
1031                 int matchPosition = -1;
1032 
1033                 // "-A" by itself is recognized by apt but not javac
1034                 if (args[i] != null && args[i].equals("-A")) {
1035                     newArgsLength--;
1036                     args[i] = null;
1037                     continue argLoop;
1038                 } else {
1039                     optionLoop:
1040                     for(int j = 0; j < recognizedOptions.length; j++) {
1041                         if (args[i] != null && recognizedOptions[j].matches(args[i])) {
1042                             matchPosition = j;
1043                             break optionLoop;
1044                         }
1045                     }
1046 
1047                     if (matchPosition != -1) {
1048                         Option op = recognizedOptions[matchPosition];
1049                         if (op.aptOnly) {
1050                             newArgsLength--;
1051                             args[i] = null;
1052                             if (op.hasArg()) {
1053                                 newArgsLength--;
1054                                 args[i+1] = null;
1055                             }
1056                         } else {
1057                             if (op.hasArg()) { // skip over next string
1058                                 i++;
1059                                 continue argLoop;
1060                             }
1061 
1062                             if ((options.get("-XclassesAsDecls") != null) &&
1063                                 (matchPosition == (recognizedOptions.length-1)) ){
1064                                 // Remove class file names from
1065                                 // consideration by javac.
1066                                 if (! args[i].endsWith(".java")) {
1067                                     newArgsLength--;
1068                                     args[i] = null;
1069                                 }
1070                             }
1071                         }
1072                     }
1073                 }
1074             }
1075 
1076             String newArgs[] = new String[newArgsLength];
1077 
1078             int j = 0;
1079             for(int i=0; i < args.length; i++) {
1080                 if (args[i] != null)
1081                     newArgs[j++] = args[i];
1082             }
1083 
1084             if (needClassPath)
1085                 newArgs[j++] = "-XD-classpath=" + augmentedClassPath;
1086 
1087             if (needSourcePath) {
1088                 newArgs[j++] = "-XD-sourcepath=" + augmentedSourcePath;
1089 
1090                 for(String s: aggregateGenSourceFileNames)
1091                     newArgs[j++] = s;
1092             }
1093 
1094             returnCode = com.sun.tools.javac.Main.compile(newArgs);
1095         }
1096 
1097         return returnCode;
1098     }
1099 
1100     /** Programmatic interface for main function.
1101      * @param args    The command line parameters.
1102      */
1103     int compile(String[] args, Context context) {
1104         boolean assertionsEnabled = false;
1105         assert assertionsEnabled = true;
1106         if (!assertionsEnabled) {
1107             // Bark.printLines(out, "fatal error: assertions must be enabled when running javac");
1108             // return EXIT_ABNORMAL;
1109         }
1110         int exitCode = EXIT_OK;
1111 
1112         JavaCompiler comp = null;
1113         try {
1114             context.put(Bark.outKey, out);
1115 
1116             comp = JavaCompiler.instance(context);
1117             if (comp == null)
1118                 return EXIT_SYSERR;
1119 
1120             java.util.List<String> nameList = new java.util.LinkedList<String>();
1121             nameList.addAll(sourceFileNames);
1122             if (options.get("-XclassesAsDecls") != null)
1123                 nameList.addAll(classFileNames);
1124 
1125             List<Symbol.ClassSymbol> cs
1126                 = comp.compile(List.from(nameList.toArray(new String[0])),
1127                                origOptions,
1128                                aptCL,
1129                                providedFactory,
1130                                productiveFactories,
1131                                aggregateGenFiles);
1132 
1133             /*
1134              * If there aren't new source files, we shouldn't bother
1135              *  running javac if there were errors.
1136              *
1137              * If there are new files, we should try running javac in
1138              * case there were typing errors.
1139              *
1140              */
1141 
1142             if (comp.errorCount() != 0 ||
1143                 options.get("-Werror") != null && comp.warningCount() != 0)
1144                 return EXIT_ERROR;
1145         } catch (IOException ex) {
1146             ioMessage(ex);
1147             return EXIT_SYSERR;
1148         } catch (OutOfMemoryError ex) {
1149             resourceMessage(ex);
1150             return EXIT_SYSERR;
1151         } catch (StackOverflowError ex) {
1152             resourceMessage(ex);
1153             return EXIT_SYSERR;
1154         } catch (FatalError ex) {
1155             feMessage(ex);
1156             return EXIT_SYSERR;
1157         } catch (UsageMessageNeededException umne) {
1158             help();
1159             return EXIT_CMDERR; // will cause usage message to be printed
1160         } catch (AnnotationProcessingError ex) {
1161             apMessage(ex);
1162             return EXIT_ABNORMAL;
1163         } catch (sun.misc.ServiceConfigurationError sce) {
1164             sceMessage(sce);
1165             return EXIT_ABNORMAL;
1166         } catch (Throwable ex) {
1167             bugMessage(ex);
1168             return EXIT_ABNORMAL;
1169         } finally {
1170             if (comp != null) {
1171                 comp.close();
1172                 genSourceFileNames.addAll(comp.getSourceFileNames());
1173                 genClassFileNames.addAll(comp.getClassFileNames());
1174             }
1175             sourceFileNames = new java.util.LinkedList<String>();
1176             classFileNames  = new java.util.LinkedList<String>();
1177         }
1178         return exitCode;
1179     }
1180 
1181     /** Print a message reporting an internal error.
1182      */
1183     void bugMessage(Throwable ex) {
1184         Bark.printLines(out, getLocalizedString("msg.bug",
1185                                                JavaCompiler.version()));
1186         ex.printStackTrace(out);
1187     }
1188 
1189     /** Print a message reporting an fatal error.
1190      */
1191     void apMessage(AnnotationProcessingError ex) {
1192         Bark.printLines(out, getLocalizedString("misc.Problem"));
1193         ex.getCause().printStackTrace(out);
1194     }
1195 
1196     /** Print a message about sun.misc.Service problem.
1197      */
1198     void sceMessage(sun.misc.ServiceConfigurationError ex) {
1199         Bark.printLines(out, getLocalizedString("misc.SunMiscService"));
1200         ex.printStackTrace(out);
1201     }
1202 
1203     /** Print a message reporting an fatal error.
1204      */
1205     void feMessage(Throwable ex) {
1206         Bark.printLines(out, ex.toString());
1207     }
1208 
1209     /** Print a message reporting an input/output error.
1210      */
1211     void ioMessage(Throwable ex) {
1212         Bark.printLines(out, getLocalizedString("msg.io"));
1213         ex.printStackTrace(out);
1214     }
1215 
1216     /** Print a message reporting an out-of-resources error.
1217      */
1218     void resourceMessage(Throwable ex) {
1219         Bark.printLines(out, getLocalizedString("msg.resource"));
1220         ex.printStackTrace(out);
1221     }
1222 
1223     /* ************************************************************************
1224      * Internationalization
1225      *************************************************************************/
1226 
1227     /** Find a localized string in the resource bundle.
1228      *  @param key     The key for the localized string.
1229      */
1230     private static String getLocalizedString(String key, Object... args) {
1231         return getText(key, args);
1232     }
1233 
1234     private static final String javacRB =
1235         "com.sun.tools.javac.resources.javac";
1236 
1237     private static final String aptRB =
1238         "com.sun.tools.apt.resources.apt";
1239 
1240     private static ResourceBundle messageRBjavac;
1241     private static ResourceBundle messageRBapt;
1242 
1243     /** Initialize ResourceBundle.
1244      */
1245     private static void initResource() {
1246         try {
1247             messageRBapt   = ResourceBundle.getBundle(aptRB);
1248             messageRBjavac = ResourceBundle.getBundle(javacRB);
1249         } catch (MissingResourceException e) {
1250             Error x = new FatalError("Fatal Error: Resource for apt or javac is missing");
1251             x.initCause(e);
1252             throw x;
1253         }
1254     }
1255 
1256     /** Get and format message string from resource.
1257      */
1258     private static String getText(String key, Object... _args) {
1259         String[] args = new String[_args.length];
1260         for (int i=0; i<_args.length; i++) {
1261             args[i] = "" + _args[i];
1262         }
1263         if (messageRBapt == null || messageRBjavac == null )
1264             initResource();
1265         try {
1266             return MessageFormat.format(messageRBapt.getString("apt." + key),
1267                                         (Object[]) args);
1268         } catch (MissingResourceException e) {
1269             try {
1270                 return MessageFormat.format(messageRBjavac.getString("javac." + key),
1271                                             (Object[]) args);
1272             } catch (MissingResourceException f) {
1273                 String msg = "apt or javac message file broken: key={0} "
1274                     + "arguments={1}, {2}";
1275                 return MessageFormat.format(msg, (Object[]) args);
1276             }
1277         }
1278     }
1279 
1280     // Borrowed from DocletInvoker
1281     /**
1282      * Utility method for converting a search path string to an array
1283      * of directory and JAR file URLs.
1284      *
1285      * @param path the search path string
1286      * @return the resulting array of directory and JAR file URLs
1287      */
1288     static URL[] pathToURLs(String path) {
1289         StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
1290         URL[] urls = new URL[st.countTokens()];
1291         int count = 0;
1292         while (st.hasMoreTokens()) {
1293             URL url = fileToURL(new File(st.nextToken()));
1294             if (url != null) {
1295                 urls[count++] = url;
1296             }
1297         }
1298         if (urls.length != count) {
1299             URL[] tmp = new URL[count];
1300             System.arraycopy(urls, 0, tmp, 0, count);
1301             urls = tmp;
1302         }
1303         return urls;
1304     }
1305 
1306     /**
1307      * Returns the directory or JAR file URL corresponding to the specified
1308      * local file name.
1309      *
1310      * @param file the File object
1311      * @return the resulting directory or JAR file URL, or null if unknown
1312      */
1313     static URL fileToURL(File file) {
1314         String name;
1315         try {
1316             name = file.getCanonicalPath();
1317         } catch (IOException e) {
1318             name = file.getAbsolutePath();
1319         }
1320         name = name.replace(File.separatorChar, '/');
1321         if (!name.startsWith("/")) {
1322             name = "/" + name;
1323         }
1324         // If the file does not exist, then assume that it's a directory
1325         if (!file.isFile()) {
1326             name = name + "/";
1327         }
1328         try {
1329             return new URL("file", "", name);
1330         } catch (MalformedURLException e) {
1331             throw new IllegalArgumentException("file");
1332         }
1333     }
1334 }