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