KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * ArchiveController.java
3  *
4  * Created on 23. Oktober 2004, 20:41
5  */

6 /*
7  * Copyright 2004-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.ArchiveFileSystem.Delta;
25 import de.schlichtherle.io.archive.*;
26 import de.schlichtherle.io.archive.spi.*;
27 import de.schlichtherle.io.rof.*;
28 import de.schlichtherle.key.*;
29
30 import java.io.*;
31 import java.lang.ref.*;
32 import java.util.*;
33 import java.util.logging.*;
34
35 import javax.swing.Icon JavaDoc;
36
37 /**
38  * This is the base class for any archive controller, providing all the
39  * essential services required by the {@link File} class to implement its
40  * behaviour.
41  * Each instance of this class manages a globally unique archive file
42  * (the <i>target file</i>) in order to allow random access to it as if it
43  * were a regular directory in the native file system.
44  * <p>
45  * In terms of software patterns, an <code>ArchiveController</code> is
46  * similar to a Builder, with the <code>ArchiveDriver</code> interface as
47  * its Abstract Factory.
48  * However, an archive controller does not necessarily build a new archive.
49  * It may also simply be used to access an existing archive for read-only
50  * operations, such as listing its top level directory, or reading entry data.
51  * Whatever type of operation it's used for, an archive controller provides
52  * and controls <em>all</em> access to any particular archive file by the
53  * client application and deals with the rather complex details of its
54  * states and transitions.
55  * <p>
56  * Each instance of this class maintains a virtual file system, provides input
57  * and output streams for the entries of the archive file and methods
58  * to update the contents of the virtual file system to the target file
59  * in the native file system.
60  * In cooperation with the {@link File} class, it also knows how to deal with
61  * nested archive files (such as <code>"outer.zip/inner.tar.gz"</code>
62  * and <i>false positives</i>, i.e. plain files or directories or file or
63  * directory entries in an enclosing archive file which have been incorrectly
64  * recognized to be <i>prospective archive files</i> by the
65  * {@link ArchiveDetector} interface.
66  * <p>
67  * To ensure that for each archive file there is at most one
68  * <code>ArchiveController</code>, the path name of the archive file (called
69  * <i>target</i>) is canonicalized, so it doesn't matter whether the
70  * {@link File} class addresses an archive file as <code>"archive.zip"</code>
71  * or <code>"/dir/archive.zip"</code> if <code>"/dir"</code> is the client
72  * application's current directory.
73  * <p>
74  * Note that in general all of its methods are reentrant on exceptions.
75  * This is important because the {@link File} class may repeatedly call them,
76  * triggered by the client application. Of course, depending on the context,
77  * some or all of the archive file's data may be lost in this case.
78  * For more information, please refer to {@link File#update()} and
79  * {@link File#umount()}.
80  * <p>
81  * This class is actually the abstract base class for any archive controller.
82  * It encapsulates all the code which is not depending on a particular entry
83  * synchronization strategy and the corresponding state of the controller.
84  * Though currently unused, this is intended to be helpful for future
85  * extensions of TrueZIP, where different synchronization strategies may be
86  * implemented.
87  *
88  * @author Christian Schlichtherle
89  * @version @version@
90  * @since TrueZIP 6.0
91  */

92 abstract class ArchiveController implements Archive, FileConstants {
93
94     //
95
// Static fields.
96
//
97

98     private static final String JavaDoc CLASS_NAME
99             = "de/schlichtherle/io/ArchiveController".replace('/', '.'); // support code obfuscation!
100
private static final Logger logger = Logger.getLogger(CLASS_NAME, CLASS_NAME);
101
102     /**
103      * The map of all archive controllers.
104      * The keys are plain {@link java.io.File} instances and the values
105      * are either <code>ArchiveController</code>s or {@link WeakReference}s
106      * to <code>ArchiveController</code>s.
107      * All access to this map must be externally synchronized!
108      */

109     private static final Map controllers = new WeakHashMap();
110
111     private static final LiveStatistics liveStats = new LiveStatistics();
112
113     /**
114      * A lock used when copying data from one archive to another.
115      * This lock must be acquired before any other locks on the controllers
116      * are acquired in order to prevent dead locks.
117      */

118     private static final CopyLock copyLock = new CopyLock();
119
120     private static final Comparator REVERSE_CONTROLLERS = new Comparator() {
121         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
122             return ((ArchiveController) o2).getTarget().compareTo(
123                     ((ArchiveController) o1).getTarget());
124         }
125     };
126
127     //
128
// Instance fields.
129
//
130

131     /**
132      * A weak reference to this archive controller.
133      * This field is for exclusive use by {@link #setScheduled(boolean)}.
134      */

135     private final WeakReference weakThis = new WeakReference(this);
136
137     /**
138      * the canonicalized or at least normalized absolute path name
139      * representation of the target file.
140      */

141     private final java.io.File JavaDoc target;
142
143     /**
144      * The archive controller of the enclosing archive, if any.
145      */

146     private final ArchiveController enclController;
147
148     /**
149      * The name of the entry for this archive in the enclosing archive, if any.
150      */

151     private final String JavaDoc enclEntryName;
152
153     /**
154      * The {@link ArchiveDriver} to use for this controller's target file.
155      */

156     private /*volatile*/ ArchiveDriver driver;
157
158     private final ReentrantLock readLock;
159     private final ReentrantLock writeLock;
160
161     //
162
// Static initializers.
163
//
164

165     static {
166         Runtime.getRuntime().addShutdownHook(ShutdownHook.singleton);
167     }
168
169     //
170
// Constructors.
171
//
172

173     /**
174      * This constructor schedules this controller to be thrown away if no
175      * more <code>File</code> objects are referring to it.
176      * The subclass must update this schedule according to the controller's
177      * state.
178      * For example, if the controller has started to update some entry data,
179      * it must call {@link #setScheduled} in order to force the
180      * controller to be updated on the next call to {@link #update} even if
181      * no more <code>File</code> objects are referring to it.
182      * Otherwise, all changes may get lost!
183      *
184      * @see #setScheduled(boolean)
185      */

186     protected ArchiveController(
187             final java.io.File JavaDoc target,
188             final ArchiveController enclController,
189             final String JavaDoc enclEntryName,
190             final ArchiveDriver driver) {
191         assert target != null;
192         assert target.isAbsolute();
193         assert (enclController != null) == (enclEntryName != null);
194         assert driver != null;
195
196         this.target = target;
197         this.enclController = enclController;
198         this.enclEntryName = enclEntryName;
199         this.driver = driver;
200
201         ReadWriteLock rwl = new ReentrantReadWriteLock();
202         this.readLock = rwl.readLock();
203         this.writeLock = rwl.writeLock();
204
205         setScheduled(false);
206     }
207
208     //
209
// Methods.
210
//
211

212     public final ReentrantLock readLock() {
213         return readLock;
214     }
215
216     public final ReentrantLock writeLock() {
217         return writeLock;
218     }
219
220     /**
221      * Runs the given {@link IORunnable} while this controller has
222      * acquired its write lock regardless of the state of its read lock.
223      * You must use this method if this controller may have acquired a
224      * read lock in order to prevent a dead lock.
225      * <p>
226      * <b>Warning:</b> This method temporarily releases the read lock
227      * before the write lock is acquired and the runnable is run!
228      * Hence, the runnable should recheck the state of the controller
229      * before it proceeds with any write operations.
230      *
231      * @param runnable The {@link IORunnable} to run while the write
232      * lock is acquired.
233      * No read lock is acquired while it's running.
234      */

235     protected final void runWriteLocked(IORunnable runnable)
236     throws IOException {
237         // A read lock cannot get upgraded to a write lock.
238
// Hence the following mess is required.
239
// Note that this is not just a limitation of the current
240
// implementation in JSE 5: If automatic upgrading were implemented,
241
// two threads holding a read lock try to upgrade concurrently,
242
// they would dead lock each other!
243
final int lockCount = readLock().lockCount();
244         for (int c = lockCount; c > 0; c--)
245             readLock().unlock();
246
247         // The current thread may get deactivated here!
248
writeLock().lock();
249         try {
250             try {
251                 runnable.run();
252             } finally {
253                 // Restore lock count - effectively downgrading the lock
254
for (int c = lockCount; c > 0; c--)
255                     readLock().lock();
256             }
257         } finally {
258             writeLock().unlock();
259         }
260     }
261
262     /**
263      * Factory method returning a <code>ArchiveController</code> object for the
264      * archive file <code>target</code> and all its enclosing archive files.
265      * <p>
266      * <b>Notes:</b>
267      * <ul>
268      * <li>Neither <code>file</code> nor the enclosing archive file(s)
269      * need to actually exist for this to return a valid <code>ArchiveController</code>.
270      * Just the parent directories of <code>file</code> need to look like either
271      * an ordinary directory or an archive file, e.g. their lowercase
272      * representation needs to have a .zip or .jar ending.</li>
273      * <li>It is an error to call this method on a target file which is
274      * not a valid name for an archive file</li>
275      * </ul>
276      */

277     static ArchiveController getInstance(final File file) {
278         assert file != null;
279         assert file.isArchive();
280
281         java.io.File JavaDoc target = file.getDelegate();
282         try {
283             target = target.getCanonicalFile();
284         } catch (IOException failure) {
285             target = File.normalize(target.getAbsoluteFile());
286         }
287
288         final ArchiveDriver driver = file.getArchiveDetector()
289                 .getArchiveDriver(target.getPath());
290
291         ArchiveController controller = null;
292         boolean reconfigure = false;
293         try {
294             synchronized (controllers) {
295                 final Object JavaDoc value = controllers.get(target);
296                 if (value instanceof Reference) {
297                     controller = (ArchiveController) ((Reference) value).get();
298                     // Check that the controller hasn't been garbage collected
299
// meanwhile!
300
if (controller != null) {
301                         // If required, reconfiguration of the ArchiveController
302
// must be deferred until we have released the lock on
303
// controllers in order to prevent dead locks.
304
reconfigure = controller.getDriver() != driver;
305                         return controller;
306                     }
307                 } else if (value != null) {
308                     // Do NOT reconfigure this ArchiveController with another
309
// ArchiveDetector: This controller is touched, i.e. it
310
// most probably has mounted the virtual file system and
311
// using another ArchiveDetector could potentially break
312
// the update process.
313
// In effect, for an application this means that the
314
// reconfiguration of a previously used ArchiveController
315
// is only guaranteed to happen if
316
// (1) File.update() or File.umount() has been called and
317
// (2) a new File instance referring to the previously used
318
// archive file as either the file itself or one
319
// of its ancestors is created with a different
320
// ArchiveDetector.
321
return (ArchiveController) value;
322                 }
323
324                 final File enclArchive = file.getEnclArchive();
325                 final ArchiveController enclController;
326                 final String JavaDoc enclEntryName;
327                 if (enclArchive != null) {
328                     enclController = enclArchive.getArchiveController();
329                     enclEntryName = file.getEnclEntryName();
330                 } else {
331                     enclController = null;
332                     enclEntryName = null;
333                 }
334
335                 // TODO: Refactor this to a more flexible design which supports
336
// different update strategies, like update or append.
337
controller = new UpdatingArchiveController(
338                         target, enclController, enclEntryName, driver);
339             }
340         } finally {
341             if (reconfigure) {
342                 controller.writeLock().lock();
343                 try {
344                     controller.setDriver(driver);
345                 } finally {
346                     controller.writeLock().unlock();
347                 }
348             }
349         }
350
351         return controller;
352     }
353
354     /**
355      * Returns the canonical or at least normalized absolute
356      * <code>java.io.File</code> object for the archive file to control.
357      */

358     public final java.io.File JavaDoc getTarget() {
359         return target;
360     }
361
362     public final String JavaDoc getPath() {
363         return target.getPath();
364     }
365
366     /**
367      * Returns the {@link ArchiveController} of the enclosing archive file,
368      * if any.
369      */

370     public final ArchiveController getEnclController() {
371         return enclController;
372     }
373
374     /**
375      * Returns the entry name of this controller within the enclosing archive
376      * file, if any.
377      */

378     public final String JavaDoc getEnclEntryName() {
379         return enclEntryName;
380     }
381
382     public final String JavaDoc enclEntryName(final String JavaDoc entryName) {
383         return EMPTY != entryName
384                 ? enclEntryName + ENTRY_SEPARATOR + entryName
385                 : enclEntryName;
386     }
387
388     /**
389      * Returns the driver instance which is used for the target archive.
390      * All access to this method must be externally synchronized on this
391      * controller's read lock!
392      *
393      * @return A valid reference to an {@link ArchiveDriver} object
394      * - never <code>null</code>.
395      */

396     public final ArchiveDriver getDriver() {
397         return driver;
398     }
399
400     /**
401      * Sets the driver instance which is used for the target archive.
402      * All access to this method must be externally synchronized on this
403      * controller's write lock!
404      *
405      * @param driver A valid reference to an {@link ArchiveDriver} object
406      * - never <code>null</code>.
407      */

408     protected final void setDriver(ArchiveDriver driver) {
409         // This affects all subsequent creations of the driver's products
410
// (In/OutputArchive and ArchiveEntry) and hence ArchiveFileSystem.
411
// Normally, these are initialized together in mountFileSystem(...)
412
// which is externally synchronized on this controller's write lock,
413
// so we don't need to be afraid of this.
414
this.driver = driver;
415     }
416
417     static final ArchiveStatistics getLiveStatistics() {
418         return liveStats;
419     }
420
421     /**
422      * Returns <code>true</code> if and only if the target file of this
423      * controller should be considered to be a true file in the native
424      * file system (although it does not actually need to exist).
425      */

426     protected final boolean usesNativeTargetFile() {
427         // May be called from FileOutputStream while unlocked!
428
//assert readLock().isLocked() || writeLock().isLocked();
429

430         // True iff not enclosed or the enclosing target is actually a plain
431
// directory or the target is a plain file.
432
return enclController == null
433                 || enclController.getTarget().isDirectory();
434     }
435
436     /**
437      * Returns <code>true</code> if and only if the file system has been
438      * touched.
439      */

440     protected abstract boolean isTouched();
441
442     /**
443      * (Re)schedules this archive controller for the next call to
444      * {@link #updateAll(String, boolean, boolean, boolean, boolean, boolean, boolean)}.
445      *
446      * @param scheduled If set to <code>true</code>, this controller and hence
447      * its target archive file is guaranteed to get updated during the
448      * next call to <code>updateAll()</code> even if there are no more
449      * {@link File} objects referring to it meanwhile.
450      * Call this method with this parameter value whenever the virtual
451      * file system has been touched, i.e. modified.
452      * <p>
453      * If set to <code>false</code>, this controller is conditionally
454      * scheduled to get updated.
455      * In this case, the controller gets automatically removed from
456      * the controllers weak hash map and discarded once the last file
457      * object directly or indirectly referring to it has been discarded
458      * unless <code>setScheduled(true)</code> has been called meanwhile.
459      * Call this method if the archive controller has been newly created
460      * or successfully updated.
461      */

462     protected final void setScheduled(final boolean scheduled) {
463         assert weakThis.get() != null || !scheduled; // (garbage collected => no scheduling) == (scheduling => not garbage collected)
464

465         synchronized (controllers) {
466             controllers.put(getTarget(), scheduled ? (Object JavaDoc) this : weakThis);
467         }
468     }
469
470     /**
471      * A factory method returning an input stream which is positioned
472      * at the beginning of the given entry in the target archive file.
473      *
474      * @param entryName An entry in the virtual archive file system
475      * - <code>null</code> or <code>""</code> is not permitted.
476      * @return A valid <code>InputStream</code> object
477      * - <code>null</code> is never returned.
478      * @throws FileNotFoundException If the entry cannot get read for
479      * any reason.
480      */

481     public final InputStream getInputStream(final String JavaDoc entryName)
482     throws FileNotFoundException {
483         assert entryName != null;
484         assert EMPTY != entryName; // possibly assigned by File.init(...)
485

486         try {
487             return getInputStream0(entryName);
488         } catch (FalsePositiveEntryException failure) { // TODO: Review: Was: FalsePositiveDirectoryEntryException
489
return enclController.getInputStream(enclEntryName(entryName));
490         } catch (FileNotFoundException failure) {
491             throw failure;
492         } catch (ArchiveBusyException failure) {
493             throw new FileBusyException(failure);
494         } catch (IOException failure) {
495             final FileNotFoundException fnfe
496                     = new FileNotFoundException(failure.toString());
497             fnfe.initCause(failure);
498             throw fnfe;
499         }
500     }
501
502     private InputStream getInputStream0(final String JavaDoc entryName)
503     throws IOException {
504         // Order is important!
505
readLock().lock();
506         try {
507             if (hasNewData(entryName)) {
508                 runWriteLocked(new IORunnable() {
509                     public void run() throws IOException {
510                         if (hasNewData(entryName)) // check again!
511
update();
512                     }
513                 });
514             }
515
516             final ArchiveEntry entry = getFileSystem(false).get(entryName);
517             if (entry == null)
518                 throw new ArchiveEntryNotFoundException(entryName, "no such entry");
519
520             return getInputStream(entry, null);
521         } finally {
522             readLock().unlock();
523         }
524     }
525
526     private final InputStream getInputStream(
527             final ArchiveEntry entry,
528             final ArchiveEntry dstEntry)
529     throws FileNotFoundException {
530         assert readLock().isLocked() || writeLock().isLocked();
531         assert !hasNewData(entry.getName());
532
533         try {
534             return getInputStreamImpl(entry, dstEntry);
535         } catch (FileNotFoundException failure) {
536             throw failure;
537         } catch (IOException failure) {
538             final FileNotFoundException fnfe
539                     = new FileNotFoundException(failure.toString());
540             fnfe.initCause(failure);
541             throw fnfe;
542         }
543     }
544
545     /**
546      * <b>Important:</b>
547      * <ul>
548      * <li>This controller's read <em>or</em> write lock must be acquired.
549      * <li><code>entry</code> must not have received
550      * {@link #hasNewData new data}.
551      * <ul>
552      */

553     protected abstract InputStream getInputStreamImpl(
554             ArchiveEntry entry,
555             ArchiveEntry dstEntry)
556     throws IOException;
557
558     /**
559      * A factory method returning an <code>OutputStream</code> allowing to
560      * (re)write the given entry in the target archive file.
561      *
562      * @param entryName An entry in the virtual archive file system
563      * - <code>null</code> or <code>""</code> is not permitted.
564      * @return A valid <code>OutputStream</code> object
565      * - <code>null</code> is never returned.
566      * @throws FileNotFoundException If the entry cannot get (re)written for
567      * any reason.
568      */

569     public final OutputStream getOutputStream(
570             final String JavaDoc entryName,
571             final boolean append)
572     throws FileNotFoundException {
573         assert entryName != null;
574         assert EMPTY != entryName; // possibly assigned by File.init(...)
575

576         try {
577             // Order is important here because of side effects in the file
578
// system!
579
InputStream in = null;
580             if (append && isFile(entryName))
581                 in = getInputStream0(entryName);
582             final OutputStream out = getOutputStream0(entryName);
583             if (in != null) {
584                 try {
585                     File.cat(in, out);
586                 } finally {
587                     in.close();
588                 }
589             }
590             return out;
591         } catch (FalsePositiveEntryException failure) { // TODO: Review: Was: FalsePositiveDirectoryEntryException
592
return enclController.getOutputStream(enclEntryName(entryName),
593                     append);
594         } catch (FileNotFoundException failure) {
595             throw failure;
596         } catch (ArchiveBusyException failure) {
597             throw new FileBusyException(failure);
598         } catch (IOException failure) {
599             final FileNotFoundException exc
600                     = new FileNotFoundException(failure.toString());
601             exc.initCause(failure);
602             throw exc;
603         }
604     }
605
606     private OutputStream getOutputStream0(final String JavaDoc entryName)
607     throws IOException {
608         assert entryName != null;
609
610         class OutputStreamCreator implements IORunnable {
611             OutputStream out;
612
613             public void run() throws IOException {
614                 if (hasNewData(entryName))
615                     update();
616
617                 final boolean lenient = File.isLenient();
618                 final ArchiveFileSystem fileSystem = getFileSystem(lenient);
619
620                 // Start transaction, creating a new archive entry.
621
final Delta delta = fileSystem.beginCreateAndLink(
622                         entryName, lenient);
623
624                 // Get output stream.
625
out = getOutputStream(delta.getEntry(), null);
626
627                 // Commit the transaction, linking the entry into the virtual
628
// archive file system.
629
delta.commit();
630             }
631         } // class OutputStreamCreator
632

633         final OutputStreamCreator creator = new OutputStreamCreator();
634         runWriteLocked(creator);
635         return creator.out;
636     }
637
638     private final OutputStream getOutputStream(
639             final ArchiveEntry entry,
640             final ArchiveEntry srcEntry)
641     throws FileNotFoundException {
642         try {
643             return getOutputStreamImpl(entry, srcEntry);
644         } catch (FileNotFoundException failure) {
645             throw failure;
646         } catch (IOException failure) {
647             final FileNotFoundException fnfe
648                     = new FileNotFoundException(failure.toString());
649             fnfe.initCause(failure);
650             throw fnfe;
651         }
652     }
653
654     /**
655      * <b>Important:</b>
656      * <ul>
657      * <li>This controller's <em>write</em> lock must be acquired.
658      * <li><code>entry</code> must not have received
659      * {@link #hasNewData new data}.
660      * <ul>
661      */

662     protected abstract OutputStream getOutputStreamImpl(
663             ArchiveEntry entry,
664             ArchiveEntry srcEntry)
665     throws IOException;
666
667     /**
668      * Tests if the archive entry with the given name has received or is
669      * currently receiving new data via an output stream.
670      * As an implication, the entry cannot receive new data from another
671      * output stream before the next call to {@link #update()}.
672      * Note that for directories this method will always return
673      * <code>false</code>!
674      */

