1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional;
  18. import java.io.File;
  19. import java.lang.reflect.Constructor;
  20. import java.lang.reflect.Method;
  21. import java.util.Enumeration;
  22. import java.util.StringTokenizer;
  23. import java.util.Vector;
  24. import org.apache.tools.ant.BuildException;
  25. import org.apache.tools.ant.Project;
  26. import org.apache.tools.ant.Task;
  27. import org.apache.tools.ant.types.Commandline;
  28. import org.apache.tools.ant.types.Path;
  29. import org.apache.tools.ant.types.Reference;
  30. import org.apache.tools.ant.util.JavaEnvUtils;
  31. /**
  32. * Generates JNI header files using javah.
  33. *
  34. * This task can take the following arguments:
  35. * <ul>
  36. * <li>classname - the fully-qualified name of a class</li>
  37. * <li>outputFile - Concatenates the resulting header or source files for all
  38. * the classes listed into this file</li>
  39. * <li>destdir - Sets the directory where javah saves the header files or the
  40. * stub files</li>
  41. * <li>classpath</li>
  42. * <li>bootclasspath</li>
  43. * <li>force - Specifies that output files should always be written
  44. (JDK1.2 only)</li>
  45. * <li>old - Specifies that old JDK1.0-style header files should be generated
  46. * (otherwise output file contain JNI-style native method
  47. * function prototypes) (JDK1.2 only)</li>
  48. * <li>stubs - generate C declarations from the Java object file (used with old)</li>
  49. * <li>verbose - causes javah to print a message to stdout concerning the status
  50. * of the generated files</li>
  51. * <li>extdirs - Override location of installed extensions</li>
  52. * </ul>
  53. * Of these arguments, either <b>outputFile</b> or <b>destdir</b> is required,
  54. * but not both. More than one classname may be specified, using a comma-separated
  55. * list or by using <code><class name="xxx"></code> elements within the task.
  56. * <p>
  57. * When this task executes, it will generate C header and source files that
  58. * are needed to implement native methods.
  59. *
  60. */
  61. public class Javah extends Task {
  62. private Vector classes = new Vector(2);
  63. private String cls;
  64. private File destDir;
  65. private Path classpath = null;
  66. private File outputFile = null;
  67. private boolean verbose = false;
  68. private boolean force = false;
  69. private boolean old = false;
  70. private boolean stubs = false;
  71. private Path bootclasspath;
  72. //private Path extdirs;
  73. private static String lSep = System.getProperty("line.separator");
  74. /**
  75. * the fully-qualified name of the class (or classes, separated by commas).
  76. */
  77. public void setClass(String cls) {
  78. this.cls = cls;
  79. }
  80. /**
  81. * Adds class to process.
  82. */
  83. public ClassArgument createClass() {
  84. ClassArgument ga = new ClassArgument();
  85. classes.addElement(ga);
  86. return ga;
  87. }
  88. public class ClassArgument {
  89. private String name;
  90. public ClassArgument() {
  91. }
  92. public void setName(String name) {
  93. this.name = name;
  94. log("ClassArgument.name=" + name);
  95. }
  96. public String getName() {
  97. return name;
  98. }
  99. }
  100. /**
  101. * Set the destination directory into which the Java source
  102. * files should be compiled.
  103. */
  104. public void setDestdir(File destDir) {
  105. this.destDir = destDir;
  106. }
  107. /**
  108. * the classpath to use.
  109. */
  110. public void setClasspath(Path src) {
  111. if (classpath == null) {
  112. classpath = src;
  113. } else {
  114. classpath.append(src);
  115. }
  116. }
  117. /**
  118. * Path to use for classpath.
  119. */
  120. public Path createClasspath() {
  121. if (classpath == null) {
  122. classpath = new Path(getProject());
  123. }
  124. return classpath.createPath();
  125. }
  126. /**
  127. * Adds a reference to a classpath defined elsewhere.
  128. * @todo this needs to be documented in the HTML docs
  129. */
  130. public void setClasspathRef(Reference r) {
  131. createClasspath().setRefid(r);
  132. }
  133. /**
  134. * location of bootstrap class files.
  135. */
  136. public void setBootclasspath(Path src) {
  137. if (bootclasspath == null) {
  138. bootclasspath = src;
  139. } else {
  140. bootclasspath.append(src);
  141. }
  142. }
  143. /**
  144. * Adds path to bootstrap class files.
  145. */
  146. public Path createBootclasspath() {
  147. if (bootclasspath == null) {
  148. bootclasspath = new Path(getProject());
  149. }
  150. return bootclasspath.createPath();
  151. }
  152. /**
  153. * Adds a reference to a classpath defined elsewhere.
  154. * @todo this needs to be documented in the HTML
  155. */
  156. public void setBootClasspathRef(Reference r) {
  157. createBootclasspath().setRefid(r);
  158. }
  159. ///**
  160. // * Sets the extension directories that will be used during the
  161. // * compilation.
  162. // */
  163. //public void setExtdirs(Path extdirs) {
  164. // if (this.extdirs == null) {
  165. // this.extdirs = extdirs;
  166. // } else {
  167. // this.extdirs.append(extdirs);
  168. // }
  169. //}
  170. ///**
  171. // * Maybe creates a nested classpath element.
  172. // */
  173. //public Path createExtdirs() {
  174. // if (extdirs == null) {
  175. // extdirs = new Path(project);
  176. // }
  177. // return extdirs.createPath();
  178. //}
  179. /**
  180. * Concatenates the resulting header or source files for all
  181. * the classes listed into this file.
  182. */
  183. public void setOutputFile(File outputFile) {
  184. this.outputFile = outputFile;
  185. }
  186. /**
  187. * If true, output files should always be written (JDK1.2 only).
  188. */
  189. public void setForce(boolean force) {
  190. this.force = force;
  191. }
  192. /**
  193. * If true, specifies that old JDK1.0-style header files should be
  194. * generated.
  195. * (otherwise output file contain JNI-style native method function prototypes) (JDK1.2 only)
  196. */
  197. public void setOld(boolean old) {
  198. this.old = old;
  199. }
  200. /**
  201. * If true, generate C declarations from the Java object file (used with old).
  202. */
  203. public void setStubs(boolean stubs) {
  204. this.stubs = stubs;
  205. }
  206. /**
  207. * If true, causes Javah to print a message concerning
  208. * the status of the generated files.
  209. */
  210. public void setVerbose(boolean verbose) {
  211. this.verbose = verbose;
  212. }
  213. /**
  214. * Execute the task
  215. *
  216. * @throws BuildException is there is a problem in the task execution.
  217. */
  218. public void execute() throws BuildException {
  219. // first off, make sure that we've got a srcdir
  220. if ((cls == null) && (classes.size() == 0)) {
  221. throw new BuildException("class attribute must be set!",
  222. getLocation());
  223. }
  224. if ((cls != null) && (classes.size() > 0)) {
  225. throw new BuildException("set class attribute or class element, "
  226. + "not both.", getLocation());
  227. }
  228. if (destDir != null) {
  229. if (!destDir.isDirectory()) {
  230. throw new BuildException("destination directory \"" + destDir
  231. + "\" does not exist or is not a directory", getLocation());
  232. }
  233. if (outputFile != null) {
  234. throw new BuildException("destdir and outputFile are mutually "
  235. + "exclusive", getLocation());
  236. }
  237. }
  238. if (classpath == null) {
  239. classpath = (new Path(getProject())).concatSystemClasspath("last");
  240. } else {
  241. classpath = classpath.concatSystemClasspath("ignore");
  242. }
  243. String compiler = getProject().getProperty("build.compiler");
  244. if (compiler == null) {
  245. if (!JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)
  246. && !JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_2)) {
  247. compiler = "modern";
  248. } else {
  249. compiler = "classic";
  250. }
  251. }
  252. doClassicCompile();
  253. }
  254. // XXX
  255. // we need a way to not use the current classpath.
  256. /**
  257. * Performs a compile using the classic compiler that shipped with
  258. * JDK 1.1 and 1.2.
  259. */
  260. private void doClassicCompile() throws BuildException {
  261. Commandline cmd = setupJavahCommand();
  262. // Use reflection to be able to build on all JDKs
  263. /*
  264. // provide the compiler a different message sink - namely our own
  265. sun.tools.javac.Main compiler =
  266. new sun.tools.javac.Main(new LogOutputStream(this, Project.MSG_WARN), "javac");
  267. if (!compiler.compile(cmd.getArguments())) {
  268. throw new BuildException("Compile failed");
  269. }
  270. */
  271. try {
  272. Class javahMainClass = null;
  273. try {
  274. // first search for the "old" javah class in 1.4.2 tools.jar
  275. javahMainClass = Class.forName("com.sun.tools.javah.oldjavah.Main");
  276. } catch (ClassNotFoundException cnfe) {
  277. // assume older than 1.4.2 tools.jar
  278. javahMainClass = Class.forName("com.sun.tools.javah.Main");
  279. }
  280. // now search for the constructor that takes in String[] arguments.
  281. Class[] strings = new Class[] {String[].class};
  282. Constructor constructor = javahMainClass.getConstructor(strings);
  283. // construct the javah Main instance
  284. Object javahMain = constructor.newInstance(new Object[] {cmd.getArguments()});
  285. // find the run method
  286. Method runMethod = javahMainClass.getMethod("run", new Class[0]);
  287. runMethod.invoke(javahMain, new Object[0]);
  288. } catch (Exception ex) {
  289. if (ex instanceof BuildException) {
  290. throw (BuildException) ex;
  291. } else {
  292. throw new BuildException("Error starting javah: " + ex, ex, getLocation());
  293. }
  294. }
  295. }
  296. /**
  297. * Does the command line argument processing common to classic and
  298. * modern.
  299. */
  300. private Commandline setupJavahCommand() {
  301. Commandline cmd = new Commandline();
  302. if (destDir != null) {
  303. cmd.createArgument().setValue("-d");
  304. cmd.createArgument().setFile(destDir);
  305. }
  306. if (outputFile != null) {
  307. cmd.createArgument().setValue("-o");
  308. cmd.createArgument().setFile(outputFile);
  309. }
  310. if (classpath != null) {
  311. cmd.createArgument().setValue("-classpath");
  312. cmd.createArgument().setPath(classpath);
  313. }
  314. // JDK1.1 is rather simpler than JDK1.2
  315. if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
  316. if (verbose) {
  317. cmd.createArgument().setValue("-v");
  318. }
  319. } else {
  320. if (verbose) {
  321. cmd.createArgument().setValue("-verbose");
  322. }
  323. if (old) {
  324. cmd.createArgument().setValue("-old");
  325. }
  326. if (force) {
  327. cmd.createArgument().setValue("-force");
  328. }
  329. }
  330. if (stubs) {
  331. if (!old) {
  332. throw new BuildException("stubs only available in old mode.", getLocation());
  333. }
  334. cmd.createArgument().setValue("-stubs");
  335. }
  336. if (bootclasspath != null) {
  337. cmd.createArgument().setValue("-bootclasspath");
  338. cmd.createArgument().setPath(bootclasspath);
  339. }
  340. logAndAddFilesToCompile(cmd);
  341. return cmd;
  342. }
  343. /**
  344. * Logs the compilation parameters, adds the files to compile and logs the
  345. * "niceSourceList"
  346. */
  347. protected void logAndAddFilesToCompile(Commandline cmd) {
  348. int n = 0;
  349. log("Compilation " + cmd.describeArguments(),
  350. Project.MSG_VERBOSE);
  351. StringBuffer niceClassList = new StringBuffer();
  352. if (cls != null) {
  353. StringTokenizer tok = new StringTokenizer(cls, ",", false);
  354. while (tok.hasMoreTokens()) {
  355. String aClass = tok.nextToken().trim();
  356. cmd.createArgument().setValue(aClass);
  357. niceClassList.append(" " + aClass + lSep);
  358. n++;
  359. }
  360. }
  361. Enumeration e = classes.elements();
  362. while (e.hasMoreElements()) {
  363. ClassArgument arg = (ClassArgument) e.nextElement();
  364. String aClass = arg.getName();
  365. cmd.createArgument().setValue(aClass);
  366. niceClassList.append(" " + aClass + lSep);
  367. n++;
  368. }
  369. StringBuffer prefix = new StringBuffer("Class");
  370. if (n > 1) {
  371. prefix.append("es");
  372. }
  373. prefix.append(" to be compiled:");
  374. prefix.append(lSep);
  375. log(prefix.toString() + niceClassList.toString(), Project.MSG_VERBOSE);
  376. }
  377. }