/* * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javah; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVisitor; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleTypeVisitor7; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import javax.tools.ToolProvider; import static javax.tools.Diagnostic.Kind.*; import com.sun.tools.javac.code.Symbol.CompletionFailure; /** * Javah generates support files for native methods. * Parse commandline options & Invokes javadoc to execute those commands. * *

This is NOT part of any supported API. * If you write code that depends on this, you do so at your own * risk. This code and its internal interfaces are subject to change * or deletion without notice.

* * @author Sucheta Dambalkar * @author Jonathan Gibbons */ public class JavahTask implements NativeHeaderTool.NativeHeaderTask { public class BadArgs extends Exception { private static final long serialVersionUID = 1479361270874789045L; BadArgs(String key, Object... args) { super(JavahTask.this.getMessage(key, args)); this.key = key; this.args = args; } BadArgs showUsage(boolean b) { showUsage = b; return this; } final String key; final Object[] args; boolean showUsage; } static abstract class Option { Option(boolean hasArg, String... aliases) { this.hasArg = hasArg; this.aliases = aliases; } boolean isHidden() { return false; } boolean matches(String opt) { for (String a: aliases) { if (a.equals(opt)) return true; } return false; } boolean ignoreRest() { return false; } abstract void process(JavahTask task, String opt, String arg) throws BadArgs; final boolean hasArg; final String[] aliases; } static abstract class HiddenOption extends Option { HiddenOption(boolean hasArg, String... aliases) { super(hasArg, aliases); } @Override boolean isHidden() { return true; } } static Option[] recognizedOptions = { new Option(true, "-o") { void process(JavahTask task, String opt, String arg) { task.ofile = new File(arg); } }, new Option(true, "-d") { void process(JavahTask task, String opt, String arg) { task.odir = new File(arg); } }, new HiddenOption(true, "-td") { void process(JavahTask task, String opt, String arg) { // ignored; for backwards compatibility } }, new HiddenOption(false, "-stubs") { void process(JavahTask task, String opt, String arg) { // ignored; for backwards compatibility } }, new Option(false, "-v", "-verbose") { void process(JavahTask task, String opt, String arg) { task.verbose = true; } }, new Option(false, "-h", "-help", "--help", "-?") { void process(JavahTask task, String opt, String arg) { task.help = true; } }, new HiddenOption(false, "-trace") { void process(JavahTask task, String opt, String arg) { task.trace = true; } }, new Option(false, "-version") { void process(JavahTask task, String opt, String arg) { task.version = true; } }, new HiddenOption(false, "-fullversion") { void process(JavahTask task, String opt, String arg) { task.fullVersion = true; } }, new Option(false, "-jni") { void process(JavahTask task, String opt, String arg) { task.jni = true; } }, new Option(false, "-force") { void process(JavahTask task, String opt, String arg) { task.force = true; } }, new HiddenOption(false, "-Xnew") { void process(JavahTask task, String opt, String arg) { // we're already using the new javah } }, new HiddenOption(false, "-old") { void process(JavahTask task, String opt, String arg) { task.old = true; } }, new HiddenOption(false, "-llni", "-Xllni") { void process(JavahTask task, String opt, String arg) { task.llni = true; } }, new HiddenOption(false, "-llnidouble") { void process(JavahTask task, String opt, String arg) { task.llni = true; task.doubleAlign = true; } }, new HiddenOption(false) { boolean matches(String opt) { return opt.startsWith("-XD"); } void process(JavahTask task, String opt, String arg) { task.javac_extras.add(opt); } }, }; JavahTask() { } JavahTask(Writer out, JavaFileManager fileManager, DiagnosticListener diagnosticListener, Iterable options, Iterable classes) { this(); this.log = getPrintWriterForWriter(out); this.fileManager = fileManager; this.diagnosticListener = diagnosticListener; try { handleOptions(options, false); } catch (BadArgs e) { throw new IllegalArgumentException(e.getMessage()); } this.classes = new ArrayList(); if (classes != null) { for (String classname: classes) { classname.getClass(); // null-check this.classes.add(classname); } } } public void setLocale(Locale locale) { if (locale == null) locale = Locale.getDefault(); task_locale = locale; } public void setLog(PrintWriter log) { this.log = log; } public void setLog(OutputStream s) { setLog(getPrintWriterForStream(s)); } static PrintWriter getPrintWriterForStream(OutputStream s) { return new PrintWriter(s, true); } static PrintWriter getPrintWriterForWriter(Writer w) { if (w == null) return getPrintWriterForStream(null); else if (w instanceof PrintWriter) return (PrintWriter) w; else return new PrintWriter(w, true); } public void setDiagnosticListener(DiagnosticListener dl) { diagnosticListener = dl; } public void setDiagnosticListener(OutputStream s) { setDiagnosticListener(getDiagnosticListenerForStream(s)); } private DiagnosticListener getDiagnosticListenerForStream(OutputStream s) { return getDiagnosticListenerForWriter(getPrintWriterForStream(s)); } private DiagnosticListener getDiagnosticListenerForWriter(Writer w) { final PrintWriter pw = getPrintWriterForWriter(w); return new DiagnosticListener () { public void report(Diagnostic diagnostic) { if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { pw.print(getMessage("err.prefix")); pw.print(" "); } pw.println(diagnostic.getMessage(null)); } }; } int run(String[] args) { try { handleOptions(args); boolean ok = run(); return ok ? 0 : 1; } catch (BadArgs e) { diagnosticListener.report(createDiagnostic(e.key, e.args)); return 1; } catch (InternalError e) { diagnosticListener.report(createDiagnostic("err.internal.error", e.getMessage())); return 1; } catch (Util.Exit e) { return e.exitValue; } finally { log.flush(); } } public void handleOptions(String[] args) throws BadArgs { handleOptions(Arrays.asList(args), true); } private void handleOptions(Iterable args, boolean allowClasses) throws BadArgs { if (log == null) { log = getPrintWriterForStream(System.out); if (diagnosticListener == null) diagnosticListener = getDiagnosticListenerForStream(System.err); } else { if (diagnosticListener == null) diagnosticListener = getDiagnosticListenerForWriter(log); } if (fileManager == null) fileManager = getDefaultFileManager(diagnosticListener, log); Iterator iter = args.iterator(); noArgs = !iter.hasNext(); while (iter.hasNext()) { String arg = iter.next(); if (arg.startsWith("-")) handleOption(arg, iter); else if (allowClasses) { if (classes == null) classes = new ArrayList(); classes.add(arg); while (iter.hasNext()) classes.add(iter.next()); } else throw new BadArgs("err.unknown.option", arg).showUsage(true); } if ((classes == null || classes.size() == 0) && !(noArgs || help || version || fullVersion)) { throw new BadArgs("err.no.classes.specified"); } if (jni && llni) throw new BadArgs("jni.llni.mixed"); if (odir != null && ofile != null) throw new BadArgs("dir.file.mixed"); } private void handleOption(String name, Iterator rest) throws BadArgs { for (Option o: recognizedOptions) { if (o.matches(name)) { if (o.hasArg) { if (rest.hasNext()) o.process(this, name, rest.next()); else throw new BadArgs("err.missing.arg", name).showUsage(true); } else o.process(this, name, null); if (o.ignoreRest()) { while (rest.hasNext()) rest.next(); } return; } } if (fileManager.handleOption(name, rest)) return; throw new BadArgs("err.unknown.option", name).showUsage(true); } public Boolean call() { return run(); } public boolean run() throws Util.Exit { Util util = new Util(log, diagnosticListener); if (noArgs || help) { showHelp(); return help; // treat noArgs as an error for purposes of exit code } if (version || fullVersion) { showVersion(fullVersion); return true; } util.verbose = verbose; Gen g; if (llni) g = new LLNI(doubleAlign, util); else { // if (stubs) // throw new BadArgs("jni.no.stubs"); g = new JNI(util); } if (ofile != null) { if (!(fileManager instanceof StandardJavaFileManager)) { diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-o")); return false; } Iterable iter = ((StandardJavaFileManager) fileManager).getJavaFileObjectsFromFiles(Collections.singleton(ofile)); JavaFileObject fo = iter.iterator().next(); g.setOutFile(fo); } else { if (odir != null) { if (!(fileManager instanceof StandardJavaFileManager)) { diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-d")); return false; } if (!odir.exists()) if (!odir.mkdirs()) util.error("cant.create.dir", odir.toString()); try { ((StandardJavaFileManager) fileManager).setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(odir)); } catch (IOException e) { Object msg = e.getLocalizedMessage(); if (msg == null) { msg = e; } diagnosticListener.report(createDiagnostic("err.ioerror", odir, msg)); return false; } } g.setFileManager(fileManager); } /* * Force set to false will turn off smarts about checking file * content before writing. */ g.setForce(force); if (fileManager instanceof JavahFileManager) ((JavahFileManager) fileManager).setIgnoreSymbolFile(true); JavaCompiler c = ToolProvider.getSystemJavaCompiler(); List opts = new ArrayList(); opts.add("-proc:only"); opts.addAll(javac_extras); CompilationTask t = c.getTask(log, fileManager, diagnosticListener, opts, internalize(classes), null); JavahProcessor p = new JavahProcessor(g); t.setProcessors(Collections.singleton(p)); boolean ok = t.call(); if (p.exit != null) throw new Util.Exit(p.exit); return ok; } private List internalize(List classes) { List l = new ArrayList(); for (String c: classes) { l.add(c.replace('$', '.')); } return l; } private List pathToFiles(String path) { List files = new ArrayList(); for (String f: path.split(File.pathSeparator)) { if (f.length() > 0) files.add(new File(f)); } return files; } static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener dl, PrintWriter log) { return JavahFileManager.create(dl, log); } private void showHelp() { log.println(getMessage("main.usage", progname)); for (Option o: recognizedOptions) { if (o.isHidden()) continue; String name = o.aliases[0].substring(1); // there must always be at least one name log.println(getMessage("main.opt." + name)); } String[] fmOptions = { "-classpath", "-bootclasspath" }; for (String o: fmOptions) { if (fileManager.isSupportedOption(o) == -1) continue; String name = o.substring(1); log.println(getMessage("main.opt." + name)); } log.println(getMessage("main.usage.foot")); } private void showVersion(boolean full) { log.println(version(full)); } private static final String versionRBName = "com.sun.tools.javah.resources.version"; private static ResourceBundle versionRB; private String version(boolean full) { String msgKey = (full ? "javah.fullVersion" : "javah.version"); String versionKey = (full ? "full" : "release"); // versionKey=product: mm.nn.oo[-milestone] // versionKey=full: mm.mm.oo[-milestone]-build if (versionRB == null) { try { versionRB = ResourceBundle.getBundle(versionRBName); } catch (MissingResourceException e) { return getMessage("version.resource.missing", System.getProperty("java.version")); } } try { return getMessage(msgKey, "javah", versionRB.getString(versionKey)); } catch (MissingResourceException e) { return getMessage("version.unknown", System.getProperty("java.version")); } } private Diagnostic createDiagnostic(final String key, final Object... args) { return new Diagnostic() { public Kind getKind() { return Diagnostic.Kind.ERROR; } public JavaFileObject getSource() { return null; } public long getPosition() { return Diagnostic.NOPOS; } public long getStartPosition() { return Diagnostic.NOPOS; } public long getEndPosition() { return Diagnostic.NOPOS; } public long getLineNumber() { return Diagnostic.NOPOS; } public long getColumnNumber() { return Diagnostic.NOPOS; } public String getCode() { return key; } public String getMessage(Locale locale) { return JavahTask.this.getMessage(locale, key, args); } }; } private String getMessage(String key, Object... args) { return getMessage(task_locale, key, args); } private String getMessage(Locale locale, String key, Object... args) { if (bundles == null) { // could make this a HashMap> // and for efficiency, keep a hard reference to the bundle for the task // locale bundles = new HashMap(); } if (locale == null) locale = Locale.getDefault(); ResourceBundle b = bundles.get(locale); if (b == null) { try { b = ResourceBundle.getBundle("com.sun.tools.javah.resources.l10n", locale); bundles.put(locale, b); } catch (MissingResourceException e) { throw new InternalError("Cannot find javah resource bundle for locale " + locale, e); } } try { return MessageFormat.format(b.getString(key), args); } catch (MissingResourceException e) { return key; //throw new InternalError(e, key); } } File ofile; File odir; String bootcp; String usercp; List classes; boolean verbose; boolean noArgs; boolean help; boolean trace; boolean version; boolean fullVersion; boolean jni; boolean llni; boolean doubleAlign; boolean force; boolean old; Set javac_extras = new LinkedHashSet(); PrintWriter log; JavaFileManager fileManager; DiagnosticListener diagnosticListener; Locale task_locale; Map bundles; private static final String progname = "javah"; @SupportedAnnotationTypes("*") class JavahProcessor extends AbstractProcessor { private Messager messager; JavahProcessor(Gen g) { this.g = g; } @Override public SourceVersion getSupportedSourceVersion() { // since this is co-bundled with javac, we can assume it supports // the latest source version return SourceVersion.latest(); } @Override public void init(ProcessingEnvironment pEnv) { super.init(pEnv); messager = processingEnv.getMessager(); } public boolean process(Set annotations, RoundEnvironment roundEnv) { try { Set classes = getAllClasses(ElementFilter.typesIn(roundEnv.getRootElements())); if (classes.size() > 0) { checkMethodParameters(classes); g.setProcessingEnvironment(processingEnv); g.setClasses(classes); g.run(); } } catch (CompletionFailure cf) { messager.printMessage(ERROR, getMessage("class.not.found", cf.sym.getQualifiedName().toString())); } catch (ClassNotFoundException cnfe) { messager.printMessage(ERROR, getMessage("class.not.found", cnfe.getMessage())); } catch (IOException ioe) { messager.printMessage(ERROR, getMessage("io.exception", ioe.getMessage())); } catch (Util.Exit e) { exit = e; } return true; } private Set getAllClasses(Set classes) { Set allClasses = new LinkedHashSet(); getAllClasses0(classes, allClasses); return allClasses; } private void getAllClasses0(Iterable classes, Set allClasses) { for (TypeElement c: classes) { allClasses.add(c); getAllClasses0(ElementFilter.typesIn(c.getEnclosedElements()), allClasses); } } // 4942232: // check that classes exist for all the parameters of native methods private void checkMethodParameters(Set classes) { Types types = processingEnv.getTypeUtils(); for (TypeElement te: classes) { for (ExecutableElement ee: ElementFilter.methodsIn(te.getEnclosedElements())) { for (VariableElement ve: ee.getParameters()) { TypeMirror tm = ve.asType(); checkMethodParametersVisitor.visit(tm, types); } } } } private TypeVisitor checkMethodParametersVisitor = new SimpleTypeVisitor7() { @Override public Void visitArray(ArrayType t, Types types) { visit(t.getComponentType(), types); return null; } @Override public Void visitDeclared(DeclaredType t, Types types) { t.asElement().getKind(); // ensure class exists for (TypeMirror st: types.directSupertypes(t)) visit(st, types); return null; } }; private Gen g; private Util.Exit exit; } }