KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * OutputArchiveMetaData.java
3  *
4  * Created on 8. M�rz 2006, 12:24
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 OutputArchive} with the methods required
40  * for safe writing 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 OutputArchiveMetaData {
49
50     private static final String JavaDoc CLASS_NAME
51             = "de/schlichtherle/io/OutputArchiveMetaData".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 OutputArchive outArchive;
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>OutputArchiveMetaData</code>
100      * and sets itself as the meta data for the given output archive.
101      */

102     OutputArchiveMetaData(final Archive archive, final OutputArchive outArchive) {
103         assert outArchive != null;
104
105         this.archive = archive;
106         this.outArchive = outArchive;
107
108         outArchive.setMetaData(this);
109     }
110
111     synchronized OutputStream getOutputStream(
112             final ArchiveEntry entry,
113             final ArchiveEntry srcEntry)
114     throws IOException {
115         assert entry != null;
116         final OutputStream out = outArchive.getOutputStream(entry, srcEntry);
117         return out != null ? new EntryOutputStream(out) : 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 waitAllOutputStreamsByOtherThreads(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 EntryOutputStream out = (EntryOutputStream) i.next();
168             try {
169                 out.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 OutputStream} to write the entry data to an
184      * {@link OutputArchive}.
185      * This output stream provides support for finalization and throws an
186      * {@link IOException} on any subsequent attempt to write data after
187      * {@link #closeAllStreams} has been called.
188      */

189     private final class EntryOutputStream extends SynchronizedOutputStream {
190         private /*volatile*/ boolean closed;
191
192         private EntryOutputStream(final OutputStream out) {
193             super(out, OutputArchiveMetaData.this);
194             assert out != null;
195             streams.put(this, OutputArchiveMetaData.this); // don't map to this - would not be removed from weak hash map!
196
tlStreams.increment();
197             OutputArchiveMetaData.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 void write(int b) throws IOException {
206             ensureNotStopped();
207             super.write(b);
208         }
209
210         public void write(byte[] b) throws IOException {
211             ensureNotStopped();
212             super.write(b);
213         }
214
215         public void write(byte[] b, int off, int len) throws IOException {
216             ensureNotStopped();
217             super.write(b, off, len);
218         }
219
220         public void flush() throws IOException {
221             ensureNotStopped();
222             super.flush();
223         }
224
225         /**
226          * Closes this archive entry output stream and releases any resources
227          * associated with it.
228          * This method tolerates multiple calls to it and calls
229          * {@link #doClose} on the first call only.
230          *
231          * @see #doClose
232          */

233         public final void close() throws IOException {
234             if (closed)
235                 return;
236
237             // Order is important!
238
synchronized (lock) {
239                 try {
240                     try {
241                         super.flush();
242                     } finally {
243                         doClose();
244                     }
245                 } finally {
246                     final Object JavaDoc removed = streams.remove(this);
247                     assert removed == OutputArchiveMetaData.this;
248                     tlStreams.decrement();
249                     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!";
250                     OutputArchiveMetaData.this.notify(); // there can be only one waiting thread!
251
}
252             }
253         }
254
255         /**
256          * Closes this archive entry output stream and releases any resources
257          * associated with it.
258          * <p>
259          * This method tolerates multiple calls to it and calls
260          * <code>super.close()</code>
261          * on the first call only.
262          *
263          * @throws IOException If an I/O exception occurs.
264          */

265         private void doClose() throws IOException {
266             assert !closed;
267             if (closed)
268                 return;
269
270             closed = true;
271             try {
272                 //out.flush(); // do NOT mask bugs in client apps!
273
} finally {
274                 out.close(); // no need to synchronize
275
}
276         }
277
278         /**
279          * The finalizer in this class forces this archive entry output
280          * stream to close.
281          * This is used to ensure that an archive can be updated although
282          * the client may have "forgot" to close this output stream before.
283          */

284         protected void finalize() {
285             if (closed)
286                 return;
287
288             logger.finer("finalize.open");
289             try {
290                 doClose();
291             } catch (IOException failure) {
292                 logger.log(Level.FINE, "finalize.exception", failure);
293             }
294         }
295     } // class EntryOutputStream
296
}
297
Popular Tags