
de.schlichtherle.truezip.nio.fsp.TPath 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 de.schlichtherle.truezip.entry.Entry;
import de.schlichtherle.truezip.file.TArchiveDetector;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.fs.FsController;
import de.schlichtherle.truezip.fs.FsEntry;
import static de.schlichtherle.truezip.fs.FsEntryName.*;
import de.schlichtherle.truezip.fs.FsInputOption;
import de.schlichtherle.truezip.fs.FsMountPoint;
import de.schlichtherle.truezip.fs.FsOutputOption;
import de.schlichtherle.truezip.fs.FsPath;
import de.schlichtherle.truezip.socket.InputSocket;
import de.schlichtherle.truezip.socket.OutputSocket;
import de.schlichtherle.truezip.util.BitField;
import de.schlichtherle.truezip.util.UriBuilder;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchEvent.Modifier;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.FileAttribute;
import java.util.Iterator;
import net.jcip.annotations.Immutable;
/**
* A path implementation for the TrueZIP FileSystemProvider.
*
* @author Christian Schlichtherle
* @version $Id$
*/
@Immutable
@DefaultAnnotation(NonNull.class)
@edu.umd.cs.findbugs.annotations.SuppressWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
public final class TPath implements Path {
private static final FsMountPoint
CURRENT_DIRECTORY = FsMountPoint.create(Paths.get("").toUri());
private final TArchiveDetector detector;
private final URI uri;
private volatile FsPath path;
private volatile TFileSystem fileSystem;
private volatile Integer hashCode;
public TPath(String first, String... more) {
this((TArchiveDetector) null, first, more);
}
public TPath(final @CheckForNull TArchiveDetector detector, final String first, final String... more) {
this.detector = null != detector ? detector : getDefaultArchiveDetector();
this.uri = uri(first, more);
assert invariants();
}
public TPath(TPath parent, String first, String... more) {
this(parent, null, first, more);
}
public TPath(TPath parent, @CheckForNull TArchiveDetector detector, String first, String... more) {
this(parent.getPath(), null != detector ? detector : parent.getArchiveDetector(), first, more);
}
TPath(final FsPath parent, final @CheckForNull TArchiveDetector detector, final String first, final String... more) {
this.detector = null != detector ? detector : getDefaultArchiveDetector();
final URI uri = uri(first, more);
this.uri = parent.toUri().resolve(uri);
this.path = new Scanner(parent, detector).toPath(uri);
assert invariants();
}
public TPath(URI uri) {
this(null, uri);
}
public TPath(final @CheckForNull TArchiveDetector detector, final URI uri) {
this.detector = null != detector ? detector : getDefaultArchiveDetector();
String p = uri.getPath();
while (p.endsWith(SEPARATOR))
p = p.substring(0, p.length() - 1);
this.uri = uri.getPath().equals(p)
? uri
: new UriBuilder(uri).path(p).toUri();
assert invariants();
}
private TPath(final TArchiveDetector detector, final URI uri, final @CheckForNull FsPath path) {
this.detector = detector;
this.uri = uri;
this.path = path;
assert invariants();
}
TPath(Path path) {
this(null, path);
}
TPath(final @CheckForNull TArchiveDetector detector, final Path path) {
this.detector = null != detector ? detector : getDefaultArchiveDetector();
this.uri = new UriBuilder().path(path.toString().replace(path.getFileSystem().getSeparator(), SEPARATOR)).toUri();
}
private static URI uri(final String first, final String... more) {
final StringBuilder pb = new StringBuilder(first);
for (final String m : more)
pb .append(SEPARATOR_CHAR)
.append(m.replace(File.separatorChar, SEPARATOR_CHAR));
return new UriBuilder().path(pb.toString()).toUri();
}
/**
* Checks the invariants of this class and throws an AssertionError if
* any is violated even if assertion checking is disabled.
*
* The constructors call this method like this:
*
{@code assert invariants();}
* This calls the method if and only if assertions are enabled in order
* to assert that the instance invariants are properly obeyed.
* If assertions are disabled, the call to this method is thrown away by
* the HotSpot compiler, so there is no performance penalty.
*
* @throws AssertionError If assertions are enabled and any invariant is
* violated.
* @return {@code true}
*/
private boolean invariants() {
assert null != getArchiveDetector();
assert null != getUri();
assert null != getPath();
assert null != getFileSystem();
return true;
}
/**
* Returns the {@link TArchiveDetector} that was used to detect any archive
* files in the path of this {@code TPath} object at construction time.
*
* @return The {@link TArchiveDetector} that was used to detect any archive
* files in the path of this file object at construction time.
*/
public TArchiveDetector getArchiveDetector() {
return detector;
}
/**
* Returns {@code true} if and only if this {@code TPath} addresses an
* archive file.
* Whether or not this is true solely depends on the
* {@link TArchiveDetector} which was used to construct this {@code TPath}
* - no file system tests are performed by this method!
*
* @return {@code true} if and only if this {@code TPath} addresses an
* archive file.
* @see #isEntry
*/
public boolean isArchive() {
final FsPath path = getPath();
final boolean root = path.getEntryName().isRoot();
final FsMountPoint parent = path.getMountPoint().getParent();
return root && null != parent;
}
/**
* Returns {@code true} if and only if this {@code TPath} addresses an
* entry located within an archive file.
* Whether or not this is true solely depends on the
* {@link TArchiveDetector} which was used to construct this {@code TPath}
* - no file system tests are performed by this method!
*
* @return {@code true} if and only if this {@code TPath} addresses an
* entry located within an archive file.
* @see #isArchive
*/
public boolean isEntry() {
final FsPath path = getPath();
final boolean root = path.getEntryName().isRoot();
final FsMountPoint parent = path.getMountPoint().getParent();
return !root ? null != parent
: null != parent && null != parent.getParent();
}
/**
* Returns the {@link TArchiveDetector} to use if no archive detector is
* explicitly passed to the constructor of a {@code TPath} instance.
*
* This class property is initially set to
* {@link TArchiveDetector#ALL}
*
* @return The {@link TArchiveDetector} to use if no archive detector is
* explicitly passed to the constructor of a {@code TPath} instance.
* @see #setDefaultArchiveDetector
*/
public static TArchiveDetector getDefaultArchiveDetector() {
return TFile.getDefaultArchiveDetector();
}
/**
* Sets the {@link TArchiveDetector} to use if no archive detector is
* explicitly passed to the constructor of a {@code TPath} instance.
* When a new {@code TPath} instance is constructed, but no archive
* detector is provided, then the value of this class property is used.
* So changing the value of this class property affects only subsequently
* constructed {@code TPath} instances - not any existing ones.
*
* @param detector the {@link TArchiveDetector} to use for subsequently
* constructed {@code TPath} instances if no archive detector is
* explicitly provided to the constructor
* @see #getDefaultArchiveDetector()
*/
public static void setDefaultArchiveDetector(TArchiveDetector detector) {
TFile.setDefaultArchiveDetector(detector);
}
URI getUri() {
return this.uri;
}
FsPath getPath() {
final FsPath p = this.path;
return null != p ? p : (this.path = toPath(uri));
}
private FsPath toPath(URI uri) {
return new Scanner(
uri.isAbsolute()
? TFileSystemProvider.get(this).getRoot()
: CURRENT_DIRECTORY,
detector).toPath(uri);
}
@Override
public TFileSystem getFileSystem() {
final TFileSystem fs = this.fileSystem;
return null != fs ? fs : (this.fileSystem = TFileSystem.get(this));
}
@Override
public boolean isAbsolute() {
final URI u = getUri();
return u.isAbsolute() || u.getSchemeSpecificPart().startsWith(SEPARATOR);
}
@Override
public @Nullable TPath getRoot() {
return new TPath(getArchiveDetector(), toUri().resolve("/"), null);
}
@Override
public TPath getFileName() {
final URI uri = getUri();
final URI parent = uri.resolve(".");
final URI member = parent.relativize(uri);
return new TPath(getArchiveDetector(), member, path); // don't use getPath()!
}
@Override
public TPath getParent() {
return new TPath(getArchiveDetector(), getUri().resolve("."));
}
@Override
public int getNameCount() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public TPath getName(int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public TPath subpath(int beginIndex, int endIndex) {
throw new UnsupportedOperationException("Not supported yet.");
}
static TPath promote(Path path) {
//return path instanceof TPath ? (TPath) path : new TPath(path);
return (TPath) path;
}
@Override
public boolean startsWith(Path that) {
if (this.getFileSystem() != that.getFileSystem())
return false;
return startsWith(promote(that).getPath().getEntryName().toString());
}
@Override
public boolean startsWith(String other) {
final String name = this.getPath().getEntryName().toString();
final int ol = other.length();
return name.startsWith(other)
&& (name.length() == ol
|| SEPARATOR_CHAR == name.charAt(ol));
}
@Override
public boolean endsWith(Path that) {
if (this.getFileSystem() != that.getFileSystem())
return false;
return endsWith(promote(that).getPath().getEntryName().toString());
}
@Override
public boolean endsWith(String other) {
final String name = this.getPath().getEntryName().toString();
final int ol = other.length(), tl;
return name.endsWith(other)
&& ((tl = name.length()) == ol
|| SEPARATOR_CHAR == name.charAt(tl - ol));
}
@Override
public TPath normalize() {
return new TPath(getArchiveDetector(), getUri().normalize(), path); // don't use getPath()!
}
@Override
public Path resolve(Path other) {
return resolve(other.toString());
}
@Override
public TPath resolve(String other) {
return new TPath(getArchiveDetector(), getUri().resolve(other));
}
@Override
public TPath resolveSibling(Path other) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public TPath resolveSibling(String other) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public TPath relativize(Path other) {
return new TPath(getArchiveDetector(), toUri().relativize(other.toUri()));
}
@Override
public TFile toFile() {
final URI uri = getUri();
return uri.isAbsolute()
? new TFile(getPath())
: new TFile(uri.getSchemeSpecificPart(), getArchiveDetector());
}
@Override
public URI toUri() {
return getPath().toHierarchicalUri();
}
@Override
public TPath toAbsolutePath() {
return new TPath(getArchiveDetector(), toUri(), getPath());
}
@Override
public TPath toRealPath(LinkOption... options) throws IOException {
// FIXME: resolve symlinks!
return new TPath(getArchiveDetector(), toUri(), getPath());
}
@Override
public WatchKey register(WatchService watcher, Kind>[] events, Modifier... modifiers) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public WatchKey register(WatchService watcher, Kind>... events) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Iterator iterator() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean equals(final Object other) {
if (this == other)
return true;
if (!(other instanceof Path))
return false;
final Path that = (Path) other;
return this.toUri().equals(that.toUri());
}
@Override
public int compareTo(Path that) {
return this.toUri().compareTo(that.toUri());
}
@Override
public int hashCode() {
final Integer hashCode = this.hashCode;
return null != hashCode ? hashCode : (this.hashCode = toUri().hashCode());
}
@Override
public String toString() {
return getUri().toString();
}
FsController> getController() throws IOException {
return getFileSystem().getController(getArchiveDetector());
}
FsEntry getEntry() throws IOException {
return getFileSystem().getEntry(this);
}
public InputSocket> getInputSocket(BitField options) {
return getFileSystem().getInputSocket(this, options);
}
public OutputSocket> getOutputSocket(BitField options,
@CheckForNull Entry template) {
return getFileSystem().getOutputSocket(this, options, template);
}
DirectoryStream newDirectoryStream(Filter super Path> filter)
throws IOException {
return getFileSystem().newDirectoryStream(this, filter);
}
void createDirectory(FileAttribute>... attrs) throws IOException {
getFileSystem().createDirectory(this, attrs);
}
void delete() throws IOException {
getFileSystem().delete(this);
}
}