KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > io > archive > tar > TarInputArchive


1 /*
2  * TarInputArchive.java
3  *
4  * Created on 28. Februar 2006, 11:53
5  */

6 /*
7  * Copyright 2006 Schlichtherle IT Services
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */

21
22 package de.schlichtherle.io.archive.tar;
23
24 import de.schlichtherle.io.*;
25 import de.schlichtherle.io.archive.*;
26 import de.schlichtherle.io.archive.spi.*;
27 import de.schlichtherle.io.util.*;
28
29 import java.io.*;
30 import java.io.File JavaDoc;
31 import java.io.FileInputStream JavaDoc;
32 import java.io.FileOutputStream JavaDoc;
33 import java.util.*;
34 import java.util.zip.*;
35
36 import org.apache.tools.tar.*;
37
38 /**
39  * Presents a {@link TarInputStream} as a randomly accessible archive.
40  * <p>
41  * <b>Warning:</b>
42  * The constructor of this class extracts each entry in the archive to a
43  * temporary file.
44  * This may be very time and space consuming for large archives, but is
45  * the fastest implementation for subsequent random access, since there
46  * is no way the archive driver could predict the client application's
47  * behaviour.
48  *
49  * @author Christian Schlichtherle
50  * @version @version@
51  * @since TrueZIP 6.0
52  */

53 public class TarInputArchive implements InputArchive, TarConstants {
54
55     /** Prefix for temporary files created by this class. */
56     static final String JavaDoc TEMP_FILE_PREFIX = "tzp-tar";
57
58     private static final byte[] NULL_RECORD = new byte[TarBuffer.DEFAULT_RCDSIZE];
59
60     private static final int CHECKSUM_OFFSET
61             = NAMELEN + MODELEN + UIDLEN + GIDLEN + SIZELEN + MODTIMELEN;
62
63     /**
64      * Maps entry names to tar entries [String -> TarEntry].
65      */

66     private final Map entries = new HashMap();
67
68     private InputArchiveMetaData metaData;
69
70     /**
71      * Extracts the entire TAR input stream into a temporary directory in order
72      * to allow subsequent random access to its entries.
73      *
74      * @param in The input stream from which this input archive file should be
75      * initialized. This stream is not used by any of the methods in
76      * this class after the constructor has terminated and is
77      * <em>never</em> closed!
78      * So it is safe and recommended to close it upon termination
79      * of this constructor.
80      */

81     public TarInputArchive(final Archive archive, final InputStream JavaDoc in)
82     throws IOException {
83         final TarInputStream tin = createValidatedTarInputStream(in);
84         try {
85             org.apache.tools.tar.TarEntry tinEntry;
86             while ((tinEntry = tin.getNextEntry()) != null) {
87                 final File JavaDoc tmp;
88                 try {
89                     tmp = File.createTempFile(TEMP_FILE_PREFIX, null);
90                 } catch (IOException ex) {
91                     throw new TransientIOException(
92                             new TempFileException(archive, tinEntry, ex));
93                 }
94                 boolean ok = tmp.delete();
95                 assert ok;
96                 if (tinEntry.isDirectory()) {
97                     ok = tmp.mkdirs();
98                 } else {
99                     final FileOutputStream JavaDoc out = new FileOutputStream JavaDoc(tmp);
100                     try {
101                         de.schlichtherle.io.File.cat(tin, out); // use high performance pump (async I/O)
102
} finally {
103                         out.close();
104                     }
105                 }
106                 if (!tmp.setLastModified(tinEntry.getModTime().getTime()))
107                     throw new TransientIOException(
108                             new TempFileException(
109                                 archive, tinEntry, tmp,
110                                 "could not set last modification time"));
111                 final TarEntry entry = new TarEntry(tmp);
112                 entry.setName(Paths.normalize(tinEntry.getName(), '/'));
113                 entries.put(entry.getName(), entry);
114             }
115         } catch (IOException failure) {
116             closeImpl();
117             throw failure;
118         }
119     }
120
121     /**
122      * Returns a newly created and validated {@link TarInputStream}.
123      * This method performs a simple validation by computing the checksum
124      * for the first record only.
125      * This method is required because the <code>TarInputStream</code>
126      * unfortunately does not do any validation!
127      */

128     private static TarInputStream createValidatedTarInputStream(
129             final InputStream JavaDoc in)
130     throws IOException {
131         final byte[] buf = new byte[TarBuffer.DEFAULT_RCDSIZE];
132         final InputStream JavaDoc vin = readAhead(in, buf);
133         // If the record is the null record, the TAR file is empty and we're
134
// done with validating.
135
if (!Arrays.equals(buf, NULL_RECORD)) {
136             final long expected = TarUtils.parseOctal(buf, CHECKSUM_OFFSET, 8);
137             for (int i = 0; i < 8; i++)
138                 buf[CHECKSUM_OFFSET + i] = ' ';
139             final long is = TarUtils.computeCheckSum(buf);
140             if (expected != is)
141                 throw new IOException(
142                         "Illegal initial record in TAR file: Expected checksum " + expected + ", is " + is + "!");
143         }
144         return new TarInputStream(
145                 vin, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
146     }
147
148     /**
149      * Fills <code>buf</code> with data from the given input stream and
150      * returns an input stream from which you can still read all data,
151      * including the data in buf.
152      *
153      * @param in The stream to read from. May <em>not</em> be <code>null</code>.
154      * @param buf The buffer to fill entirely with data.
155      * @return A stream which holds all the data <code>in</code> did.
156      * @throws IOException If <code>buf</code> couldn't get filled entirely.
157      */

158     static InputStream JavaDoc readAhead(final InputStream JavaDoc in, final byte[] buf)
159     throws IOException {
160         // Unfortunately, in Sun's J2SE 1.4.2_12 implementation,
161
// InflaterInputStream pretends to support marking, but actually it
162
// doesn't - hence we need to filter this special case.
163
// This issue has been fixed in Sun's J2SE 1.5.0-b64 implementation.
164
if (!(in instanceof InflaterInputStream) && in.markSupported()) {
165             in.mark(buf.length);
166             readFully(in, buf);
167             in.reset();
168             return in;
169         } else {
170             final PushbackInputStream pin
171                     = new PushbackInputStream(in, buf.length);
172             readFully(pin, buf);
173             pin.unread(buf);
174             return pin;
175         }
176     }
177     
178     private static void readFully(final InputStream JavaDoc in, final byte[] buf)
179     throws IOException {
180         final int l = buf.length;
181         int n = 0;
182         do {
183             final int r = in.read(buf, n, l - n);
184             if (r == -1)
185                 throw new EOFException();
186             n += r;
187         } while (n < l);
188     }
189
190     public int getNumArchiveEntries() {
191         return entries.size();
192     }
193
194     public Enumeration getArchiveEntries() {
195         return Collections.enumeration(entries.values());
196     }
197
198     public ArchiveEntry getArchiveEntry(String JavaDoc name) {
199         return (TarEntry) entries.get(name);
200     }
201
202     public InputStream JavaDoc getInputStream(
203             final ArchiveEntry entry,
204             final ArchiveEntry dstEntry)
205     throws IOException {
206         return new FileInputStream JavaDoc(((TarEntry) entry).getFile());
207     }
208
209     public void close() throws IOException {
210         closeImpl();
211     }
212
213     private void closeImpl() throws IOException {
214         final Enumeration e = Collections.enumeration(entries.values());
215         while (e.hasMoreElements()) {
216             final TarEntry entry = (TarEntry) e.nextElement();
217             final File JavaDoc file = entry.getFile();
218             if (file.exists() && !file.delete()) {
219                 // Windoze: This entry file is still open for reading.
220
file.deleteOnExit();
221             }
222         }
223     }
224
225     //
226
// Metadata stuff.
227
//
228

229     public InputArchiveMetaData getMetaData() {
230         return metaData;
231     }
232
233     public void setMetaData(InputArchiveMetaData metaData) {
234         this.metaData = metaData;
235     }
236
237     //
238
// Member class.
239
//
240

241     /**
242      * This must be a {@link FileNotFoundException} in order to signal that
243      * the TAR is simply not accessible and not necessarily a false positive.
244      */

245     private static final class TempFileException extends FileNotFoundException {
246         private final Archive archive;
247         private final org.apache.tools.tar.TarEntry entry;
248         private final File JavaDoc tmp;
249
250         public TempFileException(
251                 final Archive archive,
252                 final org.apache.tools.tar.TarEntry entry,
253                 final File JavaDoc tmp,
254                 final String JavaDoc msg) {
255             this(archive, entry, tmp, msg, null);
256         }
257
258         public TempFileException(
259                 final Archive archive,
260                 final org.apache.tools.tar.TarEntry entry,
261                 final IOException cause) {
262             this(archive, entry, null, null, cause);
263         }
264
265         private TempFileException(
266                 final Archive archive,
267                 final org.apache.tools.tar.TarEntry entry,
268                 final File JavaDoc tmp,
269                 final String JavaDoc msg,
270                 final IOException cause) {
271             super(msg);
272             initCause(cause);
273             this.archive = archive;
274             this.entry = entry;
275             this.tmp = tmp;
276         }
277
278         public String JavaDoc getMessage() {
279             String JavaDoc msg = super.getMessage();
280             if (tmp != null)
281                 msg += " for \"" + tmp.getPath() + "\"";
282             msg += " (temp file for entry \"" + entry.getName()
283                     + "\" in archive file \"" + archive.getPath() + "\")";
284             return msg;
285         }
286     }
287 }
288
Popular Tags