001    /*
002     * Copyright 2004-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.apt.main;
027    
028    import java.io.File;
029    import java.io.FileOutputStream;
030    import java.io.FileWriter;
031    import java.io.IOException;
032    import java.io.PrintWriter;
033    import java.text.MessageFormat;
034    import java.util.ResourceBundle;
035    import java.util.MissingResourceException;
036    import java.util.StringTokenizer;
037    import java.util.Map;
038    import java.util.HashMap;
039    import java.util.Collections;
040    import java.util.Collection;
041    
042    import java.net.URLClassLoader;
043    import java.net.URL;
044    import java.io.File;
045    import java.net.MalformedURLException;
046    
047    import com.sun.tools.javac.file.Paths;
048    import com.sun.tools.javac.code.Source;
049    import com.sun.tools.javac.code.Symbol;
050    import com.sun.tools.javac.code.Type;
051    import com.sun.tools.javac.jvm.Target;
052    import com.sun.tools.javac.util.*;
053    
054    import com.sun.tools.apt.comp.AnnotationProcessingError;
055    import com.sun.tools.apt.comp.UsageMessageNeededException;
056    import com.sun.tools.apt.util.Bark;
057    import com.sun.mirror.apt.AnnotationProcessorFactory;
058    
059    /** This class provides a commandline interface to the apt build-time
060     *  tool.
061     *
062     *  <p><b>This is NOT part of any API supported by Sun Microsystems.
063     *  If you write code that depends on this, you do so at your own
064     *  risk.  This code and its internal interfaces are subject to change
065     *  or deletion without notice.</b>
066     */
067    public class Main {
068    
069        /** For testing: enter any options you want to be set implicitly
070         *  here.
071         */
072        static String[] forcedOpts = {
073            // Preserve parameter names from class files if the class was
074            // compiled with debug enabled
075            "-XDsave-parameter-names"
076        };
077    
078        /** The name of the compiler, for use in diagnostics.
079         */
080        String ownName;
081    
082        /** The writer to use for diagnostic output.
083         */
084        PrintWriter out;
085    
086    
087        /** Instantiated factory to use in lieu of discovery process.
088         */
089        AnnotationProcessorFactory providedFactory = null;
090    
091        /** Map representing original command-line arguments.
092         */
093        Map<String,String> origOptions = new HashMap<String, String>();
094    
095        /** Classloader to use for finding factories.
096         */
097        ClassLoader aptCL = null;
098    
099        /** 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            options = Options.instance(context);
770            Bark bark;
771    
772            /*
773             * Process the command line options to create the intial
774             * options data.  This processing is at least partially reused
775             * by any recursive apt calls.
776             */
777    
778            // For testing: assume all arguments in forcedOpts are
779            // prefixed to command line arguments.
780            processArgs(forcedOpts);
781    
782    
783            /*
784             * A run of apt only gets passed the most recently generated
785             * files; the initial run of apt gets passed the files from
786             * the command line.
787             */
788    
789            java.util.List<String> origFilenames;
790            try {
791                // assign args the result of parse to capture results of
792                // '@file' expansion
793                origFilenames = processArgs((args=CommandLine.parse(args)));
794                if (origFilenames == null) {
795                    return EXIT_CMDERR;
796                } else if (origFilenames.size() == 0) {
797                    // it is allowed to compile nothing if just asking for help
798                    if (options.get("-help") != null ||
799                        options.get("-X") != null)
800                        return EXIT_OK;
801                }
802            } catch (java.io.FileNotFoundException e) {
803                Bark.printLines(out, ownName + ": " +
804                               getLocalizedString("err.file.not.found",
805                                                  e.getMessage()));
806                return EXIT_SYSERR;
807            } catch (IOException ex) {
808                ioMessage(ex);
809                return EXIT_SYSERR;
810            } catch (OutOfMemoryError ex) {
811                resourceMessage(ex);
812                return EXIT_SYSERR;
813            } catch (StackOverflowError ex) {
814                resourceMessage(ex);
815                return EXIT_SYSERR;
816            } catch (FatalError ex) {
817                feMessage(ex);
818                return EXIT_SYSERR;
819            } catch (sun.misc.ServiceConfigurationError sce) {
820                sceMessage(sce);
821                return EXIT_ABNORMAL;
822            } catch (Throwable ex) {
823                bugMessage(ex);
824                return EXIT_ABNORMAL;
825            }
826    
827    
828            boolean firstRound = true;
829            boolean needSourcePath = false;
830            boolean needClassPath  = false;
831            boolean classesAsDecls = options.get("-XclassesAsDecls") != null;
832    
833            /*
834             * Create augumented classpath and sourcepath values.
835             *
836             * If any of the prior apt rounds generated any new source
837             * files, the n'th apt round (and any javac invocation) has the
838             * source destination path ("-s path") as the last element of
839             * the "-sourcepath" to the n'th call.
840             *
841             * If any of the prior apt rounds generated any new class files,
842             * the n'th apt round (and any javac invocation) has the class
843             * destination path ("-d path") as the last element of the
844             * "-classpath" to the n'th call.
845             */
846            String augmentedSourcePath = "";
847            String augmentedClassPath = "";
848            String baseClassPath = "";
849    
850            try {
851                /*
852                 * Record original options for future annotation processor
853                 * invocations.
854                 */
855                origOptions = new HashMap<String, String>(options.size());
856                for(String s: options.keySet()) {
857                    String value;
858                    if (s.equals(value = options.get(s)))
859                        origOptions.put(s, (String)null);
860                    else
861                        origOptions.put(s, value);
862                }
863                origOptions = Collections.unmodifiableMap(origOptions);
864    
865                {
866                    // Note: it might be necessary to check for an empty
867                    // component ("") of the source path or class path
868                    Paths paths = Paths.instance(context);
869    
870                    String sourceDest = options.get("-s");
871                    if (paths.sourcePath() != null) {
872                        for(File f: paths.sourcePath())
873                            augmentedSourcePath += (f + File.pathSeparator);
874                        augmentedSourcePath += (sourceDest == null)?".":sourceDest;
875                    } else {
876                        augmentedSourcePath = ".";
877    
878                        if (sourceDest != null)
879                            augmentedSourcePath += (File.pathSeparator + sourceDest);
880                    }
881    
882                    String classDest = options.get("-d");
883                    if (paths.userClassPath() != null) {
884                        for(File f: paths.userClassPath())
885                            baseClassPath += (f + File.pathSeparator);
886                        // put baseClassPath into map to handle any
887                        // value needed for the classloader
888                        options.put("-classpath", baseClassPath);
889    
890                        augmentedClassPath = baseClassPath + ((classDest == null)?".":classDest);
891                    } else {
892                        baseClassPath = ".";
893                        if (classDest != null)
894                            augmentedClassPath = baseClassPath + (File.pathSeparator + classDest);
895                    }
896                    assert options.get("-classpath") != null;
897                }
898    
899                /*
900                 * Create base and augmented class loaders
901                 */
902                ClassLoader augmentedAptCL = null;
903                {
904                /*
905                 * Use a url class loader to look for classes on the
906                 * user-specified class path. Prepend computed bootclass
907                 * path, which includes extdirs, to the URLClassLoader apt
908                 * uses.
909                 */
910                    String aptclasspath = "";
911                    Paths paths = Paths.instance(context);
912                    String bcp = "";
913                    Collection<File> bootclasspath = paths.bootClassPath();
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    }