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