All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.io.InputArchiveMetaData Maven / Gradle / Ivy

Go to download

TrueZIP is a Java based Virtual File System (VFS) to enable transparent, multi-threaded read/write access to archive files (ZIP, TAR etc.) as if they were directories. Archive files may be arbitrarily nested and the nesting level is only limited by heap and file system size.

The newest version!
/*
 * Copyright (C) 2006-2010 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.schlichtherle.io;

import de.schlichtherle.io.*;
import de.schlichtherle.io.archive.*;
import de.schlichtherle.io.archive.spi.*;
import de.schlichtherle.io.util.*;
import de.schlichtherle.util.*;

import java.io.*;
import java.util.*;
import java.util.logging.*;

/**
 * This class is not intended for public use!
 * It's only public in order to implement
 * {@link de.schlichtherle.io.archive.spi.ArchiveDriver}s.
 * 

* Annotates an {@link InputArchive} with the methods required for safe * reading of archive entries. * As an implication of this, it's also responsible for the synchronization * of the streams between multiple threads. * * @author Christian Schlichtherle * @version $Id: InputArchiveMetaData.java,v 1.4 2010/08/20 13:09:41 christian_schlichtherle Exp $ * @since TrueZIP 6.0 */ public final class InputArchiveMetaData { private static final String CLASS_NAME = "de.schlichtherle.io.InputArchiveMetaData"; private static final Logger logger = Logger.getLogger(CLASS_NAME, CLASS_NAME); /** * The archive which uses this instance. * Although this field is actually never used in this class, it makes an * archive controller strongly reachable from any entry stream in use by * any thread. * This is required to keep the archive controller from being garbarge * collected meanwhile. *

* Detail: While this is really required for input streams for * archives which are unmodified, it's actually not required for output * streams, since the archive file system is touched for these streams * anyway, which in turn schedules the archive controller for the next * update, which in turn prevents it from being garbage collected. * However, it's provided for symmetry between input archive meta data * and output archive meta data. */ private final Archive archive; private final InputArchive inArchive; /** * The pool of all open entry streams. * This is implemented as a map where the keys are the streams and the * value is the current thread. * If {@code File.isLenient()} is true, then the map is actually * instantiated as a {@link WeakHashMap}. Otherwise, it's a {@link HashMap}. * The weak hash map allows the garbage collector to pick up an entry * stream if there are no more references to it. * This reduces the likeliness of an {@link ArchiveBusyWarningException} * in case a sloppy client application has forgot to close a stream before * calling {@link File#umount} or {@link File#update}. */ private final Map streams = File.isLenient() ? (Map) new WeakHashMap() : new HashMap(); private volatile boolean stopped; /** * Creates a new instance of {@code InputArchiveMetaData} * and sets itself as the meta data for the given input archive. */ InputArchiveMetaData(final Archive archive, final InputArchive inArchive) { assert inArchive != null; this.archive = archive; this.inArchive = inArchive; } synchronized InputStream createInputStream( final ArchiveEntry entry, final ArchiveEntry dstEntry) throws IOException { assert !stopped; assert entry != null; final InputStream in = inArchive.getInputStream(entry, dstEntry); return in != null ? new EntryInputStream(in) : null; } /** * Waits until all entry streams which have been opened (and not yet closed) * by all other threads are closed or a timeout occurs. * If the current thread is interrupted while waiting, * a warning message is logged using {@code java.util.logging} and * this method returns. *

* Unless otherwise prevented, another thread could immediately open * another stream upon return of this method. * So there is actually no guarantee that really all streams * are closed upon return of this method - use carefully! * * @return The number of all open streams. */ synchronized int waitAllInputStreamsByOtherThreads(final long timeout) { assert !stopped; final long start = System.currentTimeMillis(); final int threadStreams = threadStreams(); //Thread.interrupted(); // cancel pending interrupt try { while (streams.size() > threadStreams) { long toWait; if (timeout > 0) { toWait = timeout - (System.currentTimeMillis() - start); if (toWait <= 0) break; } else { toWait = 0; } if (File.isLenient()) { System.gc(); // trigger garbage collection System.runFinalization(); // trigger finalizers - is this required at all? } wait(toWait); } } catch (InterruptedException ignored) { logger.warning("interrupted"); } return streams.size(); } /** * Returns the number of streams opened by the current thread. */ private int threadStreams() { final Thread thisThread = Thread.currentThread(); int n = 0; for (final Iterator i = streams.values().iterator(); i.hasNext(); ) { final Thread thread = (Thread) i.next(); if (thisThread == thread) n++; } return n; } /** * Closes and disconnects all entry streams for the archive * containing this metadata object. * Disconnecting means that any subsequent operation on the entry * streams will throw an {@code IOException}, with the exception of * their {@code close()} method. */ synchronized ArchiveException closeAllInputStreams( ArchiveException exceptionChain) { assert !stopped; stopped = true; for (final Iterator i = streams.keySet().iterator(); i.hasNext(); ) { final EntryInputStream in = (EntryInputStream) i.next(); try { in.doClose(); } catch (IOException failure) { exceptionChain = new ArchiveWarningException( exceptionChain, failure); } } streams.clear(); return exceptionChain; } /** * An {@link InputStream} to read the entry data from an * {@link InputArchive}. * This input stream provides support for finalization and throws an * {@link IOException} on any subsequent attempt to read data after * {@link #closeAllInputStreams} has been called. */ private final class EntryInputStream extends SynchronizedInputStream { private /*volatile*/ boolean closed; private EntryInputStream(final InputStream in) { super(in, InputArchiveMetaData.this); assert in != null; streams.put(this, Thread.currentThread()); InputArchiveMetaData.this.notify(); // there can be only one waiting thread! } private final void ensureNotStopped() throws IOException { if (stopped) throw new ArchiveEntryStreamClosedException(); } public int read() throws IOException { ensureNotStopped(); return super.read(); } public int read(byte[] b) throws IOException { ensureNotStopped(); return super.read(b); } public int read(byte[] b, int off, int len) throws IOException { ensureNotStopped(); return super.read(b, off, len); } public long skip(long n) throws IOException { ensureNotStopped(); return super.skip(n); } public int available() throws IOException { ensureNotStopped(); return super.available(); } /** * Closes this archive entry stream and releases any resources * associated with it. * This method tolerates multiple calls to it: Only the first * invocation closes the underlying stream. * * @throws IOException If an I/O exception occurs. */ public final void close() throws IOException { assert InputArchiveMetaData.this == lock; synchronized (InputArchiveMetaData.this) { if (closed) return; // Order is important! try { doClose(); } finally { streams.remove(this); InputArchiveMetaData.this.notify(); // there can be only one waiting thread! } } } /** * Closes the underlying stream and marks this stream as being closed. * It is an error to call this method on an already closed stream. * This method does not remove this stream from the pool. * This method is not synchronized! * * @throws IOException If an I/O exception occurs. */ protected void doClose() throws IOException { assert !closed; /*if (closed) return;*/ // Order is important! closed = true; super.doClose(); } public void mark(int readlimit) { if (!stopped) super.mark(readlimit); } public void reset() throws IOException { ensureNotStopped(); super.reset(); } public boolean markSupported() { return !stopped && super.markSupported(); } /** * The finalizer in this class forces this archive entry input * stream to close. * This is used to ensure that an archive can be updated although * the client may have "forgot" to close this input stream before. */ protected void finalize() throws Throwable { try { if (closed) return; logger.finer("finalize.open"); try { doClose(); } catch (IOException failure) { logger.log(Level.FINE, "finalize.exception", failure); } } finally { super.finalize(); } } } // class EntryInputStream }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy