de.schlichtherle.io.DefaultArchiveDetector Maven / Gradle / Ivy
Show all versions of truezip Show documentation
/*
* Copyright (C) 2005-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.archive.spi.*;
import de.schlichtherle.io.util.*;
import de.schlichtherle.util.regex.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
/**
* An {@link ArchiveDetector} which matches file paths against a pattern of
* archive file suffixes in order to detect prospective archive files and
* look up their corresponding {@link ArchiveDriver} in its registry.
*
* When this class is initialized, it enumerates all instances of the relative
* path META-INF/services/de.schlichtherle.io.registry.properties
* on the class path (this ensures that TrueZIP is compatible with JNLP as used
* by Java Web Start and can be safely added to the Extension Class Path).
*
* These configuration files are processed in arbitrary order
* to configure the global registry of archive file suffixes and
* archive drivers.
* This allows archive drivers to be "plugged in" by simply providing
* their own configuration file somewhere on the class path.
* One such instance is located inside the JAR for TrueZIP itself and contains
* TrueZIP's default configuration (please refer to this file for full details
* on the syntax).
* Likewise, client applications may provide their own configuration
* file somewhere on the class path in order to extend or override the settings
* configured by TrueZIP and any optional plug-in drivers.
*
* Each instance has a local registry. Constructors are provided which
* allow an instance to:
*
* - Filter the set of archive file suffixes in the global registry.
* For example, {@code "tar|zip"} could
* be accepted by the filter in order to recognize only the TAR and ZIP
* file formats.
* - Add custom archive file suffixes for supported archive types to the
* local registry in order to create pseudo archive types.
* For example, {@code "myapp"}
* could be added as an custom archive file suffix for the JAR file format.
* - Add custom archive file suffixes and archive drivers to the local
* registry in order to support new archive types.
* For example, the suffix {@code "7z"}
* could be associated to a custom archive driver which supports the 7z
* file format.
* - Put multiple instances in a chain of responsibility:
* The first instance which holds a mapping for any given archive file
* suffix in its registry determines the archive driver to be used.
*
*
* Altogether, this enables to build arbitrary complex configurations with
* very few lines of Java code or properties in the configuration file(s).
*
* Where a constructor expects a suffix list as a parameter, this string must
* have the form {@code "suffix[|suffix]*"}, where
* {@code suffix} is a combination of case insensitive letters.
* Empty or duplicated suffixes and leading dots are silently ignored
* and {@code null} is interpreted as an empty list.
* As an example, the parameter {@code "zip|jar"} would cause
* the archive detector to recognize ZIP and JAR files in a path.
* The same would be true for {@code "||.ZIP||.JAR||ZIP||JAR||"},
* but this notation is discouraged because it's not in canonical form
* (see {@link #getSuffixes}.
*
* {@link ArchiveDriver} classes are loaded on demand by the
* {@link #getArchiveDriver} method using the current thread's context class
* loader. This usually happens when a client application instantiates the
* {@link File} class.
*
* This implementation is (virtually) immutable and thread safe.
*
* Since TrueZIP 6.4, this class is serializable in order to meet the
* requirements of the {@link de.schlichtherle.io.File} class.
* However, it's not recommended to serialize DefaultArchiveDetector instances:
* Together with the instance, all associated archive drivers are serialized,
* too, which is pretty inefficient for a single instance.
*
* @author Christian Schlichtherle
* @version $Id$
* @see ArchiveDetector#NULL
* @see ArchiveDetector#DEFAULT
* @see ArchiveDetector#ALL
* @since TrueZIP 6.0
*/
public class DefaultArchiveDetector
extends AbstractArchiveDetector
implements Serializable {
private static final long serialVersionUID = 848158760183179884L;
/**
* The canonical list of archive file suffixes in the global registry
* which have been configured to be recognized by default.
*
* @deprecated This field is not for public use and will vanish
* private access in the next major release.
* Use {@code ArchiveDetector.DEFAULT.getSuffixes()} instead.
*/
public static final String DEFAULT_SUFFIXES
= GlobalArchiveDriverRegistry.INSTANCE.defaultSuffixes;
/**
* The canonical list of all archive file suffixes in the global registry.
*
* @deprecated This field is not for public use and will vanish
* private access in the next major release.
* Use {@code ArchiveDetector.ALL.getSuffixes()} instead.
*/
public static final String ALL_SUFFIXES
= GlobalArchiveDriverRegistry.INSTANCE.allSuffixes;
/**
* The local registry for archive file suffixes and archive drivers.
* This could actually be the global registry
* ({@link GlobalArchiveDriverRegistry#INSTANCE}), filtered by a custom
* {@link #list}.
*/
private final ArchiveDriverRegistry registry;
/**
* The canonical suffix list recognized by this archive detector.
* This list is used to filter the registered archive file suffixes in
* {@link #registry}.
*/
private final String list;
/**
* The thread local matcher used to match archive file suffixes.
* This field should be considered final!
*/
private transient ThreadLocalMatcher matcher; // never transmit this over the wire!
/**
* Creates a new {@code DefaultArchiveDetector} by filtering the
* global registry for all canonicalized suffixes in {@code list}.
*
* @param list A list of suffixes which shall identify prospective
* archive files. May be {@code null} or empty, but must
* obeye the usual syntax.
* @see DefaultArchiveDetector Syntax Definition for Suffix Lists
* @throws IllegalArgumentException If any of the suffixes in the suffix
* list names a suffix for which no {@link ArchiveDriver} is
* configured in the global registry.
*/
public DefaultArchiveDetector(final String list) {
registry = GlobalArchiveDriverRegistry.INSTANCE;
final SuffixSet set = new SuffixSet(list);
final SuffixSet all = registry.suffixes();
if (set.retainAll(all)) {
final SuffixSet unknown = new SuffixSet(list);
unknown.removeAll(all);
throw new IllegalArgumentException("\"" + unknown + "\" (no archive driver installed for these suffixes)");
}
this.list = set.toString();
matcher = new ThreadLocalMatcher(set.toRegex());
}
/**
* Equivalent to
* {@link #DefaultArchiveDetector(DefaultArchiveDetector, String, ArchiveDriver)
* DefaultArchiveDetector(ArchiveDetector.NULL, list, driver)}.
*/
public DefaultArchiveDetector(String list, ArchiveDriver driver) {
this(NULL, list, driver);
}
/**
* Creates a new {@code DefaultArchiveDetector} by
* decorating the configuration of {@code delegate} with
* mappings for all canonicalized suffixes in {@code list} to
* {@code driver}.
*
* @param delegate The {@code DefaultArchiveDetector} which's
* configuration is to be virtually inherited.
* @param list A non-null, non-empty archive file suffix list, obeying
* the usual syntax.
* @param driver The archive driver to map for the suffix list.
* This must either be an archive driver instance or
* {@code null}.
* A {@code null} archive driver may be used to shadow a
* mapping for the same archive driver in {@code delegate},
* effectively removing it.
* @see DefaultArchiveDetector Syntax Definition for Suffix Lists
* @throws NullPointerException If {@code delegate} or
* {@code list} is {@code null}.
* @throws IllegalArgumentException If any other parameter precondition
* does not hold or an illegal keyword is found in the
* suffix list.
*/
public DefaultArchiveDetector(
DefaultArchiveDetector delegate,
String list,
ArchiveDriver driver) {
this(delegate, new Object[] { list, driver });
}
/**
* Creates a new {@code DefaultArchiveDetector} by
* decorating the configuration of {@code delegate} with
* mappings for all entries in {@code config}.
*
* @param delegate The {@code DefaultArchiveDetector} which's
* configuration is to be virtually inherited.
* @param config An array of suffix lists and archive driver IDs.
* Each key in this map must be a non-null, non-empty archive file
* suffix list, obeying the usual syntax.
* Each value must either be an archive driver instance, an archive
* driver class, a string with the fully qualified name name of
* an archive driver class, or {@code null}.
* A {@code null} archive driver may be used to shadow a
* mapping for the same archive driver in {@code delegate},
* effectively removing it.
* @throws NullPointerException If any parameter or configuration element
* other than an archive driver is {@code null}.
* @throws IllegalArgumentException If any other parameter precondition
* does not hold or an illegal keyword is found in the
* configuration.
* @see DefaultArchiveDetector Syntax Definition for Suffix Lists
*/
public DefaultArchiveDetector(
DefaultArchiveDetector delegate,
Object[] config) {
this(delegate, toMap(config));
}
/**
* Creates a new {@code DefaultArchiveDetector} by
* decorating the configuration of {@code delegate} with
* mappings for all entries in {@code config}.
*
* @param delegate The {@code DefaultArchiveDetector} which's
* configuration is to be virtually inherited.
* @param config A map of suffix lists and archive drivers.
* Each key in this map must be a non-null, non-empty archive file
* suffix list, obeying the usual syntax.
* Each value must either be an archive driver instance, an archive
* driver class, a string with the fully qualified name name of
* an archive driver class, or {@code null}.
* A {@code null} archive driver may be used to shadow a
* mapping for the same archive driver in {@code delegate},
* effectively removing it.
* @throws NullPointerException If any parameter or configuration element
* other than an archive driver is {@code null}.
* @throws IllegalArgumentException If any other parameter precondition
* does not hold or an illegal keyword is found in the
* configuration.
* @see DefaultArchiveDetector Syntax Definition for Suffix Lists
*/
public DefaultArchiveDetector(
final DefaultArchiveDetector delegate,
final Map config) {
registry = new ArchiveDriverRegistry(delegate.registry, config);
final SuffixSet set = registry.decorate(new SuffixSet(delegate.list)); // may be a subset of delegate.registry.decorate(new SuffixSet())!
list = set.toString();
matcher = new ThreadLocalMatcher(set.toRegex());
}
private void readObject(final ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
matcher = new ThreadLocalMatcher(new SuffixSet(list).toRegex());
}
private static Map toMap(final Object[] config) {
if (config == null)
return null;
final Map map = new LinkedHashMap((int) (config.length / .75) + 1); // order may be important!
for (int i = 0, l = config.length; i < l; i++)
map.put(config[i], config[++i]);
return map;
}
/**
* Looks up a registered archive driver for the given (file) path by
* matching it against the set of configured archive file suffixes.
* An archive driver is looked up in the registry as follows:
*
* - If the registry holds a string, it's supposed to be the fully
* qualified class name of an {@code ArchiveDriver}
* implementation. The class will be loaded and stored in the registry.
*
- If the registry then holds a class instance, it's instantiated
* with its no-arguments constructor, cast to the
* {@code ArchiveDriver} type and stored in the registry.
*
- If the registry then holds an instance of an
* {@code ArchiveDriver} implementation, it's returned.
*
- Otherwise, {@code null} is returned.
*
*
* @throws RuntimeException A subclass is thrown if loading or
* instantiating an archive driver class fails.
*/
public ArchiveDriver getArchiveDriver(final String path) {
final Matcher m = matcher.reset(path);
if (!m.matches())
return null;
final ArchiveDriver driver = registry.getArchiveDriver(
m.group(1).toLowerCase(Locale.ENGLISH));
assert driver != null : "archive driver does not exist for a recognized suffix";
return driver;
}
/**
* Returns the set of archive file suffixes recognized by this archive
* detector in canonical form.
*
* @return Either {@code ""} to indicate an empty set or
* a string of the form {@code "suffix[|suffix]*"},
* where {@code suffix} is a combination of lower case
* letters which does not start with a dot.
* The string never contains empty or duplicated suffixes and the
* suffixes are sorted in natural order.
* @see #DefaultArchiveDetector(String)
*/
public String getSuffixes() {
return list; // canonical form
}
}