675     protected abstract boolean hasNewData(String JavaDoc entryName);
676
677     /**
678      * Updates all archive files in the native file system which's canonical
679      * path name start with <code>prefix</code> with the contents of their
680      * virtual file system, resets all cached state and deletes all temporary
681      * files.
682      * This method is thread safe.
683      *
684      * @param prefix The prefix of the canonical path name of the archive files
685      * which shall get updated - <code>null</code> is not allowed!
686      * If the canonical pathname of an archive file does not start with
687      * this string, then it is not updated.
688      * @param waitInputStreams Suppose any other thread has still one or more
689      * archive entry input streams open.
690      * Then if and only if this parameter is <code>true</code>, this
691      * method will wait until all other threads have closed their
692      * archive entry input streams.
693      * Archive entry input streams opened (and not yet closed) by the
694      * current thread are always ignored.
695      * If the current thread gets interrupted while waiting, it will
696      * stop waiting and proceed normally as if this parameter were
697      * <code>false</code>.
698      * Be careful with this parameter value: If a stream has not been
699      * closed because the client application does not always properly
700      * close its streams, even on an {@link IOException} (which is a
701      * typical bug in many Java applications), then this method may
702      * not return until the current thread gets interrupted!
703      * @param closeInputStreams Suppose there are any open input streams
704      * for any archive entries because the application has forgot to
705      * close all {@link FileInputStream} objects or another thread is
706      * still busy doing I/O on an archive.
707      * Then if this parameter is <code>true</code>, an update is forced
708      * and an {@link ArchiveBusyWarningException} is finally thrown to
709      * indicate that any subsequent operations on these streams
710      * will fail with an {@link ArchiveEntryStreamClosedException}
711      * because they have been forced to close.
712      * This may also be used to recover an application from a
713      * {@link FileBusyException} thrown by a constructor of
714      * {@link FileInputStream} or {@link FileOutputStream}.
715      * If this parameter is <code>false</code>, the respective archive
716      * file is <em>not</em> updated and an {@link ArchiveBusyException}
717      * is thrown to indicate that the application must close all entry
718      * input streams first.
719      * @param waitOutputStreams Similar to <code>waitInputStreams</code>,
720      * but applies to archive entry output streams instead.
721      * @param closeOutputStreams Similar to <code>closeInputStreams</code>,
722      * but applies to archive entry output streams instead.
723      * If this parameter is <code>true</code>, then
724      * <code>closeInputStreams</code> must be <code>true</code>, too.
725      * Otherwise, an <code>IllegalArgumentException</code> is thrown.
726      * @param umount If <code>true</code>, all temporary files get deleted, too.
727      * Thereafter, the archive controller will behave as if it has just been
728      * created and any subsequent operations on its entries will remount
729      * the virtual file system from the archive file again.
730      * Use this to allow subsequent changes to the archive files
731      * by other processes or via the <code>java.io.File*</code> classes
732      * <em>before</em> this package is used for read or write access to
733      * these archive files again.
734      * @param resetCounters If and only if this parameter is <code>true</code>,
735      * the input and output stream counters will be reset if this hasn't
736      * yet happened automatically.
737      * @throws ArchiveBusyWarningExcepion If a archive file has been updated
738      * while the application is using any open streams to access it
739      * concurrently.
740      * These streams have been forced to close and the entries of
741      * output streams may contain only partial data.
742      * @throws ArchiveWarningException If only warning conditions occur
743      * throughout the course of this method which imply that the
744      * respective archive file has been updated with
745      * constraints, such as a failure to set the last modification
746      * time of the archive file to the last modification time of its
747      * implicit root directory.
748      * @throws ArchiveBusyException If an archive file could not get updated
749      * because the application is using an open stream.
750      * No data is lost and the archive file can still get updated by
751      * calling this method again.
752      * @throws ArchiveException If any error conditions occur throughout the
753      * course of this method which imply loss of data.
754      * This usually means that at least one of the archive files
755      * has been created externally and was corrupted or it cannot
756      * get updated because the file system of the temp file or target
757      * file folder is full.
758      * @throws NullPointerException If <code>prefix</code> is <code>null</code>.
759      * @throws IllegalArgumentException If <code>closeInputStreams</code> is
760      * <code>false</code> and <code>closeOutputStreams</code> is
761      * <code>true</code>.
762      */

763     static void updateAll(
764             final String JavaDoc prefix,
765             final boolean waitInputStreams,
766             final boolean closeInputStreams,
767             final boolean waitOutputStreams,
768             final boolean closeOutputStreams,
769             final boolean umount,
770             final boolean resetCounters)
771     throws ArchiveException {
772         if (prefix == null)
773             throw new NullPointerException JavaDoc();
774         if (!closeInputStreams && closeOutputStreams)
775             throw new IllegalArgumentException JavaDoc();
776
777         int controllersTotal = 0, controllersTouched = 0;
778         logger.log(Level.FINE, "updateAll.entering", // NOI18N
779
new Object JavaDoc[] {
780             prefix,
781             Boolean.valueOf(waitInputStreams),
782             Boolean.valueOf(closeInputStreams),
783             Boolean.valueOf(waitOutputStreams),
784             Boolean.valueOf(closeOutputStreams),
785             Boolean.valueOf(umount),
786             Boolean.valueOf(resetCounters),
787         });
788         try {
789             if (resetCounters) {
790                 // This if-block prevents the statistics from being
791
// cleared in case the application has called
792
// File.update() or File.umount() and there is no
793
// work to do.
794
CountingReadOnlyFile.resetOnReuse();
795                 CountingOutputStream.resetOnReuse();
796             }
797             try {
798                 // We must ensure that archive controllers which have been removed
799
// from the weak hash map finally umount, i.e. remove their
800
// temporary files.
801
// Running the garbage collector would be an alternative and may
802
// even remove more archive controllers from the weak hash map, but
803
// this could also be a severe performance penalty in large
804
// applications.
805
// In addition, there is no guarantee that the garbage collector
806
// immediately runs the finalize() methods.
807
if (umount) {
808                     System.runFinalization();
809                     // TODO: Consider this to ensure the finalizer thread has a
810
// chance to do its work before we create the controller's
811
// enumeration.
812
/*Thread.interrupted(); // clear interruption status
813                     try {
814                         Thread.sleep(50); // allow concurrent finalization
815                     } catch (InterruptedException ignored) {
816                     }*/

817                 }
818
819                 // Used to chain archive exceptions.
820
ArchiveException exceptionChain = null;
821
822                 // The general algorithm is to sort the targets in descending order
823
// of their pathnames (considering the system's default name
824
// separator character) and then walk the array in reverse order to
825
// call the update() method on each respective archive controller.
826
// This ensures that an archive file will always be updated
827
// before its enclosing archive file.
828
final Enumeration e = new ControllerEnumeration(
829                         prefix, REVERSE_CONTROLLERS);
830                 while (e.hasMoreElements()) {
831                     final ArchiveController controller
832                             = (ArchiveController) e.nextElement();
833                     controller.writeLock().lock();
834                     try {
835                         if (controller.isTouched())
836                             controllersTouched++;
837                         try {
838                             // Upon return, some new ArchiveWarningException's may
839
// have been generated. We need to remember them for
840
// later throwing.
841
controller.update(exceptionChain,
842                                     waitInputStreams, closeInputStreams,
843                                     waitOutputStreams, closeOutputStreams,
844                                     umount, true);
845                         } catch (ArchiveException exception) {
846                             // Updating the archive file or wrapping it back into
847
// one of it's enclosing archive files resulted in an
848
// exception for some reason.
849
// We are bullheaded and store the exception chain for
850
// later throwing only and continue updating the rest.
851
exceptionChain = exception;
852                         }
853                     } finally {
854                         controller.writeLock().unlock();
855                     }
856                     controllersTotal++;
857                 }
858
859                 // Reorder exception chain if necessary to support conditional
860
// exception catching based on their priority (i.e. class).
861
if (exceptionChain != null)
862                     throw (ArchiveException) exceptionChain.sortPriority();
863             } finally {
864                 CountingReadOnlyFile.setResetOnReuse();
865                 CountingOutputStream.setResetOnReuse();
866             }
867         } catch (ArchiveException failure) {
868             logger.log(Level.FINE, "updateAll.throwing", failure);// NOI18N
869
throw failure;
870         }
871         logger.log(Level.FINE, "updateAll.exiting", // NOI18N
872
new Object JavaDoc[] {
873             new Integer JavaDoc(controllersTotal),
874             new Integer JavaDoc(controllersTouched)
875         });
876     }
877
878     /**
879      * Equivalent to
880      * {@link #update(ArchiveException, boolean, boolean, boolean, boolean, boolean, boolean)
881      * update(null, false, false, false, false, false, false)}.
882      * <p>
883      * <b>Warning:</b> As a side effect, all data structures returned by this
884      * controller get reset (filesystem, entries, streams, etc.)!
885      * Hence, this method requires external synchronization on this
886      * controller's write lock!
887      *
888      * @see #update(ArchiveException, boolean, boolean, boolean, boolean, boolean, boolean)
889      * @see #updateAll
890      * @see ArchiveException
891      */

892     public final void update() throws ArchiveException {
893         assert writeLock().isLocked();
894         update(null, false, false, false, false, false, false);
895     }
896
897     /**
898      * Updates the contents of the archive file managed by this archive
899      * controller to the native file system.
900      * <p>
901      * <b>Warning:</b> As a side effect, all data structures returned by this
902      * controller get reset (filesystem, entries, streams, etc.)!
903      * Hence, this method requires external synchronization on this
904      * controller's write lock!
905      *
906      * @param waitInputStreams See {@link #updateAll}.
907      * @param closeInputStreams See {@link #updateAll}.
908      * @param waitOutputStreams See {@link #updateAll}.
909      * @param closeOutputStreams See {@link #updateAll}.
910      * @param umount See {@link #updateAll}.
911      * @param reassemble Let's assume this archive file is enclosed
912      * in another archive file.
913      * Then if this parameter is <code>true</code>, the updated archive
914      * file is also written to its enclosing archive file.
915      * Note that this parameter <em>must</em> be set if <code>umount</code>
916      * is set as well. Failing to comply to this requirement may throw
917      * a {@link java.lang.AssertionError} and will incur loss of data!
918      * @return If any warning condition occurs throughout the course of this
919      * method, an {@link ArchiveWarningException} is created (but not
920      * thrown), prepended to <code>exceptionChain</code> and finally
921      * returned.
922      * If multiple warning conditions occur,
923      * the prepended exceptions are ordered by appearance so that the
924      * <i>last</i> exception created is the head of the returned
925      * exception chain.
926      * @throws ArchiveException If any exception condition occurs throughout
927      * the course of this method, an {@link ArchiveException}
928      * is created, prepended to <code>exceptionChain</code> and finally
929      * thrown.
930      * @see #update()
931      * @see #updateAll
932      * @see ArchiveException
933      */

934     protected abstract void update(
935             ArchiveException exceptionChain,
936             final boolean waitInputStreams,
937             final boolean closeInputStreams,
938             final boolean waitOutputStreams,
939             final boolean closeOutputStreams,
940             final boolean umount,
941             final boolean reassemble)
942     throws ArchiveException;
943
944     // TODO: Document this!
945
protected abstract int waitAllInputStreamsByOtherThreads(long timeout);
946
947     // TODO: Document this!
948
protected abstract int waitAllOutputStreamsByOtherThreads(long timeout);
949
950     /**
951      * Returns the virtual archive file system mounted from the target file.
952      * This method is reentrant with respect to any exceptions it may throw.
953      * <p>
954      * <b>Warning:</b> Either the read or the write lock of this controller
955      * must be acquired while this method is called!
956      *
957      * @param autoCreate If the archive file does not exist
958      * and this is <code>true</code>, a new file system with only a root
959      * directory is created with its last modification time set to the
960      * system's current time.
961      *
962      * @return A valid archive file system - <code>null</code> is never returned.
963      *
964      * @throws FalsePositiveException
965      * @throws IOException On any other I/O related issue with the target file
966      * or the target file of any enclosing archive file's controller.
967      */

968     protected abstract ArchiveFileSystem getFileSystem(boolean autoCreate)
969     throws IOException;
970
971     /**
972      * Resets the archive controller to its initial state - all changes to the
973      * archive file which have not yet been updated get lost!
974      * Thereafter, the archive controller will behave as if it has just been
975      * created and any subsequent operations on its entries will remount
976      * the virtual file system from the archive file again.
977      * <p>
978      * This method should be overridden by subclasses, but must still be
979      * called when doing so.
980      */

981     protected abstract void reset()
982     throws IOException;
983
984     public String JavaDoc toString() {
985         return getClass().getName() + "@" + System.identityHashCode(this) + "(" + getPath() + ")";
986     }
987
988     //
989
// File system operations used by the File class.
990
// Read only operations.
991
//
992

993     public final boolean exists(final String JavaDoc entryName)
994     throws FalsePositiveNativeException {
995         try {
996             return exists0(entryName);
997         } catch (FalsePositiveEntryException failure) {
998             return enclController.exists(enclEntryName(entryName));
999         } catch (FalsePositiveNativeException failure) {
1000            throw failure;
1001        } catch (IOException failure) {
1002            return false;
1003        }
1004    }
1005
1006    private final boolean exists0(final String JavaDoc entryName)
1007    throws IOException {
1008        assert entryName != EMPTY;
1009
1010        readLock().lock();
1011        try {
1012            /*if (EMPTY == entryName && enclController != null)
1013                return enclController.exists(enclEntryName);*/

1014            final ArchiveFileSystem fileSystem = getFileSystem(false);
1015            return fileSystem.exists(entryName);
1016        } finally {
1017            readLock().unlock();
1018        }
1019    }
1020
1021    public final boolean isFile(final String JavaDoc entryName)
1022    throws FalsePositiveNativeException {
1023        try {
1024            return isFile0(entryName);
1025        } catch (ArchiveController.FalsePositiveEntryException failure) {
1026            if (EMPTY == entryName
1027                    && failure.getCause() instanceof FileNotFoundException) {
1028                // This appears to be an archive file, but we could not
1029
// access it.
1030
// One of the many reasons may be that the target file is an
1031
// RAES encrypted ZIP file for which password prompting has
1032
// been disabled or cancelled by the user.
1033
// In any of these cases we do not want this package to treat
1034
// this file like a plain file.
1035
// For the forementioned case, this implies that the RAES
1036
// encrypted file is identified as a special file, i.e.
1037
// exists() returns true, while both isFile() and isDirectory()
1038
// return false.
1039
return false;
1040            } else {
1041                return enclController.isFile(enclEntryName(entryName));
1042            }
1043        } catch (ArchiveController.FalsePositiveNativeException failure) {
1044            throw failure;
1045        } catch (IOException failure) {
1046            return false;
1047        }
1048    }
1049
1050    private final boolean isFile0(final String JavaDoc entryName)
1051    throws IOException {
1052        readLock().lock();
1053        try {
1054            final ArchiveFileSystem fileSystem = getFileSystem(false);
1055            return fileSystem.isFile(entryName);
1056        } finally {
1057            readLock().unlock();
1058        }
1059    }
1060
1061    public final boolean isDirectory(final String JavaDoc entryName)
1062    throws FalsePositiveNativeException {
1063        try {
1064            return isDirectory0(entryName);
1065        } catch (ArchiveController.FalsePositiveEntryException failure) {
1066            return enclController.isDirectory(enclEntryName(entryName));
1067        } catch (ArchiveController.FalsePositiveNativeException failure) {
1068            throw failure;
1069        } catch (IOException failure) {
1070            // The controller's target file or one of its enclosing archive
1071
// files is actually a plain file which is not compatible to the
1072
// archive file format.
1073
return false;
1074        }
1075    }
1076
1077    private final boolean isDirectory0(final String JavaDoc entryName)
1078    throws IOException {
1079        readLock().lock();
1080        try {
1081            final ArchiveFileSystem fileSystem = getFileSystem(false);
1082            return fileSystem.isDirectory(entryName);
1083        } finally {
1084            readLock().unlock();
1085        }
1086    }
1087
1088    public final Icon JavaDoc getOpenIcon(final String JavaDoc entryName)
1089    throws FalsePositiveNativeException {
1090        try {
1091            return getOpenIcon0(entryName);
1092        } catch (ArchiveController.FalsePositiveEntryException failure) {
1093            return enclController.getOpenIcon(enclEntryName(entryName));
1094        } catch (ArchiveController.FalsePositiveNativeException failure) {
1095            throw failure;
1096        } catch (IOException failure) {
1097            return null;
1098        }
1099    }
1100
1101    private final Icon JavaDoc getOpenIcon0(final String JavaDoc entryName)
1102    throws IOException {
1103        readLock().lock();
1104        try {
1105            final ArchiveFileSystem fileSystem = getFileSystem(false); // detect false positives!
1106
if (EMPTY != entryName) // possibly assigned by File.init(...)
1107
return fileSystem.getOpenIcon(entryName);
1108            else
1109                return getDriver().getOpenIcon(this);
1110        } finally {
1111            readLock().unlock();
1112        }
1113    }
1114
1115    public final Icon JavaDoc getClosedIcon(final String JavaDoc entryName)
1116    throws FalsePositiveNativeException {
1117        try {
1118            return getClosedIcon0(entryName);
1119        } catch (ArchiveController.FalsePositiveEntryException failure) {
1120            return enclController.getOpenIcon(enclEntryName(entryName));
1121        } catch (ArchiveController.FalsePositiveNativeException failure) {
1122            throw failure;
1123        } catch (IOException failure) {
1124            return null;
1125        }
1126    }
1127
1128    private final Icon JavaDoc getClosedIcon0(final String JavaDoc entryName)
1129    throws IOException {
1130        readLock().lock();
1131        try {
1132            final ArchiveFileSystem fileSystem = getFileSystem(false); // detect false positives!
1133
if (EMPTY != entryName) // possibly assigned by File.init(...)
1134
return fileSystem.getClosedIcon(entryName);
1135            else
1136                return getDriver().getClosedIcon(this);
1137        } finally {
1138            readLock().unlock();
1139        }
1140    }
1141
1142    public final boolean canRead(final String JavaDoc entryName)
1143    throws FalsePositiveNativeException {
1144        try {
1145            return canRead0(entryName);
1146        } catch (ArchiveController.FalsePositiveEntryException failure) {
1147            return enclController.canRead(enclEntryName(entryName));
1148        } catch (ArchiveController.FalsePositiveNativeException failure) {
1149            throw failure;
1150        } catch (IOException failure) {
1151            return false;
1152        }
1153    }
1154
1155    private final boolean canRead0(final String JavaDoc entryName)
1156    throws IOException {
1157        readLock().lock();
1158        try {
1159            final ArchiveFileSystem fileSystem = getFileSystem(false);
1160            return fileSystem.exists(entryName);
1161        } finally {
1162            readLock().unlock();
1163        }
1164    }
1165
1166    public final boolean canWrite(final String JavaDoc entryName)
1167    throws FalsePositiveNativeException {
1168        try {
1169            return canWrite0(entryName);
1170        } catch (ArchiveController.FalsePositiveEntryException failure) {
1171            return enclController.canWrite(enclEntryName(entryName));
1172        } catch (ArchiveController.FalsePositiveNativeException failure) {
1173            throw failure;
1174        } catch (IOException failure) {
1175            return false;
1176        }
1177    }
1178
1179    private final boolean canWrite0(final String JavaDoc entryName)
1180    throws IOException {
1181        readLock().lock();
1182        try {
1183            final ArchiveFileSystem fileSystem = getFileSystem(false);
1184            return fileSystem.canWrite(entryName);
1185        } finally {
1186            readLock().unlock();
1187        }
1188    }
1189
1190    public final long length(final String JavaDoc entryName)
1191    throws FalsePositiveNativeException {
1192        try {
1193            return length0(entryName);
1194        } catch (ArchiveController.FalsePositiveEntryException failure) {
1195            return enclController.length(enclEntryName(entryName));
1196        } catch (ArchiveController.FalsePositiveNativeException failure) {
1197            throw failure;
1198        } catch (IOException failure) {
1199            return 0;
1200        }
1201    }
1202
1203    private final long length0(final String JavaDoc entryName)
1204    throws IOException {
1205        readLock().lock();
1206        try {
1207            final ArchiveFileSystem fileSystem = getFileSystem(false);
1208            return fileSystem.length(entryName);
1209        } finally {
1210            readLock().unlock();
1211        }
1212    }
1213
1214    public final long lastModified(final String JavaDoc entryName)
1215    throws FalsePositiveNativeException {
1216        try {
1217            return lastModified0(entryName);
1218        } catch (ArchiveController.FalsePositiveEntryException failure) {
1219            return enclController.lastModified(enclEntryName(entryName));
1220        } catch (ArchiveController.FalsePositiveNativeException failure) {
1221            throw failure;
1222        } catch (IOException failure) {
1223            return 0;
1224        }
1225    }
1226
1227    private final long lastModified0(final String JavaDoc entryName)
1228    throws IOException {
1229        readLock().lock();
1230        try {
1231            final ArchiveFileSystem fileSystem = getFileSystem(false);
1232            return fileSystem.lastModified(entryName);
1233        } finally {
1234            readLock().unlock();
1235        }
1236    }
1237
1238    public final String JavaDoc[] list(final String JavaDoc entryName)
1239    throws FalsePositiveNativeException {
1240        try {
1241            return list0(entryName);
1242        } catch (ArchiveController.FalsePositiveEntryException failure) {
1243            return enclController.list(enclEntryName(entryName));
1244        } catch (ArchiveController.FalsePositiveNativeException failure) {
1245            throw failure;
1246        } catch (IOException failure) {
1247            return null;
1248        }
1249    }
1250
1251    private final String JavaDoc[] list0(final String JavaDoc entryName)
1252    throws IOException {
1253        readLock().lock();
1254        try {
1255            final ArchiveFileSystem fileSystem = getFileSystem(false);
1256            return fileSystem.list(entryName);
1257        } finally {
1258            readLock().unlock();
1259        }
1260    }
1261
1262    public final String JavaDoc[] list(
1263            final String JavaDoc entryName,
1264            final FilenameFilter filenameFilter,
1265            final File dir)
1266    throws FalsePositiveNativeException {
1267        try {
1268            return list0(entryName, filenameFilter, dir);
1269        } catch (ArchiveController.FalsePositiveEntryException failure) {
1270            return enclController.list(enclEntryName(entryName),
1271                    filenameFilter, dir);
1272        } catch (ArchiveController.FalsePositiveNativeException failure) {
1273            throw failure;
1274        } catch (IOException failure) {
1275            return null;
1276        }
1277    }
1278
1279    private final String JavaDoc[] list0(
1280            final String JavaDoc entryName,
1281            final FilenameFilter filenameFilter,
1282            final File dir)
1283    throws IOException {
1284        readLock().lock();
1285        try {
1286            final ArchiveFileSystem fileSystem = getFileSystem(false);
1287            return fileSystem.list(entryName, filenameFilter, dir);
1288        } finally {
1289            readLock().unlock();
1290        }
1291    }
1292
1293    public final File[] listFiles(
1294            final String JavaDoc entryName,
1295            final FilenameFilter filenameFilter,
1296            final File dir,
1297            final FileFactory factory)
1298    throws FalsePositiveNativeException {
1299        try {
1300            return listFiles0(entryName, filenameFilter, dir, factory);
1301        } catch (ArchiveController.FalsePositiveEntryException failure) {
1302            return enclController.listFiles(enclEntryName(entryName),
1303                    filenameFilter, dir, factory);
1304        } catch (ArchiveController.FalsePositiveNativeException failure) {
1305            throw failure;
1306        } catch (IOException failure) {
1307            return null;
1308        }
1309    }
1310
1311    private final File[] listFiles0(
1312            final String JavaDoc entryName,
1313            final FilenameFilter filenameFilter,
1314            final File dir,
1315            final FileFactory factory)
1316    throws IOException {
1317        readLock().lock();
1318        try {
1319            final ArchiveFileSystem fileSystem = getFileSystem(false);
1320            return fileSystem.listFiles(entryName, filenameFilter, dir, factory);
1321        } finally {
1322            readLock().unlock();
1323        }
1324    }
1325
1326    public final File[] listFiles(
1327            final String JavaDoc entryName,
1328            final FileFilter fileFilter,
1329            final File dir,
1330            final FileFactory factory)
1331    throws FalsePositiveNativeException {
1332        try {
1333            return listFiles0(entryName, fileFilter, dir, factory);
1334        } catch (ArchiveController.FalsePositiveEntryException failure) {
1335            return enclController.listFiles(enclEntryName(entryName),
1336                    fileFilter, dir, factory);
1337        } catch (ArchiveController.FalsePositiveNativeException failure) {
1338            throw failure;
1339        } catch (IOException failure) {
1340            return null;
1341        }
1342    }
1343
1344    private final File[] listFiles0(
1345            final String JavaDoc entryName,
1346            final FileFilter fileFilter,
1347            final File dir,
1348            final FileFactory factory)
1349    throws IOException {
1350        readLock().lock();
1351        try {
1352            final ArchiveFileSystem fileSystem = getFileSystem(false);
1353            return fileSystem.listFiles(entryName, fileFilter, dir, factory);
1354        } finally {
1355            readLock().unlock();
1356        }
1357    }
1358
1359    //
1360
// File system operations used by the File class.
1361
// Write operations.
1362
//
1363

1364    public final boolean setReadOnly(final String JavaDoc entryName)
1365    throws FalsePositiveNativeException {
1366        try {
1367            return setReadOnly0(entryName);
1368        } catch (ArchiveController.FalsePositiveEntryException failure) {
1369            return enclController.setReadOnly(enclEntryName(entryName));
1370        } catch (ArchiveController.FalsePositiveNativeException failure) {
1371            throw failure;
1372        } catch (IOException failure) {
1373            return false;
1374        }
1375    }
1376
1377    private final boolean setReadOnly0(final String JavaDoc entryName)
1378    throws IOException {
1379        writeLock().lock();
1380        try {
1381            final ArchiveFileSystem fileSystem = getFileSystem(false);
1382            return fileSystem.setReadOnly(entryName);
1383        } finally {
1384            writeLock().unlock();
1385        }
1386    }
1387
1388    public final boolean setLastModified(
1389            final String JavaDoc entryName,
1390            final long time)
1391    throws FalsePositiveNativeException {
1392        try {
1393            return setLastModified0(entryName, time);
1394        } catch (ArchiveController.FalsePositiveEntryException failure) {
1395            return enclController.setLastModified(enclEntryName(entryName),
1396                    time);
1397        } catch (ArchiveController.FalsePositiveNativeException failure) {
1398            throw failure;
1399        } catch (IOException failure) {
1400            return false;
1401        }
1402    }
1403
1404    private final boolean setLastModified0(
1405            final String JavaDoc entryName,
1406            final long time)
1407    throws IOException {
1408        writeLock().lock();
1409        try {
1410            ArchiveFileSystem fileSystem = getFileSystem(false);
1411            if (fileSystem.isReadOnly()) // check shortcut
1412
return false;
1413            if (hasNewData(entryName)) {
1414                update();
1415                fileSystem = getFileSystem(false); // fileSystem has been reset by update!
1416
}
1417            return fileSystem.setLastModified(entryName, time);
1418        } finally {
1419            writeLock().unlock();
1420        }
1421    }
1422
1423    public final boolean createNewFile(
1424            final String JavaDoc entryName,
1425            final boolean autoCreate)
1426    throws IOException {
1427        try {
1428            return createNewFile0(entryName, autoCreate);
1429        } catch (ArchiveController.FalsePositiveEntryException failure) {
1430            return enclController.createNewFile(enclEntryName(entryName),
1431                    autoCreate);
1432        }
1433    }
1434
1435    private final boolean createNewFile0(
1436            final String JavaDoc entryName,
1437            final boolean autoCreate)
1438    throws IOException {
1439        assert entryName != EMPTY;
1440
1441        writeLock().lock();
1442        try {
1443            final ArchiveFileSystem fileSystem = getFileSystem(autoCreate);
1444            if (fileSystem.exists(entryName))
1445                return false;
1446
1447            // If we got until here without an exception,
1448
// write an empty file now.
1449
getOutputStream0(entryName).close();
1450
1451            return true;
1452        } finally {
1453            writeLock().unlock();
1454        }
1455    }
1456
1457    public final boolean mkdir(
1458            final String JavaDoc entryName,
1459            final boolean autoCreate)
1460    throws FalsePositiveNativeException {
1461        try {
1462            mkdir0(entryName, autoCreate);
1463            return true;
1464        } catch (ArchiveController.FalsePositiveEntryException failure) {
1465            return enclController.mkdir(enclEntryName(entryName), autoCreate);
1466        } catch (ArchiveController.FalsePositiveNativeException failure) {
1467            throw failure;
1468        } catch (IOException failure) {
1469            return false;
1470        }
1471    }
1472
1473    private final void mkdir0(final String JavaDoc entryName, final boolean autoCreate)
1474    throws IOException {
1475        writeLock().lock();
1476        try {
1477            if (EMPTY != entryName) { // possibly assigned by File.init(...)
1478
// This file is a regular archive entry.
1479
final ArchiveFileSystem fileSystem = getFileSystem(autoCreate);
1480                fileSystem.mkdir(entryName, autoCreate);
1481            } else { // EMPTY == entryName
1482
// This is the root of an archive file system, so we are
1483
// actually working on the controller's target file.
1484
if (usesNativeTargetFile()) {
1485                    if (target.exists())
1486                        throw new IOException("target file exists already!");
1487                } else {
1488                    if (enclController.exists(enclEntryName))
1489                        throw new IOException("target file exists already!");
1490                }
1491                // Ensure file system existence.
1492
getFileSystem(true);
1493            }
1494        } finally {
1495            writeLock().unlock();
1496        }
1497    }
1498
1499    public final boolean delete(final String JavaDoc entryName)
1500    throws FalsePositiveNativeException {
1501        try {
1502            delete0(entryName);
1503            return true;
1504        } catch (ArchiveController.FalsePositiveDirectoryEntryException failure) {
1505            return enclController.delete(enclEntryName(entryName));
1506        } catch (ArchiveController.FalsePositiveFileEntryException failure) {
1507            if (EMPTY == entryName
1508                    && failure.getCause() instanceof FileNotFoundException) {
1509                // This appears to be an archive file, but we could not
1510
// access it.
1511
// One of the many reasons may be that the target file is an
1512
// RAES encrypted ZIP file for which password prompting has
1513
// been disabled or cancelled by the user.
1514
// Another reason could be that the archive has been tampered
1515
// with and hence authentication failed.
1516
// In any of these cases we do not want this package to treat
1517
// this file like a plain file and delete it.
1518
return false;
1519            } else {
1520                return enclController.delete(enclEntryName(entryName));
1521            }
1522        } catch (ArchiveController.FalsePositiveNativeException failure) {
1523            throw failure;
1524        } catch (IOException failure) {
1525            return false;
1526        }
1527    }
1528
1529    private final void delete0(final String JavaDoc entryName)
1530    throws IOException {
1531        writeLock().lock();
1532        try {
1533            // update() invalidates the file system, so it has to be
1534
// called before getFileSystem().
1535
// TODO: Consider adding configuration switch to allow
1536
// rewriting an archive entry to the same output archive
1537
// multiple times, whereby only the last written entry is
1538
// added to the central directory of the archive
1539
// (if the archive type supports this concept).
1540
// Obviously, this wouldn't work with the TAR file format as
1541
// there is no central directory...
1542
if (hasNewData(entryName))
1543                update();
1544
1545            if (EMPTY != entryName) { // possibly assigned by File.init(...)
1546
final ArchiveFileSystem fileSystem = getFileSystem(false);
1547                fileSystem.delete(entryName);
1548            } else { // EMPTY == entryName
1549
// Get the file system or die trying!
1550
final ArchiveFileSystem fileSystem;
1551                try {
1552                    fileSystem = getFileSystem(false);
1553                } catch (FalsePositiveException falsePositive) {
1554                    // The File instance is going to delete the target file
1555
// anyway, so we need to reset now.
1556
try {
1557                        reset();
1558                    } catch (IOException cannotHappen) {
1559                        throw new AssertionError JavaDoc(cannotHappen);
1560                    }
1561                    throw falsePositive;
1562                }
1563
1564                // We are actually working on the controller's target file.
1565
// Do not use the number of entries in the file system
1566
// for the following test - it's size would count absolute
1567
// pathnames as well!
1568
final String JavaDoc[] members = fileSystem.list(entryName);
1569                if (members != null && members.length != 0)
1570                    throw new IOException("archive file system not empty!");
1571                final int outputStreams = waitAllOutputStreamsByOtherThreads(50);
1572                // TODO: Review: This policy may be changed - see method start.
1573
assert outputStreams <= 0
1574                        : "Entries for open output streams should not be deletable!";
1575                // Note: Entries for open input streams ARE deletable!
1576
final int inputStreams = waitAllInputStreamsByOtherThreads(50);
1577                if (inputStreams > 0 || outputStreams > 0)
1578                    throw new IOException("archive file has open streams!");
1579                reset();
1580                // Just in case our target is an RAES encrypted ZIP file,
1581
// forget it's password as well.
1582
// TODO: Review: This is an archive driver dependency!
1583
// Calling it doesn't harm, but please consider a more opaque
1584
// way to model this.
1585
PromptingKeyManager.resetKeyProvider(getPath());
1586                // Delete the native file or the entry in the enclosing
1587
// archive file, too.
1588
if (usesNativeTargetFile()) {
1589                    // The target file of the controller is NOT enclosed
1590
// in another archive file.
1591
if (!target.delete())
1592                        throw new IOException("couldn't delete archive file!");
1593                } else {
1594                    // The target file of the controller IS enclosed in
1595
// another archive file.
1596
enclController.delete0(enclEntryName(entryName));
1597                }
1598            }
1599        } finally {
1600            writeLock().unlock();
1601        }
1602    }
1603
1604    //
1605
// Static copy methods:
1606
//
1607

