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

de.schlichtherle.truezip.nio.fsp.TFileSystemProvider 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.nio.fsp;

import static de.schlichtherle.truezip.entry.Entry.*;
import static de.schlichtherle.truezip.entry.Entry.Access.*;
import static de.schlichtherle.truezip.entry.Entry.Size.*;
import static de.schlichtherle.truezip.entry.Entry.Type.*;
import de.schlichtherle.truezip.fs.FsController;
import de.schlichtherle.truezip.fs.FsEntry;
import de.schlichtherle.truezip.file.TArchiveDetector;
import de.schlichtherle.truezip.fs.FsEntryName;
import de.schlichtherle.truezip.fs.FsInputOption;
import static de.schlichtherle.truezip.fs.FsInputOptions.*;
import de.schlichtherle.truezip.fs.FsMountPoint;
import de.schlichtherle.truezip.fs.FsOutputOption;
import static de.schlichtherle.truezip.fs.FsOutputOption.*;
import static de.schlichtherle.truezip.fs.FsOutputOptions.*;
import de.schlichtherle.truezip.fs.FsScheme;
import static de.schlichtherle.truezip.nio.fsp.TPath.*;
import de.schlichtherle.truezip.socket.IOSocket;
import de.schlichtherle.truezip.socket.InputSocket;
import de.schlichtherle.truezip.socket.OutputSocket;
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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileStore;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.spi.FileSystemProvider;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author  Christian Schlichtherle
 * @version $Id$
 */
@DefaultAnnotation(NonNull.class)
public class TFileSystemProvider extends FileSystemProvider {

    private static volatile TFileSystemProvider
            DEFAULT = new TFileSystemProvider();

    private final String scheme;
    private final FsMountPoint root;

    /**
     * Obtains a file system provider for the given path.
     * 
     * @param  path a path.
     * @return A file system provider.
     */
    public static TFileSystemProvider get(final TPath path) {
        final TFileSystemProvider provider = DEFAULT;
        return null != provider ? provider : new TFileSystemProvider();
    }

    /**
     * @deprecated This constructor is solely provided in order to use this
     *             file system provider class with the service loading feature
     *             of NIO.2.
     * @see        #get(TPath) 
     */
    @SuppressWarnings("LeakingThisInConstructor")
    @Deprecated
    public TFileSystemProvider() {
        this("truezip", FsMountPoint.create(URI.create("file:/")));
        DEFAULT = this;
    }

    private TFileSystemProvider(final String scheme, final FsMountPoint root) {
        this.scheme = FsScheme.create(scheme).toString(); // check syntax
        if (null == root)
            throw new NullPointerException();
        this.root = root;
    }

    /**
     * Returns the URI scheme that identifies this provider.
     *
     * @return The URI scheme that identifies this provider.
     */
    @Override
    public String getScheme() {
        return scheme;
    }

    public FsMountPoint getRoot() {
        return root;
    }

    private static TArchiveDetector getArchiveDetector(@CheckForNull Map env) {
        if (null == env)
            return TPath.getDefaultArchiveDetector();
        TArchiveDetector detector = (TArchiveDetector) env.get(
                Parameter.ARCHIVE_DETECTOR);
        return null != detector ? detector : TPath.getDefaultArchiveDetector();
    }

    /**
     * {@inheritDoc}
     * 
     * @param env If {@code null} or does not contain a {@link TArchiveDetector}
     *        for the key {@link Parameter#ARCHIVE_DETECTOR}, then
     *        {@link TPath#getDefaultArchiveDetector()} is used to detect prospective
     *        archive files.
     */
    @Override
    public TFileSystem newFileSystem(Path path, Map env) {
        TPath p = new TPath(getArchiveDetector(env), path);
        if (null == p.getPath().getMountPoint().getParent())
            throw new UnsupportedOperationException("no prospective archive file detected"); // don't be greedy!
        return p.getFileSystem();
    }

    /**
     * {@inheritDoc}
     * 
     * @param env If {@code null} or does not contain a {@link TArchiveDetector}
     *        for the key {@link Parameter#ARCHIVE_DETECTOR}, then
     *        {@link TPath#getDefaultArchiveDetector()} is used to detect prospective
     *        archive files.
     */
    @Override
    public TFileSystem newFileSystem(URI uri, @CheckForNull Map env) {
        return new TPath(getArchiveDetector(env), uri).getFileSystem();
    }

    /**
     * Equivalent to {@link #newFileSystem(URI, Map) newFileSystem(uri, null)}.
     * Note that TFileSystem objects are transient - they solely exist to
     * please the NIO.2 API.
     * 
     */
    @Override
    public TFileSystem getFileSystem(URI uri) {
        return newFileSystem(uri, null);
    }

