![JAR search and dependency download from the Maven repository](/logo.png)
de.schlichtherle.truezip.file.TConfig Maven / Gradle / Ivy
Show all versions of truezip-file Show documentation
/*
* Copyright (C) 2011 Schlichtherle IT Services
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package de.schlichtherle.truezip.file;
import de.schlichtherle.truezip.fs.FsInputOption;
import de.schlichtherle.truezip.fs.FsInputOptions;
import static de.schlichtherle.truezip.fs.FsInputOptions.*;
import de.schlichtherle.truezip.fs.FsManager;
import de.schlichtherle.truezip.fs.FsOutputOption;
import static de.schlichtherle.truezip.fs.FsOutputOption.*;
import de.schlichtherle.truezip.fs.FsOutputOptions;
import static de.schlichtherle.truezip.fs.FsOutputOptions.*;
import de.schlichtherle.truezip.fs.sl.FsManagerLocator;
import de.schlichtherle.truezip.util.BitField;
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;
import net.jcip.annotations.ThreadSafe;
/**
* A container for configuration options with global or inheritable thread
* local scope.
*
* A client application can use {@link #push()} to create a new
* inheritable thread local 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 {@code 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.
*
* Whenever a child thread is started, it will share the current configuration
* with its parent thread.
*
*
Examples
*
* Changing The Global Configuration
*
* If no {@link #push()} without a corresponding {@link #close()} or
* {@link #pop()} has been called before, then the {@link #get()} method will
* return the global configuration.
* This feature is intended to get used during the application setup to change
* some configuration options with a global scope like this:
*
{@code
class MyApplication extends TApplication {
//@Override
protected void setup() {
// This should obtain the global configuration.
TConfig config = TConfig.get();
// Configure custom application file format.
config.setArchiveDetector(new TArchiveDetector("aff",
new JarDriver(IOPoolLocator.SINGLETON)));
// Set FsOutputOption.GROW for appending-to rather than reassembling
// existing archive files.
config.setOutputPreferences(
config.getOutputPreferences.set(FsOutputOption.GROW));
}
...
}
* }
*
* Setting The Default Archive Detector In The Current Thread
*
* If an application needs to change the configuration of just the current
* thread rather than changing the global configuration, then the
* {@link #push()} method needs to get called like this:
*
{@code
TFile file1 = new TFile("file.aff");
assert !file1.isArchive();
// First, push a new current configuration onto the inheritable thread local
// stack.
TConfig config = TConfig.push();
try {
// Configure custom application file format "aff".
config.setArchiveDetector(new TArchiveDetector("aff",
new JarDriver(IOPoolLocator.SINGLETON)));
// Now use the current configuration.
TFile file2 = new TFile("file.aff");
assert file2.isArchive();
// Do some I/O here.
...
} finally {
// Pop the current 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.aff");
assert !file1.isArchive();
// First, push a new current configuration onto the inheritable thread local
// stack.
try (TConfig config = TConfig.push()) {
// Configure custom application file format "aff".
config.setArchiveDetector(new TArchiveDetector("aff",
new JarDriver(IOPoolLocator.SINGLETON)));
// Now use the current configuration.
TFile file2 = new TFile("file.aff");
assert file2.isArchive();
// Do some I/O here.
...
}
* }
*
* Appending To Archive Files In The Current Thread
*
* By default, TrueZIP is configured to produce the smallest possible archive
* files.
* This is achieved by selecting the maximum compression ratio in the archive
* drivers and by performing an archive update whenever an existing archive
* entry is going to get overwritten with new contents or updated with new meta
* data in order to avoid the writing of redundant data to the resulting
* archive file.
* An archive update is basically a copy operation where all archive entries
* which haven't been written yet get copied from the input archive file to the
* output archive file.
* However, while this strategy produces the smallest possible archive files,
* it may yield bad performance if the number and contents of the archive
* entries to create or update are pretty small compared to the total size of
* the resulting archive file.
*
* Therefore, you can change this strategy by setting the
* {@link FsOutputOption#GROW} output option preference when writing archive
* entry contents or updating their meta data.
* When set, this output option allows archive files to grow by appending new
* or updated archive entries to their end and inhibiting archive update
* operations.
* You can set this preference in the global configuration as shown above or
* you can set it on a case-by-case basis as follows:
*
{@code
// We are going to append "entry" to "archive.zip".
TFile file = new TFile("archive.zip/entry");
// First, push a new current configuration on the inheritable thread local
// stack.
TConfig config = TConfig.push();
try {
// Set FsOutputOption.GROW for appending-to rather than reassembling
// existing archive files.
config.setOutputPreferences(
config.getOutputPreferences.set(FsOutputOption.GROW));
// Now use the current configuration and append the entry to the archive
// file even if it's already present.
TFileOutputStream out = new TFileOutputStream(file);
try {
// Do some output here.
...
} finally {
out.close();
}
} finally {
// Pop the current configuration off the inheritable thread local stack.
config.close();
}
* }
*
* Note that it's specific to the archive file system driver if this output
* option preference is supported or not.
* If it's not supported, then it gets silently ignored, thereby falling back
* to the default strategy of performing an archive update whenever required
* to avoid writing redundant archive entry data.
* Currently, the situation is like this:
*
* - The drivers of the module TrueZIP Driver ZIP fully support this output
* option preference, so it's available for EAR, JAR, WAR etc.
* - The drivers of the module TrueZIP Driver ZIP.RAES only allow redundant
* archive entry contents and meta data.
* You cannot append to an existing ZIP.RAES file, however.
* - The drivers of the module TrueZIP Driver TAR only allow redundant
* archive entry contents.
* You cannot append to an existing TAR file, however.
*
*
* @since TrueZIP 7.2
* @author Christian Schlichtherle
* @version $Id$
*/
@ThreadSafe
@DefaultAnnotation(NonNull.class)
public final class TConfig implements Closeable {
/**
* The default value of the
* {@link #getInputPreferences input preferences} property, which is
* {@link FsInputOptions#NO_INPUT_OPTIONS}.
*
* @since TrueZIP 7.3
*/
public static final BitField
DEFAULT_INPUT_PREFERENCES = NO_INPUT_OPTIONS;
private static final BitField
INPUT_PREFERENCES_COMPLEMENT_MASK = INPUT_PREFERENCES_MASK.not();
/**
* The default value of the
* {@link #getOutputPreferences output preferences} property, which is
* {@link BitField}.of({@link FsOutputOption#CREATE_PARENTS})
.
*
* @since TrueZIP 7.3
*/
public static final BitField
DEFAULT_OUTPUT_PREFERENCES = BitField.of(CREATE_PARENTS);
private static final BitField
OUTPUT_PREFERENCES_COMPLEMENT_MASK = OUTPUT_PREFERENCES_MASK.not();
private static volatile @CheckForNull InheritableThreadLocalConfigStack configs;
// I don't think these fields should be volatile.
// This would make a difference if and only if two threads were changing
// the GLOBAL configuration concurrently, which they should never do.
// Instead, the GLOBAL configurations should only changed once at
// application startup and then each thread should modify only its thread
// local configuration which has been obtained by a call to TConfig.push().
private TArchiveDetector detector;
private BitField inputPreferences;
private BitField outputPreferences;
/**
* 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 Global.INSTANCE;
final TConfig session = configs.get().peek();
return null != session ? session : Global.INSTANCE;
}
/**
* Creates a new current configuration by copying the current configuration
* and pushing the copy onto 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)
TConfig.configs = configs = new InheritableThreadLocalConfigStack();
}
final Deque stack = configs.get();
TConfig template = stack.peek();
if (null == template)
template = Global.INSTANCE;
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 for the global configuration. */
private TConfig() {
this.detector = TArchiveDetector.ALL;
this.inputPreferences = DEFAULT_INPUT_PREFERENCES;
this.outputPreferences = DEFAULT_OUTPUT_PREFERENCES;
}
/** Copy constructor for inheritable thread local configurations. */
private TConfig(final TConfig template) {
this.detector = template.getArchiveDetector();
this.inputPreferences = template.getInputPreferences();
this.outputPreferences = template.getOutputPreferences();
}
/**
* Returns the file system manager to use within this package.
* Note that the current implementation effectively returns
* {@code FsManagerLocator.SINGLETON.get()}, but this is considered to be
* an implementation detail which may be subject to change.
*
* @return The file system manager to use within this package.
*/
FsManager getManager() {
return FsManagerLocator.SINGLETON.get();
}
/**
* Returns the value of the property {@code lenient}.
*
* @return The value of the property {@code lenient}.
* @see #setLenient(boolean)
*/
public boolean isLenient() {
return this.outputPreferences.get(CREATE_PARENTS);
}
/**
* 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 create 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 create the innermost element of the path {@code c}.
*
* This enables 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 create
* directories in the platform file system!
*
* @param lenient the value of the property {@code lenient}.
* @see #isLenient()
*/
public void setLenient(final boolean lenient) {
this.outputPreferences = this.outputPreferences
.set(CREATE_PARENTS, 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 this.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(final TArchiveDetector detector) {
if (null == detector)
throw new NullPointerException();
this.detector = detector;
}
/**
* Returns the input preferences.
*
* @return The input preferences.
* @since TrueZIP 7.3
*/
public BitField getInputPreferences() {
return this.inputPreferences;
}
/**
* Sets the input preferences.
* These preferences are usually not cached, so changing them should take
* effect immediately.
*
* @param preferences the input preferences.
* @throws IllegalArgumentException if an option is present in
* {@code preferences} which is not present in
* {@link FsInputOptions#INPUT_PREFERENCES_MASK}.
* @since TrueZIP 7.3
*/
public void setInputPreferences(final BitField preferences) {
final BitField illegal = preferences
.and(INPUT_PREFERENCES_COMPLEMENT_MASK);
if (!illegal.isEmpty())
throw new IllegalArgumentException(preferences + " (illegal input preferences)");
this.inputPreferences = preferences;
}
/**
* Returns the output preferences.
*
* @return The output preferences.
* @since TrueZIP 7.3
*/
public BitField getOutputPreferences() {
return this.outputPreferences;
}
/**
* Sets the output preferences.
* These preferences are usually not cached, so changing them should take
* effect immediately.
*
* @param preferences the output preferences.
* @throws IllegalArgumentException if an option is present in
* {@code preferences} which is not present in
* {@link FsOutputOptions#OUTPUT_PREFERENCES_MASK} or if both
* {@link FsOutputOption#STORE} and
* {@link FsOutputOption#COMPRESS} have been set.
* @since TrueZIP 7.3
*/
public void setOutputPreferences(final BitField preferences) {
final BitField illegal = preferences
.and(OUTPUT_PREFERENCES_COMPLEMENT_MASK);
if (!illegal.isEmpty())
throw new IllegalArgumentException(preferences + " (illegal output preferences)");
if (preferences.get(STORE) && preferences.get(COMPRESS))
throw new IllegalArgumentException(preferences + " (either STORE or COMPRESS may be set, but not both)");
this.outputPreferences = preferences;
}
/**
* 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;
}
} // InheritableThreadLocalConfigStack
/** Holds the global configuration. */
private static final class Global {
static final TConfig INSTANCE = new TConfig();
/** Make lint happy. */
private Global() {
}
} // Holder
}