JavahTask.java revision 2675:4be0e35f385a
1/*
2 * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javah;
27
28import java.io.File;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.io.OutputStream;
32import java.io.PrintWriter;
33import java.io.Writer;
34import java.text.MessageFormat;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.Collections;
38import java.util.HashMap;
39import java.util.Iterator;
40import java.util.LinkedHashSet;
41import java.util.List;
42import java.util.Locale;
43import java.util.Map;
44import java.util.MissingResourceException;
45import java.util.ResourceBundle;
46import java.util.Set;
47
48import javax.annotation.processing.AbstractProcessor;
49import javax.annotation.processing.Messager;
50import javax.annotation.processing.ProcessingEnvironment;
51import javax.annotation.processing.RoundEnvironment;
52import javax.annotation.processing.SupportedAnnotationTypes;
53
54import javax.lang.model.SourceVersion;
55import javax.lang.model.element.ExecutableElement;
56import javax.lang.model.element.TypeElement;
57import javax.lang.model.element.VariableElement;
58import javax.lang.model.type.ArrayType;
59import javax.lang.model.type.DeclaredType;
60import javax.lang.model.type.TypeMirror;
61import javax.lang.model.type.TypeVisitor;
62import javax.lang.model.util.ElementFilter;
63import javax.lang.model.util.SimpleTypeVisitor9;
64import javax.lang.model.util.Types;
65
66import javax.tools.Diagnostic;
67import javax.tools.DiagnosticListener;
68import javax.tools.JavaCompiler;
69import javax.tools.JavaCompiler.CompilationTask;
70import javax.tools.JavaFileManager;
71import javax.tools.JavaFileObject;
72import javax.tools.StandardJavaFileManager;
73import javax.tools.StandardLocation;
74import javax.tools.ToolProvider;
75import static javax.tools.Diagnostic.Kind.*;
76
77import com.sun.tools.javac.code.Symbol.CompletionFailure;
78import com.sun.tools.javac.main.CommandLine;
79import com.sun.tools.javac.util.DefinedBy;
80import com.sun.tools.javac.util.DefinedBy.Api;
81
82/**
83 * Javah generates support files for native methods.
84 * Parse commandline options and invokes javadoc to execute those commands.
85 *
86 * <p><b>This is NOT part of any supported API.
87 * If you write code that depends on this, you do so at your own
88 * risk.  This code and its internal interfaces are subject to change
89 * or deletion without notice.</b></p>
90 *
91 * @author Sucheta Dambalkar
92 * @author Jonathan Gibbons
93 */
94public class JavahTask implements NativeHeaderTool.NativeHeaderTask {
95    public class BadArgs extends Exception {
96        private static final long serialVersionUID = 1479361270874789045L;
97        BadArgs(String key, Object... args) {
98            super(JavahTask.this.getMessage(key, args));
99            this.key = key;
100            this.args = args;
101        }
102
103        BadArgs showUsage(boolean b) {
104            showUsage = b;
105            return this;
106        }
107
108        final String key;
109        final Object[] args;
110        boolean showUsage;
111    }
112
113    static abstract class Option {
114        Option(boolean hasArg, String... aliases) {
115            this.hasArg = hasArg;
116            this.aliases = aliases;
117        }
118
119        boolean isHidden() {
120            return false;
121        }
122
123        boolean matches(String opt) {
124            for (String a: aliases) {
125                if (a.equals(opt))
126                    return true;
127            }
128            return false;
129        }
130
131        boolean ignoreRest() {
132            return false;
133        }
134
135        abstract void process(JavahTask task, String opt, String arg) throws BadArgs;
136
137        final boolean hasArg;
138        final String[] aliases;
139    }
140
141    static abstract class HiddenOption extends Option {
142        HiddenOption(boolean hasArg, String... aliases) {
143            super(hasArg, aliases);
144        }
145
146        @Override
147        boolean isHidden() {
148            return true;
149        }
150    }
151
152    static final Option[] recognizedOptions = {
153        new Option(true, "-o") {
154            void process(JavahTask task, String opt, String arg) {
155                task.ofile = new File(arg);
156            }
157        },
158
159        new Option(true, "-d") {
160            void process(JavahTask task, String opt, String arg) {
161                task.odir = new File(arg);
162            }
163        },
164
165        new HiddenOption(true, "-td") {
166            void process(JavahTask task, String opt, String arg) {
167                 // ignored; for backwards compatibility
168            }
169        },
170
171        new HiddenOption(false, "-stubs") {
172            void process(JavahTask task, String opt, String arg) {
173                 // ignored; for backwards compatibility
174            }
175        },
176
177        new Option(false, "-v", "-verbose") {
178            void process(JavahTask task, String opt, String arg) {
179                task.verbose = true;
180            }
181        },
182
183        new Option(false, "-h", "-help", "--help", "-?") {
184            void process(JavahTask task, String opt, String arg) {
185                task.help = true;
186            }
187        },
188
189        new HiddenOption(false, "-trace") {
190            void process(JavahTask task, String opt, String arg) {
191                task.trace = true;
192            }
193        },
194
195        new Option(false, "-version") {
196            void process(JavahTask task, String opt, String arg) {
197                task.version = true;
198            }
199        },
200
201        new HiddenOption(false, "-fullversion") {
202            void process(JavahTask task, String opt, String arg) {
203                task.fullVersion = true;
204            }
205        },
206
207        new Option(false, "-jni") {
208            void process(JavahTask task, String opt, String arg) {
209                task.jni = true;
210            }
211        },
212
213        new Option(false, "-force") {
214            void process(JavahTask task, String opt, String arg) {
215                task.force = true;
216            }
217        },
218
219        new HiddenOption(false, "-Xnew") {
220            void process(JavahTask task, String opt, String arg) {
221                // we're already using the new javah
222            }
223        },
224
225        new HiddenOption(false, "-llni", "-Xllni") {
226            void process(JavahTask task, String opt, String arg) {
227                task.llni = true;
228            }
229        },
230
231        new HiddenOption(false, "-llnidouble") {
232            void process(JavahTask task, String opt, String arg) {
233                task.llni = true;
234                task.doubleAlign = true;
235            }
236        },
237
238        new HiddenOption(false) {
239            boolean matches(String opt) {
240                return opt.startsWith("-XD");
241            }
242            void process(JavahTask task, String opt, String arg) {
243                task.javac_extras.add(opt);
244            }
245        },
246    };
247
248    JavahTask() {
249    }
250
251    JavahTask(Writer out,
252            JavaFileManager fileManager,
253            DiagnosticListener<? super JavaFileObject> diagnosticListener,
254            Iterable<String> options,
255            Iterable<String> classes) {
256        this();
257        this.log = getPrintWriterForWriter(out);
258        this.fileManager = fileManager;
259        this.diagnosticListener = diagnosticListener;
260
261        try {
262            handleOptions(options, false);
263        } catch (BadArgs e) {
264            throw new IllegalArgumentException(e.getMessage());
265        }
266
267        this.classes = new ArrayList<>();
268        if (classes != null) {
269            for (String classname: classes) {
270                classname.getClass(); // null-check
271                this.classes.add(classname);
272            }
273        }
274    }
275
276    public void setLocale(Locale locale) {
277        if (locale == null)
278            locale = Locale.getDefault();
279        task_locale = locale;
280    }
281
282    public void setLog(PrintWriter log) {
283        this.log = log;
284    }
285
286    public void setLog(OutputStream s) {
287        setLog(getPrintWriterForStream(s));
288    }
289
290    static PrintWriter getPrintWriterForStream(OutputStream s) {
291        return new PrintWriter(s, true);
292    }
293
294    static PrintWriter getPrintWriterForWriter(Writer w) {
295        if (w == null)
296            return getPrintWriterForStream(null);
297        else if (w instanceof PrintWriter)
298            return (PrintWriter) w;
299        else
300            return new PrintWriter(w, true);
301    }
302
303    public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) {
304        diagnosticListener = dl;
305    }
306
307    public void setDiagnosticListener(OutputStream s) {
308        setDiagnosticListener(getDiagnosticListenerForStream(s));
309    }
310
311    private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) {
312        return getDiagnosticListenerForWriter(getPrintWriterForStream(s));
313    }
314
315    private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) {
316        final PrintWriter pw = getPrintWriterForWriter(w);
317        return new DiagnosticListener<JavaFileObject> () {
318            @DefinedBy(Api.COMPILER)
319            public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
320                if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
321                    pw.print(getMessage("err.prefix"));
322                    pw.print(" ");
323                }
324                pw.println(diagnostic.getMessage(null));
325            }
326        };
327    }
328
329    int run(String[] args) {
330        try {
331            handleOptions(args);
332            boolean ok = run();
333            return ok ? 0 : 1;
334        } catch (BadArgs e) {
335            diagnosticListener.report(createDiagnostic(e.key, e.args));
336            return 1;
337        } catch (InternalError e) {
338            diagnosticListener.report(createDiagnostic("err.internal.error", e.getMessage()));
339            return 1;
340        } catch (Util.Exit e) {
341            return e.exitValue;
342        } finally {
343            log.flush();
344        }
345    }
346
347    public void handleOptions(String[] args) throws BadArgs {
348        handleOptions(Arrays.asList(args), true);
349    }
350
351    private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs {
352        if (log == null) {
353            log = getPrintWriterForStream(System.out);
354            if (diagnosticListener == null)
355              diagnosticListener = getDiagnosticListenerForStream(System.err);
356        } else {
357            if (diagnosticListener == null)
358              diagnosticListener = getDiagnosticListenerForWriter(log);
359        }
360
361        if (fileManager == null)
362            fileManager = getDefaultFileManager(diagnosticListener, log);
363
364        Iterator<String> iter = expandAtArgs(args).iterator();
365        noArgs = !iter.hasNext();
366
367        while (iter.hasNext()) {
368            String arg = iter.next();
369            if (arg.startsWith("-"))
370                handleOption(arg, iter);
371            else if (allowClasses) {
372                if (classes == null)
373                    classes = new ArrayList<>();
374                classes.add(arg);
375                while (iter.hasNext())
376                    classes.add(iter.next());
377            } else
378                throw new BadArgs("err.unknown.option", arg).showUsage(true);
379        }
380
381        if ((classes == null || classes.size() == 0) &&
382                !(noArgs || help || version || fullVersion)) {
383            throw new BadArgs("err.no.classes.specified");
384        }
385
386        if (jni && llni)
387            throw new BadArgs("jni.llni.mixed");
388
389        if (odir != null && ofile != null)
390            throw new BadArgs("dir.file.mixed");
391    }
392
393    private void handleOption(String name, Iterator<String> rest) throws BadArgs {
394        for (Option o: recognizedOptions) {
395            if (o.matches(name)) {
396                if (o.hasArg) {
397                    if (rest.hasNext())
398                        o.process(this, name, rest.next());
399                    else
400                        throw new BadArgs("err.missing.arg", name).showUsage(true);
401                } else
402                    o.process(this, name, null);
403
404                if (o.ignoreRest()) {
405                    while (rest.hasNext())
406                        rest.next();
407                }
408                return;
409            }
410        }
411
412        if (fileManager.handleOption(name, rest))
413            return;
414
415        throw new BadArgs("err.unknown.option", name).showUsage(true);
416    }
417
418    private Iterable<String> expandAtArgs(Iterable<String> args) throws BadArgs {
419        try {
420            List<String> l = new ArrayList<>();
421            for (String arg: args) l.add(arg);
422            return Arrays.asList(CommandLine.parse(l.toArray(new String[l.size()])));
423        } catch (FileNotFoundException e) {
424            throw new BadArgs("at.args.file.not.found", e.getLocalizedMessage());
425        } catch (IOException e) {
426            throw new BadArgs("at.args.io.exception", e.getLocalizedMessage());
427        }
428    }
429
430    public Boolean call() {
431        return run();
432    }
433
434    public boolean run() throws Util.Exit {
435
436        Util util = new Util(log, diagnosticListener);
437
438        if (noArgs || help) {
439            showHelp();
440            return help; // treat noArgs as an error for purposes of exit code
441        }
442
443        if (version || fullVersion) {
444            showVersion(fullVersion);
445            return true;
446        }
447
448        util.verbose = verbose;
449
450        Gen g;
451
452        if (llni)
453            g = new LLNI(doubleAlign, util);
454        else {
455//            if (stubs)
456//                throw new BadArgs("jni.no.stubs");
457            g = new JNI(util);
458        }
459
460        if (ofile != null) {
461            if (!(fileManager instanceof StandardJavaFileManager)) {
462                diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-o"));
463                return false;
464            }
465            Iterable<? extends JavaFileObject> iter =
466                    ((StandardJavaFileManager) fileManager).getJavaFileObjectsFromFiles(Collections.singleton(ofile));
467            JavaFileObject fo = iter.iterator().next();
468            g.setOutFile(fo);
469        } else {
470            if (odir != null) {
471                if (!(fileManager instanceof StandardJavaFileManager)) {
472                    diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-d"));
473                    return false;
474                }
475
476                if (!odir.exists())
477                    if (!odir.mkdirs())
478                        util.error("cant.create.dir", odir.toString());
479                try {
480                    ((StandardJavaFileManager) fileManager).setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(odir));
481                } catch (IOException e) {
482                    Object msg = e.getLocalizedMessage();
483                    if (msg == null) {
484                        msg = e;
485                    }
486                    diagnosticListener.report(createDiagnostic("err.ioerror", odir, msg));
487                    return false;
488                }
489            }
490            g.setFileManager(fileManager);
491        }
492
493        /*
494         * Force set to false will turn off smarts about checking file
495         * content before writing.
496         */
497        g.setForce(force);
498
499        if (fileManager instanceof JavahFileManager)
500            ((JavahFileManager) fileManager).setSymbolFileEnabled(false);
501
502        JavaCompiler c = ToolProvider.getSystemJavaCompiler();
503        List<String> opts = new ArrayList<>();
504        opts.add("-proc:only");
505        opts.addAll(javac_extras);
506        CompilationTask t = c.getTask(log, fileManager, diagnosticListener, opts, classes, null);
507        JavahProcessor p = new JavahProcessor(g);
508        t.setProcessors(Collections.singleton(p));
509
510        boolean ok = t.call();
511        if (p.exit != null)
512            throw new Util.Exit(p.exit);
513        return ok;
514    }
515
516    private List<File> pathToFiles(String path) {
517        List<File> files = new ArrayList<>();
518        for (String f: path.split(File.pathSeparator)) {
519            if (f.length() > 0)
520                files.add(new File(f));
521        }
522        return files;
523    }
524
525    static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) {
526        return JavahFileManager.create(dl, log);
527    }
528
529    private void showHelp() {
530        log.println(getMessage("main.usage", progname));
531        for (Option o: recognizedOptions) {
532            if (o.isHidden())
533                continue;
534            String name = o.aliases[0].substring(1); // there must always be at least one name
535            log.println(getMessage("main.opt." + name));
536        }
537        String[] fmOptions = { "-classpath", "-cp", "-bootclasspath" };
538        for (String o: fmOptions) {
539            if (fileManager.isSupportedOption(o) == -1)
540                continue;
541            String name = o.substring(1);
542            log.println(getMessage("main.opt." + name));
543        }
544        log.println(getMessage("main.usage.foot"));
545    }
546
547    private void showVersion(boolean full) {
548        log.println(version(full));
549    }
550
551    private static final String versionRBName = "com.sun.tools.javah.resources.version";
552    private static ResourceBundle versionRB;
553
554    private String version(boolean full) {
555        String msgKey = (full ? "javah.fullVersion" : "javah.version");
556        String versionKey = (full ? "full" : "release");
557        // versionKey=product:  mm.nn.oo[-milestone]
558        // versionKey=full:     mm.mm.oo[-milestone]-build
559        if (versionRB == null) {
560            try {
561                versionRB = ResourceBundle.getBundle(versionRBName);
562            } catch (MissingResourceException e) {
563                return getMessage("version.resource.missing", System.getProperty("java.version"));
564            }
565        }
566        try {
567            return getMessage(msgKey, "javah", versionRB.getString(versionKey));
568        }
569        catch (MissingResourceException e) {
570            return getMessage("version.unknown", System.getProperty("java.version"));
571        }
572    }
573
574    private Diagnostic<JavaFileObject> createDiagnostic(final String key, final Object... args) {
575        return new Diagnostic<JavaFileObject>() {
576            @DefinedBy(Api.COMPILER)
577            public Kind getKind() {
578                return Diagnostic.Kind.ERROR;
579            }
580
581            @DefinedBy(Api.COMPILER)
582            public JavaFileObject getSource() {
583                return null;
584            }
585
586            @DefinedBy(Api.COMPILER)
587            public long getPosition() {
588                return Diagnostic.NOPOS;
589            }
590
591            @DefinedBy(Api.COMPILER)
592            public long getStartPosition() {
593                return Diagnostic.NOPOS;
594            }
595
596            @DefinedBy(Api.COMPILER)
597            public long getEndPosition() {
598                return Diagnostic.NOPOS;
599            }
600
601            @DefinedBy(Api.COMPILER)
602            public long getLineNumber() {
603                return Diagnostic.NOPOS;
604            }
605
606            @DefinedBy(Api.COMPILER)
607            public long getColumnNumber() {
608                return Diagnostic.NOPOS;
609            }
610
611            @DefinedBy(Api.COMPILER)
612            public String getCode() {
613                return key;
614            }
615
616            @DefinedBy(Api.COMPILER)
617            public String getMessage(Locale locale) {
618                return JavahTask.this.getMessage(locale, key, args);
619            }
620
621        };
622    }
623
624    private String getMessage(String key, Object... args) {
625        return getMessage(task_locale, key, args);
626    }
627
628    private String getMessage(Locale locale, String key, Object... args) {
629        if (bundles == null) {
630            // could make this a HashMap<Locale,SoftReference<ResourceBundle>>
631            // and for efficiency, keep a hard reference to the bundle for the task
632            // locale
633            bundles = new HashMap<>();
634        }
635
636        if (locale == null)
637            locale = Locale.getDefault();
638
639        ResourceBundle b = bundles.get(locale);
640        if (b == null) {
641            try {
642                b = ResourceBundle.getBundle("com.sun.tools.javah.resources.l10n", locale);
643                bundles.put(locale, b);
644            } catch (MissingResourceException e) {
645                throw new InternalError("Cannot find javah resource bundle for locale " + locale, e);
646            }
647        }
648
649        try {
650            return MessageFormat.format(b.getString(key), args);
651        } catch (MissingResourceException e) {
652            return key;
653            //throw new InternalError(e, key);
654        }
655    }
656
657    File ofile;
658    File odir;
659    String bootcp;
660    String usercp;
661    List<String> classes;
662    boolean verbose;
663    boolean noArgs;
664    boolean help;
665    boolean trace;
666    boolean version;
667    boolean fullVersion;
668    boolean jni;
669    boolean llni;
670    boolean doubleAlign;
671    boolean force;
672    Set<String> javac_extras = new LinkedHashSet<>();
673
674    PrintWriter log;
675    JavaFileManager fileManager;
676    DiagnosticListener<? super JavaFileObject> diagnosticListener;
677    Locale task_locale;
678    Map<Locale, ResourceBundle> bundles;
679
680    private static final String progname = "javah";
681
682    @SupportedAnnotationTypes("*")
683    class JavahProcessor extends AbstractProcessor {
684        private Messager messager;
685
686        JavahProcessor(Gen g) {
687            this.g = g;
688        }
689
690        @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
691        public SourceVersion getSupportedSourceVersion() {
692            // since this is co-bundled with javac, we can assume it supports
693            // the latest source version
694            return SourceVersion.latest();
695        }
696
697        @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
698        public void init(ProcessingEnvironment pEnv) {
699            super.init(pEnv);
700            messager  = processingEnv.getMessager();
701        }
702
703        @DefinedBy(Api.ANNOTATION_PROCESSING)
704        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
705            try {
706                Set<TypeElement> classes = getAllClasses(ElementFilter.typesIn(roundEnv.getRootElements()));
707                if (classes.size() > 0) {
708                    checkMethodParameters(classes);
709                    g.setProcessingEnvironment(processingEnv);
710                    g.setClasses(classes);
711                    g.run();
712                }
713            } catch (CompletionFailure cf) {
714                messager.printMessage(ERROR, getMessage("class.not.found", cf.sym.getQualifiedName().toString()));
715            } catch (ClassNotFoundException cnfe) {
716                messager.printMessage(ERROR, getMessage("class.not.found", cnfe.getMessage()));
717            } catch (IOException ioe) {
718                messager.printMessage(ERROR, getMessage("io.exception", ioe.getMessage()));
719            } catch (Util.Exit e) {
720                exit = e;
721            }
722
723            return true;
724        }
725
726        private Set<TypeElement> getAllClasses(Set<? extends TypeElement> classes) {
727            Set<TypeElement> allClasses = new LinkedHashSet<>();
728            getAllClasses0(classes, allClasses);
729            return allClasses;
730        }
731
732        private void getAllClasses0(Iterable<? extends TypeElement> classes, Set<TypeElement> allClasses) {
733            for (TypeElement c: classes) {
734                allClasses.add(c);
735                getAllClasses0(ElementFilter.typesIn(c.getEnclosedElements()), allClasses);
736            }
737        }
738
739        // 4942232:
740        // check that classes exist for all the parameters of native methods
741        private void checkMethodParameters(Set<TypeElement> classes) {
742            Types types = processingEnv.getTypeUtils();
743            for (TypeElement te: classes) {
744                for (ExecutableElement ee: ElementFilter.methodsIn(te.getEnclosedElements())) {
745                    for (VariableElement ve: ee.getParameters()) {
746                        TypeMirror tm = ve.asType();
747                        checkMethodParametersVisitor.visit(tm, types);
748                    }
749                }
750            }
751        }
752
753        private TypeVisitor<Void,Types> checkMethodParametersVisitor =
754                new SimpleTypeVisitor9<Void,Types>() {
755            @Override @DefinedBy(Api.LANGUAGE_MODEL)
756            public Void visitArray(ArrayType t, Types types) {
757                visit(t.getComponentType(), types);
758                return null;
759            }
760            @Override @DefinedBy(Api.LANGUAGE_MODEL)
761            public Void visitDeclared(DeclaredType t, Types types) {
762                t.asElement().getKind(); // ensure class exists
763                for (TypeMirror st: types.directSupertypes(t))
764                    visit(st, types);
765                return null;
766            }
767        };
768
769        private Gen g;
770        private Util.Exit exit;
771    }
772}
773