/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.taskdefs.optional; import java.io.File; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.Vector; import java.util.stream.Stream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.optional.javah.JavahAdapter; import org.apache.tools.ant.taskdefs.optional.javah.JavahAdapterFactory; import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; import org.apache.tools.ant.util.StringUtils; import org.apache.tools.ant.util.facade.FacadeTaskHelper; import org.apache.tools.ant.util.facade.ImplementationSpecificArgument; /** * Generates JNI header files using javah. * * This task can take the following arguments: * <ul> * <li>classname - the fully-qualified name of a class</li> * <li>outputFile - Concatenates the resulting header or source files for all * the classes listed into this file</li> * <li>destdir - Sets the directory where javah saves the header files or the * stub files</li> * <li>classpath</li> * <li>bootclasspath</li> * <li>force - Specifies that output files should always be written (JDK1.2 only)</li> * <li>old - Specifies that old JDK1.0-style header files should be generated * (otherwise output file contain JNI-style native method * function prototypes) (JDK1.2 only)</li> * <li>stubs - generate C declarations from the Java object file (used with old)</li> * <li>verbose - causes javah to print a message to stdout concerning the status * of the generated files</li> * <li>extdirs - Override location of installed extensions</li> * </ul> * Of these arguments, either <b>outputFile</b> or <b>destdir</b> is required, * but not both. More than one classname may be specified, using a comma-separated * list or by using <code><class name="xxx"></code> elements within the task. * <p> * When this task executes, it will generate C header and source files that * are needed to implement native methods. * */ public class Javah extends Task { private List<ClassArgument> classes = new Vector<>(2); private String cls; private File destDir; private Path classpath = null; private File outputFile = null; private boolean verbose = false; private boolean force = false; private boolean old = false; private boolean stubs = false; private Path bootclasspath; private FacadeTaskHelper facade = null; private Vector<FileSet> files = new Vector<>(); private JavahAdapter nestedAdapter = null; /** * No arg constructor. */ public Javah() { facade = new FacadeTaskHelper(JavahAdapterFactory.getDefault()); } /** * the fully-qualified name of the class (or classes, separated by commas). * @param cls the classname (or classnames). */ public void setClass(String cls) { this.cls = cls; } /** * Adds class to process. * @return a <code>ClassArgument</code> to be configured. */ public ClassArgument createClass() { ClassArgument ga = new ClassArgument(); classes.add(ga); return ga; } /** * A class corresponding the the nested "class" element. * It contains a "name" attribute. */ public class ClassArgument { private String name; /** * Set the name attribute. * @param name the name attribute. */ public void setName(String name) { this.name = name; } /** * Get the name attribute. * @return the name attribute. */ public String getName() { return name; } } /** * Add a fileset. * @param fs the fileset to add. */ public void addFileSet(FileSet fs) { files.add(fs); } /** * Names of the classes to process. * @return the array of classes. * @since Ant 1.6.3 */ public String[] getClasses() { Stream<String> stream = Stream.concat( files.stream() .map(fs -> fs.getDirectoryScanner(getProject()) .getIncludedFiles()) .flatMap(Stream::of) .map(s -> s.replace('\\', '.').replace('/', '.') .replaceFirst("\\.class$", "")), classes.stream().map(ClassArgument::getName)); if (cls != null) { stream = Stream.concat(Stream.of(cls.split(",")).map(String::trim), stream); } return stream.toArray(String[]::new); } /** * Set the destination directory into which the Java source * files should be compiled. * @param destDir the destination directory. */ public void setDestdir(File destDir) { this.destDir = destDir; } /** * The destination directory, if any. * @return the destination directory. * @since Ant 1.6.3 */ public File getDestdir() { return destDir; } /** * the classpath to use. * @param src the classpath. */ public void setClasspath(Path src) { if (classpath == null) { classpath = src; } else { classpath.append(src); } } /** * Path to use for classpath. * @return a path to be configured. */ public Path createClasspath() { if (classpath == null) { classpath = new Path(getProject()); } return classpath.createPath(); } /** * Adds a reference to a classpath defined elsewhere. * @param r a reference to a classpath. * @todo this needs to be documented in the HTML docs. */ public void setClasspathRef(Reference r) { createClasspath().setRefid(r); } /** * The classpath to use. * @return the classpath. * @since Ant 1.6.3 */ public Path getClasspath() { return classpath; } /** * location of bootstrap class files. * @param src the bootstrap classpath. */ public void setBootclasspath(Path src) { if (bootclasspath == null) { bootclasspath = src; } else { bootclasspath.append(src); } } /** * Adds path to bootstrap class files. * @return a path to be configured. */ public Path createBootclasspath() { if (bootclasspath == null) { bootclasspath = new Path(getProject()); } return bootclasspath.createPath(); } /** * To the bootstrap path, this adds a reference to a classpath defined elsewhere. * @param r a reference to a classpath * @todo this needs to be documented in the HTML. */ public void setBootClasspathRef(Reference r) { createBootclasspath().setRefid(r); } /** * The bootclasspath to use. * @return the bootclass path. * @since Ant 1.6.3 */ public Path getBootclasspath() { return bootclasspath; } /** * Concatenates the resulting header or source files for all * the classes listed into this file. * @param outputFile the output file. */ public void setOutputFile(File outputFile) { this.outputFile = outputFile; } /** * The destination file, if any. * @return the destination file. * @since Ant 1.6.3 */ public File getOutputfile() { return outputFile; } /** * If true, output files should always be written (JDK1.2 only). * @param force the value to use. */ public void setForce(boolean force) { this.force = force; } /** * Whether output files should always be written. * @return the force attribute. * @since Ant 1.6.3 */ public boolean getForce() { return force; } /** * If true, specifies that old JDK1.0-style header files should be * generated. * (otherwise output file contain JNI-style native method function * prototypes) (JDK1.2 only). * @param old if true use old 1.0 style header files. */ public void setOld(boolean old) { this.old = old; } /** * Whether old JDK1.0-style header files should be generated. * @return the old attribute. * @since Ant 1.6.3 */ public boolean getOld() { return old; } /** * If true, generate C declarations from the Java object file (used with old). * @param stubs if true, generated C declarations. */ public void setStubs(boolean stubs) { this.stubs = stubs; } /** * Whether C declarations from the Java object file should be generated. * @return the stubs attribute. * @since Ant 1.6.3 */ public boolean getStubs() { return stubs; } /** * If true, causes Javah to print a message concerning * the status of the generated files. * @param verbose if true, do verbose printing. */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Whether verbose output should get generated. * @return the verbose attribute. * @since Ant 1.6.3 */ public boolean getVerbose() { return verbose; } /** * Choose the implementation for this particular task. * @param impl the name of the implementation. * @since Ant 1.6.3 */ public void setImplementation(String impl) { if ("default".equals(impl)) { facade.setImplementation(JavahAdapterFactory.getDefault()); } else { facade.setImplementation(impl); } } /** * Adds an implementation specific command-line argument. * @return a ImplementationSpecificArgument to be configured. * * @since Ant 1.6.3 */ public ImplementationSpecificArgument createArg() { ImplementationSpecificArgument arg = new ImplementationSpecificArgument(); facade.addImplementationArgument(arg); return arg; } /** * Returns the (implementation specific) settings given as nested * arg elements. * @return the arguments. * @since Ant 1.6.3 */ public String[] getCurrentArgs() { return facade.getArgs(); } /** * The classpath to use when loading the javah implementation * if it is not a built-in one. * * @since Ant 1.8.0 */ public Path createImplementationClasspath() { return facade.getImplementationClasspath(getProject()); } /** * Set the adapter explicitly. * @since Ant 1.8.0 */ public void add(JavahAdapter adapter) { if (nestedAdapter != null) { throw new BuildException("Can't have more than one javah adapter"); } nestedAdapter = adapter; } /** * Execute the task * * @throws BuildException is there is a problem in the task execution. */ @Override public void execute() throws BuildException { // first off, make sure that we've got a srcdir final Set<Settings> settings = EnumSet.noneOf(Settings.class); if (cls != null) { settings.add(Settings.cls); } if (!classes.isEmpty()) { settings.add(Settings.classes); } if (!files.isEmpty()) { settings.add(Settings.files); } if (settings.size() > 1) { throw new BuildException("Exactly one of " + Settings.values() + " attributes is required", getLocation()); } if (destDir != null) { if (!destDir.isDirectory()) { throw new BuildException("destination directory \"" + destDir + "\" does not exist or is not a directory", getLocation()); } if (outputFile != null) { throw new BuildException("destdir and outputFile are mutually " + "exclusive", getLocation()); } } if (classpath == null) { classpath = new Path(getProject()).concatSystemClasspath("last"); } else { classpath = classpath.concatSystemClasspath("ignore"); } JavahAdapter ad = nestedAdapter != null ? nestedAdapter : JavahAdapterFactory.getAdapter(facade.getImplementation(), this, createImplementationClasspath()); if (!ad.compile(this)) { throw new BuildException("compilation failed"); } } /** * Logs the compilation parameters, adds the files to compile and logs the * "niceSourceList" * @param cmd the command line. */ public void logAndAddFiles(Commandline cmd) { logAndAddFilesToCompile(cmd); } /** * Logs the compilation parameters, adds the files to compile and logs the * "niceSourceList" * @param cmd the command line to add parameters to. */ protected void logAndAddFilesToCompile(Commandline cmd) { log("Compilation " + cmd.describeArguments(), Project.MSG_VERBOSE); String[] c = getClasses(); StringBuilder message = new StringBuilder("Class"); if (c.length > 1) { message.append("es"); } message.append(" to be compiled:"); message.append(StringUtils.LINE_SEP); for (String element : c) { cmd.createArgument().setValue(element); message.append(" ").append(element).append(StringUtils.LINE_SEP); } log(message.toString(), Project.MSG_VERBOSE); } private enum Settings { cls, files, classes; } }