Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 454   Methods: 16
NCLOC: 241   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
jlink.java 0% 0% 0% 0%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000,2002 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 /**
 55   
  * jlink.java links together multiple .jar files Original code by Patrick
 56   
  * Beard. Modifications to work with ANT by Matthew Kuperus Heun.
 57   
  *
 58   
  * @author <a href="mailto:beard@netscape.com>Patrick C. Beard</a> .
 59   
  * @author <a href="mailto:matthew.k.heun@gaerospace.com>Matthew Kuperus Heun
 60   
  *      </a>
 61   
  */
 62   
 package org.apache.tools.ant.taskdefs.optional.jlink;
 63   
 
 64   
 import java.io.BufferedInputStream;
 65   
 import java.io.File;
 66   
 import java.io.FileInputStream;
 67   
 import java.io.FileOutputStream;
 68   
 import java.io.IOException;
 69   
 import java.io.InputStream;
 70   
 import java.util.Enumeration;
 71   
 import java.util.Vector;
 72   
 import java.util.zip.CRC32;
 73   
 import java.util.zip.Deflater;
 74   
 import java.util.zip.ZipEntry;
 75   
 import java.util.zip.ZipException;
 76   
 import java.util.zip.ZipFile;
 77   
 import java.util.zip.ZipOutputStream;
 78   
 
 79   
 public class jlink extends Object {
 80   
 
 81   
     /** The file that will be created by this instance of jlink.  */
 82  0
     public void setOutfile(String outfile) {
 83  0
         if (outfile == null) {
 84  0
             return;
 85   
         }
 86  0
         this.outfile = outfile;
 87   
     }
 88   
 
 89   
 
 90   
     /** Adds a file to be merged into the output.  */
 91  0
     public void addMergeFile(String mergefile) {
 92  0
         if (mergefile == null) {
 93  0
             return;
 94   
         }
 95  0
         mergefiles.addElement(mergefile);
 96   
     }
 97   
 
 98   
 
 99   
     /** Adds a file to be added into the output.  */
 100  0
     public void addAddFile(String addfile) {
 101  0
         if (addfile == null) {
 102  0
             return;
 103   
         }
 104  0
         addfiles.addElement(addfile);
 105   
     }
 106   
 
 107   
 
 108   
     /** Adds several files to be merged into the output.  */
 109  0
     public void addMergeFiles(String[] mergefiles) {
 110  0
         if (mergefiles == null) {
 111  0
             return;
 112   
         }
 113  0
         for (int i = 0; i < mergefiles.length; i++) {
 114  0
             addMergeFile(mergefiles[i]);
 115   
         }
 116   
     }
 117   
 
 118   
 
 119   
     /** Adds several file to be added into the output.  */
 120  0
     public void addAddFiles(String[] addfiles) {
 121  0
         if (addfiles == null) {
 122  0
             return;
 123   
         }
 124  0
         for (int i = 0; i < addfiles.length; i++) {
 125  0
             addAddFile(addfiles[i]);
 126   
         }
 127   
     }
 128   
 
 129   
 
 130   
     /** Determines whether output will be compressed.  */
 131  0
     public void setCompression(boolean compress) {
 132  0
         this.compression = compress;
 133   
     }
 134   
 
 135   
 
 136   
     /**
 137   
      * Performs the linking of files. Addfiles are added to the output as-is.
 138   
      * For example, a jar file is added to the output as a jar file. However,
 139   
      * mergefiles are first examined for their type. If it is a jar or zip
 140   
      * file, the contents will be extracted from the mergefile and entered
 141   
      * into the output. If a zip or jar file is encountered in a subdirectory
 142   
      * it will be added, not merged. If a directory is encountered, it becomes
 143   
      * the root entry of all the files below it. Thus, you can provide
 144   
      * multiple, disjoint directories, as addfiles: they will all be added in
 145   
      * a rational manner to outfile.
 146   
      */
 147  0
     public void link() throws Exception {
 148  0
         ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outfile));
 149   
 
 150  0
         if (compression) {
 151  0
             output.setMethod(ZipOutputStream.DEFLATED);
 152  0
             output.setLevel(Deflater.DEFAULT_COMPRESSION);
 153   
         } else {
 154  0
             output.setMethod(ZipOutputStream.STORED);
 155   
         }
 156   
 
 157  0
         Enumeration merges = mergefiles.elements();
 158   
 
 159  0
         while (merges.hasMoreElements()) {
 160  0
             String path = (String) merges.nextElement();
 161  0
             File f = new File(path);
 162   
 
 163  0
             if (f.getName().endsWith(".jar") || f.getName().endsWith(".zip")) {
 164   
                 //Do the merge
 165  0
                 mergeZipJarContents(output, f);
 166   
             } else {
 167   
                 //Add this file to the addfiles Vector and add it
 168   
                 //later at the top level of the output file.
 169  0
                 addAddFile(path);
 170   
             }
 171   
         }
 172   
 
 173  0
         Enumeration adds = addfiles.elements();
 174   
 
 175  0
         while (adds.hasMoreElements()) {
 176  0
             String name = (String) adds.nextElement();
 177  0
             File f = new File(name);
 178   
 
 179  0
             if (f.isDirectory()) {
 180   
                 //System.out.println("in jlink: adding directory contents of " + f.getPath());
 181  0
                 addDirContents(output, f, f.getName() + '/', compression);
 182   
             } else {
 183  0
                 addFile(output, f, "", compression);
 184   
             }
 185   
         }
 186  0
         if (output != null) {
 187  0
             try {
 188  0
                 output.close();
 189   
             } catch (IOException ioe) {
 190   
             }
 191   
         }
 192   
     }
 193   
 
 194   
 
 195  0
     public static void main(String[] args) {
 196   
         // jlink output input1 ... inputN
 197  0
         if (args.length < 2) {
 198  0
             System.out.println("usage: jlink output input1 ... inputN");
 199  0
             System.exit(1);
 200   
         }
 201  0
         jlink linker = new jlink();
 202   
 
 203  0
         linker.setOutfile(args[0]);
 204   
         //To maintain compatibility with the command-line version, we will only add files to be merged.
 205  0
         for (int i = 1; i < args.length; i++) {
 206  0
             linker.addMergeFile(args[i]);
 207   
         }
 208  0
         try {
 209  0
             linker.link();
 210   
         } catch (Exception ex) {
 211  0
             System.err.print(ex.getMessage());
 212   
         }
 213   
     }
 214   
 
 215   
 
 216   
     /*
 217   
      * Actually performs the merging of f into the output.
 218   
      * f should be a zip or jar file.
 219   
      */
 220  0
     private void mergeZipJarContents(ZipOutputStream output, File f) throws IOException {
 221   
         //Check to see that the file with name "name" exists.
 222  0
         if (!f.exists()) {
 223  0
             return;
 224   
         }
 225  0
         ZipFile zipf = new ZipFile(f);
 226  0
         Enumeration entries = zipf.entries();
 227   
 
 228  0
         while (entries.hasMoreElements()) {
 229  0
             ZipEntry inputEntry = (ZipEntry) entries.nextElement();
 230   
             //Ignore manifest entries.  They're bound to cause conflicts between
 231   
             //files that are being merged.  User should supply their own
 232   
             //manifest file when doing the merge.
 233  0
             String inputEntryName = inputEntry.getName();
 234  0
             int index = inputEntryName.indexOf("META-INF");
 235   
 
 236  0
             if (index < 0) {
 237   
                 //META-INF not found in the name of the entry. Go ahead and process it.
 238  0
                 try {
 239  0
                     output.putNextEntry(processEntry(zipf, inputEntry));
 240   
                 } catch (ZipException ex) {
 241   
                     //If we get here, it could be because we are trying to put a
 242   
                     //directory entry that already exists.
 243   
                     //For example, we're trying to write "com", but a previous
 244   
                     //entry from another mergefile was called "com".
 245   
                     //In that case, just ignore the error and go on to the
 246   
                     //next entry.
 247  0
                     String mess = ex.getMessage();
 248   
 
 249  0
                     if (mess.indexOf("duplicate") >= 0) {
 250   
                         //It was the duplicate entry.
 251  0
                         continue;
 252   
                     } else {
 253   
                         //I hate to admit it, but we don't know what happened here.  Throw the Exception.
 254  0
                         throw ex;
 255   
                     }
 256   
                 }
 257   
 
 258  0
                 InputStream in = zipf.getInputStream(inputEntry);
 259  0
                 int len = buffer.length;
 260  0
                 int count = -1;
 261   
 
 262  0
                 while ((count = in.read(buffer, 0, len)) > 0) {
 263  0
                     output.write(buffer, 0, count);
 264   
                 }
 265  0
                 in.close();
 266  0
                 output.closeEntry();
 267   
             }
 268   
         }
 269  0
         zipf.close();
 270   
     }
 271   
 
 272   
 
 273   
     /*
 274   
      * Adds contents of a directory to the output.
 275   
      */
 276  0
     private void addDirContents(ZipOutputStream output, File dir, String prefix, boolean compress) throws IOException {
 277  0
         String[] contents = dir.list();
 278   
 
 279  0
         for (int i = 0; i < contents.length; ++i) {
 280  0
             String name = contents[i];
 281  0
             File file = new File(dir, name);
 282   
 
 283  0
             if (file.isDirectory()) {
 284  0
                 addDirContents(output, file, prefix + name + '/', compress);
 285   
             } else {
 286  0
                 addFile(output, file, prefix, compress);
 287   
             }
 288   
         }
 289   
     }
 290   
 
 291   
 
 292   
     /*
 293   
      * Gets the name of an entry in the file.  This is the real name
 294   
      * which for a class is the name of the package with the class
 295   
      * name appended.
 296   
      */
 297  0
     private String getEntryName(File file, String prefix) {
 298  0
         String name = file.getName();
 299   
 
 300  0
         if (!name.endsWith(".class")) {
 301   
             // see if the file is in fact a .class file, and determine its actual name.
 302  0
             try {
 303  0
                 InputStream input = new FileInputStream(file);
 304  0
                 String className = ClassNameReader.getClassName(input);
 305   
 
 306  0
                 input.close();
 307  0
                 if (className != null) {
 308  0
                     return className.replace('.', '/') + ".class";
 309   
                 }
 310   
             } catch (IOException ioe) {
 311   
             }
 312   
         }
 313  0
         System.out.println("From " + file.getPath() + " and prefix " + prefix + ", creating entry " + prefix + name);
 314  0
         return (prefix + name);
 315   
     }
 316   
 
 317   
 
 318   
     /*
 319   
      * Adds a file to the output stream.
 320   
      */
 321  0
     private void addFile(ZipOutputStream output, File file, String prefix, boolean compress) throws IOException {
 322   
         //Make sure file exists
 323  0
         if (!file.exists()) {
 324  0
             return;
 325   
         }
 326  0
         ZipEntry entry = new ZipEntry(getEntryName(file, prefix));
 327   
 
 328  0
         entry.setTime(file.lastModified());
 329  0
         entry.setSize(file.length());
 330  0
         if (!compress) {
 331  0
             entry.setCrc(calcChecksum(file));
 332   
         }
 333  0
         FileInputStream input = new FileInputStream(file);
 334   
 
 335  0
         addToOutputStream(output, input, entry);
 336   
     }
 337   
 
 338   
 
 339   
     /*
 340   
      * A convenience method that several other methods might call.
 341   
      */
 342  0
     private void addToOutputStream(ZipOutputStream output, InputStream input, ZipEntry ze) throws IOException {
 343  0
         try {
 344  0
             output.putNextEntry(ze);
 345   
         } catch (ZipException zipEx) {
 346   
             //This entry already exists. So, go with the first one.
 347  0
             input.close();
 348  0
             return;
 349   
         }
 350   
 
 351  0
         int numBytes = -1;
 352   
 
 353  0
         while ((numBytes = input.read(buffer)) > 0) {
 354  0
             output.write(buffer, 0, numBytes);
 355   
         }
 356  0
         output.closeEntry();
 357  0
         input.close();
 358   
     }
 359   
 
 360   
 
 361   
     /*
 362   
      * A method that does the work on a given entry in a mergefile.
 363   
      * The big deal is to set the right parameters in the ZipEntry
 364   
      * on the output stream.
 365   
      */
 366  0
     private ZipEntry processEntry(ZipFile zip, ZipEntry inputEntry) throws IOException {
 367   
         /*
 368   
           First, some notes.
 369   
           On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the
 370   
           ZipInputStream does not work for compressed (deflated) files.  Those calls return -1.
 371   
           For uncompressed (stored) files, those calls do work.
 372   
           However, using ZipFile.getEntries() works for both compressed and
 373   
           uncompressed files.
 374   
 
 375   
           Now, from some simple testing I did, it seems that the value of CRC-32 is
 376   
           independent of the compression setting. So, it should be easy to pass this
 377   
           information on to the output entry.
 378   
         */
 379  0
         String name = inputEntry.getName();
 380   
 
 381  0
         if (!(inputEntry.isDirectory() || name.endsWith(".class"))) {
 382  0
             try {
 383  0
                 InputStream input = zip.getInputStream(zip.getEntry(name));
 384  0
                 String className = ClassNameReader.getClassName(input);
 385   
 
 386  0
                 input.close();
 387  0
                 if (className != null) {
 388  0
                     name = className.replace('.', '/') + ".class";
 389   
                 }
 390   
             } catch (IOException ioe) {
 391   
             }
 392   
         }
 393  0
         ZipEntry outputEntry = new ZipEntry(name);
 394   
 
 395  0
         outputEntry.setTime(inputEntry.getTime());
 396  0
         outputEntry.setExtra(inputEntry.getExtra());
 397  0
         outputEntry.setComment(inputEntry.getComment());
 398  0
         outputEntry.setTime(inputEntry.getTime());
 399  0
         if (compression) {
 400  0
             outputEntry.setMethod(ZipEntry.DEFLATED);
 401   
             //Note, don't need to specify size or crc for compressed files.
 402   
         } else {
 403  0
             outputEntry.setMethod(ZipEntry.STORED);
 404  0
             outputEntry.setCrc(inputEntry.getCrc());
 405  0
             outputEntry.setSize(inputEntry.getSize());
 406   
         }
 407  0
         return outputEntry;
 408   
     }
 409   
 
 410   
 
 411   
     /*
 412   
      * Necessary in the case where you add a entry that
 413   
      * is not compressed.
 414   
      */
 415  0
     private long calcChecksum(File f) throws IOException {
 416  0
         BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
 417   
 
 418  0
         return calcChecksum(in);
 419   
     }
 420   
 
 421   
 
 422   
     /*
 423   
      * Necessary in the case where you add a entry that
 424   
      * is not compressed.
 425   
      */
 426  0
     private long calcChecksum(InputStream in) throws IOException {
 427  0
         CRC32 crc = new CRC32();
 428  0
         int len = buffer.length;
 429  0
         int count = -1;
 430  0
         int haveRead = 0;
 431   
 
 432  0
         while ((count = in.read(buffer, 0, len)) > 0) {
 433  0
             haveRead += count;
 434  0
             crc.update(buffer, 0, count);
 435   
         }
 436  0
         in.close();
 437  0
         return crc.getValue();
 438   
     }
 439   
 
 440   
 
 441   
     private String outfile = null;
 442   
 
 443   
     private Vector mergefiles = new Vector(10);
 444   
 
 445   
     private Vector addfiles = new Vector(10);
 446   
 
 447   
     private boolean compression = false;
 448   
 
 449   
     byte[] buffer = new byte[8192];
 450   
 
 451   
 }
 452   
 
 453   
 
 454