1608    /**
1609     * Copies a source file to a destination file, optionally preserving the
1610     * source's last modification time.
1611     * We know nothing about the source or destination file yet.
1612     *
1613     * @throws FileBusyException If an archive entry cannot get accessed
1614     * because the client application is trying to input or output
1615     * to the same archive file simultaneously and the respective
1616     * archive driver does not support this or the archive file needs
1617     * an automatic update which cannot get performed because the
1618     * client is still using other open {@link FileInputStream}s or
1619     * {@link FileOutputStream}s for other entries in the same archive
1620     * file.
1621     * @throws FileNotFoundException If either the source or the destination
1622     * cannot get accessed.
1623     * @throws InputIOException If copying the data fails because of an
1624     * IOException in the source.
1625     * @throws IOException If copying the data fails because of an
1626     * IOException in the destination.
1627     */

1628    protected static void cp(
1629            final java.io.File JavaDoc src,
1630            final java.io.File JavaDoc dst,
1631            final boolean preserve)
1632            throws IOException {
1633        assert src != null;
1634        assert dst != null;
1635
1636        try {
1637            if (src instanceof File) {
1638                final File srcFile = (File) src;
1639                srcFile.ensureNotVirtualDirectory("cannot read");
1640                final String JavaDoc srcEntryName = srcFile.getEnclEntryName();
1641                if (srcEntryName != null) {
1642                    cp( srcFile,
1643                        srcFile.getEnclArchive().getArchiveController(),
1644                        srcEntryName, dst,
1645                        preserve);
1646                    return;
1647                }
1648            }
1649
1650            // Treat the source like a regular file.
1651
final InputStream in = new java.io.FileInputStream JavaDoc(src);
1652            try {
1653                cp(src, in, dst, preserve);
1654            } finally {
1655                try {
1656                    in.close();
1657                } catch (IOException failure) {
1658                    throw new InputIOException(failure);
1659                }
1660            }
1661        } catch (ArchiveBusyException failure) {
1662            throw new FileBusyException(failure);
1663        }
1664    }
1665
1666    /**
1667     * Copies a source file to a destination file, optionally preserving the
1668     * source's last modification time.
1669     * We already have an input stream to read the source file,
1670     * but we know nothing about the destination file yet.
1671     * Note that this method <em>never</em> closes the given input stream!
1672     *
1673     * @throws FileNotFoundException If either the source or the destination
1674     * cannot get accessed.
1675     * @throws InputIOException If copying the data fails because of an
1676     * IOException in the source.
1677     * @throws IOException If copying the data fails because of an
1678     * IOException in the destination.
1679     */

1680    protected static void cp(
1681            final java.io.File JavaDoc src,
1682            final InputStream in,
1683            final java.io.File JavaDoc dst,
1684            final boolean preserve)
1685            throws IOException {
1686        if (dst instanceof File) {
1687            final File dstFile = (File) dst;
1688            dstFile.ensureNotVirtualDirectory("cannot write");
1689            final String JavaDoc dstEntryName = dstFile.getEnclEntryName();
1690            if (dstEntryName != null) {
1691                cp( src, in, dstFile,
1692                    dstFile.getEnclArchive().getArchiveController(),
1693                    dstEntryName, preserve);
1694                return;
1695            }
1696        }
1697
1698        // Treat the destination like a regular file.
1699
final OutputStream out = new java.io.FileOutputStream JavaDoc(dst);
1700        try {
1701            File.cat(in, out);
1702        } finally {
1703            out.close();
1704        }
1705        if (preserve && !dst.setLastModified(src.lastModified()))
1706            throw new IOException(dst.getPath() +
1707                    " (couldn't preserve last modification time)");
1708    }
1709
1710    /**
1711     * Copies a source file to a destination file, optionally preserving the
1712     * source's last modification time.
1713     * We know that the source file appears to be an entry in an archive
1714     * file, but we know nothing about the destination file yet.
1715     * <p>
1716     * Note that this method synchronizes on the class object in order
1717     * to prevent dead locks by two threads copying archive entries to the
1718     * other's source archive concurrently!
1719     *
1720     * @throws FalsePositiveException If the source or the destination is a
1721     * false positive and the exception
1722     * cannot get resolved within this method.
1723     * @throws InputIOException If copying the data fails because of an
1724     * IOException in the source.
1725     * @throws IOException If copying the data fails because of an
1726     * IOException in the destination.
1727     */

1728    protected static void cp(
1729            final File srcFile,
1730            final ArchiveController srcController,
1731            final String JavaDoc srcEntryName,
1732            final java.io.File JavaDoc dst,
1733            final boolean preserve)
1734    throws IOException {
1735        // Do not assume anything about the lock status of the controller:
1736
// This method may be called from a subclass while a lock is acquired!
1737
//assert !srcController.readLock().isLocked();
1738
//assert !srcController.writeLock().isLocked();
1739

1740        try {
1741            try {
1742                if (dst instanceof File) {
1743                    final File dstFile = (File) dst;
1744                    dstFile.ensureNotVirtualDirectory("cannot write");
1745                    final String JavaDoc dstEntryName = dstFile.getEnclEntryName();
1746                    if (dstEntryName != null) {
1747                        cp( srcFile, srcController, srcEntryName,
1748                            dstFile,
1749                            dstFile.getEnclArchive().getArchiveController(),
1750                            dstEntryName,
1751                            preserve);
1752                        return;
1753                    }
1754                }
1755
1756                final InputStream in;
1757                final long time;
1758                srcController.readLock().lock();
1759                try {
1760                    in = srcController.getInputStream(srcEntryName); // detects false positives!
1761
time = srcController.lastModified0(srcEntryName);
1762                } finally {
1763                    srcController.readLock().unlock();
1764                }
1765
1766                // Treat the destination like a regular file.
1767
final OutputStream out;
1768                try {
1769                    out = new java.io.FileOutputStream JavaDoc(dst);
1770                } catch (IOException failure) {
1771                    try {
1772                        in.close();
1773                    } catch (IOException inFailure) {
1774                        throw new InputIOException(inFailure);
1775                    }
1776                    throw failure;
1777                }
1778
1779                File.cp(in, out);
1780                if (preserve && !dst.setLastModified(time))
1781                    throw new IOException(dst.getPath() +
1782                            " (couldn't preserve last modification time)");
1783            } catch (FalsePositiveDirectoryEntryException failure) {
1784                assert srcController == failure.getSource();
1785                // Reroute call to the source's enclosing archive controller.
1786
cp( srcFile, srcController.getEnclController(),
1787                    srcController.enclEntryName(srcEntryName),
1788                    dst, preserve);
1789            }
1790        } catch (FalsePositiveNativeException failure) {
1791            assert srcController == failure.getSource();
1792            // Reroute call to treat the source like a regular file.
1793
cp(srcFile.getDelegate(), dst, preserve);
1794        }
1795    }
1796
1797    /**
1798     * Copies a source file to a destination file, optionally preserving the
1799     * source's last modification time.
1800     * We know that the source and destination files both appear to be entries
1801     * in an archive file.
1802     *
1803     * @throws FalsePositiveException If the source or the destination is a
1804     * false positive and the exception for the destination
1805     * cannot get resolved within this method.
1806     * @throws InputIOException If copying the data fails because of an
1807     * IOException in the source.
1808     * @throws IOException If copying the data fails because of an
1809     * IOException in the destination.
1810     */

1811    protected static void cp(
1812            final File srcFile,
1813            final ArchiveController srcController,
1814            final String JavaDoc srcEntryName,
1815            final File dstFile,
1816            final ArchiveController dstController,
1817            final String JavaDoc dstEntryName,
1818            final boolean preserve)
1819    throws IOException {
1820        // Do not assume anything about the lock status of the controller:
1821
// This method may be called from a subclass while a lock is acquired!
1822
//assert !srcController.readLock().isLocked();
1823
//assert !srcController.writeLock().isLocked();
1824
//assert !dstController.readLock().isLocked();
1825
//assert !dstController.writeLock().isLocked();
1826

1827        try {
1828            class IOStreamCreator implements IORunnable {
1829                InputStream in;
1830                OutputStream out;
1831
1832                public void run() throws IOException {
1833                    // Update controllers.
1834
// This may invalidate the file system object, so it must be
1835
// done first in case srcController and dstController are the
1836
// same!
1837
class SrcControllerUpdater implements IORunnable {
1838                        public void run() throws IOException {
1839                            if (srcController.hasNewData(srcEntryName))
1840                                srcController.update();
1841                            srcController.readLock().lock(); // downgrade to read lock upon return
1842
}
1843                    } // class SrcControllerUpdater
1844

1845                    final ArchiveEntry srcEntry, dstEntry;
1846                    final Delta delta;
1847                    srcController.runWriteLocked(new SrcControllerUpdater());
1848                    try {
1849                        if (dstController.hasNewData(dstEntryName))
1850                            dstController.update();
1851
1852                        // Get source archive entry.
1853
final ArchiveFileSystem srcFileSystem
1854                                = srcController.getFileSystem(false);
1855                        srcEntry = srcFileSystem.get(srcEntryName);
1856
1857                        // Get destination archive entry.
1858
final boolean isLenient = File.isLenient();
1859                        final ArchiveFileSystem dstFileSystem
1860                                = dstController.getFileSystem(isLenient);
1861                        delta = dstFileSystem.beginCreateAndLink(
1862                                dstEntryName, isLenient, preserve ? srcEntry : null);
1863                        dstEntry = delta.getEntry();
1864
1865                        // Get input stream.
1866
in = srcController.getInputStream(srcEntry, dstEntry);
1867                    } finally {
1868                        srcController.readLock().unlock();
1869                    }
1870
1871                    try {
1872                        // Get output stream.
1873
out = dstController.getOutputStream(dstEntry, srcEntry);
1874
1875                        try {
1876                            // Commit the transaction to create the destination entry.
1877
delta.commit();
1878                        } catch (IOException failure) {
1879                            out.close();
1880                            throw failure;
1881                        }
1882                    } catch (IOException failure) {
1883                        try {
1884                            in.close();
1885                        } catch (IOException inFailure) {
1886                            throw new InputIOException(inFailure);
1887                        }
1888                        throw failure;
1889                    }
1890                }
1891            } // class IOStreamCreator
1892

1893            final IOStreamCreator streams = new IOStreamCreator();
1894            synchronized (copyLock) {
1895                dstController.runWriteLocked(streams);
1896            }
1897
1898            // Finally copy the entry data.
1899
File.cp(streams.in, streams.out);
1900        } catch (FalsePositiveEntryException failure) {
1901            // Both the source and/or the destination may be false positives,
1902
// so we need to use the exception's additional information to
1903
// find out which controller actually detected the false positive.
1904
if (dstController != failure.getSource())
1905                throw failure; // not my job - pass on!
1906

1907            // Reroute call to the destination's enclosing archive controller.
1908
cp( srcFile, srcController, srcEntryName,
1909                dstFile, dstController.getEnclController(),
1910                dstController.enclEntryName(dstEntryName),
1911                preserve);
1912        } catch (FalsePositiveNativeException failure) {
1913            // Both the source and/or the destination may be false positives,
1914
// so we need to use the exception's additional information to
1915
// find out which controller actually detected the false positive.
1916
if (dstController != failure.getSource())
1917                throw failure; // not my job - pass on!
1918

1919            // Reroute call to treat the destination like a regular file.
1920
cp( srcFile, srcController, srcEntryName,
1921                dstFile.getDelegate(),
1922                preserve);
1923        }
1924    }
1925
1926    /**
1927     * Copies a source file to a destination file, optionally preserving the
1928     * source's last modification time.
1929     * We already have an input stream to read the source file and the
1930     * destination appears to be an entry in an archive file.
1931     * Note that this method <em>never</em> closes the given input stream!
1932     * <p>
1933     * Note that this method synchronizes on the class object in order
1934     * to prevent dead locks by two threads copying archive entries to the
1935     * other's source archive concurrently!
1936     *
1937     * @throws FalsePositiveException If the destination is a
1938     * false positive and the exception
1939     * cannot get resolved within this method.
1940     * @throws InputIOException If copying the data fails because of an
1941     * IOException in the source.
1942     * @throws IOException If copying the data fails because of an
1943     * IOException in the destination.
1944     */

1945    protected static void cp(
1946            final java.io.File JavaDoc src,
1947            final InputStream in,
1948            final File dstFile,
1949            final ArchiveController dstController,
1950            final String JavaDoc dstEntryName,
1951            final boolean preserve)
1952    throws IOException {
1953        // Do not assume anything about the lock status of the controller:
1954
// This method may be called from a subclass while a lock is acquired!
1955
//assert !dstController.readLock().isLocked();
1956
//assert !dstController.writeLock().isLocked();
1957

1958        try {
1959            class OStreamCreator implements IORunnable {
1960                OutputStream out;
1961
1962                public void run() throws IOException {
1963                    // Update controller.
1964
// This may invalidate the file system object, so it must be
1965
// done first in case srcController and dstController are the
1966
// same!
1967
if (dstController.hasNewData(dstEntryName))
1968                        dstController.update();
1969
1970                    final boolean isLenient = File.isLenient();
1971
1972                    // Get source archive entry.
1973
final ArchiveEntry srcEntry
1974                            = new File2ArchiveEntryAdapter(src);
1975
1976                    // Get destination archive entry.
1977
final ArchiveFileSystem dstFileSystem
1978                            = dstController.getFileSystem(isLenient);
1979                    final Delta transaction = dstFileSystem.beginCreateAndLink(
1980                                dstEntryName, isLenient, preserve ? srcEntry : null);
1981                    final ArchiveEntry dstEntry = transaction.getEntry();
1982
1983                    // Get output stream.
1984
out = dstController.getOutputStream(dstEntry, srcEntry);
1985
1986                    // Commit the transaction to create the destination entry.
1987
transaction.commit();
1988                }
1989            }
1990
1991            // Create the output stream while the destination controller is
1992
// write locked.
1993
final OStreamCreator stream = new OStreamCreator();
1994            dstController.runWriteLocked(stream);
1995            final OutputStream out = stream.out;
1996
1997            // Finally copy the entry data.
1998
try {
1999                File.cat(in, out);
2000            } finally {
2001                out.close();
2002            }
2003        } catch (FalsePositiveEntryException failure) {
2004            assert dstController == failure.getSource();
2005            // Reroute call to the destination's enclosing ArchiveController.
2006
cp( src, in,
2007                dstFile, dstController.getEnclController(),
2008                dstController.enclEntryName(dstEntryName),
2009                preserve);
2010        } catch (FalsePositiveNativeException failure) {
2011            assert dstController == failure.getSource();
2012            // Reroute call to treat the destination like a regular file.
2013
cp(src, in, dstFile.getDelegate(), preserve);
2014        }
2015    }
2016
2017    //
2018
// Static member classes and interfaces.
2019
//
2020

2021    /**
2022     * This interface is used as the closure for {@link #runWriteLocked}.
2023     */

2024    protected interface IORunnable {
2025        void run() throws IOException;
2026    }
2027
2028    /**
2029     * A lock used when copying data from one archive to another.
2030     * This lock must be acquired before any other locks on the controllers
2031     * are acquired in order to prevent dead locks.
2032     */

2033    private static final class CopyLock { }
2034
2035    static final class ShutdownHook extends Thread JavaDoc {
2036        private static final ShutdownHook singleton = new ShutdownHook();
2037
2038        /**
2039         * The set of files to delete when the shutdown hook is run.
2040         * When iterating over it, its elements are returned in insertion order.
2041         */

2042        static final Set deleteOnExit
2043                = Collections.synchronizedSet(new LinkedHashSet());
2044
2045        private ShutdownHook() {
2046            super("TrueZIP ArchiveController Shutdown Hook");
2047            setPriority(Thread.MAX_PRIORITY);
2048        }
2049
2050        /**
2051         * Deletes all files that have been marked by
2052         * {@link File#deleteOnExit} and finally unmounts all controllers.
2053         * <p>
2054         * Logging and password prompting will be disabled (they wouldn't work
2055         * in a JVM shutdown hook anyway) in order to provide a deterministic
2056         * behaviour and in order to avoid RuntimeExceptions or even Errors
2057         * in the API.
2058         * <p>
2059         * Any exceptions thrown throughout the update will be printed on
2060         * standard error output.
2061         * <p>
2062         * Note that this method is <em>not</em> re-entrant and should not be
2063         * directly called except for unit testing (you couldn't do a unit test
2064         * on a shutdown hook otherwise, could you?).
2065         */

2066        public void run() {
2067            synchronized (PromptingKeyManager.class) {
2068                try { // paranoid, but safe.
2069
PromptingKeyManager.setPrompting(false);
2070                    logger.setLevel(Level.OFF);
2071
2072                    for (Iterator i = deleteOnExit.iterator(); i.hasNext(); ) {
2073                        final File file = (File) i.next();
2074                        if (file.exists() && !file.delete()) {
2075                            System.err.println(
2076                                    file.getPath() + ": failed to deleteOnExit()!");
2077                        }
2078                    }
2079                } finally {
2080                    try {
2081                        updateAll("", false, true, false, true, true, false);
2082                    } catch (ArchiveException oops) {
2083                        oops.printStackTrace();
2084                    }
2085                }
2086            }
2087        }
2088    } // class ShutdownHook
2089

2090    private static final class LiveStatistics implements ArchiveStatistics {
2091        public long getUpdateTotalByteCountRead() {
2092            return CountingReadOnlyFile.getTotal();
2093        }
2094
2095        public long getUpdateTotalByteCountWritten() {
2096            return CountingOutputStream.getTotal();
2097        }
2098
2099        public int getArchivesTotal() {
2100            // This is not 100% correct:
2101
// Controllers which have been removed from the WeakReference
2102
// VALUE in the map meanwhile, but not yet removed from the map
2103
// are counted as well.
2104
// But hey, this is only statistics, right?
2105
return controllers.size();
2106        }
2107
2108        public int getArchivesTouched() {
2109            int result = 0;
2110
2111            final Enumeration e = new ControllerEnumeration();
2112            while (e.hasMoreElements()) {
2113                final ArchiveController c = (ArchiveController) e.nextElement();
2114                c.readLock().lock();
2115                try {
2116                    if (c.isTouched())
2117                        result++;
2118                } finally {
2119                    c.readLock().unlock();
2120                }
2121            }
2122
2123            return result;
2124        }
2125
2126        public int getTopLevelArchivesTotal() {
2127            int result = 0;
2128
2129            final Enumeration e = new ControllerEnumeration();
2130            while (e.hasMoreElements()) {
2131                final ArchiveController c = (ArchiveController) e.nextElement();
2132                if (c.getEnclController() == null)
2133                    result++;
2134            }
2135
2136            return result;
2137        }
2138
2139        public int getTopLevelArchivesTouched() {
2140            int result = 0;
2141
2142            final Enumeration e = new ControllerEnumeration();
2143            while (e.hasMoreElements()) {
2144                final ArchiveController c = (ArchiveController) e.nextElement();
2145                c.readLock().lock();
2146                try {
2147                    if (c.getEnclController() == null && c.isTouched())
2148                        result++;
2149                } finally {
2150                    c.readLock().unlock();
2151                }
2152            }
2153
2154            return result;
2155        }
2156    } // class LiveStatistics
2157

2158    private static final class ControllerEnumeration implements Enumeration {
2159        private final Iterator it;
2160
2161        public ControllerEnumeration() {
2162            this("", null);
2163        }
2164
2165        public ControllerEnumeration(final String JavaDoc prefix, final Comparator c) {
2166            assert prefix != null;
2167
2168            final Set snapshot;
2169            synchronized (controllers) {
2170                if (c != null) {
2171                    snapshot = new TreeSet(c);
2172                } else {
2173                    snapshot = new HashSet((int) (controllers.size() / 0.75f));
2174                }
2175
2176                final Iterator it = controllers.values().iterator();
2177                while (it.hasNext()) {
2178                    Object JavaDoc value = it.next();
2179                    if (value instanceof Reference) {
2180                        value = ((Reference) value).get(); // dereference
2181
if (value != null) {
2182                            assert value instanceof ArchiveController;
2183                            if (((ArchiveController) value).getPath().startsWith(prefix))
2184                                snapshot.add(value);
2185                        //} else {
2186
// This may happen if there are no more strong
2187
// references to the controller and it has been
2188
// removed from the weak reference in the hash
2189
// map's value before it's been removed from the
2190
// hash map's key (shit happens)!
2191
}
2192                    } else {
2193                        assert value != null;
2194                        assert value instanceof ArchiveController;
2195                        if (((ArchiveController) value).getPath().startsWith(prefix))
2196                            snapshot.add(value);
2197                    }
2198                }
2199            }
2200
2201            it = snapshot.iterator();
2202        }
2203
2204        public boolean hasMoreElements() {
2205            return it.hasNext();
2206        }
2207
2208        public Object JavaDoc nextElement() {
2209            return it.next();
2210        }
2211    } // class ControllerEnumeration
2212

2213    // TODO: Document this!
2214
static final class CountingReadOnlyFile extends SimpleReadOnlyFile {
2215        private static volatile long _total;
2216        //private static volatile boolean _resetOnReuse;
2217

2218        CountingReadOnlyFile(java.io.File JavaDoc file)
2219        throws FileNotFoundException {
2220            super(file);
2221            resetOnReuse();
2222        }
2223
2224        public static long getTotal() {
2225            return _total;
2226        }
2227
2228        private static void setResetOnReuse() {
2229            //_resetOnReuse = true;
2230
}
2231
2232        private static void resetOnReuse() {
2233            /*if (_resetOnReuse) {
2234                _resetOnReuse = false;*/

2235                _total = 0;
2236            //}
2237
}
2238
2239        public int read() throws IOException {
2240            int ret = super.read();
2241            if (ret != -1)
2242                _total++;
2243            return ret;
2244        }
2245
2246        public int read(byte[] b) throws IOException {
2247            int ret = super.read(b);
2248            if (ret != -1)
2249                _total += ret;
2250            return ret;
2251        }
2252
2253        public int read(byte[] b, int off, int len) throws IOException {
2254            int ret = super.read(b, off, len);
2255            if (ret != -1)
2256                _total += ret;
2257            return ret;
2258        }
2259
2260        public int skipBytes(int n) throws IOException {
2261            int ret = super.skipBytes(n);
2262            _total += ret;
2263            return ret;
2264        }
2265    } // class CountingReadOnlyFile
2266

2267    /*static final class CountingInputStream extends FilterInputStream {
2268        private static volatile long _total;
2269        private static volatile boolean _resetOnReuse;
2270
2271        CountingInputStream(InputStream in) {
2272            super(in);
2273            resetOnReuse();
2274        }
2275
2276        public static long getTotal() {
2277            return _total;
2278        }
2279
2280        private static void setResetOnReuse() {
2281            _resetOnReuse = true;
2282        }
2283
2284        private static void resetOnReuse() {
2285            if (_resetOnReuse) {
2286                _resetOnReuse = false;
2287                _total = 0;
2288            }
2289        }
2290
2291        public int read() throws IOException {
2292            int ret = in.read();
2293            if (ret != -1)
2294                _total++;
2295            return ret;
2296        }
2297
2298        public int read(byte b[], int off, int len) throws IOException {
2299            int n = in.read(b, off, len);
2300            if (n != -1)
2301                _total += n;
2302            return n;
2303        }
2304
2305        public long skip(long n) throws IOException {
2306            n = in.skip(n);
2307            _total += n;
2308            return n;
2309        }
2310
2311        public boolean markSupported() {
2312            return false;
2313        }
2314    }*/

2315
2316    static final class CountingOutputStream extends FilterOutputStream {
2317        private static volatile long _total;
2318        private static volatile boolean _resetOnReuse;
2319
2320        CountingOutputStream(OutputStream out) {
2321            super(out);
2322            resetOnReuse();
2323        }
2324
2325        public static long getTotal() {
2326            return _total;
2327        }
2328
2329        private static void setResetOnReuse() {
2330            _resetOnReuse = true;
2331        }
2332
2333        private static void resetOnReuse() {
2334            if (_resetOnReuse) {
2335                _resetOnReuse = false;
2336                _total = 0;
2337            }
2338        }
2339
2340        public void write(final int b) throws IOException {
2341            out.write(b);
2342            _total++;
2343        }
2344
2345        public void write(byte b[], int off, int len) throws IOException {
2346            out.write(b, off, len);
2347            _total += len;
2348        }
2349    } // class CountingOutputStream
2350

2351    private static final class File2ArchiveEntryAdapter implements ArchiveEntry {
2352        private final java.io.File JavaDoc file;
2353
2354        private File2ArchiveEntryAdapter(final java.io.File JavaDoc file) {
2355            assert file != null;
2356            this.file = file;
2357        }
2358
2359        public String JavaDoc getName() {
2360            assert false : "Drivers should never call this method!";
2361            // The returned name is not really useful, but should be OK for
2362
// this simple adapter.
2363
if (file.isDirectory())
2364                return file.getName() + "/";
2365            else
2366                return file.getName();
2367        }
2368
2369        public boolean isDirectory() {
2370            return file.isDirectory();
2371        }
2372
2373        public long getSize() {
2374            return file.length();
2375        }
2376
2377        public long getTime() {
2378            return file.lastModified();
2379        }
2380
2381        public void setTime(long time) {
2382            assert false : "Drivers should never call this method!";
2383            file.setLastModified(time); // ignores return value
2384
}
2385
2386        public Icon JavaDoc getOpenIcon() {
2387            return null;
2388        }
2389
2390        public Icon JavaDoc getClosedIcon() {
2391            return null;
2392        }
2393
2394        public ArchiveEntryMetaData getMetaData() {
2395            throw new AssertionError JavaDoc("Drivers should never call this method!");
2396        }
2397
2398        public void setMetaData(ArchiveEntryMetaData metaData) {
2399            throw new AssertionError JavaDoc("Drivers should never call this method!");
2400        }
2401    } // class File2ArchiveEntryAdapter
2402

2403    //
2404
// Exception classes.
2405
// Note that these are all inner classes, not just static member classes.
2406
//
2407

2408    /**
2409     * Thrown to indicate that a controller's target file is a false positive
2410     * archive file which actually exists as a plain file or directory
2411     * in the native file system or in an enclosing archive file.
2412     */

2413    abstract class FalsePositiveException extends FileNotFoundException {
2414        private boolean cacheable = true;
2415
2416        FalsePositiveException(String JavaDoc path) {
2417            super(path);
2418        }
2419
2420        /**
2421         * Initializes the cause of this exception.
2422         * This method can be called at most once.
2423         *
2424         * @param cause The cause for this exception.
2425         * If this is an instance of {@link TransientIOException},
2426         * then its transient cause is unwrapped and used as the cause
2427         * instead and {@link FalsePositiveException#isCacheable}
2428         * is set to return <code>false</code>.
2429         */

2430        public Throwable JavaDoc initCause(Throwable JavaDoc cause) {
2431            // A transient I/O exception is just a wrapper exception to mark
2432
// the real transient cause, therefore we can safely throw it away.
2433
// We must do this in order to allow the File class to inspect
2434
// the real transient cause and act accordingly.
2435
final boolean trans = cause instanceof TransientIOException;
2436            super.initCause(trans ? cause.getCause() : cause);
2437            cacheable = !trans;
2438            return this;
2439        }
2440
2441        /**
2442         * Returns the archive controller which has thrown this exception.
2443         * This is the controller which detected the false positive archive
2444         * file.
2445         */

2446        public ArchiveController getSource() {
2447            return ArchiveController.this;
2448        }
2449
2450        /**
2451         * Returns <code>true</code> if and only if there is no cause
2452         * associated with this exception or it is safe to cache it.
2453         */

2454        public boolean isCacheable() {
2455            return cacheable;
2456        }
2457    } // class FalsePositiveException
2458

2459    /**
2460     * Thrown to indicate that a controller's target file is a false positive
2461     * archive file which actually exists as a plain file or directory
2462     * in the native file system.
2463     * <p>
2464     * Instances of this class are always associated with an
2465     * <code>IOException</code> as their cause.
2466     */

2467    class FalsePositiveNativeException extends FalsePositiveException {
2468
2469        /**
2470         * Creates a new <code>FalsePositiveNativeException</code>.
2471         *
2472         * @param cause The cause for this exception.
2473         * If this is an instance of {@link TransientIOException},
2474         * then its transient cause is unwrapped and used as the cause
2475         * instead and {@link FalsePositiveException#isCacheable}
2476         * is set to return <code>false</code>.
2477         */

2478        FalsePositiveNativeException(IOException cause) {
2479            super(getPath());
2480            initCause(cause);
2481        }
2482    } // class FalsePositiveNativeException
2483

2484    /**
2485     * Thrown to indicate that a controller's target file is a false positive
2486     * archive file which actually exists as a plain file or directory
2487     * in an enclosing archive file.
2488     */

2489    abstract class FalsePositiveEntryException extends FalsePositiveException {
2490
2491        private final ArchiveController target;
2492
2493        /**
2494         * @param target The controller in which the archive file exists
2495         * as a false positive - never <code>null</code>.
2496         * @param entryName The entry path name of the archive file
2497         * which is a false positive - never <code>null</code>.
2498         */

2499        FalsePositiveEntryException(
2500                ArchiveController target,
2501                String JavaDoc entryName) {
2502            this(target, entryName, null);
2503        }
2504
2505        /**
2506         * Creates a new <code>FalsePositiveEntryException</code>.
2507         *
2508         * @param target The controller in which the archive file exists
2509         * as a false positive - never <code>null</code>.
2510         * @param entryName The entry path name of the archive file
2511         * which is a false positive - never <code>null</code>.
2512         * @param cause The cause for this exception.
2513         * If this is an instance of {@link TransientIOException},
2514         * then its transient cause is unwrapped and used as the cause
2515         * instead and {@link FalsePositiveException#isCacheable}
2516         * is set to return <code>false</code>.
2517         */

2518        FalsePositiveEntryException(
2519                ArchiveController target,
2520                String JavaDoc entryName,
2521                IOException cause) {
2522            super(target.getPath() + File.separator + entryName);
2523            assert target != null;
2524            assert entryName != null;
2525            initCause(cause);
2526            this.target = target;
2527            this.entryName = entryName;
2528        }
2529
2530        /**
2531         * Returns the controller which's target file contains the
2532         * false positive archive file as an entry.
2533         * Never <code>null</code>.
2534         */

2535        public ArchiveController getTarget() {
2536            return target;
2537        }
2538
2539        private final String JavaDoc entryName;
2540
2541        /**
2542         * Returns the entry path name of the false positive archive file.
2543         * Never <code>null</code>.
2544         */

2545        public String JavaDoc getEntryName() {
2546            return entryName;
2547        }
2548    } // class FalsePositiveEntryException
2549

2550    /**
2551     * Thrown to indicate that a controller's target file is a false positive
2552     * archive file which actually exists as a plain file in an enclosing
2553     * archive file.
2554     * <p>
2555     * Instances of this class are always associated with an
2556     * <code>IOException</code> as their cause.
2557     */

2558    class FalsePositiveFileEntryException extends FalsePositiveEntryException {
2559
2560        /**
2561         * Creates a new <code>FalsePositiveFileEntryException</code>.
2562         *
2563         * @param cause The cause for this exception.
2564         * If this is an instance of {@link TransientIOException},
2565         * then its transient cause is unwrapped and used as the cause
2566         * instead and {@link FalsePositiveException#isCacheable}
2567         * is set to return <code>false</code>.
2568         */

2569        FalsePositiveFileEntryException(
2570                ArchiveController enclController,
2571                String JavaDoc enclEntryName,
2572                IOException cause) {
2573            super(enclController, enclEntryName, cause);
2574            assert cause != null;
2575        }
2576    } // class FalsePositiveFileEntryException
2577

2578    /**
2579     * Thrown to indicate that a controller's target file is a false positive
2580     * archive file which actually exists as a plain directory in an enclosing
2581     * archive file.
2582     */

2583    class FalsePositiveDirectoryEntryException extends FalsePositiveEntryException {
2584
2585        /**
2586         * Creates a new <code>FalsePositiveDirectoryEntryException</code>.
2587         */

2588        FalsePositiveDirectoryEntryException(
2589                ArchiveController enclController,
2590                String JavaDoc enclEntryName) {
2591            super(enclController, enclEntryName);
2592        }
2593    } // class FalsePositiveDirectoryEntryException
2594

2595    /**
2596     * Thrown to indicate that a controller's target file does not exist
2597     * or is not accessible.
2598     * May be thrown by {@link #getFileSystem(boolean)} if automatic creation
2599     * of the target file is not allowed.
2600     */

2601    class ArchiveNotFoundException extends FileNotFoundException {
2602        ArchiveNotFoundException() {
2603        }
2604
2605        ArchiveNotFoundException(String JavaDoc msg) {
2606            super(msg);
2607        }
2608
2609        public String JavaDoc getMessage() {
2610            String JavaDoc msg = super.getMessage();
2611            if (msg != null)
2612                return getPath() + " (" + msg + ")";
2613            else
2614                return getPath();
2615        }
2616    } // class ArchiveNotFoundException
2617

2618    /**
2619     * Thrown to indicate that an archive entry does not exist
2620     * or is not accessible.
2621     * May be thrown by {@link #getInputStreamImpl}.
2622     */

2623    class ArchiveEntryNotFoundException extends FileNotFoundException {
2624        private final String JavaDoc name;
2625
2626        ArchiveEntryNotFoundException(String JavaDoc name, String JavaDoc msg) {
2627            super(msg);
2628            assert name != null;
2629            assert msg != null;
2630            this.name = name;
2631        }
2632
2633        public String JavaDoc getMessage() {
2634            String JavaDoc msg = super.getMessage();
2635            if (msg != null)
2636                return getPath() + ENTRY_SEPARATOR + name + " (" + msg + ")";
2637            else
2638                return getPath() + ENTRY_SEPARATOR + name;
2639        }
2640    } // class ArchiveEntryNotFoundException
2641
}
2642
Popular Tags