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