/*
 * Copyright (c) 2002, 2016, 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.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.NoSuchFileException;
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.Objects;
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.SimpleTypeVisitor9;
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 com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.main.CommandLine;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;

import static javax.tools.Diagnostic.Kind.*;


Javah generates support files for native methods. Parse commandline options and 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, Jonathan Gibbons
/** * Javah generates support files for native methods. * Parse commandline options and invokes javadoc to execute those commands. * * <p><b>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.</b></p> * * @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 final 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 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, "-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<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> 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) { Objects.requireNonNull(classname); 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<? super JavaFileObject> dl) { diagnosticListener = dl; } public void setDiagnosticListener(OutputStream s) { setDiagnosticListener(getDiagnosticListenerForStream(s)); } private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) { return getDiagnosticListenerForWriter(getPrintWriterForStream(s)); } private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) { final PrintWriter pw = getPrintWriterForWriter(w); return 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<String> 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<String> iter = expandAtArgs(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<String> 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); } private Iterable<String> expandAtArgs(Iterable<String> args) throws BadArgs { try { List<String> l = new ArrayList<>(); for (String arg: args) l.add(arg); return Arrays.asList(CommandLine.parse(l.toArray(new String[l.size()]))); } catch (FileNotFoundException | NoSuchFileException e) { throw new BadArgs("at.args.file.not.found", e.getLocalizedMessage()); } catch (IOException e) { throw new BadArgs("at.args.io.exception", e.getLocalizedMessage()); } } public Boolean call() { return run(); } public boolean run() throws Util.Exit { if (!javac_extras.contains("-XDsuppress-tool-removal-message")) { log.println(getMessage("javah.misc.Deprecation")); } 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 { g = new JNI(util); } if (ofile != null) { if (!(fileManager instanceof StandardJavaFileManager)) { diagnosticListener.report(createDiagnostic("err.cant.use.option.for.fm", "-o")); return false; } Iterable<? extends JavaFileObject> 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).setSymbolFileEnabled(false); JavaCompiler c = ToolProvider.getSystemJavaCompiler(); List<String> opts = new ArrayList<>(); opts.add("-proc:only"); opts.addAll(javac_extras); CompilationTask t; try { t = c.getTask(log, fileManager, diagnosticListener, opts, classes, null); } catch (IllegalArgumentException e) { util.error("bad.arg", e.getMessage()); return false; } 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; } static StandardJavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> 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 = { "--module-path", "--system", "--class-path", "-classpath", "-cp", "-bootclasspath" }; for (String o: fmOptions) { if (fileManager.isSupportedOption(o) == -1) continue; String name = o.replaceAll("^-+", "").replaceAll("-+", "_"); 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<JavaFileObject> createDiagnostic(final String key, final Object... args) { return new Diagnostic<JavaFileObject>() { @DefinedBy(Api.COMPILER) public Kind getKind() { return Diagnostic.Kind.ERROR; } @DefinedBy(Api.COMPILER) public JavaFileObject getSource() { return null; } @DefinedBy(Api.COMPILER) public long getPosition() { return Diagnostic.NOPOS; } @DefinedBy(Api.COMPILER) public long getStartPosition() { return Diagnostic.NOPOS; } @DefinedBy(Api.COMPILER) public long getEndPosition() { return Diagnostic.NOPOS; } @DefinedBy(Api.COMPILER) public long getLineNumber() { return Diagnostic.NOPOS; } @DefinedBy(Api.COMPILER) public long getColumnNumber() { return Diagnostic.NOPOS; } @DefinedBy(Api.COMPILER) public String getCode() { return key; } @DefinedBy(Api.COMPILER) 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<Locale,SoftReference<ResourceBundle>> // 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<String> classes; boolean verbose; boolean noArgs; boolean help; boolean trace; boolean version; boolean fullVersion; boolean jni; boolean llni; boolean doubleAlign; boolean force; Set<String> javac_extras = new LinkedHashSet<>(); PrintWriter log; JavaFileManager fileManager; DiagnosticListener<? super JavaFileObject> diagnosticListener; Locale task_locale; Map<Locale, ResourceBundle> bundles; private static final String progname = "javah"; @SupportedAnnotationTypes("*") class JavahProcessor extends AbstractProcessor { private Messager messager; JavahProcessor(Gen g) { this.g = g; } @Override @DefinedBy(Api.ANNOTATION_PROCESSING) public SourceVersion getSupportedSourceVersion() { // since this is co-bundled with javac, we can assume it supports // the latest source version return SourceVersion.latest(); } @Override @DefinedBy(Api.ANNOTATION_PROCESSING) public void init(ProcessingEnvironment pEnv) { super.init(pEnv); messager = processingEnv.getMessager(); } @DefinedBy(Api.ANNOTATION_PROCESSING) public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { Set<TypeElement> 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<TypeElement> getAllClasses(Set<? extends TypeElement> classes) { Set<TypeElement> allClasses = new LinkedHashSet<>(); getAllClasses0(classes, allClasses); return allClasses; } private void getAllClasses0(Iterable<? extends TypeElement> classes, Set<TypeElement> 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<TypeElement> 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<Void,Types> checkMethodParametersVisitor = new SimpleTypeVisitor9<Void,Types>() { @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitArray(ArrayType t, Types types) { visit(t.getComponentType(), types); return null; } @Override @DefinedBy(Api.LANGUAGE_MODEL) 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; } }