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;
18  
19  import java.io.Closeable;
20  import java.io.File;
21  import java.io.IOException;
22  import java.lang.reflect.UndeclaredThrowableException;
23  import java.nio.charset.Charset;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.NoSuchElementException;
31  import java.util.Set;
32  import javax.annotation.Nonnull;
33  import org.codehaus.plexus.PlexusConstants;
34  import org.codehaus.plexus.PlexusContainer;
35  import org.codehaus.plexus.archiver.manager.ArchiverManager;
36  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
37  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
38  import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
39  import org.codehaus.plexus.components.io.functions.ResourceAttributeSupplier;
40  import org.codehaus.plexus.components.io.resources.AbstractPlexusIoResourceCollection;
41  import org.codehaus.plexus.components.io.resources.EncodingSupported;
42  import org.codehaus.plexus.components.io.resources.PlexusIoArchivedResourceCollection;
43  import org.codehaus.plexus.components.io.resources.PlexusIoFileResourceCollection;
44  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
45  import org.codehaus.plexus.components.io.resources.PlexusIoResourceCollection;
46  import org.codehaus.plexus.components.io.resources.proxy.PlexusIoProxyResourceCollection;
47  import org.codehaus.plexus.context.Context;
48  import org.codehaus.plexus.context.ContextException;
49  import org.codehaus.plexus.logging.AbstractLogEnabled;
50  import org.codehaus.plexus.logging.Logger;
51  import org.codehaus.plexus.logging.console.ConsoleLogger;
52  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
53  import org.codehaus.plexus.util.Os;
54  import static org.codehaus.plexus.archiver.util.DefaultArchivedFileSet.archivedFileSet;
55  import static org.codehaus.plexus.archiver.util.DefaultFileSet.fileSet;
56  
57  public abstract class AbstractArchiver
58      extends AbstractLogEnabled
59      implements Archiver, Contextualizable, FinalizerEnabled
60  {
61  
62      private Logger logger;
63  
64      private File destFile;
65  
66      /**
67       * A list of the following objects:
68       * <ul>
69       * <li>Instances of {@link ArchiveEntry}, which are passed back by {@link #getResources()} without modifications
70       * .</li>
71       * <li>Instances of {@link PlexusIoResourceCollection}, which are converted into an {@link Iterator} over instances
72       * of {@link ArchiveEntry} by {@link #getResources()}.
73       * </ul>
74       */
75      private final List<Object> resources = new ArrayList<Object>();
76  
77      private boolean includeEmptyDirs = true;
78  
79      private int forcedFileMode = -1; // Will always be used
80  
81      private int forcedDirectoryMode = -1; // Will always be used
82  
83      private int defaultFileMode = -1; // Optionally used if a value is needed
84  
85      private int defaultDirectoryMode = -1; // Optionally used if a value is needed
86  
87      private boolean forced = true;
88  
89      private List<ArchiveFinalizer> finalizers;
90  
91      private File dotFileDirectory;
92  
93      private String duplicateBehavior = Archiver.DUPLICATES_SKIP;
94  
95      // On lunix-like systems, we replace windows backslashes with forward slashes
96      private final boolean replacePathSlashesToJavaPaths = File.separatorChar == '/';
97  
98      private final List<Closeable> closeables = new ArrayList<Closeable>();
99  
100     /**
101      * since 2.2 is on by default
102      *
103      * @since 1.1
104      */
105     private boolean useJvmChmod = true;
106 
107     // contextualized.
108     private ArchiverManager archiverManager;
109 
110     private static class AddedResourceCollection
111     {
112 
113         private final PlexusIoResourceCollection resources;
114 
115         private final int forcedFileMode;
116 
117         private final int forcedDirectoryMode;
118 
119         public AddedResourceCollection( PlexusIoResourceCollection resources, int forcedFileMode, int forcedDirMode )
120         {
121             this.resources = resources;
122             this.forcedFileMode = forcedFileMode;
123             this.forcedDirectoryMode = forcedDirMode;
124         }
125 
126         private int maybeOverridden( int suggestedMode, boolean isDir )
127         {
128             if ( isDir )
129             {
130                 return forcedDirectoryMode >= 0 ? forcedDirectoryMode : suggestedMode;
131             }
132             else
133             {
134                 return forcedFileMode >= 0 ? forcedFileMode : suggestedMode;
135 
136             }
137         }
138 
139     }
140 
141     /**
142      * @since 1.1
143      */
144     private boolean ignorePermissions = false;
145 
146     @Override
147     public String getDuplicateBehavior()
148     {
149         return duplicateBehavior;
150     }
151 
152     @Override
153     public void setDuplicateBehavior( final String duplicate )
154     {
155         if ( !Archiver.DUPLICATES_VALID_BEHAVIORS.contains( duplicate ) )
156         {
157             throw new IllegalArgumentException(
158                 "Invalid duplicate-file behavior: \'" + duplicate + "\'. Please specify one of: "
159                     + Archiver.DUPLICATES_VALID_BEHAVIORS );
160         }
161 
162         duplicateBehavior = duplicate;
163     }
164 
165     @Override
166     public final void setFileMode( final int mode )
167     {
168         if ( mode >= 0 )
169         {
170             forcedFileMode = ( mode & UnixStat.PERM_MASK ) | UnixStat.FILE_FLAG;
171         }
172         else
173         {
174             forcedFileMode = -1;
175         }
176     }
177 
178     @Override
179     public final void setDefaultFileMode( final int mode )
180     {
181         defaultFileMode = ( mode & UnixStat.PERM_MASK ) | UnixStat.FILE_FLAG;
182     }
183 
184     @Override
185     public final int getOverrideFileMode()
186     {
187         return forcedFileMode;
188     }
189 
190     @Override
191     public final int getFileMode()
192     {
193         if ( forcedFileMode < 0 )
194         {
195             if ( defaultFileMode < 0 )
196             {
197                 return DEFAULT_FILE_MODE;
198             }
199 
200             return defaultFileMode;
201         }
202 
203         return forcedFileMode;
204     }
205 
206     @Override
207     public final int getDefaultFileMode()
208     {
209         return defaultFileMode;
210     }
211 
212     /**
213      * @deprecated Use {@link Archiver#getDefaultFileMode()}.
214      */
215     @Deprecated
216     public final int getRawDefaultFileMode()
217     {
218         return getDefaultFileMode();
219     }
220 
221     @Override
222     public final void setDirectoryMode( final int mode )
223     {
224         if ( mode >= 0 )
225         {
226             forcedDirectoryMode = ( mode & UnixStat.PERM_MASK ) | UnixStat.DIR_FLAG;
227         }
228         else
229         {
230             forcedDirectoryMode = -1;
231         }
232     }
233 
234     @Override
235     public final void setDefaultDirectoryMode( final int mode )
236     {
237         defaultDirectoryMode = ( mode & UnixStat.PERM_MASK ) | UnixStat.DIR_FLAG;
238     }
239 
240     @Override
241     public final int getOverrideDirectoryMode()
242     {
243         return forcedDirectoryMode;
244     }
245 
246     @Override
247     public final int getDirectoryMode()
248     {
249         if ( forcedDirectoryMode < 0 )
250         {
251             if ( defaultDirectoryMode < 0 )
252             {
253                 return DEFAULT_DIR_MODE;
254             }
255 
256             return defaultDirectoryMode;
257         }
258 
259         return forcedDirectoryMode;
260     }
261 
262     @Override
263     public final int getDefaultDirectoryMode()
264     {
265         if ( defaultDirectoryMode < 0 )
266         {
267             return DEFAULT_DIR_MODE;
268         }
269         else
270         {
271             return defaultDirectoryMode;
272         }
273     }
274 
275     @Override
276     public boolean getIncludeEmptyDirs()
277     {
278         return includeEmptyDirs;
279     }
280 
281     @Override
282     public void setIncludeEmptyDirs( final boolean includeEmptyDirs )
283     {
284         this.includeEmptyDirs = includeEmptyDirs;
285     }
286 
287     @Override
288     public void addDirectory( @Nonnull final File directory )
289         throws ArchiverException
290     {
291         addFileSet(
292             fileSet( directory ).prefixed( "" ).includeExclude( null, null ).includeEmptyDirs( includeEmptyDirs ) );
293     }
294 
295     @Override
296     public void addDirectory( @Nonnull final File directory, final String prefix )
297         throws ArchiverException
298     {
299         addFileSet(
300             fileSet( directory ).prefixed( prefix ).includeExclude( null, null ).includeEmptyDirs( includeEmptyDirs ) );
301     }
302 
303     @Override
304     public void addDirectory( @Nonnull final File directory, final String[] includes, final String[] excludes )
305         throws ArchiverException
306     {
307         addFileSet( fileSet( directory ).prefixed( "" ).includeExclude( includes, excludes ).includeEmptyDirs(
308             includeEmptyDirs ) );
309     }
310 
311     @Override
312     public void addDirectory( @Nonnull final File directory, final String prefix, final String[] includes,
313                               final String[] excludes )
314         throws ArchiverException
315     {
316         addFileSet( fileSet( directory ).prefixed( prefix ).includeExclude( includes, excludes ).includeEmptyDirs(
317             includeEmptyDirs ) );
318     }
319 
320     @Override
321     public void addFileSet( @Nonnull final FileSet fileSet )
322         throws ArchiverException
323     {
324         final File directory = fileSet.getDirectory();
325         if ( directory == null )
326         {
327             throw new ArchiverException( "The file sets base directory is null." );
328         }
329 
330         if ( !directory.isDirectory() )
331         {
332             throw new ArchiverException( directory.getAbsolutePath() + " isn't a directory." );
333         }
334 
335         // The PlexusIoFileResourceCollection contains platform-specific File.separatorChar which
336         // is an interesting cause of grief, see PLXCOMP-192
337         final PlexusIoFileResourceCollection collection = new PlexusIoFileResourceCollection();
338         collection.setFollowingSymLinks( false );
339 
340         collection.setIncludes( fileSet.getIncludes() );
341         collection.setExcludes( fileSet.getExcludes() );
342         collection.setBaseDir( directory );
343         collection.setFileSelectors( fileSet.getFileSelectors() );
344         collection.setIncludingEmptyDirectories( fileSet.isIncludingEmptyDirectories() );
345         collection.setPrefix( fileSet.getPrefix() );
346         collection.setCaseSensitive( fileSet.isCaseSensitive() );
347         collection.setUsingDefaultExcludes( fileSet.isUsingDefaultExcludes() );
348         collection.setStreamTransformer( fileSet.getStreamTransformer() );
349         collection.setFileMappers( fileSet.getFileMappers() );
350 
351         if ( getOverrideDirectoryMode() > -1 || getOverrideFileMode() > -1 )
352         {
353             collection.setOverrideAttributes( -1, null, -1, null, getOverrideFileMode(), getOverrideDirectoryMode() );
354         }
355 
356         if ( getDefaultDirectoryMode() > -1 || getDefaultFileMode() > -1 )
357         {
358             collection.setDefaultAttributes( -1, null, -1, null, getDefaultFileMode(), getDefaultDirectoryMode() );
359         }
360 
361         addResources( collection );
362     }
363 
364     @Override
365     public void addFile( @Nonnull final File inputFile, @Nonnull final String destFileName )
366         throws ArchiverException
367     {
368         final int fileMode = getOverrideFileMode();
369 
370         addFile( inputFile, destFileName, fileMode );
371     }
372 
373     @Override
374     public void addSymlink( String symlinkName, String symlinkDestination )
375         throws ArchiverException
376     {
377         final int fileMode = getOverrideFileMode();
378 
379         addSymlink( symlinkName, fileMode, symlinkDestination );
380     }
381 
382     @Override
383     public void addSymlink( String symlinkName, int permissions, String symlinkDestination )
384         throws ArchiverException
385     {
386         doAddResource(
387             ArchiveEntry.createSymlinkEntry( symlinkName, permissions, symlinkDestination, getDirectoryMode() ) );
388     }
389 
390     protected ArchiveEntry asArchiveEntry( @Nonnull final PlexusIoResource resource, final String destFileName,
391                                            final int permissions, PlexusIoResourceCollection collection )
392         throws ArchiverException
393     {
394         if ( !resource.isExisting() )
395         {
396             throw new ArchiverException( resource.getName() + " not found." );
397         }
398 
399         if ( resource.isFile() )
400         {
401             return ArchiveEntry.createFileEntry( destFileName, resource, permissions, collection, getDirectoryMode() );
402         }
403         else
404         {
405             return ArchiveEntry.createDirectoryEntry( destFileName, resource, permissions, getDirectoryMode() );
406         }
407     }
408 
409     private ArchiveEntry asArchiveEntry( final AddedResourceCollection collection, final PlexusIoResource resource )
410         throws ArchiverException
411     {
412         final String destFileName = collection.resources.getName( resource );
413 
414         int fromResource = PlexusIoResourceAttributes.UNKNOWN_OCTAL_MODE;
415         if ( resource instanceof ResourceAttributeSupplier )
416         {
417             final PlexusIoResourceAttributes attrs = ( (ResourceAttributeSupplier) resource ).getAttributes();
418 
419             if ( attrs != null )
420             {
421                 fromResource = attrs.getOctalMode();
422             }
423         }
424 
425         return asArchiveEntry( resource, destFileName,
426                                collection.maybeOverridden( fromResource, resource.isDirectory() ),
427                                collection.resources );
428     }
429 
430     @Override
431     public void addResource( final PlexusIoResource resource, final String destFileName, final int permissions )
432         throws ArchiverException
433     {
434         doAddResource( asArchiveEntry( resource, destFileName, permissions, null ) );
435     }
436 
437     @Override
438     public void addFile( @Nonnull final File inputFile, @Nonnull String destFileName, int permissions )
439         throws ArchiverException
440     {
441         if ( !inputFile.isFile() || !inputFile.exists() )
442         {
443             throw new ArchiverException( inputFile.getAbsolutePath() + " isn't a file." );
444         }
445 
446         if ( replacePathSlashesToJavaPaths )
447         {
448             destFileName = destFileName.replace( '\\', '/' );
449         }
450 
451         if ( permissions < 0 )
452         {
453             permissions = getOverrideFileMode();
454         }
455 
456         try
457         {
458             // do a null check here, to avoid creating a file stream if there are no filters...
459             doAddResource( ArchiveEntry.createFileEntry( destFileName, inputFile, permissions, getDirectoryMode() ) );
460         }
461         catch ( final IOException e )
462         {
463             throw new ArchiverException( "Failed to determine inclusion status for: " + inputFile, e );
464         }
465     }
466 
467     @Nonnull
468     @Override
469     public ResourceIterator getResources()
470         throws ArchiverException
471     {
472         return new ResourceIterator()
473         {
474 
475             private final Iterator addedResourceIter = resources.iterator();
476 
477             private AddedResourceCollection currentResourceCollection;
478 
479             private Iterator ioResourceIter;
480 
481             private ArchiveEntry nextEntry;
482 
483             private final Set<String> seenEntries = new HashSet<String>();
484 
485             @Override
486             public boolean hasNext()
487             {
488                 do
489                 {
490                     if ( nextEntry == null )
491                     {
492                         if ( ioResourceIter == null )
493                         {
494                             if ( addedResourceIter.hasNext() )
495                             {
496                                 final Object o = addedResourceIter.next();
497                                 if ( o instanceof ArchiveEntry )
498                                 {
499                                     nextEntry = (ArchiveEntry) o;
500                                 }
501                                 else if ( o instanceof AddedResourceCollection )
502                                 {
503                                     currentResourceCollection = (AddedResourceCollection) o;
504 
505                                     try
506                                     {
507                                         ioResourceIter = currentResourceCollection.resources.getResources();
508                                     }
509                                     catch ( final IOException e )
510                                     {
511                                         throw new ArchiverException( e.getMessage(), e );
512                                     }
513                                 }
514                                 else
515                                 {
516                                     return throwIllegalResourceType( o );
517                                 }
518                             }
519                             else
520                             {
521                                 nextEntry = null;
522                             }
523                         }
524                         else
525                         {
526                             if ( ioResourceIter.hasNext() )
527                             {
528                                 final PlexusIoResource resource = (PlexusIoResource) ioResourceIter.next();
529                                 nextEntry = asArchiveEntry( currentResourceCollection, resource );
530                             }
531                             else
532                             {
533                                 // this will leak handles in the IO iterator if the iterator is not fully consumed.
534                                 // alternately we'd have to make this method return a Closeable iterator back
535                                 // to the client and ditch the whole issue onto the client.
536                                 // this does not really make any sense either, might equally well change the
537                                 // api into something that is not broken by design.
538                                 addCloseable( ioResourceIter );
539                                 ioResourceIter = null;
540                             }
541                         }
542                     }
543 
544                     if ( nextEntry != null && seenEntries.contains( normalizedForDuplicateCheck( nextEntry ) ) )
545                     {
546                         final String path = nextEntry.getName();
547 
548                         if ( Archiver.DUPLICATES_PRESERVE.equals( duplicateBehavior )
549                                  || Archiver.DUPLICATES_SKIP.equals( duplicateBehavior ) )
550                         {
551                             if ( nextEntry.getType() == ArchiveEntry.FILE )
552                             {
553                                 getLogger().debug( path + " already added, skipping" );
554                             }
555 
556                             nextEntry = null;
557                         }
558                         else if ( Archiver.DUPLICATES_FAIL.equals( duplicateBehavior ) )
559                         {
560                             throw new ArchiverException(
561                                 "Duplicate file " + path + " was found and the duplicate " + "attribute is 'fail'." );
562                         }
563                         else
564                         {
565                             // duplicate equal to add, so we continue
566                             getLogger().debug( "duplicate file " + path + " found, adding." );
567                         }
568                     }
569                 }
570                 while ( nextEntry == null && !( ioResourceIter == null && !addedResourceIter.hasNext() ) );
571 
572                 return nextEntry != null;
573             }
574 
575             private boolean throwIllegalResourceType( Object o )
576             {
577                 throw new IllegalStateException(
578                     "An invalid resource of type: " + o.getClass().getName() + " was added to archiver: "
579                         + getClass().getName() );
580             }
581 
582             @Override
583             public ArchiveEntry next()
584             {
585                 if ( !hasNext() )
586                 {
587                     throw new NoSuchElementException();
588                 }
589 
590                 final ArchiveEntry next = nextEntry;
591                 nextEntry = null;
592 
593                 seenEntries.add( normalizedForDuplicateCheck( next ) );
594 
595                 return next;
596             }
597 
598             @Override
599             public void remove()
600             {
601                 throw new UnsupportedOperationException( "Does not support iterator" );
602             }
603 
604             private String normalizedForDuplicateCheck( ArchiveEntry entry )
605             {
606                 return entry.getName().replace( '\\', '/' );
607             }
608 
609         };
610 
611     }
612 
613     private static void closeIfCloseable( Object resource )
614         throws IOException
615     {
616         if ( resource == null )
617         {
618             return;
619         }
620         if ( resource instanceof Closeable )
621         {
622             ( (Closeable) resource ).close();
623         }
624 
625     }
626 
627     private static void closeQuietlyIfCloseable( Object resource )
628     {
629         try
630         {
631             closeIfCloseable( resource );
632         }
633         catch ( IOException e )
634         {
635             throw new RuntimeException( e );
636         }
637     }
638 
639     @Override
640     public Map<String, ArchiveEntry> getFiles()
641     {
642         try
643         {
644             final Map<String, ArchiveEntry> map = new HashMap<String, ArchiveEntry>();
645             for ( final ResourceIterator iter = getResources(); iter.hasNext(); )
646             {
647                 final ArchiveEntry entry = iter.next();
648                 if ( includeEmptyDirs || entry.getType() == ArchiveEntry.FILE )
649                 {
650                     map.put( entry.getName(), entry );
651                 }
652             }
653             return map;
654         }
655         catch ( final ArchiverException e )
656         {
657             throw new UndeclaredThrowableException( e );
658         }
659     }
660 
661     @Override
662     public File getDestFile()
663     {
664         return destFile;
665     }
666 
667     @Override
668     public void setDestFile( final File destFile )
669     {
670         this.destFile = destFile;
671 
672         if ( destFile != null )
673         {
674             destFile.getParentFile().mkdirs();
675         }
676     }
677 
678     @Override
679     protected Logger getLogger()
680     {
681         if ( logger == null )
682         {
683             if ( super.getLogger() != null )
684             {
685                 logger = super.getLogger();
686             }
687             else
688             {
689                 logger = new ConsoleLogger( Logger.LEVEL_INFO, "console" );
690             }
691         }
692 
693         return logger;
694     }
695 
696     protected PlexusIoResourceCollection asResourceCollection( final ArchivedFileSet fileSet, Charset charset )
697         throws ArchiverException
698     {
699         final File archiveFile = fileSet.getArchive();
700 
701         final PlexusIoResourceCollection resources;
702         try
703         {
704             resources = archiverManager.getResourceCollection( archiveFile );
705         }
706         catch ( final NoSuchArchiverException e )
707         {
708             throw new ArchiverException(
709                 "Error adding archived file-set. PlexusIoResourceCollection not found for: " + archiveFile, e );
710         }
711 
712         if ( resources instanceof EncodingSupported )
713         {
714             ( (EncodingSupported) resources ).setEncoding( charset );
715         }
716 
717         if ( resources instanceof PlexusIoArchivedResourceCollection )
718         {
719             ( (PlexusIoArchivedResourceCollection) resources ).setFile( fileSet.getArchive() );
720         }
721         else
722         {
723             throw new ArchiverException( "Expected " + PlexusIoArchivedResourceCollection.class.getName() + ", got "
724                                              + resources.getClass().getName() );
725         }
726 
727         if ( resources instanceof AbstractPlexusIoResourceCollection )
728         {
729             ( (AbstractPlexusIoResourceCollection) resources ).setStreamTransformer( fileSet.getStreamTransformer() );
730         }
731         final PlexusIoProxyResourceCollection proxy = new PlexusIoProxyResourceCollection( resources );
732 
733         proxy.setExcludes( fileSet.getExcludes() );
734         proxy.setIncludes( fileSet.getIncludes() );
735         proxy.setIncludingEmptyDirectories( fileSet.isIncludingEmptyDirectories() );
736         proxy.setCaseSensitive( fileSet.isCaseSensitive() );
737         proxy.setPrefix( fileSet.getPrefix() );
738         proxy.setUsingDefaultExcludes( fileSet.isUsingDefaultExcludes() );
739         proxy.setFileSelectors( fileSet.getFileSelectors() );
740         proxy.setStreamTransformer( fileSet.getStreamTransformer() );
741         proxy.setFileMappers( fileSet.getFileMappers() );
742 
743         if ( getOverrideDirectoryMode() > -1 || getOverrideFileMode() > -1 )
744         {
745             proxy.setOverrideAttributes( -1, null, -1, null, getOverrideFileMode(), getOverrideDirectoryMode() );
746         }
747 
748         if ( getDefaultDirectoryMode() > -1 || getDefaultFileMode() > -1 )
749         {
750             proxy.setDefaultAttributes( -1, null, -1, null, getDefaultFileMode(), getDefaultDirectoryMode() );
751         }
752 
753         return proxy;
754     }
755 
756     /**
757      * Adds a resource collection to the archive.
758      */
759     @Override
760     public void addResources( final PlexusIoResourceCollection collection )
761         throws ArchiverException
762     {
763         doAddResource( new AddedResourceCollection( collection, forcedFileMode, forcedDirectoryMode ) );
764     }
765 
766     private void doAddResource( Object item )
767     {
768         resources.add( item );
769     }
770 
771     @Override
772     public void addArchivedFileSet( final ArchivedFileSet fileSet )
773         throws ArchiverException
774     {
775         final PlexusIoResourceCollection resourceCollection = asResourceCollection( fileSet, null );
776         addResources( resourceCollection );
777     }
778 
779     @Override
780     public void addArchivedFileSet( final ArchivedFileSet fileSet, Charset charset )
781         throws ArchiverException
782     {
783         final PlexusIoResourceCollection resourceCollection = asResourceCollection( fileSet, charset );
784         addResources( resourceCollection );
785     }
786 
787     /**
788      * @since 1.0-alpha-7
789      */
790     @Override
791     public void addArchivedFileSet( @Nonnull final File archiveFile, final String prefix, final String[] includes,
792                                     final String[] excludes )
793         throws ArchiverException
794     {
795         addArchivedFileSet(
796             archivedFileSet( archiveFile ).prefixed( prefix ).includeExclude( includes, excludes ).includeEmptyDirs(
797                 includeEmptyDirs ) );
798     }
799 
800     /**
801      * @since 1.0-alpha-7
802      */
803     @Override
804     public void addArchivedFileSet( @Nonnull final File archiveFile, final String prefix )
805         throws ArchiverException
806     {
807         addArchivedFileSet( archivedFileSet( archiveFile ).prefixed( prefix ).includeEmptyDirs( includeEmptyDirs ) );
808     }
809 
810     /**
811      * @since 1.0-alpha-7
812      */
813     @Override
814     public void addArchivedFileSet( @Nonnull final File archiveFile, final String[] includes, final String[] excludes )
815         throws ArchiverException
816     {
817         addArchivedFileSet(
818             archivedFileSet( archiveFile ).includeExclude( includes, excludes ).includeEmptyDirs( includeEmptyDirs ) );
819     }
820 
821     /**
822      * @since 1.0-alpha-7
823      */
824     @Override
825     public void addArchivedFileSet( @Nonnull final File archiveFile )
826         throws ArchiverException
827     {
828         addArchivedFileSet( archivedFileSet( archiveFile ).includeEmptyDirs( includeEmptyDirs ) );
829     }
830 
831     /**
832      * Allows us to pull the ArchiverManager instance out of the container without causing a chicken-and-egg
833      * instantiation/composition problem.
834      */
835     @Override
836     public void contextualize( final Context context )
837         throws ContextException
838     {
839         final PlexusContainer container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
840 
841         try
842         {
843             archiverManager = (ArchiverManager) container.lookup( ArchiverManager.ROLE );
844         }
845         catch ( final ComponentLookupException e )
846         {
847             throw new ContextException( "Error retrieving ArchiverManager instance: " + e.getMessage(), e );
848         }
849     }
850 
851     @Override
852     public boolean isForced()
853     {
854         return forced;
855     }
856 
857     @Override
858     public void setForced( final boolean forced )
859     {
860         this.forced = forced;
861     }
862 
863     @Override
864     public void addArchiveFinalizer( final ArchiveFinalizer finalizer )
865     {
866         if ( finalizers == null )
867         {
868             finalizers = new ArrayList<ArchiveFinalizer>();
869         }
870 
871         finalizers.add( finalizer );
872     }
873 
874     @Override
875     public void setArchiveFinalizers( final List<ArchiveFinalizer> archiveFinalizers )
876     {
877         finalizers = archiveFinalizers;
878     }
879 
880     @Override
881     public void setDotFileDirectory( final File dotFileDirectory )
882     {
883         this.dotFileDirectory = dotFileDirectory;
884     }
885 
886     protected boolean isUptodate()
887         throws ArchiverException
888     {
889         final File zipFile = getDestFile();
890         final long destTimestamp = zipFile.lastModified();
891         if ( destTimestamp == 0 )
892         {
893             getLogger().debug( "isUp2date: false (Destination " + zipFile.getPath() + " not found.)" );
894             return false; // File doesn't yet exist
895         }
896 
897         final Iterator it = resources.iterator();
898         if ( !it.hasNext() )
899         {
900             getLogger().debug( "isUp2date: false (No input files.)" );
901             return false; // No timestamp to compare
902         }
903 
904         while ( it.hasNext() )
905         {
906             final Object o = it.next();
907             final long l;
908             if ( o instanceof ArchiveEntry )
909             {
910                 l = ( (ArchiveEntry) o ).getResource().getLastModified();
911             }
912             else if ( o instanceof AddedResourceCollection )
913             {
914                 try
915                 {
916                     l = ( (AddedResourceCollection) o ).resources.getLastModified();
917                 }
918                 catch ( final IOException e )
919                 {
920                     throw new ArchiverException( e.getMessage(), e );
921                 }
922             }
923             else
924             {
925                 throw new IllegalStateException( "Invalid object type: " + o.getClass().getName() );
926             }
927             if ( l == PlexusIoResource.UNKNOWN_MODIFICATION_DATE )
928             {
929                 // Don't know what to do. Safe thing is to assume not up2date.
930                 getLogger().debug( "isUp2date: false (Resource with unknown modification date found.)" );
931                 return false;
932             }
933             if ( l > destTimestamp )
934             {
935                 getLogger().debug( "isUp2date: false (Resource with newer modification date found.)" );
936                 return false;
937             }
938         }
939 
940         getLogger().debug( "isUp2date: true" );
941         return true;
942     }
943 
944     protected boolean checkForced()
945         throws ArchiverException
946     {
947         if ( !isForced() && isSupportingForced() && isUptodate() )
948         {
949             getLogger().debug( "Archive " + getDestFile() + " is uptodate." );
950             return false;
951         }
952         return true;
953     }
954 
955     @Override
956     public boolean isSupportingForced()
957     {
958         return false;
959     }
960 
961     protected void runArchiveFinalizers()
962         throws ArchiverException
963     {
964         if ( finalizers != null )
965         {
966             for ( final ArchiveFinalizer finalizer : finalizers )
967             {
968                 finalizer.finalizeArchiveCreation( this );
969             }
970         }
971     }
972 
973     @Override
974     public final void createArchive()
975         throws ArchiverException, IOException
976     {
977         validate();
978         try
979         {
980             try
981             {
982                 if ( dotFileDirectory != null )
983                 {
984                     addArchiveFinalizer( new DotDirectiveArchiveFinalizer( dotFileDirectory ) );
985                 }
986 
987                 runArchiveFinalizers();
988 
989                 execute();
990             }
991             finally
992             {
993                 close();
994             }
995         }
996         catch ( final IOException e )
997         {
998             String msg = "Problem creating " + getArchiveType() + ": " + e.getMessage();
999 
1000             final StringBuffer revertBuffer = new StringBuffer();
1001             if ( !revert( revertBuffer ) )
1002             {
1003                 msg += revertBuffer.toString();
1004             }
1005 
1006             throw new ArchiverException( msg, e );
1007         }
1008         finally
1009         {
1010             cleanUp();
1011         }
1012 
1013         postCreateArchive();
1014     }
1015 
1016     protected boolean hasVirtualFiles()
1017     {
1018         if ( finalizers != null )
1019         {
1020             for ( final ArchiveFinalizer finalizer : finalizers )
1021             {
1022                 final List virtualFiles = finalizer.getVirtualFiles();
1023 
1024                 if ( ( virtualFiles != null ) && !virtualFiles.isEmpty() )
1025                 {
1026                     return true;
1027                 }
1028             }
1029         }
1030         return false;
1031     }
1032 
1033     protected boolean revert( final StringBuffer messageBuffer )
1034     {
1035         return true;
1036     }
1037 
1038     protected void validate()
1039         throws ArchiverException, IOException
1040     {
1041     }
1042 
1043     /**
1044      * This method is called after the archive creation
1045      * completes successfully (no exceptions are thrown).
1046      *
1047      * Subclasses may override this method in order to
1048      * augment or validate the archive after it is
1049      * created.
1050      *
1051      * @since 3.6
1052      */
1053     protected void postCreateArchive()
1054         throws ArchiverException, IOException
1055     {
1056     }
1057 
1058     protected abstract String getArchiveType();
1059 
1060     private void addCloseable( Object maybeCloseable )
1061     {
1062         if ( maybeCloseable instanceof Closeable )
1063         {
1064             closeables.add( (Closeable) maybeCloseable );
1065         }
1066 
1067     }
1068 
1069     private void closeIterators()
1070     {
1071         for ( Closeable closeable : closeables )
1072         {
1073             closeQuietlyIfCloseable( closeable );
1074         }
1075 
1076     }
1077 
1078     protected abstract void close()
1079         throws IOException;
1080 
1081     protected void cleanUp()
1082         throws IOException
1083     {
1084         closeIterators();
1085 
1086         for ( Object resource : resources )
1087         {
1088             if ( resource instanceof PlexusIoProxyResourceCollection )
1089             {
1090                 resource = ( (PlexusIoProxyResourceCollection) resource ).getSrc();
1091             }
1092 
1093             closeIfCloseable( resource );
1094         }
1095         resources.clear();
1096     }
1097 
1098     protected abstract void execute()
1099         throws ArchiverException, IOException;
1100 
1101     /**
1102      * @since 1.1
1103      */
1104     @Override
1105     public boolean isUseJvmChmod()
1106     {
1107         return useJvmChmod;
1108     }
1109 
1110     /**
1111      * @since 1.1
1112      */
1113     @Override
1114     public void setUseJvmChmod( final boolean useJvmChmod )
1115     {
1116         this.useJvmChmod = useJvmChmod;
1117     }
1118 
1119     /**
1120      * @since 1.1
1121      */
1122     @Override
1123     public boolean isIgnorePermissions()
1124     {
1125         return ignorePermissions;
1126     }
1127 
1128     /**
1129      * @since 1.1
1130      */
1131     @Override
1132     public void setIgnorePermissions( final boolean ignorePermissions )
1133     {
1134         this.ignorePermissions = ignorePermissions;
1135     }
1136 
1137 }