Coverage Report - org.codehaus.plexus.archiver.zip.AbstractZipArchiver
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractZipArchiver
65%
162/247
64%
72/112
3.324
AbstractZipArchiver$1
50%
2/4
N/A
3.324
AbstractZipArchiver$2
100%
2/2
N/A
3.324
 
 1  
 /**
 2  
  *
 3  
  * Copyright 2004 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  * http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.codehaus.plexus.archiver.zip;
 18  
 
 19  
 import java.io.ByteArrayInputStream;
 20  
 import java.io.File;
 21  
 import java.io.FileOutputStream;
 22  
 import java.io.IOException;
 23  
 import java.io.InputStream;
 24  
 import java.io.OutputStream;
 25  
 import java.io.SequenceInputStream;
 26  
 import java.nio.ByteBuffer;
 27  
 import java.nio.charset.Charset;
 28  
 import java.util.Hashtable;
 29  
 import java.util.Stack;
 30  
 import java.util.concurrent.ExecutionException;
 31  
 import java.util.zip.CRC32;
 32  
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 33  
 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
 34  
 import org.apache.commons.compress.archivers.zip.ZipEncoding;
 35  
 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
 36  
 import org.apache.commons.compress.parallel.InputStreamSupplier;
 37  
 import org.apache.commons.compress.utils.Charsets;
 38  
 import org.codehaus.plexus.archiver.AbstractArchiver;
 39  
 import org.codehaus.plexus.archiver.ArchiveEntry;
 40  
 import org.codehaus.plexus.archiver.Archiver;
 41  
 import org.codehaus.plexus.archiver.ArchiverException;
 42  
 import org.codehaus.plexus.archiver.ResourceIterator;
 43  
 import org.codehaus.plexus.archiver.UnixStat;
 44  
 import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
 45  
 import org.codehaus.plexus.archiver.util.ResourceUtils;
 46  
 import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
 47  
 import org.codehaus.plexus.components.io.resources.PlexusIoResource;
 48  
 import org.codehaus.plexus.util.FileUtils;
 49  
 import org.codehaus.plexus.util.IOUtil;
 50  
 import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
 51  
 import static org.codehaus.plexus.archiver.util.Streams.fileOutputStream;
 52  
 
 53  
 @SuppressWarnings(
 54  
 {
 55  
     "NullableProblems", "UnusedDeclaration"
 56  
 } )
 57  68
 public abstract class AbstractZipArchiver
 58  
     extends AbstractArchiver
 59  
 {
 60  
 
 61  
     private String comment;
 62  
 
 63  
     /**
 64  
      * Encoding to use for filenames, defaults to the platform's
 65  
      * default encoding.
 66  
      */
 67  68
     private String encoding = "UTF8";
 68  
 
 69  68
     private boolean doCompress = true;
 70  
 
 71  68
     private boolean recompressAddedZips = true;
 72  
 
 73  68
     private boolean doUpdate = false;
 74  
 
 75  
     // shadow of the above if the value is altered in execute
 76  68
     private boolean savedDoUpdate = false;
 77  
 
 78  68
     protected String archiveType = "zip";
 79  
 
 80  68
     private boolean doFilesonly = false;
 81  
 
 82  68
     protected final Hashtable<String, String> entries = new Hashtable<String, String>();
 83  
 
 84  68
     protected final AddedDirs addedDirs = new AddedDirs();
 85  
 
 86  1
     private static final long EMPTY_CRC = new CRC32().getValue();
 87  
 
 88  68
     protected boolean doubleFilePass = false;
 89  
 
 90  68
     protected boolean skipWriting = false;
 91  
 
 92  
     /**
 93  
      * @deprecated Use {@link Archiver#setDuplicateBehavior(String)} instead.
 94  
      */
 95  68
     protected final String duplicate = Archiver.DUPLICATES_SKIP;
 96  
 
 97  
     /**
 98  
      * true when we are adding new files into the Zip file, as opposed
 99  
      * to adding back the unchanged files
 100  
      */
 101  68
     protected boolean addingNewFiles = false;
 102  
 
 103  
     // Renamed version of original file, if it exists
 104  68
     private File renamedFile = null;
 105  
 
 106  
     private File zipFile;
 107  
 
 108  
     private boolean success;
 109  
 
 110  
     private ConcurrentJarCreator zOut;
 111  
 
 112  
     protected ZipArchiveOutputStream zipArchiveOutputStream;
 113  
 
 114  
     public String getComment()
 115  
     {
 116  0
         return comment;
 117  
     }
 118  
 
 119  
     public void setComment( String comment )
 120  
     {
 121  0
         this.comment = comment;
 122  0
     }
 123  
 
 124  
     public String getEncoding()
 125  
     {
 126  147
         return encoding;
 127  
     }
 128  
 
 129  
     public void setEncoding( String encoding )
 130  
     {
 131  35
         this.encoding = encoding;
 132  35
     }
 133  
 
 134  
     public void setCompress( boolean compress )
 135  
     {
 136  1
         this.doCompress = compress;
 137  1
     }
 138  
 
 139  
     public boolean isCompress()
 140  
     {
 141  0
         return doCompress;
 142  
     }
 143  
 
 144  
     public boolean isRecompressAddedZips()
 145  
     {
 146  0
         return recompressAddedZips;
 147  
     }
 148  
 
 149  
     public void setRecompressAddedZips( boolean recompressAddedZips )
 150  
     {
 151  1
         this.recompressAddedZips = recompressAddedZips;
 152  1
     }
 153  
 
 154  
     public void setUpdateMode( boolean update )
 155  
     {
 156  0
         this.doUpdate = update;
 157  0
         savedDoUpdate = doUpdate;
 158  0
     }
 159  
 
 160  
     public boolean isInUpdateMode()
 161  
     {
 162  19
         return doUpdate;
 163  
     }
 164  
 
 165  
     /**
 166  
      * If true, emulate Sun's jar utility by not adding parent directories;
 167  
      * optional, defaults to false.
 168  
      *
 169  
      * @param f true to emilate sun jar utility
 170  
      */
 171  
     public void setFilesonly( boolean f )
 172  
     {
 173  0
         doFilesonly = f;
 174  0
     }
 175  
 
 176  
     public boolean isFilesonly()
 177  
     {
 178  0
         return doFilesonly;
 179  
     }
 180  
 
 181  
     @Override
 182  
     protected void execute()
 183  
         throws ArchiverException, IOException
 184  
     {
 185  55
         if ( !checkForced() )
 186  
         {
 187  2
             return;
 188  
         }
 189  
 
 190  53
         if ( doubleFilePass )
 191  
         {
 192  0
             skipWriting = true;
 193  0
             createArchiveMain();
 194  0
             skipWriting = false;
 195  0
             createArchiveMain();
 196  
         }
 197  
         else
 198  
         {
 199  53
             createArchiveMain();
 200  
         }
 201  
 
 202  52
         finalizeZipOutputStream( zOut );
 203  52
     }
 204  
 
 205  
     protected void finalizeZipOutputStream( ConcurrentJarCreator zOut )
 206  
         throws IOException, ArchiverException
 207  
     {
 208  35
     }
 209  
 
 210  
     private void createArchiveMain()
 211  
         throws ArchiverException, IOException
 212  
     {
 213  
         //noinspection deprecation
 214  53
         if ( !Archiver.DUPLICATES_SKIP.equals( duplicate ) )
 215  
         {
 216  
             //noinspection deprecation
 217  0
             setDuplicateBehavior( duplicate );
 218  
         }
 219  
 
 220  53
         ResourceIterator iter = getResources();
 221  53
         if ( !iter.hasNext() && !hasVirtualFiles() )
 222  
         {
 223  1
             throw new EmptyArchiveException( "archive cannot be empty" );
 224  
         }
 225  
 
 226  52
         zipFile = getDestFile();
 227  
 
 228  52
         if ( zipFile == null )
 229  
         {
 230  0
             throw new ArchiverException( "You must set the destination " + archiveType + "file." );
 231  
         }
 232  
 
 233  52
         if ( zipFile.exists() && !zipFile.isFile() )
 234  
         {
 235  0
             throw new ArchiverException( zipFile + " isn't a file." );
 236  
         }
 237  
 
 238  52
         if ( zipFile.exists() && !zipFile.canWrite() )
 239  
         {
 240  0
             throw new ArchiverException( zipFile + " is read-only." );
 241  
         }
 242  
 
 243  
         // Whether or not an actual update is required -
 244  
         // we don't need to update if the original file doesn't exist
 245  52
         addingNewFiles = true;
 246  
 
 247  52
         if ( doUpdate && !zipFile.exists() )
 248  
         {
 249  0
             doUpdate = false;
 250  0
             getLogger().debug( "ignoring update attribute as " + archiveType + " doesn't exist." );
 251  
         }
 252  
 
 253  52
         success = false;
 254  
 
 255  52
         if ( doUpdate )
 256  
         {
 257  0
             renamedFile = FileUtils.createTempFile( "zip", ".tmp", zipFile.getParentFile() );
 258  0
             renamedFile.deleteOnExit();
 259  
 
 260  
             try
 261  
             {
 262  0
                 FileUtils.rename( zipFile, renamedFile );
 263  
             }
 264  0
             catch ( SecurityException e )
 265  
             {
 266  0
                 getLogger().debug( e.toString() );
 267  0
                 throw new ArchiverException(
 268  
                     "Not allowed to rename old file (" + zipFile.getAbsolutePath() + ") to temporary file", e );
 269  
             }
 270  0
             catch ( IOException e )
 271  
             {
 272  0
                 getLogger().debug( e.toString() );
 273  0
                 throw new ArchiverException(
 274  
                     "Unable to rename old file (" + zipFile.getAbsolutePath() + ") to temporary file", e );
 275  0
             }
 276  
         }
 277  
 
 278  52
         String action = doUpdate ? "Updating " : "Building ";
 279  
 
 280  52
         getLogger().info( action + archiveType + ": " + zipFile.getAbsolutePath() );
 281  
 
 282  52
         if ( !skipWriting )
 283  
         {
 284  52
             zipArchiveOutputStream =
 285  
                 new ZipArchiveOutputStream( bufferedOutputStream( fileOutputStream( zipFile, "zip" ) ) );
 286  52
             zipArchiveOutputStream.setEncoding( encoding );
 287  52
             zipArchiveOutputStream.setCreateUnicodeExtraFields( this.getUnicodeExtraFieldPolicy() );
 288  52
             zipArchiveOutputStream.setMethod(
 289  
                 doCompress ? ZipArchiveOutputStream.DEFLATED : ZipArchiveOutputStream.STORED );
 290  
 
 291  52
             zOut = new ConcurrentJarCreator( recompressAddedZips, Runtime.getRuntime().availableProcessors() );
 292  
         }
 293  52
         initZipOutputStream( zOut );
 294  
 
 295  
         // Add the new files to the archive.
 296  52
         addResources( iter, zOut );
 297  
 
 298  
         // If we've been successful on an update, delete the
 299  
         // temporary file
 300  52
         if ( doUpdate )
 301  
         {
 302  0
             if ( !renamedFile.delete() )
 303  
             {
 304  0
                 getLogger().warn( "Warning: unable to delete temporary file " + renamedFile.getName() );
 305  
             }
 306  
         }
 307  52
         success = true;
 308  52
     }
 309  
 
 310  
     /**
 311  
      * Gets the {@code UnicodeExtraFieldPolicy} to apply.
 312  
      *
 313  
      * @return {@link ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NEVER}, if the effective encoding is
 314  
      * UTF-8; {@link ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS}, if the effective encoding is not
 315  
      * UTF-8.
 316  
      *
 317  
      * @see #getEncoding()
 318  
      */
 319  
     private ZipArchiveOutputStream.UnicodeExtraFieldPolicy getUnicodeExtraFieldPolicy()
 320  
     {
 321  
         // Copied from ZipEncodingHelper.isUTF8()
 322  52
         String effectiveEncoding = this.getEncoding();
 323  
 
 324  52
         if ( effectiveEncoding == null )
 325  
         {
 326  0
             effectiveEncoding = Charset.defaultCharset().name();
 327  
         }
 328  
 
 329  52
         boolean utf8 = Charsets.UTF_8.name().equalsIgnoreCase( effectiveEncoding );
 330  
 
 331  52
         if ( !utf8 )
 332  
         {
 333  48
             for ( String alias : Charsets.UTF_8.aliases() )
 334  
             {
 335  96
                 if ( alias.equalsIgnoreCase( effectiveEncoding ) )
 336  
                 {
 337  48
                     utf8 = true;
 338  48
                     break;
 339  
                 }
 340  48
             }
 341  
         }
 342  
 
 343  
         // Using ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NOT_ENCODEABLE as a fallback makes no sense here.
 344  
         // If the encoding is UTF-8 and a name is not encodeable using UTF-8, the Info-ZIP Unicode Path extra field
 345  
         // is not encodeable as well. If the effective encoding is not UTF-8, we always add the extra field. If it is
 346  
         // UTF-8, we never add the extra field.
 347  52
         return utf8
 348  
                    ? ZipArchiveOutputStream.UnicodeExtraFieldPolicy.NEVER
 349  
                    : ZipArchiveOutputStream.UnicodeExtraFieldPolicy.ALWAYS;
 350  
 
 351  
     }
 352  
 
 353  
     /**
 354  
      * Add the given resources.
 355  
      *
 356  
      * @param resources the resources to add
 357  
      * @param zOut the stream to write to
 358  
      */
 359  
     @SuppressWarnings(
 360  
     {
 361  
         "JavaDoc"
 362  
     } )
 363  
     protected final void addResources( ResourceIterator resources, ConcurrentJarCreator zOut )
 364  
         throws IOException, ArchiverException
 365  
     {
 366  48299
         while ( resources.hasNext() )
 367  
         {
 368  48247
             ArchiveEntry entry = resources.next();
 369  48247
             String name = entry.getName();
 370  48247
             name = name.replace( File.separatorChar, '/' );
 371  
 
 372  48247
             if ( "".equals( name ) )
 373  
             {
 374  25
                 continue;
 375  
             }
 376  
 
 377  48222
             if ( entry.getResource().isDirectory() && !name.endsWith( "/" ) )
 378  
             {
 379  757
                 name = name + "/";
 380  
             }
 381  
 
 382  48222
             addParentDirs( entry, null, name, zOut );
 383  
 
 384  48222
             if ( entry.getResource().isFile() )
 385  
             {
 386  47356
                 zipFile( entry, zOut, name );
 387  
             }
 388  
             else
 389  
             {
 390  866
                 zipDir( entry.getResource(), zOut, name, entry.getMode(), encoding );
 391  
             }
 392  48222
         }
 393  52
     }
 394  
 
 395  
     /**
 396  
      * Ensure all parent dirs of a given entry have been added.
 397  
      * <p/>
 398  
      * This method is computed in terms of the potentially remapped entry (that may be disconnected from the file system)
 399  
      * we do not *relly* know the entry's connection to the file system so establishing the attributes of the parents can
 400  
      * be impossible and is not really supported.
 401  
      */
 402  
     @SuppressWarnings(
 403  
     {
 404  
         "JavaDoc"
 405  
     } )
 406  
     private void addParentDirs( ArchiveEntry archiveEntry, File baseDir, String entry, ConcurrentJarCreator zOut )
 407  
         throws IOException
 408  
     {
 409  48222
         if ( !doFilesonly && getIncludeEmptyDirs() )
 410  
         {
 411  48222
             Stack<String> directories = addedDirs.asStringStack( entry );
 412  
 
 413  48374
             while ( !directories.isEmpty() )
 414  
             {
 415  152
                 String dir = directories.pop();
 416  
                 File f;
 417  152
                 if ( baseDir != null )
 418  
                 {
 419  0
                     f = new File( baseDir, dir );
 420  
                 }
 421  
                 else
 422  
                 {
 423  152
                     f = new File( dir );
 424  
                 }
 425  
                 // the
 426  
                 // At this point we could do something like read the atr
 427  152
                 final PlexusIoResource res = new AnonymousResource( f );
 428  152
                 zipDir( res, zOut, dir, archiveEntry.getDefaultDirMode(), encoding );
 429  152
             }
 430  
         }
 431  48222
     }
 432  
 
 433  
     /**
 434  
      * Adds a new entry to the archive, takes care of duplicates as well.
 435  
      *
 436  
      * @param in the stream to read data for the entry from.
 437  
      * @param zOut the stream to write to.
 438  
      * @param vPath the name this entry shall have in the archive.
 439  
      * @param lastModified last modification time for the entry.
 440  
      * @param fromArchive the original archive we are copying this
 441  
      * @param symlinkDestination
 442  
      * @param addInParallel Indicates if the entry should be add in parallel.
 443  
      * If set to {@code false} it is added synchronously.
 444  
      * If the entry is symbolic link this parameter is ignored.
 445  
      */
 446  
     @SuppressWarnings(
 447  
     {
 448  
         "JavaDoc"
 449  
     } )
 450  
     protected void zipFile( InputStreamSupplier in, ConcurrentJarCreator zOut, String vPath,
 451  
                             long lastModified,
 452  
                             File fromArchive, int mode, String symlinkDestination, boolean addInParallel )
 453  
         throws IOException, ArchiverException
 454  
     {
 455  47375
         getLogger().debug( "adding entry " + vPath );
 456  
 
 457  47375
         entries.put( vPath, vPath );
 458  
 
 459  47375
         if ( !skipWriting )
 460  
         {
 461  47375
             ZipArchiveEntry ze = new ZipArchiveEntry( vPath );
 462  47375
             setTime( ze, lastModified );
 463  
 
 464  47375
             ze.setMethod( doCompress ? ZipArchiveEntry.DEFLATED : ZipArchiveEntry.STORED );
 465  47375
             ze.setUnixMode( UnixStat.FILE_FLAG | mode );
 466  
 
 467  
             InputStream payload;
 468  47375
             if ( ze.isUnixSymlink() )
 469  
             {
 470  78
                 final byte[] bytes = encodeArchiveEntry( symlinkDestination, getEncoding() );
 471  78
                 payload = new ByteArrayInputStream( bytes );
 472  78
                 zOut.addArchiveEntry( ze, createInputStreamSupplier( payload ), true );
 473  78
             }
 474  
             else
 475  
             {
 476  47297
                 zOut.addArchiveEntry( ze, in, addInParallel );
 477  
             }
 478  
         }
 479  47375
     }
 480  
 
 481  
     /**
 482  
      * Method that gets called when adding from java.io.File instances.
 483  
      * <p>
 484  
      * This implementation delegates to the six-arg version.</p>
 485  
      *
 486  
      * @param entry the file to add to the archive
 487  
      * @param zOut the stream to write to
 488  
      * @param vPath the name this entry shall have in the archive
 489  
      */
 490  
     @SuppressWarnings(
 491  
     {
 492  
         "JavaDoc"
 493  
     } )
 494  
     protected void zipFile( final ArchiveEntry entry, ConcurrentJarCreator zOut, String vPath )
 495  
         throws IOException, ArchiverException
 496  
     {
 497  47356
         final PlexusIoResource resource = entry.getResource();
 498  47356
         if ( ResourceUtils.isSame( resource, getDestFile() ) )
 499  
         {
 500  0
             throw new ArchiverException( "A zip file cannot include itself" );
 501  
         }
 502  
 
 503  47356
         final boolean b = entry.getResource() instanceof SymlinkDestinationSupplier;
 504  47356
         String symlinkTarget = b ? ( (SymlinkDestinationSupplier) entry.getResource() ).getSymlinkDestination() : null;
 505  47356
         InputStreamSupplier in = new InputStreamSupplier()
 506  47356
         {
 507  
 
 508  
             @Override
 509  
             public InputStream get()
 510  
             {
 511  
                 try
 512  
                 {
 513  47277
                     return entry.getInputStream();
 514  
                 }
 515  0
                 catch ( IOException e )
 516  
                 {
 517  0
                     throw new RuntimeException( e );
 518  
                 }
 519  
             }
 520  
 
 521  
         };
 522  
         try
 523  
         {
 524  47356
             zipFile( in, zOut, vPath, resource.getLastModified(), null, entry.getMode(), symlinkTarget,
 525  
                      !entry.shouldAddSynchronously() );
 526  
         }
 527  0
         catch ( IOException e )
 528  
         {
 529  0
             throw new ArchiverException( "IOException when zipping r" + entry.getName() + ": " + e.getMessage(), e );
 530  47356
         }
 531  47356
     }
 532  
 
 533  
     private void setTime( java.util.zip.ZipEntry zipEntry, long lastModified )
 534  
     {
 535  
         // Zip archives store file modification times with a
 536  
         // granularity of two seconds, so the times will either be rounded
 537  
         // up or down. If you round down, the archive will always seem
 538  
         // out-of-date when you rerun the task, so the default is to round
 539  
         // up. Rounding up may lead to a different type of problems like
 540  
         // JSPs inside a web archive that seem to be slightly more recent
 541  
         // than precompiled pages, rendering precompilation useless.
 542  
         // plexus-archiver chooses to round up.
 543  48410
         zipEntry.setTime( lastModified + 1999 );
 544  
 
 545  
         /*   Consider adding extended file stamp support.....
 546  
 
 547  
          X5455_ExtendedTimestamp ts =  new X5455_ExtendedTimestamp();
 548  
          ts.setModifyJavaTime(new Date(lastModified));
 549  
          if (zipEntry.getExtra() != null){
 550  
          // Uh-oh. What do we do now.
 551  
          throw new IllegalStateException("DIdnt expect to see xtradata here ?");
 552  
 
 553  
          }  else {
 554  
          zipEntry.setExtra(ts.getLocalFileDataData());
 555  
          }
 556  
          */
 557  48410
     }
 558  
 
 559  
     protected void zipDir( PlexusIoResource dir, ConcurrentJarCreator zOut, String vPath, int mode,
 560  
                            String encodingToUse )
 561  
         throws IOException
 562  
     {
 563  1035
         if ( addedDirs.update( vPath ) )
 564  
         {
 565  0
             return;
 566  
         }
 567  
 
 568  1035
         getLogger().debug( "adding directory " + vPath );
 569  
 
 570  1035
         if ( !skipWriting )
 571  
         {
 572  1035
             final boolean isSymlink = dir instanceof SymlinkDestinationSupplier;
 573  
 
 574  1035
             if ( isSymlink && vPath.endsWith( File.separator ) )
 575  
             {
 576  26
                 vPath = vPath.substring( 0, vPath.length() - 1 );
 577  
             }
 578  
 
 579  1035
             ZipArchiveEntry ze = new ZipArchiveEntry( vPath );
 580  
 
 581  
             /*
 582  
              * ZipOutputStream.putNextEntry expects the ZipEntry to
 583  
              * know its size and the CRC sum before you start writing
 584  
              * the data when using STORED mode - unless it is seekable.
 585  
              *
 586  
              * This forces us to process the data twice.
 587  
              */
 588  1035
             if ( isSymlink )
 589  
             {
 590  26
                 mode = UnixStat.LINK_FLAG | mode;
 591  
             }
 592  
 
 593  1035
             if ( dir != null && dir.isExisting() )
 594  
             {
 595  866
                 setTime( ze, dir.getLastModified() );
 596  
             }
 597  
             else
 598  
             {
 599  
                 // ZIPs store time with a granularity of 2 seconds, round up
 600  169
                 setTime( ze, System.currentTimeMillis() );
 601  
             }
 602  1035
             if ( !isSymlink )
 603  
             {
 604  1009
                 ze.setSize( 0 );
 605  1009
                 ze.setMethod( ZipArchiveEntry.STORED );
 606  
                 // This is faintly ridiculous:
 607  1009
                 ze.setCrc( EMPTY_CRC );
 608  
             }
 609  1035
             ze.setUnixMode( mode );
 610  
 
 611  1035
             if ( !isSymlink )
 612  
             {
 613  1009
                 zOut.addArchiveEntry( ze, createInputStreamSupplier( new ByteArrayInputStream( "".getBytes() ) ), true );
 614  
             }
 615  
             else
 616  
             {
 617  26
                 String symlinkDestination = ( (SymlinkDestinationSupplier) dir ).getSymlinkDestination();
 618  26
                 final byte[] bytes = encodeArchiveEntry( symlinkDestination, encodingToUse );
 619  26
                 ze.setMethod( ZipArchiveEntry.DEFLATED );
 620  26
                 zOut.addArchiveEntry( ze, createInputStreamSupplier( new ByteArrayInputStream( bytes ) ), true );
 621  
             }
 622  
         }
 623  1035
     }
 624  
 
 625  
     private byte[] encodeArchiveEntry( String payload, String encoding )
 626  
         throws IOException
 627  
     {
 628  104
         ZipEncoding enc = ZipEncodingHelper.getZipEncoding( encoding );
 629  104
         ByteBuffer encodedPayloadByteBuffer = enc.encode( payload );
 630  104
         byte[] encodedPayloadBytes = new byte[encodedPayloadByteBuffer.limit()];
 631  104
         encodedPayloadByteBuffer.get( encodedPayloadBytes );
 632  
 
 633  104
         return encodedPayloadBytes;
 634  
     }
 635  
 
 636  
     protected InputStreamSupplier createInputStreamSupplier( final InputStream inputStream )
 637  
     {
 638  1132
         return new InputStreamSupplier()
 639  1132
         {
 640  
 
 641  
             @Override
 642  
             public InputStream get()
 643  
             {
 644  123
                 return inputStream;
 645  
             }
 646  
 
 647  
         };
 648  
     }
 649  
 
 650  
     /**
 651  
      * Create an empty zip file
 652  
      *
 653  
      * @param zipFile The file
 654  
      *
 655  
      * @return true for historic reasons
 656  
      */
 657  
     @SuppressWarnings(
 658  
     {
 659  
         "JavaDoc"
 660  
     } )
 661  
     protected boolean createEmptyZip( File zipFile )
 662  
         throws ArchiverException
 663  
     {
 664  
         // In this case using java.util.zip will not work
 665  
         // because it does not permit a zero-entry archive.
 666  
         // Must create it manually.
 667  0
         getLogger().info( "Note: creating empty " + archiveType + " archive " + zipFile );
 668  0
         OutputStream os = null;
 669  
         try
 670  
         {
 671  0
             os = new FileOutputStream( zipFile );
 672  
             // Cf. PKZIP specification.
 673  0
             byte[] empty = new byte[ 22 ];
 674  0
             empty[0] = 80; // P
 675  0
             empty[1] = 75; // K
 676  0
             empty[2] = 5;
 677  0
             empty[3] = 6;
 678  
             // remainder zeros
 679  0
             os.write( empty );
 680  0
             os.close();
 681  0
             os = null;
 682  
         }
 683  0
         catch ( IOException ioe )
 684  
         {
 685  0
             throw new ArchiverException( "Could not create empty ZIP archive " + "(" + ioe.getMessage() + ")", ioe );
 686  
         }
 687  
         finally
 688  
         {
 689  0
             IOUtil.close( os );
 690  0
         }
 691  0
         return true;
 692  
     }
 693  
 
 694  
     /**
 695  
      * Do any clean up necessary to allow this instance to be used again.
 696  
      * <p>
 697  
      * When we get here, the Zip file has been closed and all we
 698  
      * need to do is to reset some globals.</p>
 699  
      * <p>
 700  
      * This method will only reset globals that have been changed
 701  
      * during execute(), it will not alter the attributes or nested
 702  
      * child elements. If you want to reset the instance so that you
 703  
      * can later zip a completely different set of files, you must use
 704  
      * the reset method.</p>
 705  
      *
 706  
      * @see #reset
 707  
      */
 708  
     @Override
 709  
     protected void cleanUp()
 710  
         throws IOException
 711  
     {
 712  55
         super.cleanUp();
 713  55
         addedDirs.clear();
 714  55
         entries.clear();
 715  55
         addingNewFiles = false;
 716  55
         doUpdate = savedDoUpdate;
 717  55
         success = false;
 718  55
         zOut = null;
 719  55
         renamedFile = null;
 720  55
         zipFile = null;
 721  55
     }
 722  
 
 723  
     /**
 724  
      * Makes this instance reset all attributes to their default
 725  
      * values and forget all children.
 726  
      *
 727  
      * @see #cleanUp
 728  
      */
 729  
     public void reset()
 730  
     {
 731  0
         setDestFile( null );
 732  
 //        duplicate = "add";
 733  0
         archiveType = "zip";
 734  0
         doCompress = true;
 735  0
         doUpdate = false;
 736  0
         doFilesonly = false;
 737  0
         encoding = null;
 738  0
     }
 739  
 
 740  
     /**
 741  
      * method for subclasses to override
 742  
      *
 743  
      * @param zOut The output stream
 744  
      */
 745  
     protected void initZipOutputStream( ConcurrentJarCreator zOut )
 746  
         throws ArchiverException, IOException
 747  
     {
 748  52
     }
 749  
 
 750  
     /**
 751  
      * method for subclasses to override
 752  
      */
 753  
     @Override
 754  
     public boolean isSupportingForced()
 755  
     {
 756  5
         return true;
 757  
     }
 758  
 
 759  
     @Override
 760  
     protected boolean revert( StringBuffer messageBuffer )
 761  
     {
 762  0
         int initLength = messageBuffer.length();
 763  
 
 764  
         // delete a bogus ZIP file (but only if it's not the original one)
 765  0
         if ( ( !doUpdate || renamedFile != null ) && !zipFile.delete() )
 766  
         {
 767  0
             messageBuffer.append( " (and the archive is probably corrupt but I could not delete it)" );
 768  
         }
 769  
 
 770  0
         if ( doUpdate && renamedFile != null )
 771  
         {
 772  
             try
 773  
             {
 774  0
                 FileUtils.rename( renamedFile, zipFile );
 775  
             }
 776  0
             catch ( IOException e )
 777  
             {
 778  0
                 messageBuffer.append( " (and I couldn't rename the temporary file " );
 779  0
                 messageBuffer.append( renamedFile.getName() );
 780  0
                 messageBuffer.append( " back)" );
 781  0
             }
 782  
         }
 783  
 
 784  0
         return messageBuffer.length() == initLength;
 785  
     }
 786  
 
 787  
     @Override
 788  
     protected void close()
 789  
         throws IOException
 790  
     {
 791  
         // Close the output stream.
 792  
         try
 793  
         {
 794  55
             if ( zipArchiveOutputStream != null )
 795  
             {
 796  52
                 if ( zOut != null )
 797  
                 {
 798  52
                     zOut.writeTo( zipArchiveOutputStream );
 799  
                 }
 800  52
                 zipArchiveOutputStream.close();
 801  
             }
 802  
         }
 803  0
         catch ( IOException ex )
 804  
         {
 805  
             // If we're in this finally clause because of an
 806  
             // exception, we don't really care if there's an
 807  
             // exception when closing the stream. E.g. if it
 808  
             // throws "ZIP file must have at least one entry",
 809  
             // because an exception happened before we added
 810  
             // any files, then we must swallow this
 811  
             // exception. Otherwise, the error that's reported
 812  
             // will be the close() error, which is not the
 813  
             // real cause of the problem.
 814  0
             if ( success )
 815  
             {
 816  0
                 throw ex;
 817  
             }
 818  
 
 819  
         }
 820  0
         catch ( InterruptedException e )
 821  
         {
 822  0
             IOException ex = new IOException( "InterruptedException exception" );
 823  0
             ex.initCause( e.getCause() );
 824  0
             throw ex;
 825  
         }
 826  0
         catch ( ExecutionException e )
 827  
         {
 828  0
             IOException ex = new IOException( "Execution exception" );
 829  0
             ex.initCause( e.getCause() );
 830  0
             throw ex;
 831  55
         }
 832  55
     }
 833  
 
 834  
     @Override
 835  
     protected String getArchiveType()
 836  
     {
 837  0
         return archiveType;
 838  
     }
 839  
 
 840  
 }