de.schlichtherle.truezip.file.TConfig Maven / Gradle / Ivy
/*
* Copyright (C) 2011 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.truezip.file;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.Closeable;
import java.util.Deque;
import java.util.LinkedList;
import java.util.NoSuchElementException;
/**
* A container for some configuration options.
*
* A client application can use {@link #push()} to create a new
* current configuration by copying the current configuration and
* pushing the copy on top of an inheritable thread local stack.
*
* A client application can use {@link #pop()} or {@link #close()} to
* pop the current configuration or this configuration respectively from the
* top of the inheritable thread local stack.
*
* Finally, a client application can use {@link #get()} to get access to the
* current configuration.
* If no configuration has been pushed on the inheritable thread local stack
* before, the global configuration is returned instead.
*
* Whenever a child thread is started, it will share the current configuration
* with its parent thread.
*
*
Examples
*
* The standard use case looks like this:
*
{@code
TFile file1 = new TFile("file.mok");
assert !file1.isArchive();
// Push a new current configuration on the inheritable thread local stack.
TConfig config = TConfig.push();
try {
// Change the inheritable thread local configuration.
config.setArchiveDetector(new TArchiveDetector("mok", new MockArchiveDriver()));
// Use the inheritable thread local configuration.
TFile file2 = new TFile("file.mok");
assert file2.isArchive();
// Do some I/O here.
...
} finally {
// Pop the configuration off the inheritable thread local stack.
config.close();
}
* }
*
* Using try-with-resources in JSE 7, this can get shortened to:
*
{@code
TFile file1 = new TFile("file.mok");
assert !file1.isArchive();
// Push a new current configuration on the inheritable thread local stack.
try (TConfig config = TConfig.push()) {
// Change the inheritable thread local configuration.
config.setArchiveDetector(new TArchiveDetector("mok", new MockArchiveDriver()));
// Use the inheritable thread local configuration.
TFile file2 = new TFile("file.mok");
assert file2.isArchive();
// Do some I/O here.
...
}
* }
*
* @since TrueZIP 7.2
* @author Christian Schlichtherle
* @version $Id: TConfig.java 6086334f333b 2011/06/22 19:54:19 christian $
*/
@DefaultAnnotation(NonNull.class)
public final class TConfig implements Closeable {
private static volatile @CheckForNull InheritableThreadLocalConfigStack configs;
private boolean lenient;
private TArchiveDetector detector;
/**
* Returns the current configuration.
* First, this method peeks the inheritable thread local configuration stack.
* If no configuration has been {@link #push() pushed} yet, the global
* configuration is returned.
* Mind that the global configuration is shared by all threads.
*
* @return The current configuration.
* @see #push()
*/
public static TConfig get() {
final InheritableThreadLocalConfigStack configs = TConfig.configs;
if (null == configs)
return Holder.GLOBAL;
final TConfig session = configs.get().peek();
return null != session ? session : Holder.GLOBAL;
}
/**
* Creates a new current configuration by copying the current configuration
* and pushing the copy on the inheritable thread local stack.
*
* @return The new current configuration.
* @see #get()
*/
public static TConfig push() {
InheritableThreadLocalConfigStack configs;
synchronized (TConfig.class) {
configs = TConfig.configs;
if (null == configs)
configs = TConfig.configs = new InheritableThreadLocalConfigStack();
}
final Deque stack = configs.get();
TConfig template = stack.peek();
if (null == template)
template = Holder.GLOBAL;
final TConfig config = new TConfig(template);
stack.push(config);
return config;
}
/**
* Pops the current configuration off the inheritable thread local stack.
*
* @throws IllegalStateException If the current configuration is the global
* configuration.
*/
public static void pop() {
get().close();
}
private static void pop(final TConfig config) {
final InheritableThreadLocalConfigStack configs = TConfig.configs;
if (null == configs)
throw new IllegalStateException("Inheritable thread local configuration stack is empty.");
final Deque stack = configs.get();
final TConfig found;
try {
found = stack.pop();
} catch (NoSuchElementException ex) {
throw new IllegalStateException("Inheritable thread local configuration stack is empty.", ex);
}
if (config != found) {
stack.push(found);
throw new IllegalStateException("Not the top element of the inheritable thread local configuration stack.");
}
if (stack.isEmpty())
configs.remove();
}
/** Default constructor. */
private TConfig() {
this.lenient = true;
this.detector = TArchiveDetector.ALL;
}
/** Copy constructor. */
private TConfig(final TConfig template) {
this.lenient = template.isLenient();
this.detector = template.getArchiveDetector();
}
/**
* Returns the value of the property {@code lenient}.
*
* @return The value of the property {@code lenient}.
* @see #setLenient(boolean)
*/
public boolean isLenient() {
return lenient;
}
/**
* Sets the value of the property {@code lenient}.
* This property controls whether archive files and their member
* directories get automatically created whenever required.
* By default, the value of this class property is {@code true}!
*
* Consider the following path: {@code a/outer.zip/b/inner.zip/c}.
* Now let's assume that {@code a} exists as a plain directory in the
* platform file system, while all other segments of this path don't, and
* that the module TrueZIP Driver ZIP is present on the run-time class path
* in order to detect {@code outer.zip} and {@code inner.zip} as ZIP files
* according to the initial setup.
*
* Now, if this property is set to {@code false}, then an application
* needs to call {@code new TFile("a/outer.zip/b/inner.zip").mkdirs()}
* before it can actually push the innermost {@code c} entry as a file
* or directory.
*
* More formally, before an application can access an entry in a federated
* file system, all its parent directories need to exist, including archive
* files.
* This emulates the behaviour of the platform file system.
*
* If this property is set to {@code true} however, then any missing
* parent directories (including archive files) up to the outermost archive
* file {@code outer.zip} get automatically created when using operations
* to push the innermost element of the path {@code c}.
*
* This allows applications to succeed with doing this:
* {@code new TFile("a/outer.zip/b/inner.zip/c").createNewFile()},
* or that:
* {@code new TFileOutputStream("a/outer.zip/b/inner.zip/c")}.
*
* Note that in either case the parent directory of the outermost archive
* file {@code a} must exist - TrueZIP does not automatically push
* directories in the platform file system!
*
* @param lenient the value of the property {@code lenient}.
* @see #isLenient()
*/
public void setLenient(final boolean lenient) {
this.lenient = lenient;
}
/**
* Returns the default {@link TArchiveDetector} to use for scanning path
* names for prospective archive files if no {@code TArchiveDetector} has
* been explicitly provided to a constructor.
*
* @return The default {@link TArchiveDetector} to use for scanning
* path names for prospective archive files.
* @see #setArchiveDetector
*/
public TArchiveDetector getArchiveDetector() {
return detector;
}
/**
* Sets the default {@link TArchiveDetector} to use for scanning path
* names for prospective archive files if no {@code TArchiveDetector} has
* been explicitly provided to a constructor.
* Changing the value of this property affects the scanning of path names
* of subsequently constructed {@link TFile} objects only.
* Any existing {@code TFile} objects are not affected.
*
* @param detector the default {@link TArchiveDetector} to use for scanning
* path names for prospective archive files.
* @see #getArchiveDetector()
*/
public void setArchiveDetector(TArchiveDetector detector) {
if (null == detector)
throw new NullPointerException();
this.detector = detector;
}
/**
* Pops this configuration off the inheritable thread local stack.
*
* @throws IllegalStateException If this configuration is not the top
* element of the inheritable thread local stack.
*/
@Override
public void close() {
pop(this);
}
/**
* An inheritable thread local configuration stack.
* Whenever a child thread is started, it will share the current
* configuration with its parent thread by creating a new inheritable
* thread local stack with the parent thread's current configuration as the
* only element.
*/
private static final class InheritableThreadLocalConfigStack
extends InheritableThreadLocal> {
@Override
protected Deque initialValue() {
return new LinkedList();
}
@Override
protected Deque childValue(final Deque parent) {
final Deque child = new LinkedList();
final TConfig element = parent.peek();
if (null != element)
child.push(element);
return child;
}
} // class ThreadLocalConfigStack
/** Holds the global configuration. */
private static final class Holder {
static final TConfig GLOBAL = new TConfig();
/** Make lint happy. */
private Holder() {
}
} // class Holder
}