KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > io > InputArchiveMetaData


1 /*
2  * InputArchiveMetaData.java
3  *
4  * Created on 8. M�rz 2006, 12:23
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;
23
24 import de.schlichtherle.io.*;
25 import de.schlichtherle.io.archive.Archive;
26 import de.schlichtherle.io.archive.spi.*;
27 import de.schlichtherle.io.util.*;
28 import de.schlichtherle.util.*;
29
30 import java.io.*;
31 import java.util.*;
32 import java.util.logging.*;
33
34 /**
35  * <em>This class is <b>not</b> intended for public use!</em>
36  * It's only public in order to implement
37  * {@link de.schlichtherle.io.archive.spi.ArchiveDriver}s.
38  * <p>
39  * This class annotates an {@link InputArchive} with the methods required
40  * for safe reading of archive entries.
41  * As an implication of this, it's also responsible for the synchronization
42  * of the streams between multiple threads.
43  *
44  * @author Christian Schlichtherle
45  * @version @version@
46  * @since TrueZIP 6.0
47  */

48 public final class InputArchiveMetaData {
49
50     private static final String JavaDoc CLASS_NAME
51             = "de/schlichtherle/io/InputArchiveMetaData".replace('/', '.'); // support code obfuscation!
52
private static final Logger logger = Logger.getLogger(CLASS_NAME, CLASS_NAME);
53
54     /**
55      * The archive which uses this instance.
56      * Although this field is actually never used in this class, it makes an
57      * archive controller strongly reachable from any entry stream in use by
58      * any thread.
59      * This is required to keep the archive controller from being garbarge
60      * collected meanwhile.
61      * <p>
62      * <b>Detail:</b> While this is really required for input streams for
63      * archives which are unmodified, it's actually not required for output
64      * streams, since the archive file system is touched for these streams
65      * anyway, which in turn schedules the archive controller for the next
66      * update, which in turn prevents it from being garbage collected.
67      * However, it's provided for symmetry between input archive meta data
68      * and output archive meta data.
69      */

70     private final Archive archive;
71
72     private final InputArchive inArchive;
73
74     /**
75      * The set of all entry streams currently open.
76      * This is implemented as a map where the keys are the streams and the
77      * value is (always) this object.
78      * If <code>File.isLenient()</code> is true, then the map is actually
79      * instantiated as a {@link WeakHashMap}. Otherwise, it's a {@link HashMap}.
80      * The weak hash map allows the garbage collector to pick up an entry
81      * stream if there are no more references to it.
82      * This reduces the likeliness of an {@link ArchiveBusyWarningException}
83      * in case a sloppy client application has forgot to close a stream before
84      * calling {@link File#update()} or {@link File#umount()}.
85      */

86     private final Map streams = File.isLenient()
87             ? (Map) new WeakHashMap()
88             : new HashMap();
89
90     /**
91      * A counter for the number of entry streams opened (and not yet closed)
92      * by the current thread.
93      */

94     private final ThreadLocalCounter tlStreams = new ThreadLocalCounter();
95
96     private volatile boolean stopped;
97
98     /**
99      * Creates a new instance of <code>InputArchiveMetaData</code>
100      * and sets itself as the meta data for the given input archive.
101      */

102     InputArchiveMetaData(final Archive archive, final InputArchive inArchive) {
103         assert inArchive != null;
104
105         this.archive = archive;
106         this.inArchive = inArchive;
107
108         inArchive.setMetaData(this);
109     }
110
111     synchronized InputStream getInputStream(
112             final ArchiveEntry entry,
113             final ArchiveEntry dstEntry)
114     throws IOException {
115         assert entry != null;
116         final InputStream in = inArchive.getInputStream(entry, dstEntry);
117         return in != null ? new EntryInputStream(in) : null;
118     }
119
120     /**
121      * Waits until all entry streams opened (and not yet closed) by all other
122      * threads are closed or a timeout occurs.
123      * Streams opened by the current thread are ignored.
124      * In addition, if the current thread is interrupted while waiting,
125      * this method returns.
126      * Furthermore, unless otherwise prevented, another thread could
127      * immediately open another stream upon return of this method.
128      * So there is actually no guarantee that really <em>all</em> streams
129      * are closed upon return of this method - use carefully!
130      *
131      * @return The number of open streams, <em>including</em> the current thread.
132      */

133     synchronized int waitAllInputStreamsByOtherThreads(final long timeout) {
134         final long start = System.currentTimeMillis();
135         final int localStreams = tlStreams.getCounter();
136         //Thread.interrupted(); // clear interruption status for the upcoming wait() call
137
try {
138             while (streams.size() > localStreams) {
139                 long toWait;
140                 if (timeout > 0) {
141                     toWait = timeout - (System.currentTimeMillis() - start);
142                     if (toWait <= 0)
143                         break;
144                 } else {
145                     toWait = 0;
146                 }
147                 System.gc(); // trigger garbage collection
148
System.runFinalization(); // trigger finalizers - is this required at all?
149
wait(toWait);
150             }
151         } catch (InterruptedException JavaDoc ignored) {
152         }
153
154         return streams.size();
155     }
156
157     /**
158      * Closes and disconnects <em>all</em> entry streams for the archive
159      * containing this metadata object.
160      * <i>Disconnecting</i> means that any subsequent operation on the entry
161      * streams will throw an {@link IOException}, with the exception of their
162      * <code>close()</code> method.
163      */

164     synchronized ArchiveException closeAllStreams(
165             ArchiveException exceptionChain) {
166         for (final Iterator i = streams.keySet().iterator(); i.hasNext(); ) {
167             final EntryInputStream in = (EntryInputStream) i.next();
168             try {
169                 in.doClose();
170             } catch (IOException failure) {
171                 exceptionChain = new ArchiveWarningException(
172                         exceptionChain, failure);
173             }
174         }
175
176         stopped = true;
177         streams.clear();
178
179         return exceptionChain;
180     }
181
182     /**
183      * An {@link InputStream} to read the entry data from an
184      * {@link InputArchive}.
185      * This input stream provides support for finalization and throws an
186      * {@link IOException} on any subsequent attempt to read data after
187      * {@link #closeAllStreams} has been called.
188      */

189     private final class EntryInputStream extends SynchronizedInputStream {
190         private /*volatile*/ boolean closed;
191
192         private EntryInputStream(final InputStream in) {
193             super(in, InputArchiveMetaData.this);
194             assert in != null;
195             streams.put(this, InputArchiveMetaData.this); // don't map to this - would not be removed from weak hash map!
196
tlStreams.increment();
197             InputArchiveMetaData.this.notify(); // there can be only one waiting thread!
198
}
199
200         private final void ensureNotStopped() throws IOException {
201             if (stopped)
202                 throw new ArchiveEntryStreamClosedException();
203         }
204
205         public int read() throws IOException {
206             ensureNotStopped();
207             return super.read();
208         }
209
210         public int read(byte[] b) throws IOException {
211             ensureNotStopped();
212             return super.read(b);
213         }
214
215         public int read(byte[] b, int off, int len) throws IOException {
216             ensureNotStopped();
217             return super.read(b, off, len);
218         }
219
220         public long skip(long n) throws IOException {
221             ensureNotStopped();
222             return super.skip(n);
223         }
224
225         public int available() throws IOException {
226             ensureNotStopped();
227             return super.available();
228         }
229
230         /**
231          * Closes this archive entry input stream and releases any resources
232          * associated with it.
233          * This method tolerates multiple calls to it and calls
234          * {@link #doClose} on the first call only.
235          *
236          * @see #doClose
237          */

238         public final void close() throws IOException {
239             if (closed)
240                 return;
241
242             // Order is important!
243
synchronized (lock) {
244                 try {
245                     doClose();
246                 } finally {
247                     final Object JavaDoc removed = streams.remove(this);
248                     assert removed == InputArchiveMetaData.this;
249                     tlStreams.decrement();
250                     assert tlStreams.getCounter() >= 0 : "This stream has been closed by a different thread than the thread which created it - this is considered an application bug!";
251                     InputArchiveMetaData.this.notify(); // there can be only one waiting thread!
252
}
253             }
254         }
255
256         /**
257          * Closes this archive entry input stream and releases any resources
258          * associated with it.
259          * <p>
260          * This method tolerates multiple calls to it and calls
261          * <code>super.close()</code>
262          * on the first call only.
263          *
264          * @throws IOException If an I/O exception occurs.
265          */

266         private void doClose() throws IOException {
267             assert !closed;
268             if (closed)
269                 return;
270
271             closed = true;
272             in.close(); // no need to synchronize
273
}
274
275         public void mark(int readlimit) {
276             if (!stopped)
277                 super.mark(readlimit);
278         }
279
280         public void reset() throws IOException {
281             ensureNotStopped();
282             super.reset();
283         }
284
285         public boolean markSupported() {
286             return !stopped && super.markSupported();
287         }
288
289         /**
290          * The finalizer in this class forces this archive entry input
291          * stream to close.
292          * This is used to ensure that an archive can be updated although
293          * the client may have "forgot" to close this input stream before.
294          */

295         protected void finalize() {
296             if (closed)
297                 return;
298
299             logger.finest("finalize.open");
300             try {
301                 doClose();
302             } catch (IOException failure) {
303                 logger.log(Level.FINE, "finalize.exception", failure);
304             }
305         }
306     } // class EntryInputStream
307
}
308
Popular Tags