| /******************************************************************************* |
| * 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(); |
| } |
| |
| |
| } |