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

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

/*
 * 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: *

    *
  1. 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.
  2. *
  3. 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.
  4. *
  5. 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.
  6. *
  7. 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.
  8. *
*

* 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: *

    *
  1. 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. *
  2. 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. *
  3. If the registry then holds an instance of an * {@code ArchiveDriver} implementation, it's returned. *
  4. 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 } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy