1. /*
  2. * Copyright 2000,2002-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. /**
  18. * jlink.java links together multiple .jar files Original code by Patrick
  19. * Beard. Modifications to work with ANT by Matthew Kuperus Heun.
  20. *
  21. */
  22. package org.apache.tools.ant.taskdefs.optional.jlink;
  23. import java.io.BufferedInputStream;
  24. import java.io.File;
  25. import java.io.FileInputStream;
  26. import java.io.FileOutputStream;
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.util.Enumeration;
  30. import java.util.Vector;
  31. import java.util.zip.CRC32;
  32. import java.util.zip.Deflater;
  33. import java.util.zip.ZipEntry;
  34. import java.util.zip.ZipException;
  35. import java.util.zip.ZipFile;
  36. import java.util.zip.ZipOutputStream;
  37. public class jlink extends Object {
  38. /** The file that will be created by this instance of jlink. */
  39. public void setOutfile(String outfile) {
  40. if (outfile == null) {
  41. return;
  42. }
  43. this.outfile = outfile;
  44. }
  45. /** Adds a file to be merged into the output. */
  46. public void addMergeFile(String mergefile) {
  47. if (mergefile == null) {
  48. return;
  49. }
  50. mergefiles.addElement(mergefile);
  51. }
  52. /** Adds a file to be added into the output. */
  53. public void addAddFile(String addfile) {
  54. if (addfile == null) {
  55. return;
  56. }
  57. addfiles.addElement(addfile);
  58. }
  59. /** Adds several files to be merged into the output. */
  60. public void addMergeFiles(String[] mergefiles) {
  61. if (mergefiles == null) {
  62. return;
  63. }
  64. for (int i = 0; i < mergefiles.length; i++) {
  65. addMergeFile(mergefiles[i]);
  66. }
  67. }
  68. /** Adds several file to be added into the output. */
  69. public void addAddFiles(String[] addfiles) {
  70. if (addfiles == null) {
  71. return;
  72. }
  73. for (int i = 0; i < addfiles.length; i++) {
  74. addAddFile(addfiles[i]);
  75. }
  76. }
  77. /** Determines whether output will be compressed. */
  78. public void setCompression(boolean compress) {
  79. this.compression = compress;
  80. }
  81. /**
  82. * Performs the linking of files. Addfiles are added to the output as-is.
  83. * For example, a jar file is added to the output as a jar file. However,
  84. * mergefiles are first examined for their type. If it is a jar or zip
  85. * file, the contents will be extracted from the mergefile and entered
  86. * into the output. If a zip or jar file is encountered in a subdirectory
  87. * it will be added, not merged. If a directory is encountered, it becomes
  88. * the root entry of all the files below it. Thus, you can provide
  89. * multiple, disjoint directories, as addfiles: they will all be added in
  90. * a rational manner to outfile.
  91. */
  92. public void link() throws Exception {
  93. ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outfile));
  94. if (compression) {
  95. output.setMethod(ZipOutputStream.DEFLATED);
  96. output.setLevel(Deflater.DEFAULT_COMPRESSION);
  97. } else {
  98. output.setMethod(ZipOutputStream.STORED);
  99. }
  100. Enumeration merges = mergefiles.elements();
  101. while (merges.hasMoreElements()) {
  102. String path = (String) merges.nextElement();
  103. File f = new File(path);
  104. if (f.getName().endsWith(".jar") || f.getName().endsWith(".zip")) {
  105. //Do the merge
  106. mergeZipJarContents(output, f);
  107. } else {
  108. //Add this file to the addfiles Vector and add it
  109. //later at the top level of the output file.
  110. addAddFile(path);
  111. }
  112. }
  113. Enumeration adds = addfiles.elements();
  114. while (adds.hasMoreElements()) {
  115. String name = (String) adds.nextElement();
  116. File f = new File(name);
  117. if (f.isDirectory()) {
  118. //System.out.println("in jlink: adding directory contents of " + f.getPath());
  119. addDirContents(output, f, f.getName() + '/', compression);
  120. } else {
  121. addFile(output, f, "", compression);
  122. }
  123. }
  124. if (output != null) {
  125. try {
  126. output.close();
  127. } catch (IOException ioe) {
  128. }
  129. }
  130. }
  131. public static void main(String[] args) {
  132. // jlink output input1 ... inputN
  133. if (args.length < 2) {
  134. System.out.println("usage: jlink output input1 ... inputN");
  135. System.exit(1);
  136. }
  137. jlink linker = new jlink();
  138. linker.setOutfile(args[0]);
  139. // To maintain compatibility with the command-line version,
  140. // we will only add files to be merged.
  141. for (int i = 1; i < args.length; i++) {
  142. linker.addMergeFile(args[i]);
  143. }
  144. try {
  145. linker.link();
  146. } catch (Exception ex) {
  147. System.err.print(ex.getMessage());
  148. }
  149. }
  150. /*
  151. * Actually performs the merging of f into the output.
  152. * f should be a zip or jar file.
  153. */
  154. private void mergeZipJarContents(ZipOutputStream output, File f) throws IOException {
  155. //Check to see that the file with name "name" exists.
  156. if (!f.exists()) {
  157. return;
  158. }
  159. ZipFile zipf = new ZipFile(f);
  160. Enumeration entries = zipf.entries();
  161. while (entries.hasMoreElements()) {
  162. ZipEntry inputEntry = (ZipEntry) entries.nextElement();
  163. //Ignore manifest entries. They're bound to cause conflicts between
  164. //files that are being merged. User should supply their own
  165. //manifest file when doing the merge.
  166. String inputEntryName = inputEntry.getName();
  167. int index = inputEntryName.indexOf("META-INF");
  168. if (index < 0) {
  169. //META-INF not found in the name of the entry. Go ahead and process it.
  170. try {
  171. output.putNextEntry(processEntry(zipf, inputEntry));
  172. } catch (ZipException ex) {
  173. //If we get here, it could be because we are trying to put a
  174. //directory entry that already exists.
  175. //For example, we're trying to write "com", but a previous
  176. //entry from another mergefile was called "com".
  177. //In that case, just ignore the error and go on to the
  178. //next entry.
  179. String mess = ex.getMessage();
  180. if (mess.indexOf("duplicate") >= 0) {
  181. //It was the duplicate entry.
  182. continue;
  183. } else {
  184. // I hate to admit it, but we don't know what happened
  185. // here. Throw the Exception.
  186. throw ex;
  187. }
  188. }
  189. InputStream in = zipf.getInputStream(inputEntry);
  190. int len = buffer.length;
  191. int count = -1;
  192. while ((count = in.read(buffer, 0, len)) > 0) {
  193. output.write(buffer, 0, count);
  194. }
  195. in.close();
  196. output.closeEntry();
  197. }
  198. }
  199. zipf.close();
  200. }
  201. /*
  202. * Adds contents of a directory to the output.
  203. */
  204. private void addDirContents(ZipOutputStream output, File dir, String prefix,
  205. boolean compress) throws IOException {
  206. String[] contents = dir.list();
  207. for (int i = 0; i < contents.length; ++i) {
  208. String name = contents[i];
  209. File file = new File(dir, name);
  210. if (file.isDirectory()) {
  211. addDirContents(output, file, prefix + name + '/', compress);
  212. } else {
  213. addFile(output, file, prefix, compress);
  214. }
  215. }
  216. }
  217. /*
  218. * Gets the name of an entry in the file. This is the real name
  219. * which for a class is the name of the package with the class
  220. * name appended.
  221. */
  222. private String getEntryName(File file, String prefix) {
  223. String name = file.getName();
  224. if (!name.endsWith(".class")) {
  225. // see if the file is in fact a .class file, and determine its actual name.
  226. InputStream input = null;
  227. try {
  228. input = new FileInputStream(file);
  229. String className = ClassNameReader.getClassName(input);
  230. if (className != null) {
  231. return className.replace('.', '/') + ".class";
  232. }
  233. } catch (IOException ioe) {
  234. } finally {
  235. if (input != null) {
  236. try {
  237. input.close();
  238. } catch (IOException e) {
  239. }
  240. }
  241. }
  242. }
  243. System.out.println("From " + file.getPath() + " and prefix " + prefix
  244. + ", creating entry " + prefix + name);
  245. return (prefix + name);
  246. }
  247. /*
  248. * Adds a file to the output stream.
  249. */
  250. private void addFile(ZipOutputStream output, File file, String prefix,
  251. boolean compress) throws IOException {
  252. //Make sure file exists
  253. if (!file.exists()) {
  254. return;
  255. }
  256. ZipEntry entry = new ZipEntry(getEntryName(file, prefix));
  257. entry.setTime(file.lastModified());
  258. entry.setSize(file.length());
  259. if (!compress) {
  260. entry.setCrc(calcChecksum(file));
  261. }
  262. FileInputStream input = new FileInputStream(file);
  263. addToOutputStream(output, input, entry);
  264. }
  265. /*
  266. * A convenience method that several other methods might call.
  267. */
  268. private void addToOutputStream(ZipOutputStream output, InputStream input,
  269. ZipEntry ze) throws IOException {
  270. try {
  271. output.putNextEntry(ze);
  272. } catch (ZipException zipEx) {
  273. //This entry already exists. So, go with the first one.
  274. input.close();
  275. return;
  276. }
  277. int numBytes = -1;
  278. while ((numBytes = input.read(buffer)) > 0) {
  279. output.write(buffer, 0, numBytes);
  280. }
  281. output.closeEntry();
  282. input.close();
  283. }
  284. /*
  285. * A method that does the work on a given entry in a mergefile.
  286. * The big deal is to set the right parameters in the ZipEntry
  287. * on the output stream.
  288. */
  289. private ZipEntry processEntry(ZipFile zip, ZipEntry inputEntry) throws IOException {
  290. /*
  291. First, some notes.
  292. On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the
  293. ZipInputStream does not work for compressed (deflated) files. Those calls return -1.
  294. For uncompressed (stored) files, those calls do work.
  295. However, using ZipFile.getEntries() works for both compressed and
  296. uncompressed files.
  297. Now, from some simple testing I did, it seems that the value of CRC-32 is
  298. independent of the compression setting. So, it should be easy to pass this
  299. information on to the output entry.
  300. */
  301. String name = inputEntry.getName();
  302. if (!(inputEntry.isDirectory() || name.endsWith(".class"))) {
  303. try {
  304. InputStream input = zip.getInputStream(zip.getEntry(name));
  305. String className = ClassNameReader.getClassName(input);
  306. input.close();
  307. if (className != null) {
  308. name = className.replace('.', '/') + ".class";
  309. }
  310. } catch (IOException ioe) {
  311. }
  312. }
  313. ZipEntry outputEntry = new ZipEntry(name);
  314. outputEntry.setTime(inputEntry.getTime());
  315. outputEntry.setExtra(inputEntry.getExtra());
  316. outputEntry.setComment(inputEntry.getComment());
  317. outputEntry.setTime(inputEntry.getTime());
  318. if (compression) {
  319. outputEntry.setMethod(ZipEntry.DEFLATED);
  320. //Note, don't need to specify size or crc for compressed files.
  321. } else {
  322. outputEntry.setMethod(ZipEntry.STORED);
  323. outputEntry.setCrc(inputEntry.getCrc());
  324. outputEntry.setSize(inputEntry.getSize());
  325. }
  326. return outputEntry;
  327. }
  328. /*
  329. * Necessary in the case where you add a entry that
  330. * is not compressed.
  331. */
  332. private long calcChecksum(File f) throws IOException {
  333. BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
  334. return calcChecksum(in);
  335. }
  336. /*
  337. * Necessary in the case where you add a entry that
  338. * is not compressed.
  339. */
  340. private long calcChecksum(InputStream in) throws IOException {
  341. CRC32 crc = new CRC32();
  342. int len = buffer.length;
  343. int count = -1;
  344. int haveRead = 0;
  345. while ((count = in.read(buffer, 0, len)) > 0) {
  346. haveRead += count;
  347. crc.update(buffer, 0, count);
  348. }
  349. in.close();
  350. return crc.getValue();
  351. }
  352. private String outfile = null;
  353. private Vector mergefiles = new Vector(10);
  354. private Vector addfiles = new Vector(10);
  355. private boolean compression = false;
  356. byte[] buffer = new byte[8192];
  357. }