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

de.schlichtherle.truezip.file.TArchiveDetector 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 de.schlichtherle.truezip.fs.FsCompositeDriver;
import de.schlichtherle.truezip.fs.sl.FsDriverLocator;
import de.schlichtherle.truezip.fs.FsController;
import de.schlichtherle.truezip.fs.FsMountPoint;
import de.schlichtherle.truezip.util.SuffixSet;
import de.schlichtherle.truezip.fs.FsDriver;
import de.schlichtherle.truezip.fs.FsDriverProvider;
import de.schlichtherle.truezip.fs.FsModel;
import de.schlichtherle.truezip.fs.FsPath;
import de.schlichtherle.truezip.fs.FsScheme;
import de.schlichtherle.truezip.fs.spi.FsDriverService;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;

/**
 * Detects a prospective archive file by matching its path name
 * against a pattern of file name suffixes like .zip et al
 * and looks up its corresponding file system driver by using a file system
 * driver provider.
 * 

* There are basically two types of constructors available in this class: *

    *
  1. Constructors which filter the drivers of a given file system driver * provider by a given list of file suffixes. * For example, the drivers known by the provider * {@link FsDriverLocator#SINGLETON} could be filtered by the suffix * list {@code "tar|zip"} in order to recognize only TAR and ZIP files. *
  2. Constructors which decorate a given file system driver provider with a * given map of file system schemes to file system drivers - whereby a * number of options are available to conveniently specify the map. * This could be used to specify custom archive file suffixes or file * system schemes, i.e. file system drivers. * For example, the suffix list {@code "foo|bar"} could be used to * recognize a custom variant of the JAR file format (you would need to * provide a custom file system driver then, too). *
*

* Where a constructor expects a suffix list as a parameter, * it must obeye the syntax constraints for {@link SuffixSet}s. * 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 obviously not in canonical * form. * * @author Christian Schlichtherle * @version $Id$ */ @Immutable @DefaultAnnotation(NonNull.class) public final class TArchiveDetector implements FsCompositeDriver, FsDriverProvider { /** * This instance never recognizes any archive files in a path. * This could be used as the end of a chain of * {@code TArchiveDetector} instances or if archive files * shall be treated like ordinary files rather than (virtual) directories. */ public static final TArchiveDetector NULL = new TArchiveDetector(""); /** * This instance recognizes all archive types for which a file system * driver can be found by the file system driver service locator singleton * {@link FsDriverLocator#SINGLETON}. * A file system driver is looked up by using the suffix of the file as the * scheme of the file system. */ public static final TArchiveDetector ALL = new TArchiveDetector(null); private final Map drivers; /** * The canonical string respresentation of the set of suffixes recognized * by this archive detector. * This set is used to filter the registered archive file suffixes in * {@link #drivers}. */ private final String suffixes; /** * The thread local matcher used to match archive file suffixes. */ private final ThreadLocalMatcher matcher; /** * Equivalent to * {@link #TArchiveDetector(FsDriverProvider, String) * TArchiveDetector(FsDriverLocator.SINGLETON, suffixes)}. */ public TArchiveDetector(@CheckForNull String suffixes) { this(FsDriverLocator.SINGLETON, suffixes); } /** * Constructs a new {@code TArchiveDetector} by filtering the given driver * provider for all canonicalized suffixes in the {@code suffixes} list. * * @param provider the file system driver provider to filter. * @param suffixes A list of suffixes which shall identify prospective * archive files. * If this is {@code null}, no filtering is applied and all drivers * known by the given provider are available for use with this * archive detector. * @throws IllegalArgumentException If any of the suffixes in the list * names a suffix for which no file system driver is known by the * provider. * @see SuffixSet Syntax constraints for suffix lists. */ public TArchiveDetector(final FsDriverProvider provider, final @CheckForNull String suffixes) { final Map inDrivers = provider.get(); final SuffixSet inSuffixes; final Map outDrivers; if (null != suffixes) { inSuffixes = new SuffixSet(suffixes); outDrivers = new HashMap(inDrivers.size() * 4 / 3 + 1); } else { inSuffixes = null; outDrivers = inDrivers; } final SuffixSet outSuffixes = new SuffixSet(); for (final Map.Entry entry : inDrivers.entrySet()) { final FsDriver driver = entry.getValue(); if (null == driver) continue; final FsScheme scheme = entry.getKey(); final boolean federated = driver.isFederated(); if (null != inSuffixes) { final boolean accepted = inSuffixes.contains(scheme.toString()); if (!federated || accepted) outDrivers.put(scheme, driver); if (federated && accepted) outSuffixes.add(scheme.toString()); } else { if (federated) outSuffixes.add(scheme.toString()); } } if (null != inSuffixes && inSuffixes.retainAll(outSuffixes)) throw new IllegalArgumentException( "\"" + inSuffixes + "\" (no archive driver installed for these suffixes)"); this.drivers = Collections.unmodifiableMap(outDrivers); this.suffixes = outSuffixes.toString(); this.matcher = new ThreadLocalMatcher(outSuffixes.toPattern()); } /** * Equivalent to * {@link #TArchiveDetector(FsDriverProvider, String, FsDriver) * TArchiveDetector(TArchiveDetector.NULL, suffixes, driver)}. */ public TArchiveDetector(String suffixes, @CheckForNull FsDriver driver) { this(NULL, suffixes, driver); } /** * Constructs a new {@code TArchiveDetector} by * decorating the configuration of {@code delegate} with * mappings for all canonicalized suffixes in {@code suffixes} to * {@code driver}. * * @param delegate the file system driver provider to decorate. * @param suffixes a list of suffixes which shall identify prospective * archive files. * This must not be {@code null} and must not be empty. * @param driver the file system driver to map for the suffix list. * {@code null} may be used to shadow a mapping for an equal * file system scheme in {@code delegate} by removing it from the * resulting map for this detector. * @throws NullPointerException if a required configuration element is * {@code null}. * @throws IllegalArgumentException if any other parameter precondition * does not hold. * @see SuffixSet Syntax contraints for suffix lists. */ public TArchiveDetector(FsDriverProvider delegate, String suffixes, @CheckForNull FsDriver driver) { this(delegate, new Object[][] {{ suffixes, driver }}); } /** * Creates a new {@code TArchiveDetector} by * decorating the configuration of {@code delegate} with * mappings for all entries in {@code config}. * * @param delegate the file system driver provider to decorate. * @param config an array of key-value pair arrays. * The first element of each inner array must either be a * {@link FsScheme file system scheme}, an object {@code o} which * can get converted to a set of file system suffixes by calling * {@link SuffixSet#SuffixSet(String) new SuffixSet(o.toString())} * or a {@link Collection collection} of these. * The second element of each inner array must either be a * {@link FsDriver file system driver object}, a * {@link Class file system driver class}, a * {@link String fully qualified name of a file system driver class}, * or {@code null}. * {@code null} may be used to shadow a mapping for an equal * file system scheme in {@code delegate} by removing it from the * resulting map for this detector. * @throws NullPointerException if a required configuration element is * {@code null}. * @throws IllegalArgumentException if any other parameter precondition * does not hold. * @see SuffixSet Syntax contraints for suffix lists. */ public TArchiveDetector(FsDriverProvider delegate, Object[][] config) { this(delegate, FsDriverService.newMap(config)); } /** * Constructs a new {@code TArchiveDetector} by decorating the given driver * provider with mappings for all entries in {@code config}. * * @param provider the file system driver provider to decorate. * @param config a map of file system schemes to file system drivers. * {@code null} may be used to shadow a mapping for an equal * file system scheme in {@code provider} by removing it from the * resulting map for this detector. * @throws NullPointerException if a required configuration element is * {@code null}. * @throws ClassCastException if a configuration element is of the wrong * type. * @throws IllegalArgumentException if any other parameter precondition * does not hold. * @see SuffixSet Syntax contraints for suffix lists. */ public TArchiveDetector(final FsDriverProvider provider, final Map config) { final Map inDrivers = provider.get(); final Map outDrivers = new HashMap(inDrivers.size() * 4 / 3 + 1); final SuffixSet outSuffixes = new SuffixSet(); for (final Map.Entry entry : inDrivers.entrySet()) { final FsDriver driver = entry.getValue(); if (null == driver) continue; final FsScheme scheme = entry.getKey(); outDrivers.put(scheme, driver); if (driver.isFederated()) outSuffixes.add(scheme.toString()); } for (final Map.Entry entry : config.entrySet()) { final FsScheme scheme = entry.getKey(); final FsDriver driver = entry.getValue(); if (null != driver) { outDrivers.put(scheme, driver); outSuffixes.add(scheme.toString()); } else { outDrivers.remove(scheme); outSuffixes.remove(scheme.toString()); } } this.drivers = Collections.unmodifiableMap(outDrivers); this.suffixes = outSuffixes.toString(); this.matcher = new ThreadLocalMatcher(outSuffixes.toPattern()); } @Override public Map get() { return drivers; } /** * Detects whether the given {@code path} name identifies a prospective * archive file or not by applying heuristics to it and returns a * scheme for accessing archive files of this type or {@code null} * if the path does not denote a prospective archive file or an * appropriate scheme is unknown. *

* Please note that implementations must not check the actual * contents of the file identified by {@code path}! * This is because {@code path} may refer to a file which is not yet * existing or even an entry in a federated file system, in which case * there is no way to check the file contents in the parent file systems. * * @param path the path name of the file in the federated file system. * This does not need to be absolute and it does not need to be * accessible in its containing virtual file system! * @return A {@code scheme} for accessing the archive file or {@code null} * if the path does not denote an archive file (i.e. the path does * not have a known suffix) or an appropriate {@code scheme} is * unknown. */ public @CheckForNull FsScheme getScheme(String path) { Matcher m = matcher.reset(path); return m.matches() ? FsScheme.create(m.group(1).toLowerCase(Locale.ENGLISH)) : null; } @Override public FsController newController( final FsModel model, final @CheckForNull FsController parent) { assert null == model.getParent() ? null == parent : model.getParent().equals(parent.getModel()); final FsMountPoint mountPoint = model.getMountPoint(); final FsScheme declaredScheme = mountPoint.getScheme(); final FsPath path = mountPoint.getPath(); if (null != path) { final FsScheme detectedScheme = getScheme(path.getEntryName().getPath()); // may be null! if (!declaredScheme.equals(detectedScheme)) throw new IllegalArgumentException(mountPoint.toString() + " (declared/detected scheme mismatch)"); } final FsDriver driver = drivers.get(declaredScheme); if (null == driver) throw new ServiceConfigurationError(declaredScheme + " (unknown file system scheme - check run time class path configuration)"); return driver.newController(model, parent); } /** * Returns the canonical suffix list for all federated file system * types recognized by this {@code TArchiveDetector}. * * @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 #TArchiveDetector(String) * @see SuffixSet Syntax constraints for suffix lists. */ @Override public String toString() { return suffixes; } /** * A thread local {@link Matcher}. * This class is intended to be used in multithreaded environments for high * performance pattern matching. * * @see #reset(CharSequence) */ @ThreadSafe @DefaultAnnotation(NonNull.class) private static final class ThreadLocalMatcher extends ThreadLocal { private final Pattern pattern; /** * Constructs a new thread local matcher by using the given pattern. * * @param pattern the pattern to be used. */ ThreadLocalMatcher(Pattern pattern) { if (null == pattern) throw new NullPointerException(); this.pattern = pattern; } @Override protected Matcher initialValue() { return pattern.matcher(""); // NOI18N } /** * Resets the thread local matcher with the given character sequence and * returns it. */ Matcher reset(CharSequence input) { return get().reset(input); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy