001    /*
002     * Copyright 1999-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.javac.main;
027    
028    import java.io.File;
029    import java.io.IOException;
030    import java.io.PrintWriter;
031    import java.util.MissingResourceException;
032    
033    import com.sun.tools.javac.code.Source;
034    import com.sun.tools.javac.file.CacheFSInfo;
035    import com.sun.tools.javac.file.JavacFileManager;
036    import com.sun.tools.javac.jvm.Target;
037    import com.sun.tools.javac.main.JavacOption.Option;
038    import com.sun.tools.javac.main.RecognizedOptions.OptionHelper;
039    import com.sun.tools.javac.util.*;
040    import com.sun.tools.javac.processing.AnnotationProcessingError;
041    import javax.tools.JavaFileManager;
042    import javax.tools.JavaFileObject;
043    import javax.annotation.processing.Processor;
044    
045    /** This class provides a commandline interface to the GJC compiler.
046     *
047     *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
048     *  you write code that depends on this, you do so at your own risk.
049     *  This code and its internal interfaces are subject to change or
050     *  deletion without notice.</b>
051     */
052    public class Main {
053    
054        /** The name of the compiler, for use in diagnostics.
055         */
056        String ownName;
057    
058        /** The writer to use for diagnostic output.
059         */
060        PrintWriter out;
061    
062        /**
063         * If true, any command line arg errors will cause an exception.
064         */
065        boolean fatalErrors;
066    
067        /** Result codes.
068         */
069        static final int
070            EXIT_OK = 0,        // Compilation completed with no errors.
071            EXIT_ERROR = 1,     // Completed but reported errors.
072            EXIT_CMDERR = 2,    // Bad command-line arguments
073            EXIT_SYSERR = 3,    // System error or resource exhaustion.
074            EXIT_ABNORMAL = 4;  // Compiler terminated abnormally
075    
076        private Option[] recognizedOptions = RecognizedOptions.getJavaCompilerOptions(new OptionHelper() {
077    
078            public void setOut(PrintWriter out) {
079                Main.this.out = out;
080            }
081    
082            public void error(String key, Object... args) {
083                Main.this.error(key, args);
084            }
085    
086            public void printVersion() {
087                Log.printLines(out, getLocalizedString("version", ownName,  JavaCompiler.version()));
088            }
089    
090            public void printFullVersion() {
091                Log.printLines(out, getLocalizedString("fullVersion", ownName,  JavaCompiler.fullVersion()));
092            }
093    
094            public void printHelp() {
095                help();
096            }
097    
098            public void printXhelp() {
099                xhelp();
100            }
101    
102            public void addFile(File f) {
103                if (!filenames.contains(f))
104                    filenames.append(f);
105            }
106    
107            public void addClassName(String s) {
108                classnames.append(s);
109            }
110    
111        });
112    
113        /**
114         * Construct a compiler instance.
115         */
116        public Main(String name) {
117            this(name, new PrintWriter(System.err, true));
118        }
119    
120        /**
121         * Construct a compiler instance.
122         */
123        public Main(String name, PrintWriter out) {
124            this.ownName = name;
125            this.out = out;
126        }
127        /** A table of all options that's passed to the JavaCompiler constructor.  */
128        private Options options = null;
129    
130        /** The list of source files to process
131         */
132        public ListBuffer<File> filenames = null; // XXX sb protected
133    
134        /** List of class files names passed on the command line
135         */
136        public ListBuffer<String> classnames = null; // XXX sb protected
137    
138        /** Print a string that explains usage.
139         */
140        void help() {
141            Log.printLines(out, getLocalizedString("msg.usage.header", ownName));
142            for (int i=0; i<recognizedOptions.length; i++) {
143                recognizedOptions[i].help(out);
144            }
145            out.println();
146        }
147    
148        /** Print a string that explains usage for X options.
149         */
150        void xhelp() {
151            for (int i=0; i<recognizedOptions.length; i++) {
152                recognizedOptions[i].xhelp(out);
153            }
154            out.println();
155            Log.printLines(out, getLocalizedString("msg.usage.nonstandard.footer"));
156        }
157    
158        /** Report a usage error.
159         */
160        void error(String key, Object... args) {
161            if (fatalErrors) {
162                String msg = getLocalizedString(key, args);
163                throw new PropagatedException(new IllegalStateException(msg));
164            }
165            warning(key, args);
166            Log.printLines(out, getLocalizedString("msg.usage", ownName));
167        }
168    
169        /** Report a warning.
170         */
171        void warning(String key, Object... args) {
172            Log.printLines(out, ownName + ": "
173                           + getLocalizedString(key, args));
174        }
175    
176        public Option getOption(String flag) {
177            for (Option option : recognizedOptions) {
178                if (option.matches(flag))
179                    return option;
180            }
181            return null;
182        }
183    
184        public void setOptions(Options options) {
185            if (options == null)
186                throw new NullPointerException();
187            this.options = options;
188        }
189    
190        public void setFatalErrors(boolean fatalErrors) {
191            this.fatalErrors = fatalErrors;
192        }
193    
194        /** Process command line arguments: store all command line options
195         *  in `options' table and return all source filenames.
196         *  @param flags    The array of command line arguments.
197         */
198        public List<File> processArgs(String[] flags) { // XXX sb protected
199            int ac = 0;
200            while (ac < flags.length) {
201                String flag = flags[ac];
202                ac++;
203    
204                Option option = null;
205    
206                if (flag.length() > 0) {
207                    // quick hack to speed up file processing:
208                    // if the option does not begin with '-', there is no need to check
209                    // most of the compiler options.
210                    int firstOptionToCheck = flag.charAt(0) == '-' ? 0 : recognizedOptions.length-1;
211                    for (int j=firstOptionToCheck; j<recognizedOptions.length; j++) {
212                        if (recognizedOptions[j].matches(flag)) {
213                            option = recognizedOptions[j];
214                            break;
215                        }
216                    }
217                }
218    
219                if (option == null) {
220                    error("err.invalid.flag", flag);
221                    return null;
222                }
223    
224                if (option.hasArg()) {
225                    if (ac == flags.length) {
226                        error("err.req.arg", flag);
227                        return null;
228                    }
229                    String operand = flags[ac];
230                    ac++;
231                    if (option.process(options, flag, operand))
232                        return null;
233                } else {
234                    if (option.process(options, flag))
235                        return null;
236                }
237            }
238    
239            if (!checkDirectory("-d"))
240                return null;
241            if (!checkDirectory("-s"))
242                return null;
243    
244            String sourceString = options.get("-source");
245            Source source = (sourceString != null)
246                ? Source.lookup(sourceString)
247                : Source.DEFAULT;
248            String targetString = options.get("-target");
249            Target target = (targetString != null)
250                ? Target.lookup(targetString)
251                : Target.DEFAULT;
252            // We don't check source/target consistency for CLDC, as J2ME
253            // profiles are not aligned with J2SE targets; moreover, a
254            // single CLDC target may have many profiles.  In addition,
255            // this is needed for the continued functioning of the JSR14
256            // prototype.
257            if (Character.isDigit(target.name.charAt(0))) {
258                if (target.compareTo(source.requiredTarget()) < 0) {
259                    if (targetString != null) {
260                        if (sourceString == null) {
261                            warning("warn.target.default.source.conflict",
262                                    targetString,
263                                    source.requiredTarget().name);
264                        } else {
265                            warning("warn.source.target.conflict",
266                                    sourceString,
267                                    source.requiredTarget().name);
268                        }
269                        return null;
270                    } else {
271                        options.put("-target", source.requiredTarget().name);
272                    }
273                } else {
274                    if (targetString == null && !source.allowGenerics()) {
275                        options.put("-target", Target.JDK1_4.name);
276                    }
277                }
278            }
279            return filenames.toList();
280        }
281        // where
282            private boolean checkDirectory(String optName) {
283                String value = options.get(optName);
284                if (value == null)
285                    return true;
286                File file = new File(value);
287                if (!file.exists()) {
288                    error("err.dir.not.found", value);
289                    return false;
290                }
291                if (!file.isDirectory()) {
292                    error("err.file.not.directory", value);
293                    return false;
294                }
295                return true;
296            }
297    
298        /** Programmatic interface for main function.
299         * @param args    The command line parameters.
300         */
301        public int compile(String[] args) {
302            Context context = new Context();
303            JavacFileManager.preRegister(context); // can't create it until Log has been set up
304            int result = compile(args, context);
305            if (fileManager instanceof JavacFileManager) {
306                // A fresh context was created above, so jfm must be a JavacFileManager
307                ((JavacFileManager)fileManager).close();
308            }
309            return result;
310        }
311    
312        public int compile(String[] args, Context context) {
313            return compile(args, context, List.<JavaFileObject>nil(), null);
314        }
315    
316        /** Programmatic interface for main function.
317         * @param args    The command line parameters.
318         */
319        public int compile(String[] args,
320                           Context context,
321                           List<JavaFileObject> fileObjects,
322                           Iterable<? extends Processor> processors)
323        {
324            if (options == null)
325                options = Options.instance(context); // creates a new one
326    
327            filenames = new ListBuffer<File>();
328            classnames = new ListBuffer<String>();
329            JavaCompiler comp = null;
330            /*
331             * TODO: Logic below about what is an acceptable command line
332             * should be updated to take annotation processing semantics
333             * into account.
334             */
335            try {
336                if (args.length == 0 && fileObjects.isEmpty()) {
337                    help();
338                    return EXIT_CMDERR;
339                }
340    
341                List<File> files;
342                try {
343                    files = processArgs(CommandLine.parse(args));
344                    if (files == null) {
345                        // null signals an error in options, abort
346                        return EXIT_CMDERR;
347                    } else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) {
348                        // it is allowed to compile nothing if just asking for help or version info
349                        if (options.get("-help") != null
350                            || options.get("-X") != null
351                            || options.get("-version") != null
352                            || options.get("-fullversion") != null)
353                            return EXIT_OK;
354                        error("err.no.source.files");
355                        return EXIT_CMDERR;
356                    }
357                } catch (java.io.FileNotFoundException e) {
358                    Log.printLines(out, ownName + ": " +
359                                   getLocalizedString("err.file.not.found",
360                                                      e.getMessage()));
361                    return EXIT_SYSERR;
362                }
363    
364                boolean forceStdOut = options.get("stdout") != null;
365                if (forceStdOut) {
366                    out.flush();
367                    out = new PrintWriter(System.out, true);
368                }
369    
370                context.put(Log.outKey, out);
371    
372                // allow System property in following line as a Mustang legacy
373                boolean batchMode = (options.get("nonBatchMode") == null
374                            && System.getProperty("nonBatchMode") == null);
375                if (batchMode)
376                    CacheFSInfo.preRegister(context);
377    
378                fileManager = context.get(JavaFileManager.class);
379    
380                comp = JavaCompiler.instance(context);
381                if (comp == null) return EXIT_SYSERR;
382    
383                Log log = Log.instance(context);
384    
385                if (!files.isEmpty()) {
386                    // add filenames to fileObjects
387                    comp = JavaCompiler.instance(context);
388                    List<JavaFileObject> otherFiles = List.nil();
389                    JavacFileManager dfm = (JavacFileManager)fileManager;
390                    for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files))
391                        otherFiles = otherFiles.prepend(fo);
392                    for (JavaFileObject fo : otherFiles)
393                        fileObjects = fileObjects.prepend(fo);
394                }
395                comp.compile(fileObjects,
396                             classnames.toList(),
397                             processors);
398    
399                if (log.expectDiagKeys != null) {
400                    if (log.expectDiagKeys.size() == 0) {
401                        Log.printLines(log.noticeWriter, "all expected diagnostics found");
402                        return EXIT_OK;
403                    } else {
404                        Log.printLines(log.noticeWriter, "expected diagnostic keys not found: " + log.expectDiagKeys);
405                        return EXIT_ERROR;
406                    }
407                }
408    
409                if (comp.errorCount() != 0 ||
410                    options.get("-Werror") != null && comp.warningCount() != 0)
411                    return EXIT_ERROR;
412            } catch (IOException ex) {
413                ioMessage(ex);
414                return EXIT_SYSERR;
415            } catch (OutOfMemoryError ex) {
416                resourceMessage(ex);
417                return EXIT_SYSERR;
418            } catch (StackOverflowError ex) {
419                resourceMessage(ex);
420                return EXIT_SYSERR;
421            } catch (FatalError ex) {
422                feMessage(ex);
423                return EXIT_SYSERR;
424            } catch(AnnotationProcessingError ex) {
425                apMessage(ex);
426                return EXIT_SYSERR;
427            } catch (ClientCodeException ex) {
428                // as specified by javax.tools.JavaCompiler#getTask
429                // and javax.tools.JavaCompiler.CompilationTask#call
430                throw new RuntimeException(ex.getCause());
431            } catch (PropagatedException ex) {
432                throw ex.getCause();
433            } catch (Throwable ex) {
434                // Nasty.  If we've already reported an error, compensate
435                // for buggy compiler error recovery by swallowing thrown
436                // exceptions.
437                if (comp == null || comp.errorCount() == 0 ||
438                    options == null || options.get("dev") != null)
439                    bugMessage(ex);
440                return EXIT_ABNORMAL;
441            } finally {
442                if (comp != null) comp.close();
443                filenames = null;
444                options = null;
445            }
446            return EXIT_OK;
447        }
448    
449        /** Print a message reporting an internal error.
450         */
451        void bugMessage(Throwable ex) {
452            Log.printLines(out, getLocalizedString("msg.bug",
453                                                   JavaCompiler.version()));
454            ex.printStackTrace(out);
455        }
456    
457        /** Print a message reporting an fatal error.
458         */
459        void feMessage(Throwable ex) {
460            Log.printLines(out, ex.getMessage());
461        }
462    
463        /** Print a message reporting an input/output error.
464         */
465        void ioMessage(Throwable ex) {
466            Log.printLines(out, getLocalizedString("msg.io"));
467            ex.printStackTrace(out);
468        }
469    
470        /** Print a message reporting an out-of-resources error.
471         */
472        void resourceMessage(Throwable ex) {
473            Log.printLines(out, getLocalizedString("msg.resource"));
474    //      System.out.println("(name buffer len = " + Name.names.length + " " + Name.nc);//DEBUG
475            ex.printStackTrace(out);
476        }
477    
478        /** Print a message reporting an uncaught exception from an
479         * annotation processor.
480         */
481        void apMessage(AnnotationProcessingError ex) {
482            Log.printLines(out,
483                           getLocalizedString("msg.proc.annotation.uncaught.exception"));
484            ex.getCause().printStackTrace();
485        }
486    
487        private JavaFileManager fileManager;
488    
489        /* ************************************************************************
490         * Internationalization
491         *************************************************************************/
492    
493        /** Find a localized string in the resource bundle.
494         *  @param key     The key for the localized string.
495         */
496        public static String getLocalizedString(String key, Object... args) { // FIXME sb private
497            try {
498                if (messages == null)
499                    messages = new JavacMessages(javacBundleName);
500                return messages.getLocalizedString("javac." + key, args);
501            }
502            catch (MissingResourceException e) {
503                throw new Error("Fatal Error: Resource for javac is missing", e);
504            }
505        }
506    
507        public static void useRawMessages(boolean enable) {
508            if (enable) {
509                messages = new JavacMessages(javacBundleName) {
510                        public String getLocalizedString(String key, Object... args) {
511                            return key;
512                        }
513                    };
514            } else {
515                messages = new JavacMessages(javacBundleName);
516            }
517        }
518    
519        private static final String javacBundleName =
520            "com.sun.tools.javac.resources.javac";
521    
522        private static JavacMessages messages;
523    }