    @Override
    public TPath getPath(URI uri) {
        return new TPath(uri);
    }

    private static BitField mapInput(OpenOption... options) {
        HashSet set = new HashSet<>(options.length * 4 / 3 + 1);
        Collections.addAll(set, options);
        return mapInput(set);
    }

    private static BitField mapInput(Set options) {
        int s = options.size();
        if (0 == s || 1 == s && options.contains(StandardOpenOption.READ))
            return NO_INPUT_OPTION;
        throw new IllegalArgumentException(options.toString());
    }

    private static BitField mapOutput(OpenOption... options) {
        HashSet set = new HashSet<>(options.length * 4 / 3 + 1);
        Collections.addAll(set, options);
        return mapOutput(set);
    }

    private static BitField mapOutput(Set options) {
        if (options.isEmpty())
            return NO_OUTPUT_OPTION;
        EnumSet set = EnumSet.noneOf(FsOutputOption.class);
        for (OpenOption option : options) {
            if (!(option instanceof StandardOpenOption))
                throw new UnsupportedOperationException(option.toString());
            switch ((StandardOpenOption) option) {
                case READ:
                    throw new IllegalArgumentException(option.toString());
                case WRITE:
                case TRUNCATE_EXISTING:
                case CREATE:
                    break;
                case APPEND:
                    set.add(FsOutputOption.APPEND);
                    break;
                case CREATE_NEW:
                    set.add(FsOutputOption.EXCLUSIVE);
                    break;
                default:
                    throw new UnsupportedOperationException(option.toString());
            }
        }
        return set.isEmpty()
                ? NO_OUTPUT_OPTION
                : BitField.copyOf(set);
    }

    @Override
    public SeekableByteChannel newByteChannel(
            final Path path,
            final Set options,
            final FileAttribute... attrs)
    throws IOException {
        final TPath p = promote(path);
        if (options.isEmpty() || options.contains(StandardOpenOption.READ))
            return p.getInputSocket(
                        mapInput(options).set(FsInputOption.CACHE))
                    .newSeekableByteChannel();
        else
            return p.getOutputSocket(
                        mapOutput(options)
                            .set(FsOutputOption.CACHE)
                            .set(CREATE_PARENTS, TFileSystem.isLenient()),
                        null)
                    .newSeekableByteChannel();
    }

    @Override
    public InputStream newInputStream(Path path, OpenOption... options)
    throws IOException {
        return promote(path)
                .getInputSocket(mapInput(options))
                .newInputStream();
    }

    @Override
    public OutputStream newOutputStream(Path path, OpenOption... options)
    throws IOException {
        return promote(path)
                .getOutputSocket(
                    mapOutput(options)
                        .set(CREATE_PARENTS, TFileSystem.isLenient()),
                    null)
                .newOutputStream();
    }

    @Override
    public DirectoryStream newDirectoryStream(Path dir, Filter filter)
    throws IOException {
        return promote(dir).newDirectoryStream(filter);
    }

    @Override
    public void createDirectory(Path dir, FileAttribute... attrs) throws IOException {
        promote(dir).createDirectory(attrs);
    }

    @Override
    public void delete(Path path) throws IOException {
        delete0(promote(path));
    }

    private void delete0(TPath path) throws IOException {
        promote(path).delete();
    }

    @Override
    public void copy(Path source, Path target, CopyOption... options)
    throws IOException {
        copy0(promote(source), promote(target), options);
    }

    private void copy0( final TPath source,
                        final TPath target,
                        final CopyOption... options)
    throws IOException {
        checkContains(source, target);
        boolean preserve = false;
        BitField outputOptions = BitField
                .of(EXCLUSIVE)
                .set(CREATE_PARENTS, TFileSystem.isLenient());
        if (0 < options.length) {
            for (final CopyOption option : options) {
                if (!(option instanceof StandardCopyOption))
                    throw new UnsupportedOperationException(option.toString());
                switch ((StandardCopyOption) option) {
                    case REPLACE_EXISTING:
                        outputOptions = outputOptions.clear(EXCLUSIVE);
                        break;
                    case COPY_ATTRIBUTES:
                        preserve = true;
                        break;
                    default:
                        throw new UnsupportedOperationException(option.toString());
                }
            }
        }
        final InputSocket input = source.getInputSocket(
                NO_INPUT_OPTION);
        final OutputSocket output = target.getOutputSocket(
                outputOptions,
                preserve ? input.getLocalTarget() : null);
        IOSocket.copy(input, output);
    }

    @Override
    public void move(Path source, Path target, CopyOption... options)
    throws IOException {
        TPath s = promote(source);
        TPath t = promote(target);
        if (null != t.getEntry())
            throw new FileAlreadyExistsException(target.toString());
        copy0(s, t, StandardCopyOption.COPY_ATTRIBUTES);
        delete0(s);
    }

    private static void checkContains(TPath a, TPath b) throws IOException {
        URI ua = a.toRealPath().getPath().toHierarchicalUri();
        URI ub = b.toRealPath().getPath().toHierarchicalUri();
        if (ua.resolve(ub) != ub)
            throw new IOException(b + " (contained in " + a + ")");
    }

    @Override
    public boolean isSameFile(Path a, Path b) throws IOException {
        URI ua = promote(a).toRealPath().getPath().toHierarchicalUri();
        URI ub = promote(b).toRealPath().getPath().toHierarchicalUri();
        return ua.equals(ub);
    }

    @Override
    public boolean isHidden(Path path) throws IOException {
        return promote(path).getFileName().startsWith(".");
    }

    @Override
    public FileStore getFileStore(Path path) throws IOException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void checkAccess(Path path, AccessMode... modes) throws IOException {
        final TPath p = promote(path);
        final FsEntryName n = p.getPath().getEntryName();
        final FsController c = p.getController();
        if (null == c.getEntry(n))
            throw new NoSuchFileException(path.toString());
        for (final AccessMode m : modes) {
            switch (m) {
                case READ:
                    if (!c.isReadable(n))
                        throw new AccessDeniedException(path.toString());
                    break;
                case WRITE:
                    if (!c.isWritable(n))
                        throw new AccessDeniedException(path.toString());
                    break;
                case EXECUTE:
                    if (!c.isExecutable(n))
                        throw new AccessDeniedException(path.toString());
                    break;
                default:
                    throw new UnsupportedOperationException();
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public  V getFileAttributeView(
            Path path,
            Class type,
            LinkOption... options) {
        if (!type.isAssignableFrom(BasicFileAttributeView.class))
            throw new UnsupportedOperationException();
        return (V) new FsEntryAttributeView(promote(path));
    }

    @Override
    @SuppressWarnings("unchecked")
    public  A readAttributes(
            Path path,
            Class type,
            LinkOption... options)
    throws IOException {
        if (!type.isAssignableFrom(BasicFileAttributes.class))
            throw new UnsupportedOperationException();
        return (A) new FsEntryAttributes(promote(path));
    }

    @Override
    public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /** Keys for environment maps. */
    public interface Parameter {
        /** The key for the {@link TArchiveDetector} parameter. */
        String ARCHIVE_DETECTOR = "ARCHIVE_DETECTOR";
    }

    private static final class FsEntryAttributeView
    implements BasicFileAttributeView {
        private final TPath path;

        FsEntryAttributeView(final TPath path) {
            this.path = path;
        }

        @Override
        public String name() {
            return "basic";
        }

        @Override
        public BasicFileAttributes readAttributes() throws IOException {
            return new FsEntryAttributes(path);
        }

        @Override
        public void setTimes(   final FileTime lastModifiedTime,
                                final FileTime lastAccessTime,
                                final FileTime createTime)
        throws IOException {
            final FsController c = path.getController();
            final Map t = new EnumMap<>(Access.class);
            t.put(WRITE, toMillis(lastModifiedTime));
            t.put(READ, toMillis(lastAccessTime));
            t.put(CREATE, toMillis(createTime));
            c.setTime(path.getPath().getEntryName(), t);
        }

        private static long toMillis(FileTime time) {
            return time == null ? null : time.toMillis();
        }
    } // class FsEntryAttributeView

    private static final class FsEntryAttributes
    implements BasicFileAttributes {
        private final FsEntry e;

        FsEntryAttributes(TPath path) throws IOException {
            e = path.getEntry();
        }

        @Override
        public FileTime lastModifiedTime() {
            return e == null ? null : FileTime.fromMillis(e.getTime(WRITE));
        }

        @Override
        public FileTime lastAccessTime() {
            return e == null ? null : FileTime.fromMillis(e.getTime(READ));
        }

        @Override
        public FileTime creationTime() {
            return e == null ? null : FileTime.fromMillis(e.getTime(CREATE));
        }

        @Override
        public boolean isRegularFile() {
            return e == null ? false : e.isType(FILE);
        }

        @Override
        public boolean isDirectory() {
            return e == null ? false : e.isType(DIRECTORY);
        }

        @Override
        public boolean isSymbolicLink() {
            return e == null ? false : e.isType(SYMLINK);
        }

        @Override
        public boolean isOther() {
            return e == null ? false : e.isType(SPECIAL);
        }

        @Override
        public long size() {
            return e == null ? UNKNOWN : e.getSize(DATA);
        }

        @Override
        public Object fileKey() {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    } // class FsEntryAttributes
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy