View Javadoc
1   package org.codehaus.plexus.archiver.tar;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FilterInputStream;
6   import java.io.IOException;
7   import java.io.InputStream;
8   import java.lang.reflect.UndeclaredThrowableException;
9   import java.util.Enumeration;
10  import java.util.NoSuchElementException;
11  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
12  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
13  import org.codehaus.plexus.archiver.ArchiveFile;
14  import static org.codehaus.plexus.archiver.util.Streams.bufferedInputStream;
15  
16  /**
17   * <p>
18   * Implementation of {@link ArchiveFile} for tar files.</p>
19   * <p>
20   * Compared to
21   * {@link org.apache.commons.compress.archivers.zip.ZipFile}, this one should be used with some care, due to the
22   * nature of a tar file: While a zip file contains a catalog, a tar
23   * file does not. In other words, the only way to read a tar file in
24   * a performant manner is by iterating over it from the beginning to
25   * the end. If you try to open another entry than the "next" entry,
26   * then you force to skip entries, until the requested entry is found.
27   * This may require to reread the entire file!</p>
28   * <p>
29   * In other words, the recommended use of this class is to use
30   * {@link #getEntries()} and invoke {@link #getInputStream(TarArchiveEntry)}
31   * only for the current entry. Basically, this is to handle it like
32   * {@link TarArchiveInputStream}.</p>
33   * <p>
34   * The advantage of this class is that you may write code for the
35   * {@link ArchiveFile}, which is valid for both tar files and zip files.</p>
36   */
37  public class TarFile
38      implements ArchiveFile
39  {
40  
41      private final java.io.File file;
42  
43      private TarArchiveInputStream inputStream;
44  
45      private TarArchiveEntry currentEntry;
46  
47      /**
48       * Creates a new instance with the given file.
49       */
50      public TarFile( File file )
51      {
52          this.file = file;
53      }
54  
55      /**
56       * Implementation of {@link ArchiveFile#getEntries()}. Note, that there is
57       * an interaction between this method and {@link #getInputStream(TarArchiveEntry)},
58       * or {@link #getInputStream(org.apache.commons.compress.archivers.ArchiveEntry)}:
59       * If an input stream is opened for any other entry than the enumerations
60       * current entry, then entries may be skipped.
61       */
62      @Override
63      public Enumeration<org.apache.commons.compress.archivers.ArchiveEntry> getEntries()
64          throws IOException
65      {
66          if ( inputStream != null )
67          {
68              close();
69          }
70          open();
71          return new Enumeration<org.apache.commons.compress.archivers.ArchiveEntry>()
72          {
73  
74              boolean currentEntryValid;
75  
76              @Override
77              public boolean hasMoreElements()
78              {
79                  if ( !currentEntryValid )
80                  {
81                      try
82                      {
83                          currentEntry = inputStream.getNextTarEntry();
84                      }
85                      catch ( IOException e )
86                      {
87                          throw new UndeclaredThrowableException( e );
88                      }
89                  }
90                  return currentEntry != null;
91              }
92  
93              @Override
94              public org.apache.commons.compress.archivers.ArchiveEntry nextElement()
95              {
96                  if ( currentEntry == null )
97                  {
98                      throw new NoSuchElementException();
99                  }
100                 currentEntryValid = false;
101                 return currentEntry;
102             }
103 
104         };
105     }
106 
107     public void close()
108         throws IOException
109     {
110         if ( inputStream != null )
111         {
112             inputStream.close();
113             inputStream = null;
114         }
115     }
116 
117     @Override
118     public InputStream getInputStream( org.apache.commons.compress.archivers.ArchiveEntry entry )
119         throws IOException
120     {
121         return getInputStream( new TarArchiveEntry( entry.getName() ) );
122     }
123 
124     /**
125      * Returns an {@link InputStream} with the given entries
126      * contents. This {@link InputStream} may be closed: Nothing
127      * happens in that case, because an actual close would invalidate
128      * the underlying {@link TarArchiveInputStream}.
129      */
130     public InputStream getInputStream( TarArchiveEntry entry )
131         throws IOException
132     {
133         if ( entry.equals( (Object) currentEntry ) && inputStream != null )
134         {
135             return new FilterInputStream( inputStream )
136             {
137 
138                 public void close()
139                     throws IOException
140                 {
141                     // Does nothing.
142                 }
143 
144             };
145         }
146         return getInputStream( entry, currentEntry );
147     }
148 
149     protected InputStream getInputStream( File file )
150         throws IOException
151     {
152         return new FileInputStream( file );
153     }
154 
155     private InputStream getInputStream( TarArchiveEntry entry, TarArchiveEntry currentEntry )
156         throws IOException
157     {
158         if ( currentEntry == null || inputStream == null )
159         {
160             // Search for the entry from the beginning of the file to the end.
161             if ( inputStream != null )
162             {
163                 close();
164             }
165             open();
166             if ( !findEntry( entry, null ) )
167             {
168                 throw new IOException( "Unknown entry: " + entry.getName() );
169             }
170         }
171         else
172         {
173             // Search for the entry from the current position to the end of the file.
174             if ( findEntry( entry, null ) )
175             {
176                 return getInputStream( entry );
177             }
178             close();
179             open();
180             if ( !findEntry( entry, currentEntry ) )
181             {
182                 throw new IOException( "No such entry: " + entry.getName() );
183             }
184         }
185         return getInputStream( entry );
186     }
187 
188     private void open()
189         throws IOException
190     {
191         inputStream = new TarArchiveInputStream( bufferedInputStream( getInputStream( file ) ), "UTF8" );
192     }
193 
194     private boolean findEntry( TarArchiveEntry entry, TarArchiveEntry currentEntry )
195         throws IOException
196     {
197         for ( ;; )
198         {
199             this.currentEntry = inputStream.getNextTarEntry();
200             if ( this.currentEntry == null
201                      || ( currentEntry != null && this.currentEntry.equals( currentEntry ) ) )
202             {
203                 return false;
204             }
205             if ( this.currentEntry.equals( entry ) )
206             {
207                 return true;
208             }
209         }
210     }
211 
212 }