blob: 4b7afa0122f157a0192335b77a796ff4c39c27f4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtend.lib.macro.file;
import static com.google.common.collect.Lists.newArrayList;
import java.util.List;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* A path points to some file or folder within a {@link FileSystemSupport} and {@link MutableFileSystemSupport}.
*
* A path either starts with a slash which denotes an absolute path or a segment which denotes a relative path.
* Note that this is an abstraction over different 'real' file systems and doesn't understand platform specific things like, e.g.
* "c:/" on windows.
*
* The path segment separator is '/'.
*
* @author Sven Efftinge
*/
@Beta
public final class Path {
/**
* the segment separator used.
*/
public static final char SEGMENT_SEPARATOR = '/';
private final static Splitter splitter = Splitter.on(SEGMENT_SEPARATOR );
/**
* the root path
*/
public static final Path ROOT = new Path("/");
private final ImmutableList<String> segments;
private final boolean absolute;
/**
* Constructs a new Path object from a given string.
*
* the used file separator is '/' and a leading one indicates an absolute path.
*
* @param pathAsString
*/
public Path(String pathAsString) {
if (pathAsString == null)
throw new NullPointerException();
if (pathAsString.trim().length() == 0)
throw new IllegalArgumentException("empty path");
pathAsString = pathAsString.replace('\\', SEGMENT_SEPARATOR ); //replace windows separators
Iterable<String> iterable = splitter.split(pathAsString);
// if the first element is empty it has a leading separator;
this.absolute = iterable.iterator().next().length() == 0;
Iterable<String> withoutEmptySegments = Iterables.filter(iterable, new Predicate<String>() {
public boolean apply(String input) {
return input != null && input.trim().length() > 0;
}
});
segments = ImmutableList.copyOf(normalize(withoutEmptySegments));
}
private Path(List<String> segments, boolean isAbsolute) {
this.segments = ImmutableList.copyOf(normalize(segments));
this.absolute = isAbsolute;
}
private Iterable<String> normalize(Iterable<String> segments) {
List<String> result = newArrayList();
boolean canRemoveSegment = false;
for (String seg : segments) {
String string = seg.trim();
if (canRemoveSegment && string.equals("..")) {
result.remove(result.size()-1);
canRemoveSegment = !result.isEmpty() && !result.get(0).equals("..");
} else if (string.equals(".")) {
// do nothing
} else {
result.add(string);
canRemoveSegment = !string.equals("..");
}
}
return result;
}
/**
* @return whether this is an absolute path
*/
public boolean isAbsolute() {
return absolute;
}
/**
* @return the segments
*/
public List<String> getSegments() {
return segments;
}
/**
* @return the trailing segment, i.e. the simple name of the underlying element.
*/
public String getLastSegment() {
return segments.isEmpty() ? null : segments.get(segments.size()-1);
}
/**
* Appends the given suffix to this path.
*
* @param suffix the suffix to append to this path
* @return a new Path with the given suffix appended to this path's segments.
*/
public Path append(String suffix) {
return new Path(toString()+ SEGMENT_SEPARATOR + suffix);
}
/**
* Returns the parent of this path or null if this path is the root path.
*
* @return the parent of this path or null if this path is the root path.
*/
public Path getParent() {
if (!isAbsolute())
throw new IllegalStateException("path is not absolute: " + toString());
if (segments.isEmpty())
return null;
return new Path(segments.subList(0, segments.size()-1), true);
}
/**
* @return the file extension or <code>null</code> if this path's last segment doesn't have a file extension.
*/
public String getFileExtension() {
String lastSegment = getLastSegment();
int idx = lastSegment.lastIndexOf('.');
if (idx == -1) {
return null;
}
return lastSegment.substring(idx+1);
}
/**
* @param relativePath
* @return the absolute path
*/
public Path getAbsolutePath(String relativePath) {
Path other = new Path(relativePath);
return getAbsolutePath(other);
}
/**
* @param relativePath
* @return the absolute path
*/
public Path getAbsolutePath(Path relativePath) {
if (relativePath.isAbsolute()) {
throw new IllegalArgumentException("The given path '"+relativePath+"' is not relative.");
}
List<String> result = newArrayList(getSegments());
for (String segment : relativePath.getSegments()) {
if (segment.equals("..")) {
// go up
result.remove(result.size()-1);
} else if (segment.equals(".")) {
// stay in current directory
} else {
result.add(segment);
}
}
return new Path(result, true);
}
/**
* See {@link #relativize(Path)}
*
* @param other a string representing a path
* @return the resulting relative path or null if neither of the given paths is a prefix of the other
*/
public Path relativize(String other) {
return relativize(new Path(other));
}
/**
* Constructs a relative path between this path and a given path.
*
* <p> Relativization is the inverse of {@link #getAbsolutePath(Path) resolution}.
* This method attempts to construct a {@link #isAbsolute relative} path
* that when {@link #getAbsolutePath(Path) resolved} against this path, yields a
* path that locates the same file as the given path. For example, on UNIX,
* if this path is {@code "/a/b"} and the given path is {@code "/a/b/c/d"}
* then the resulting relative path would be {@code "c/d"}.
* Both paths must be absolute and and either this path or the given path must be a
* {@link #startsWith(Path) prefix} of the other.
*
* @param other
* the path to relativize against this path
*
* @return the resulting relative path or null if neither of the given paths is a prefix of the other
*
* @throws IllegalArgumentException
* if this path and {@code other} are not both absolute or relative
*/
public Path relativize(Path other) {
if (other.isAbsolute() != isAbsolute())
throw new IllegalArgumentException("This path and the given path are not both absolute or both relative: " + toString() + " | " + other.toString());
if (startsWith(other)) {
return internalRelativize(this, other);
} else if (other.startsWith(this)) {
return internalRelativize(other, this);
}
return null;
}
private Path internalRelativize(Path path, Path prefix) {
return new Path(path.getSegments().subList(prefix.getSegments().size(), path.getSegments().size()), false);
}
/**
* Returns whether this path starts with the same segments and
* has the same {@link #isAbsolute()} value.
*
* @param other the path, which might be a prefix of this path
* @return whether the given path is a prefix of this path
*/
public boolean startsWith(Path other) {
if (isAbsolute() != other.isAbsolute())
return false;
List<String> otherSegments = other.getSegments();
List<String> thisSegments = getSegments();
int otherSegmentSize = otherSegments.size();
int thisSegmentSize = thisSegments.size();
if (otherSegmentSize > thisSegmentSize) {
return false;
}
for (int i = 0; i < otherSegmentSize; i++) {
String otherSeg = otherSegments.get(i);
String thisSeg = thisSegments.get(i);
if (!otherSeg.equals(thisSeg))
return false;
}
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (absolute ? 1231 : 1237);
result = prime * result
+ ((segments == null) ? 0 : segments.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Path other = (Path) obj;
if (absolute != other.absolute)
return false;
if (segments == null) {
if (other.segments != null)
return false;
} else if (!segments.equals(other.segments))
return false;
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder();
if (isAbsolute()) {
result.append(SEGMENT_SEPARATOR);
}
int size = segments.size();
for (int i = 0; i < size; i++) {
String segment = segments.get(i);
result.append(segment);
if (i < size-1) {
result.append(SEGMENT_SEPARATOR);
}
}
return result.toString();
}
}