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 
350         if ( getOverrideDirectoryMode() > -1 || getOverrideFileMode() > -1 )
351         {
352             collection.setOverrideAttributes( -1, null, -1, null, getOverrideFileMode(), getOverrideDirectoryMode() );
353         }
354 
355         if ( getDefaultDirectoryMode() > -1 || getDefaultFileMode() > -1 )
356         {
357             collection.setDefaultAttributes( -1, null, -1, null, getDefaultFileMode(), getDefaultDirectoryMode() );
358         }
359 
360         addResources( collection );
361     }
362 
363     @Override
364     public void addFile( @Nonnull final File inputFile, @Nonnull final String destFileName )
365         throws ArchiverException
366     {
367         final int fileMode = getOverrideFileMode();
368 
369         addFile( inputFile, destFileName, fileMode );
370     }
371 
372     @Override
373     public void addSymlink( String symlinkName, String symlinkDestination )
374         throws ArchiverException
375     {
376         final int fileMode = getOverrideFileMode();
377 
378         addSymlink( symlinkName, fileMode, symlinkDestination );
379     }
380 
381     @Override
382     public void addSymlink( String symlinkName, int permissions, String symlinkDestination )
383         throws ArchiverException
384     {
385         doAddResource(
386             ArchiveEntry.createSymlinkEntry( symlinkName, permissions, symlinkDestination, getDirectoryMode() ) );
387     }
388 
389     protected ArchiveEntry asArchiveEntry( @Nonnull final PlexusIoResource resource, final String destFileName,
390                                            final int permissions, PlexusIoResourceCollection collection )
391         throws ArchiverException
392     {
393         if ( !resource.isExisting() )
394         {
395             throw new ArchiverException( resource.getName() + " not found." );
396         }
397 
398         if ( resource.isFile() )
399         {
400             return ArchiveEntry.createFileEntry( destFileName, resource, permissions, collection, getDirectoryMode() );
401         }
402         else
403         {
404             return ArchiveEntry.createDirectoryEntry( destFileName, resource, permissions, getDirectoryMode() );
405         }
406     }
407 
408     private ArchiveEntry asArchiveEntry( final AddedResourceCollection collection, final PlexusIoResource resource )
409         throws ArchiverException
410     {
411         final String destFileName = collection.resources.getName( resource );
412 
413         int fromResource = PlexusIoResourceAttributes.UNKNOWN_OCTAL_MODE;
414         if ( resource instanceof ResourceAttributeSupplier )
415         {
416             final PlexusIoResourceAttributes attrs = ( (ResourceAttributeSupplier) resource ).getAttributes();
417 
418             if ( attrs != null )
419             {
420                 fromResource = attrs.getOctalMode();
421             }
422         }
423 
424         return asArchiveEntry( resource, destFileName,
425                                collection.maybeOverridden( fromResource, resource.isDirectory() ),
426                                collection.resources );
427     }
428 
429     @Override
430     public void addResource( final PlexusIoResource resource, final String destFileName, final int permissions )
431         throws ArchiverException
432     {
433         doAddResource( asArchiveEntry( resource, destFileName, permissions, null ) );
434     }
435 
436     @Override
437     public void addFile( @Nonnull final File inputFile, @Nonnull String destFileName, int permissions )
438         throws ArchiverException
439     {
440         if ( !inputFile.isFile() || !inputFile.exists() )
441         {
442             throw new ArchiverException( inputFile.getAbsolutePath() + " isn't a file." );
443         }
444 
445         if ( replacePathSlashesToJavaPaths )
446         {
447             destFileName = destFileName.replace( '\\', '/' );
448         }
449 
450         if ( permissions < 0 )
451         {
452             permissions = getOverrideFileMode();
453         }
454 
455         try
456         {
457             // do a null check here, to avoid creating a file stream if there are no filters...
458             doAddResource( ArchiveEntry.createFileEntry( destFileName, inputFile, permissions, getDirectoryMode() ) );
459         }
460         catch ( final IOException e )
461         {
462             throw new ArchiverException( "Failed to determine inclusion status for: " + inputFile, e );
463         }
464     }
465 
466     @Nonnull
467     @Override
468     public ResourceIterator getResources()
469         throws ArchiverException
470     {
471         return new ResourceIterator()
472         {
473 
474             private final Iterator addedResourceIter = resources.iterator();
475 
476             private AddedResourceCollection currentResourceCollection;
477 
478             private Iterator ioResourceIter;
479 
480             private ArchiveEntry nextEntry;
481 
482             private final Set<String> seenEntries = new HashSet<String>();
483 
484             @Override
485             public boolean hasNext()
486             {
487                 do
488                 {
489                     if ( nextEntry == null )
490                     {
491                         if ( ioResourceIter == null )
492                         {
493                             if ( addedResourceIter.hasNext() )
494                             {
495                                 final Object o = addedResourceIter.next();
496                                 if ( o instanceof ArchiveEntry )
497                                 {
498                                     nextEntry = (ArchiveEntry) o;
499                                 }
500                                 else if ( o instanceof AddedResourceCollection )
501                                 {
502                                     currentResourceCollection = (AddedResourceCollection) o;
503 
504                                     try
505                                     {
506                                         ioResourceIter = currentResourceCollection.resources.getResources();
507                                     }
508                                     catch ( final IOException e )
509                                     {
510                                         throw new ArchiverException( e.getMessage(), e );
511                                     }
512                                 }
513                                 else
514                                 {
515                                     return throwIllegalResourceType( o );
516                                 }
517                             }
518                             else
519                             {
520                                 nextEntry = null;
521                             }
522                         }
523                         else
524                         {
525                             if ( ioResourceIter.hasNext() )
526                             {
527                                 final PlexusIoResource resource = (PlexusIoResource) ioResourceIter.next();
528                                 nextEntry = asArchiveEntry( currentResourceCollection, resource );
529                             }
530                             else
531                             {
532                                 // this will leak handles in the IO iterator if the iterator is not fully consumed.
533                                 // alternately we'd have to make this method return a Closeable iterator back
534                                 // to the client and ditch the whole issue onto the client.
535                                 // this does not really make any sense either, might equally well change the
536                                 // api into something that is not broken by design.
537                                 addCloseable( ioResourceIter );
538                                 ioResourceIter = null;
539                             }
540                         }
541                     }
542 
543                     if ( nextEntry != null && seenEntries.contains( normalizedForDuplicateCheck( nextEntry ) ) )
544                     {
545                         final String path = nextEntry.getName();
546 
547                         if ( Archiver.DUPLICATES_PRESERVE.equals( duplicateBehavior )
548                                  || Archiver.DUPLICATES_SKIP.equals( duplicateBehavior ) )
549                         {
550                             if ( nextEntry.getType() == ArchiveEntry.FILE )
551                             {
552                                 getLogger().debug( path + " already added, skipping" );
553                             }
554 
555                             nextEntry = null;
556                         }
557                         else if ( Archiver.DUPLICATES_FAIL.equals( duplicateBehavior ) )
558                         {
559                             throw new ArchiverException(
560                                 "Duplicate file " + path + " was found and the duplicate " + "attribute is 'fail'." );
561                         }
562                         else
563                         {
564                             // duplicate equal to add, so we continue
565                             getLogger().debug( "duplicate file " + path + " found, adding." );
566                         }
567                     }
568                 }
569                 while ( nextEntry == null && !( ioResourceIter == null && !addedResourceIter.hasNext() ) );
570 
571                 return nextEntry != null;
572             }
573 
574             private boolean throwIllegalResourceType( Object o )
575             {
576                 throw new IllegalStateException(
577                     "An invalid resource of type: " + o.getClass().getName() + " was added to archiver: "
578                         + getClass().getName() );
579             }
580 
581             @Override
582             public ArchiveEntry next()
583             {
584                 if ( !hasNext() )
585                 {
586                     throw new NoSuchElementException();
587                 }
588 
589                 final ArchiveEntry next = nextEntry;
590                 nextEntry = null;
591 
592                 seenEntries.add( normalizedForDuplicateCheck( next ) );
593 
594                 return next;
595             }
596 
597             @Override
598             public void remove()
599             {
600                 throw new UnsupportedOperationException( "Does not support iterator" );
601             }
602 
603             private String normalizedForDuplicateCheck( ArchiveEntry entry )
604             {
605                 return entry.getName().replace( '\\', '/' );
606             }
607 
608         };
609 
610     }
611 
612     private static void closeIfCloseable( Object resource )
613         throws IOException
614     {
615         if ( resource == null )
616         {
617             return;
618         }
619         if ( resource instanceof Closeable )
620         {
621             ( (Closeable) resource ).close();
622         }
623 
624     }
625 
626     private static void closeQuietlyIfCloseable( Object resource )
627     {
628         try
629         {
630             closeIfCloseable( resource );
631         }
632         catch ( IOException e )
633         {
634             throw new RuntimeException( e );
635         }
636     }
637 
638     @Override
639     public Map<String, ArchiveEntry> getFiles()
640     {
641         try
642         {
643             final Map<String, ArchiveEntry> map = new HashMap<String, ArchiveEntry>();
644             for ( final ResourceIterator iter = getResources(); iter.hasNext(); )
645             {
646                 final ArchiveEntry entry = iter.next();
647                 if ( includeEmptyDirs || entry.getType() == ArchiveEntry.FILE )
648                 {
649                     map.put( entry.getName(), entry );
650                 }
651             }
652             return map;
653         }
654         catch ( final ArchiverException e )
655         {
656             throw new UndeclaredThrowableException( e );
657         }
658     }
659 
660     @Override
661     public File getDestFile()
662     {
663         return destFile;
664     }
665 
666     @Override
667     public void setDestFile( final File destFile )
668     {
669         this.destFile = destFile;
670 
671         if ( destFile != null )
672         {
673             destFile.getParentFile().mkdirs();
674         }
675     }
676 
677     @Override
678     protected Logger getLogger()
679     {
680         if ( logger == null )
681         {
682             if ( super.getLogger() != null )
683             {
684                 logger = super.getLogger();
685             }
686             else
687             {
688                 logger = new ConsoleLogger( Logger.LEVEL_INFO, "console" );
689             }
690         }
691 
692         return logger;
693     }
694 
695     protected PlexusIoResourceCollection asResourceCollection( final ArchivedFileSet fileSet, Charset charset )
696         throws ArchiverException
697     {
698         final File archiveFile = fileSet.getArchive();
699 
700         final PlexusIoResourceCollection resources;
701         try
702         {
703             resources = archiverManager.getResourceCollection( archiveFile );
704         }
705         catch ( final NoSuchArchiverException e )
706         {
707             throw new ArchiverException(
708                 "Error adding archived file-set. PlexusIoResourceCollection not found for: " + archiveFile, e );
709         }
710 
711         if ( resources instanceof EncodingSupported )
712         {
713             ( (EncodingSupported) resources ).setEncoding( charset );
714         }
715 
716         if ( resources instanceof PlexusIoArchivedResourceCollection )
717         {
718             ( (PlexusIoArchivedResourceCollection) resources ).setFile( fileSet.getArchive() );
719         }
720         else
721         {
722             throw new ArchiverException( "Expected " + PlexusIoArchivedResourceCollection.class.getName() + ", got "
723                                              + resources.getClass().getName() );
724         }
725 
726         if ( resources instanceof AbstractPlexusIoResourceCollection )
727         {
728             ( (AbstractPlexusIoResourceCollection) resources ).setStreamTransformer( fileSet.getStreamTransformer() );
729         }
730         final PlexusIoProxyResourceCollection proxy = new PlexusIoProxyResourceCollection( resources );
731 
732         proxy.setExcludes( fileSet.getExcludes() );
733         proxy.setIncludes( fileSet.getIncludes() );
734         proxy.setIncludingEmptyDirectories( fileSet.isIncludingEmptyDirectories() );
735         proxy.setCaseSensitive( fileSet.isCaseSensitive() );
736         proxy.setPrefix( fileSet.getPrefix() );
737         proxy.setUsingDefaultExcludes( fileSet.isUsingDefaultExcludes() );
738         proxy.setFileSelectors( fileSet.getFileSelectors() );
739         proxy.setStreamTransformer( fileSet.getStreamTransformer() );
740 
741         if ( getOverrideDirectoryMode() > -1 || getOverrideFileMode() > -1 )
742         {
743             proxy.setOverrideAttributes( -1, null, -1, null, getOverrideFileMode(), getOverrideDirectoryMode() );
744         }
745 
746         if ( getDefaultDirectoryMode() > -1 || getDefaultFileMode() > -1 )
747         {
748             proxy.setDefaultAttributes( -1, null, -1, null, getDefaultFileMode(), getDefaultDirectoryMode() );
749         }
750 
751         return proxy;
752     }
753 
754     /**
755      * Adds a resource collection to the archive.
756      */
757     @Override
758     public void addResources( final PlexusIoResourceCollection collection )
759         throws ArchiverException
760     {
761         doAddResource( new AddedResourceCollection( collection, forcedFileMode, forcedDirectoryMode ) );
762     }
763 
764     private void doAddResource( Object item )
765     {
766         resources.add( item );
767     }
768 
769     @Override
770     public void addArchivedFileSet( final ArchivedFileSet fileSet )
771         throws ArchiverException
772     {
773         final PlexusIoResourceCollection resourceCollection = asResourceCollection( fileSet, null );
774         addResources( resourceCollection );
775     }
776 
777     @Override
778     public void addArchivedFileSet( final ArchivedFileSet fileSet, Charset charset )
779         throws ArchiverException
780     {
781         final PlexusIoResourceCollection resourceCollection = asResourceCollection( fileSet, charset );
782         addResources( resourceCollection );
783     }
784 
785     /**
786      * @since 1.0-alpha-7
787      */
788     @Override
789     public void addArchivedFileSet( @Nonnull final File archiveFile, final String prefix, final String[] includes,
790                                     final String[] excludes )
791         throws ArchiverException
792     {
793         addArchivedFileSet(
794             archivedFileSet( archiveFile ).prefixed( prefix ).includeExclude( includes, excludes ).includeEmptyDirs(
795                 includeEmptyDirs ) );
796     }
797 
798     /**
799      * @since 1.0-alpha-7
800      */
801     @Override
802     public void addArchivedFileSet( @Nonnull final File archiveFile, final String prefix )
803         throws ArchiverException
804     {
805         addArchivedFileSet( archivedFileSet( archiveFile ).prefixed( prefix ).includeEmptyDirs( includeEmptyDirs ) );
806     }
807 
808     /**
809      * @since 1.0-alpha-7
810      */
811     @Override
812     public void addArchivedFileSet( @Nonnull final File archiveFile, final String[] includes, final String[] excludes )
813         throws ArchiverException
814     {
815         addArchivedFileSet(
816             archivedFileSet( archiveFile ).includeExclude( includes, excludes ).includeEmptyDirs( includeEmptyDirs ) );
817     }
818 
819     /**
820      * @since 1.0-alpha-7
821      */
822     @Override
823     public void addArchivedFileSet( @Nonnull final File archiveFile )
824         throws ArchiverException
825     {
826         addArchivedFileSet( archivedFileSet( archiveFile ).includeEmptyDirs( includeEmptyDirs ) );
827     }
828 
829     /**
830      * Allows us to pull the ArchiverManager instance out of the container without causing a chicken-and-egg
831      * instantiation/composition problem.
832      */
833     @Override
834     public void contextualize( final Context context )
835         throws ContextException
836     {
837         final PlexusContainer container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
838 
839         try
840         {
841             archiverManager = (ArchiverManager) container.lookup( ArchiverManager.ROLE );
842         }
843         catch ( final ComponentLookupException e )
844         {
845             throw new ContextException( "Error retrieving ArchiverManager instance: " + e.getMessage(), e );
846         }
847     }
848 
849     @Override
850     public boolean isForced()
851     {
852         return forced;
853     }
854 
855     @Override
856     public void setForced( final boolean forced )
857     {
858         this.forced = forced;
859     }
860 
861     @Override
862     public void addArchiveFinalizer( final ArchiveFinalizer finalizer )
863     {
864         if ( finalizers == null )
865         {
866             finalizers = new ArrayList<ArchiveFinalizer>();
867         }
868 
869         finalizers.add( finalizer );
870     }
871 
872     @Override
873     public void setArchiveFinalizers( final List<ArchiveFinalizer> archiveFinalizers )
874     {
875         finalizers = archiveFinalizers;
876     }
877 
878     @Override
879     public void setDotFileDirectory( final File dotFileDirectory )
880     {
881         this.dotFileDirectory = dotFileDirectory;
882     }
883 
884     protected boolean isUptodate()
885         throws ArchiverException
886     {
887         final File zipFile = getDestFile();
888         final long destTimestamp = zipFile.lastModified();
889         if ( destTimestamp == 0 )
890         {
891             getLogger().debug( "isUp2date: false (Destination " + zipFile.getPath() + " not found.)" );
892             return false; // File doesn't yet exist
893         }
894 
895         final Iterator it = resources.iterator();
896         if ( !it.hasNext() )
897         {
898             getLogger().debug( "isUp2date: false (No input files.)" );
899             return false; // No timestamp to compare
900         }
901 
902         while ( it.hasNext() )
903         {
904             final Object o = it.next();
905             final long l;
906             if ( o instanceof ArchiveEntry )
907             {
908                 l = ( (ArchiveEntry) o ).getResource().getLastModified();
909             }
910             else if ( o instanceof AddedResourceCollection )
911             {
912                 try
913                 {
914                     l = ( (AddedResourceCollection) o ).resources.getLastModified();
915                 }
916                 catch ( final IOException e )
917                 {
918                     throw new ArchiverException( e.getMessage(), e );
919                 }
920             }
921             else
922             {
923                 throw new IllegalStateException( "Invalid object type: " + o.getClass().getName() );
924             }
925             if ( l == PlexusIoResource.UNKNOWN_MODIFICATION_DATE )
926             {
927                 // Don't know what to do. Safe thing is to assume not up2date.
928                 getLogger().debug( "isUp2date: false (Resource with unknown modification date found.)" );
929                 return false;
930             }
931             if ( l > destTimestamp )
932             {
933                 getLogger().debug( "isUp2date: false (Resource with newer modification date found.)" );
934                 return false;
935             }
936         }
937 
938         getLogger().debug( "isUp2date: true" );
939         return true;
940     }
941 
942     protected boolean checkForced()
943         throws ArchiverException
944     {
945         if ( !isForced() && isSupportingForced() && isUptodate() )
946         {
947             getLogger().debug( "Archive " + getDestFile() + " is uptodate." );
948             return false;
949         }
950         return true;
951     }
952 
953     @Override
954     public boolean isSupportingForced()
955     {
956         return false;
957     }
958 
959     protected void runArchiveFinalizers()
960         throws ArchiverException
961     {
962         if ( finalizers != null )
963         {
964             for ( final ArchiveFinalizer finalizer : finalizers )
965             {
966                 finalizer.finalizeArchiveCreation( this );
967             }
968         }
969     }
970 
971     @Override
972     public final void createArchive()
973         throws ArchiverException, IOException
974     {
975         validate();
976         try
977         {
978             try
979             {
980                 if ( dotFileDirectory != null )
981                 {
982                     addArchiveFinalizer( new DotDirectiveArchiveFinalizer( dotFileDirectory ) );
983                 }
984 
985                 runArchiveFinalizers();
986 
987                 execute();
988             }
989             finally
990             {
991                 close();
992             }
993         }
994         catch ( final IOException e )
995         {
996             String msg = "Problem creating " + getArchiveType() + ": " + e.getMessage();
997 
998             final StringBuffer revertBuffer = new StringBuffer();
999             if ( !revert( revertBuffer ) )
1000             {
1001                 msg += revertBuffer.toString();
1002             }
1003 
1004             throw new ArchiverException( msg, e );
1005         }
1006         finally
1007         {
1008             cleanUp();
1009         }
1010 
1011         postCreateArchive();
1012     }
1013 
1014     protected boolean hasVirtualFiles()
1015     {
1016         if ( finalizers != null )
1017         {
1018             for ( final ArchiveFinalizer finalizer : finalizers )
1019             {
1020                 final List virtualFiles = finalizer.getVirtualFiles();
1021 
1022                 if ( ( virtualFiles != null ) && !virtualFiles.isEmpty() )
1023                 {
1024                     return true;
1025                 }
1026             }
1027         }
1028         return false;
1029     }
1030 
1031     protected boolean revert( final StringBuffer messageBuffer )
1032     {
1033         return true;
1034     }
1035 
1036     protected void validate()
1037         throws ArchiverException, IOException
1038     {
1039     }
1040 
1041     /**
1042      * This method is called after the archive creation
1043      * completes successfully (no exceptions are thrown).
1044      *
1045      * Subclasses may override this method in order to
1046      * augment or validate the archive after it is
1047      * created.
1048      *
1049      * @since 3.6
1050      */
1051     protected void postCreateArchive()
1052         throws ArchiverException, IOException
1053     {
1054     }
1055 
1056     protected abstract String getArchiveType();
1057 
1058     private void addCloseable( Object maybeCloseable )
1059     {
1060         if ( maybeCloseable instanceof Closeable )
1061         {
1062             closeables.add( (Closeable) maybeCloseable );
1063         }
1064 
1065     }
1066 
1067     private void closeIterators()
1068     {
1069         for ( Closeable closeable : closeables )
1070         {
1071             closeQuietlyIfCloseable( closeable );
1072         }
1073 
1074     }
1075 
1076     protected abstract void close()
1077         throws IOException;
1078 
1079     protected void cleanUp()
1080         throws IOException
1081     {
1082         closeIterators();
1083 
1084         for ( Object resource : resources )
1085         {
1086             if ( resource instanceof PlexusIoProxyResourceCollection )
1087             {
1088                 resource = ( (PlexusIoProxyResourceCollection) resource ).getSrc();
1089             }
1090 
1091             closeIfCloseable( resource );
1092         }
1093         resources.clear();
1094     }
1095 
1096     protected abstract void execute()
1097         throws ArchiverException, IOException;
1098 
1099     /**
1100      * @since 1.1
1101      */
1102     @Override
1103     public boolean isUseJvmChmod()
1104     {
1105         return useJvmChmod;
1106     }
1107 
1108     /**
1109      * @since 1.1
1110      */
1111     @Override
1112     public void setUseJvmChmod( final boolean useJvmChmod )
1113     {
1114         this.useJvmChmod = useJvmChmod;
1115     }
1116 
1117     /**
1118      * @since 1.1
1119      */
1120     @Override
1121     public boolean isIgnorePermissions()
1122     {
1123         return ignorePermissions;
1124     }
1125 
1126     /**
1127      * @since 1.1
1128      */
1129     @Override
1130     public void setIgnorePermissions( final boolean ignorePermissions )
1131     {
1132         this.ignorePermissions = ignorePermissions;
1133     }
1134 
1135 }