
org.jboss.shrinkwrap.impl.nio.file.ShrinkWrapPath Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 org.jboss.shrinkwrap.impl.nio.file;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchEvent.Modifier;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.nio.file.ShrinkWrapFileSystems;
/**
* NIO.2 {@link Path} implementation adapting to the {@link ArchivePath} construct in a ShrinkWrap {@link Archive}
*
* @author Andrew Lee Rubinger
*/
public class ShrinkWrapPath implements Path {
private static final Logger log = Logger.getLogger(ShrinkWrapPath.class.getName());
private static final String DIR_BACK = "..";
private static final String DIR_THIS = ".";
/**
* Internal representation
*/
private final String path;
/**
* Owning {@link ShrinkWrapFileSystem}
*/
private final ShrinkWrapFileSystem fileSystem;
/**
* Constructs a new instance using the specified (required) canonical form and backing {@link ShrinkWrapFileSystem}
*
* @param path
* @param fileSystem
* @throws IllegalArgumentException
* If the path or file system is not specified
*/
ShrinkWrapPath(final String path, final ShrinkWrapFileSystem fileSystem) throws IllegalArgumentException {
if (path == null) {
throw new IllegalArgumentException("path must be specified");
}
if (fileSystem == null) {
throw new IllegalArgumentException("File system must be specified.");
}
this.path = path;
this.fileSystem = fileSystem;
}
/**
* Constructs a new instance using the specified (required) path and backing {@link ShrinkWrapFileSystem}
*
* @param path
* to be evaluated using {@link ArchivePath#get()}
* @param fileSystem
* @throws IllegalArgumentException
* If the path or file system is not specified
* @throws IllegalArgumentException
* If the delegate is not specified
*/
ShrinkWrapPath(final ArchivePath path, final ShrinkWrapFileSystem fileSystem) throws IllegalArgumentException {
if (path == null) {
throw new IllegalArgumentException(ArchivePath.class.getSimpleName() + " must be specified");
}
if (fileSystem == null) {
throw new IllegalArgumentException("File system must be specified.");
}
this.path = path.get();
this.fileSystem = fileSystem;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#getFileSystem()
*/
@Override
public FileSystem getFileSystem() {
return fileSystem;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#isAbsolute()
*/
@Override
public boolean isAbsolute() {
return this.path.startsWith(ArchivePath.SEPARATOR_STRING);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#getRoot()
*/
@Override
public Path getRoot() {
return this.isAbsolute() ? new ShrinkWrapPath(ArchivePaths.root(), fileSystem) : null;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#getFileName()
*/
@Override
public Path getFileName() {
// Root and empty String has no file name
if (path.length() == 0 || path.equals(ArchivePaths.root().get())) {
return null;
} else {
final List tokens = tokenize(this);
// Furthest out
final Path fileName = new ShrinkWrapPath(tokens.get(tokens.size() - 1), this.fileSystem);
return fileName;
}
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#getParent()
*/
@Override
public Path getParent() {
final List tokens = tokenize(this);
// No parent?
final int numTokens = tokens.size();
if (numTokens == 0 || (numTokens == 1 && !this.isAbsolute())) {
return null;
}
// Iterate over all but the last token and build a new path
final StringBuffer sb = new StringBuffer();
if (this.isAbsolute()) {
sb.append(ArchivePath.SEPARATOR);
}
for (int i = 0; i < numTokens - 1; i++) {
if (i >= 1) {
sb.append(ArchivePath.SEPARATOR);
}
sb.append(tokens.get(i));
}
final String parentPath = sb.toString();
return new ShrinkWrapPath(parentPath, fileSystem);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#getNameCount()
*/
@Override
public int getNameCount() {
String context = this.path;
// Kill trailing slashes
if (context.endsWith(ArchivePath.SEPARATOR_STRING)) {
context = context.substring(0, context.length() - 1);
}
// Kill preceding slashes
if (context.startsWith(ArchivePath.SEPARATOR_STRING)) {
context = context.substring(1);
}
// Root
if (context.length() == 0) {
return 0;
}
// Else count names by using the separator
final int pathSeparators = this.countOccurrences(context, ArchivePath.SEPARATOR, 0);
return pathSeparators + 1;
}
/**
* Returns the number of occurrences of the specified character in the specified {@link String}, starting at the
* specified offset
*
* @param string
* @param c
* @param offset
* @return
*/
private int countOccurrences(final String string, char c, int offset) {
assert string != null : "String must be specified";
return ((offset = string.indexOf(c, offset)) == -1) ? 0 : 1 + countOccurrences(string, c, offset + 1);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#getName(int)
*/
@Override
public Path getName(final int index) {
// Precondition checks handled by subpath impl
final Path subpath = this.subpath(0, index + 1);
return subpath;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#subpath(int, int)
*/
@Override
public Path subpath(final int beginIndex, final int endIndex) {
if (beginIndex < 0) {
throw new IllegalArgumentException("Begin index must be greater than 0");
}
if (endIndex < 0) {
throw new IllegalArgumentException("End index must be greater than 0");
}
if (endIndex <= beginIndex) {
throw new IllegalArgumentException("End index must be greater than begin index");
}
final List tokens = tokenize(this);
final int tokenCount = tokens.size();
if (beginIndex >= tokenCount) {
throw new IllegalArgumentException("Invalid begin index " + endIndex + " for " + this.toString()
+ "; must be between 0 and " + tokenCount + " exclusive");
}
if (endIndex > tokenCount) {
throw new IllegalArgumentException("Invalid end index " + endIndex + " for " + this.toString()
+ "; must be between 0 and " + tokenCount + " inclusive");
}
final StringBuilder newPathBuilder = new StringBuilder();
for (int i = 0; i < endIndex; i++) {
newPathBuilder.append(ArchivePath.SEPARATOR);
newPathBuilder.append(tokens.get(i));
}
final Path newPath = this.fromString(newPathBuilder.toString());
return newPath;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#startsWith(java.nio.file.Path)
*/
@Override
public boolean startsWith(final Path other) {
// Precondition checks
if (other == null) {
throw new IllegalArgumentException("other path must be specified");
}
// Unequal FS
if (this.getFileSystem() != other.getFileSystem()) {
return false;
}
// Tokenize each
final List ourTokens = tokenize(this);
final List otherTokens = tokenize((ShrinkWrapPath) other);
// Inequal roots
if (other.isAbsolute() && !this.isAbsolute()) {
return false;
}
// More names in the other Path than we have
final int otherTokensSize = otherTokens.size();
if (otherTokensSize > ourTokens.size()) {
return false;
}
// Ensure each of the other name elements match ours
for (int i = 0; i < otherTokensSize; i++) {
if (!otherTokens.get(i).equals(ourTokens.get(i))) {
return false;
}
}
// All conditions met
return true;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#startsWith(java.lang.String)
*/
@Override
public boolean startsWith(final String other) {
if (other == null) {
throw new IllegalArgumentException("other path input must be specified");
}
final Path otherPath = this.fromString(other);
return this.startsWith(otherPath);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#endsWith(java.nio.file.Path)
*/
@Override
public boolean endsWith(final Path other) {
// Precondition checks
if (other == null) {
throw new IllegalArgumentException("other path must be specified");
}
// Unequal FS? (also ensures that we can safely cast to this type later)
if (this.getFileSystem() != other.getFileSystem()) {
return false;
}
final List ourTokens = tokenize(this);
final List otherTokens = tokenize((ShrinkWrapPath) other);
// Bigger than us, fails
final int numOtherTokens = otherTokens.size();
if (numOtherTokens > ourTokens.size()) {
return false;
}
// Difference in component size
final int differential = ourTokens.size() - numOtherTokens;
// Given an absolute?
if (other.isAbsolute()) {
// We must have the same number of elements
if (differential != 0) {
return false;
}
}
// Compare all components
for (int i = numOtherTokens - 1; i >= 0; i--) {
if (!ourTokens.get(i + differential).equals(otherTokens.get(i))) {
// Any tokens don't match, punt
return false;
}
}
// All conditions met
return true;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#endsWith(java.lang.String)
*/
@Override
public boolean endsWith(final String other) {
if (other == null) {
throw new IllegalArgumentException("other path input must be specified");
}
final Path otherPath = this.fromString(other);
return this.endsWith(otherPath);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#normalize()
*/
@Override
public Path normalize() {
final String normalizedString = normalize(tokenize(this), this.isAbsolute());
final Path normalized = new ShrinkWrapPath(normalizedString, this.fileSystem);
return normalized;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#resolve(java.nio.file.Path)
*/
@Override
public Path resolve(final Path other) {
if (other == null) {
throw new IllegalArgumentException("other path must be specified");
}
if (other.isAbsolute()) {
return other;
}
if (other.toString().length() == 0) {
return this;
}
// Else join other to this
final StringBuilder sb = new StringBuilder(this.path);
if (!this.path.endsWith(ArchivePath.SEPARATOR_STRING)) {
sb.append(ArchivePath.SEPARATOR);
}
sb.append(other.toString());
final Path newPath = new ShrinkWrapPath(sb.toString(), this.fileSystem);
return newPath;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#resolve(java.lang.String)
*/
@Override
public Path resolve(final String other) {
// Delegate
final Path otherPath = this.fromString(other);
return this.resolve(otherPath);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#resolveSibling(java.nio.file.Path)
*/
@Override
public Path resolveSibling(final Path other) {
if (other == null) {
throw new IllegalArgumentException("other path must be specified");
}
// All paths are absolute, so just return other
return other;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#resolveSibling(java.lang.String)
*/
@Override
public Path resolveSibling(final String other) {
// Delegate
final Path otherPath = this.fromString(other);
return this.resolveSibling(otherPath);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#relativize(java.nio.file.Path)
*/
@Override
public Path relativize(final Path other) {
if (other == null) {
throw new IllegalArgumentException("other path must be specified");
}
if (!(other instanceof ShrinkWrapPath)) {
throw new IllegalArgumentException("Can only relativize paths of type "
+ ShrinkWrapPath.class.getSimpleName());
}
// Equal paths, return empty Path
if (this.equals(other)) {
return new ShrinkWrapPath("", this.fileSystem);
}
// Recursive relativization
final Path newPath = relativizeCommonRoot(this, this, other, other, 0);
return newPath;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#toUri()
*/
@Override
public URI toUri() {
final URI root = ShrinkWrapFileSystems.getRootUri(this.fileSystem.getArchive());
// Compose a new URI location, stripping out the extra "/" root
final String location = root.toString() + this.toString().substring(1);
final URI uri = URI.create(location);
return uri;
}
/**
* Resolves relative paths against the root directory, normalizing as well.
*
* @see java.nio.file.Path#toAbsolutePath()
*/
@Override
public Path toAbsolutePath() {
// Already absolute?
if (this.isAbsolute()) {
return this;
}
// Else construct a new absolute path and normalize it
final Path absolutePath = new ShrinkWrapPath(ArchivePath.SEPARATOR + this.path, this.fileSystem);
final Path normalized = absolutePath.normalize();
return normalized;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#toRealPath(java.nio.file.LinkOption[])
*/
@Override
public Path toRealPath(final LinkOption... options) throws IOException {
// All links are "real" (no symlinks) and absolute, so just return this (if exists)
if (!this.fileSystem.getArchive().contains(this.path)) {
throw new FileNotFoundException("Path points to a nonexistant file or directory: " + this.toString());
}
return this;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#toFile()
*/
@Override
public File toFile() {
throw new UnsupportedOperationException(
"This path is associated with a ShrinkWrap archive, not the default provider");
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#register(java.nio.file.WatchService, java.nio.file.WatchEvent.Kind>[],
* java.nio.file.WatchEvent.Modifier[])
*/
@Override
public WatchKey register(WatchService watcher, Kind>[] events, Modifier... modifiers) throws IOException {
throw new UnsupportedOperationException("ShrinkWrap Paths do not support registration with a watch service.");
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#register(java.nio.file.WatchService, java.nio.file.WatchEvent.Kind>[])
*/
@Override
public WatchKey register(WatchService watcher, Kind>... events) throws IOException {
return this.register(watcher, events, (Modifier) null);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#iterator()
*/
@Override
public Iterator iterator() {
final List tokens = tokenize(this);
final int tokensSize = tokens.size();
final List paths = new ArrayList<>(tokensSize);
for (int i = 0; i < tokensSize; i++) {
ArchivePath newPath = ArchivePaths.root();
for (int j = 0; j <= i; j++) {
newPath = ArchivePaths.create(newPath, tokens.get(j));
}
paths.add(new ShrinkWrapPath(newPath, this.fileSystem));
}
return paths.iterator();
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#compareTo(java.nio.file.Path)
*/
@Override
public int compareTo(final Path other) {
if (other == null) {
throw new IllegalArgumentException("other path must be specified");
}
// Just defer to alpha ordering since we're absolute
return this.toString().compareTo(other.toString());
}
/**
* {@inheritDoc}
*
* @see java.nio.file.Path#toString()
*/
@Override
public String toString() {
return this.path;
}
/**
* Creates a new {@link ShrinkWrapPath} instance from the specified input {@link String}
*
* @param path
* @return
*/
private Path fromString(final String path) {
if (path == null) {
throw new IllegalArgumentException("path must be specified");
}
// Delegate
return new ShrinkWrapPath(path, fileSystem);
}
/**
* Returns the components of this path in order from root out
*
* @return
*/
private static List tokenize(final ShrinkWrapPath path) {
final StringTokenizer tokenizer = new StringTokenizer(path.toString(), ArchivePath.SEPARATOR_STRING);
final List tokens = new ArrayList<>();
while (tokenizer.hasMoreTokens()) {
tokens.add(tokenizer.nextToken());
}
return tokens;
}
/**
* Normalizes the tokenized view of the path
*
* @param path
* @return
*/
private static String normalize(final List path, boolean absolute) {
assert path != null : "path must be specified";
// Remove unnecessary references to this dir
if (path.contains(DIR_THIS)) {
path.remove(DIR_THIS);
normalize(path, absolute);
}
// Remove unnecessary references to the back dir, and its parent
final int indexDirBack = path.indexOf(DIR_BACK);
if (indexDirBack != -1) {
if (indexDirBack > 0) {
path.remove(indexDirBack);
path.remove(indexDirBack - 1);
normalize(path, absolute);
} else {
throw new IllegalArgumentException("Cannot specify to go back \"../\" past the root");
}
}
// Nothing left to do; reconstruct
final StringBuilder sb = new StringBuilder();
if (absolute) {
sb.append(ArchivePath.SEPARATOR);
}
for (int i = 0; i < path.size(); i++) {
if (i > 0) {
sb.append(ArchivePath.SEPARATOR);
}
sb.append(path.get(i));
}
return sb.toString();
}
/**
* Relativizes the paths recursively
*
* @param thisOriginal
* @param thisCurrent
* @param otherOriginal
* @param otherCurrent
* @param backupCount
* @return
*/
private static ShrinkWrapPath relativizeCommonRoot(final ShrinkWrapPath thisOriginal, final Path thisCurrent,
final Path otherOriginal, Path otherCurrent, final int backupCount) {
// Preconditions
assert thisOriginal != null;
assert thisCurrent != null;
assert otherOriginal != null;
assert otherCurrent != null;
assert backupCount >= 0;
// Do we yet have a common root?
if (!otherCurrent.startsWith(thisCurrent)) {
// Back up until we do
final Path otherParent = otherCurrent.getParent();
final ShrinkWrapPath thisParent = (ShrinkWrapPath) thisCurrent.getParent();
if (otherParent != null && thisParent != null) {
return relativizeCommonRoot(thisOriginal, thisParent, otherOriginal, otherParent, backupCount + 1);
} else {
throw new IllegalArgumentException("No common components");
}
}
// Common root. Now relativize that.
final List thisTokens = tokenize(thisOriginal);
final List otherTokens = tokenize((ShrinkWrapPath) otherOriginal);
final int numOtherTokens = otherTokens.size();
final int numToTake = otherTokens.size() - thisTokens.size();
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < backupCount; i++) {
sb.append(DIR_BACK);
sb.append(ArchivePath.SEPARATOR);
}
final int startCounter = numOtherTokens - numToTake - backupCount;
final int stopCounter = numOtherTokens - 1;
if (log.isLoggable(Level.FINEST)) {
log.finest("Backup: " + backupCount);
log.finest("This tokens: " + thisTokens);
log.finest("Other tokens: " + otherTokens);
log.finest("Differential: " + numToTake);
log.finest("Start: " + startCounter);
log.finest("Stop: " + stopCounter);
}
for (int i = startCounter; i <= stopCounter; i++) {
if (i > startCounter) {
sb.append(ArchivePath.SEPARATOR);
}
sb.append(otherTokens.get(i));
}
return new ShrinkWrapPath(sb.toString(), thisOriginal.fileSystem);
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fileSystem == null) ? 0 : fileSystem.hashCode());
result = prime * result + ((path == null) ? 0 : path.hashCode());
return result;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ShrinkWrapPath other = (ShrinkWrapPath) obj;
if (this.fileSystem != other.fileSystem) {
return false;
}
if (path == null) {
if (other.path != null) {
return false;
}
} else if (!path.equals(other.path)) {
return false;
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy