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