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