| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.sshd.common.file.util; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.nio.file.FileSystem; |
| import java.nio.file.Path; |
| import java.nio.file.ProviderMismatchException; |
| import java.nio.file.WatchEvent; |
| import java.nio.file.WatchKey; |
| import java.nio.file.WatchService; |
| import java.util.AbstractList; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import org.apache.sshd.common.util.GenericUtils; |
| import org.apache.sshd.common.util.ValidateUtils; |
| |
| public abstract class BasePath<T extends BasePath<T, FS>, FS extends BaseFileSystem<T>> implements Path { |
| |
| protected final String root; |
| protected final List<String> names; |
| private final FS fileSystem; |
| private String strValue; |
| private int hashValue; |
| |
| public BasePath(FS fileSystem, String root, List<String> names) { |
| this.fileSystem = ValidateUtils.checkNotNull(fileSystem, "No file system provided"); |
| this.root = root; |
| this.names = names; |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected T asT() { |
| return (T) this; |
| } |
| |
| protected T create(String root, String... names) { |
| return create(root, GenericUtils.unmodifiableList(names)); |
| } |
| |
| protected T create(String root, Collection<String> names) { |
| return create(root, GenericUtils.unmodifiableList(names)); |
| } |
| |
| protected T create(String root, List<String> names) { |
| return fileSystem.create(root, names); |
| } |
| |
| @Override |
| public FS getFileSystem() { |
| return fileSystem; |
| } |
| |
| @Override |
| public boolean isAbsolute() { |
| return root != null; |
| } |
| |
| @Override |
| public T getRoot() { |
| if (isAbsolute()) { |
| return create(root); |
| } |
| return null; |
| } |
| |
| @Override |
| public T getFileName() { |
| if (!names.isEmpty()) { |
| return create(null, names.get(names.size() - 1)); |
| } |
| return null; |
| } |
| |
| @Override |
| public T getParent() { |
| if (names.isEmpty() || ((names.size() == 1) && (root == null))) { |
| return null; |
| } |
| return create(root, names.subList(0, names.size() - 1)); |
| } |
| |
| @Override |
| public int getNameCount() { |
| return names.size(); |
| } |
| |
| @Override |
| public T getName(int index) { |
| int maxIndex = getNameCount(); |
| if ((index < 0) || (index >= maxIndex)) { |
| throw new IllegalArgumentException("Invalid name index " + index + " - not in range [0-" + maxIndex + "]"); |
| } |
| return create(null, names.subList(index, index + 1)); |
| } |
| |
| @Override |
| public T subpath(int beginIndex, int endIndex) { |
| int maxIndex = getNameCount(); |
| if ((beginIndex < 0) || (beginIndex >= maxIndex) || (endIndex > maxIndex) || (beginIndex >= endIndex)) { |
| throw new IllegalArgumentException("subpath(" + beginIndex + "," + endIndex + ") bad index range - allowed [0-" + maxIndex + "]"); |
| } |
| return create(null, names.subList(beginIndex, endIndex)); |
| } |
| |
| protected boolean startsWith(List<?> list, List<?> other) { |
| return list.size() >= other.size() && list.subList(0, other.size()).equals(other); |
| } |
| |
| @Override |
| public boolean startsWith(Path other) { |
| T p1 = asT(); |
| T p2 = checkPath(other); |
| return Objects.equals(p1.getFileSystem(), p2.getFileSystem()) |
| && Objects.equals(p1.root, p2.root) |
| && startsWith(p1.names, p2.names); |
| } |
| |
| @Override |
| public boolean startsWith(String other) { |
| return startsWith(getFileSystem().getPath(other)); |
| } |
| |
| protected boolean endsWith(List<?> list, List<?> other) { |
| return other.size() <= list.size() && list.subList(list.size() - other.size(), list.size()).equals(other); |
| } |
| |
| @Override |
| public boolean endsWith(Path other) { |
| T p1 = asT(); |
| T p2 = checkPath(other); |
| if (p2.isAbsolute()) { |
| return p1.compareTo(p2) == 0; |
| } |
| return endsWith(p1.names, p2.names); |
| } |
| |
| @Override |
| public boolean endsWith(String other) { |
| return endsWith(getFileSystem().getPath(other)); |
| } |
| |
| protected boolean isNormal() { |
| int count = getNameCount(); |
| if ((count == 0) || ((count == 1) && !isAbsolute())) { |
| return true; |
| } |
| boolean foundNonParentName = isAbsolute(); // if there's a root, the path doesn't start with .. |
| boolean normal = true; |
| for (String name : names) { |
| if (name.equals("..")) { |
| if (foundNonParentName) { |
| normal = false; |
| break; |
| } |
| } else { |
| if (name.equals(".")) { |
| normal = false; |
| break; |
| } |
| foundNonParentName = true; |
| } |
| } |
| return normal; |
| } |
| |
| @Override |
| public T normalize() { |
| if (isNormal()) { |
| return asT(); |
| } |
| |
| Deque<String> newNames = new ArrayDeque<>(); |
| for (String name : names) { |
| if (name.equals("..")) { |
| String lastName = newNames.peekLast(); |
| if (lastName != null && !lastName.equals("..")) { |
| newNames.removeLast(); |
| } else if (!isAbsolute()) { |
| // if there's a root and we have an extra ".." that would go up above the root, ignore it |
| newNames.add(name); |
| } |
| } else if (!name.equals(".")) { |
| newNames.add(name); |
| } |
| } |
| |
| return newNames.equals(names) ? asT() : create(root, newNames); |
| } |
| |
| @Override |
| public T resolve(Path other) { |
| T p1 = asT(); |
| T p2 = checkPath(other); |
| if (p2.isAbsolute()) { |
| return p2; |
| } |
| if (p2.names.isEmpty()) { |
| return p1; |
| } |
| String[] names = new String[p1.names.size() + p2.names.size()]; |
| int index = 0; |
| for (String p : p1.names) { |
| names[index++] = p; |
| } |
| for (String p : p2.names) { |
| names[index++] = p; |
| } |
| return create(p1.root, names); |
| } |
| |
| @Override |
| public T resolve(String other) { |
| return resolve(getFileSystem().getPath(other, GenericUtils.EMPTY_STRING_ARRAY)); |
| } |
| |
| @Override |
| public Path resolveSibling(Path other) { |
| ValidateUtils.checkNotNull(other, "Missing sibling path argument"); |
| T parent = getParent(); |
| return parent == null ? other : parent.resolve(other); |
| } |
| |
| @Override |
| public Path resolveSibling(String other) { |
| return resolveSibling(getFileSystem().getPath(other, GenericUtils.EMPTY_STRING_ARRAY)); |
| } |
| |
| @Override |
| public T relativize(Path other) { |
| T p1 = asT(); |
| T p2 = checkPath(other); |
| if (!Objects.equals(p1.getRoot(), p2.getRoot())) { |
| throw new IllegalArgumentException("Paths have different roots: " + this + ", " + other); |
| } |
| if (p2.equals(p1)) { |
| return create(null); |
| } |
| if (p1.root == null && p1.names.isEmpty()) { |
| return p2; |
| } |
| // Common subsequence |
| int sharedSubsequenceLength = 0; |
| for (int i = 0; i < Math.min(p1.names.size(), p2.names.size()); i++) { |
| if (p1.names.get(i).equals(p2.names.get(i))) { |
| sharedSubsequenceLength++; |
| } else { |
| break; |
| } |
| } |
| int extraNamesInThis = Math.max(0, p1.names.size() - sharedSubsequenceLength); |
| List<String> extraNamesInOther = (p2.names.size() <= sharedSubsequenceLength) |
| ? Collections.<String>emptyList() |
| : p2.names.subList(sharedSubsequenceLength, p2.names.size()); |
| List<String> parts = new ArrayList<>(extraNamesInThis + extraNamesInOther.size()); |
| // add .. for each extra name in this path |
| parts.addAll(Collections.nCopies(extraNamesInThis, "..")); |
| // add each extra name in the other path |
| parts.addAll(extraNamesInOther); |
| return create(null, parts); |
| } |
| |
| @Override |
| public T toAbsolutePath() { |
| if (isAbsolute()) { |
| return asT(); |
| } |
| return fileSystem.getDefaultDir().resolve(this); |
| } |
| |
| @Override |
| public URI toUri() { |
| File file = toFile(); |
| return file.toURI(); |
| } |
| |
| @Override |
| public File toFile() { |
| throw new UnsupportedOperationException("To file " + toAbsolutePath() + " N/A"); |
| } |
| |
| @Override |
| public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException { |
| return register(watcher, events, (WatchEvent.Modifier[]) null); |
| } |
| |
| @Override |
| public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException { |
| throw new UnsupportedOperationException("Register to watch " + toAbsolutePath() + " N/A"); |
| } |
| |
| @Override |
| public Iterator<Path> iterator() { |
| return new AbstractList<Path>() { |
| @Override |
| public Path get(int index) { |
| return getName(index); |
| } |
| |
| @Override |
| public int size() { |
| return getNameCount(); |
| } |
| }.iterator(); |
| } |
| |
| @Override |
| public int compareTo(Path paramPath) { |
| T p1 = asT(); |
| T p2 = checkPath(paramPath); |
| int c = compare(p1.root, p2.root); |
| if (c != 0) { |
| return c; |
| } |
| for (int i = 0; i < Math.min(p1.names.size(), p2.names.size()); i++) { |
| String n1 = p1.names.get(i); |
| String n2 = p2.names.get(i); |
| c = compare(n1, n2); |
| if (c != 0) { |
| return c; |
| } |
| } |
| return p1.names.size() - p2.names.size(); |
| } |
| |
| protected int compare(String s1, String s2) { |
| if (s1 == null) { |
| return s2 == null ? 0 : -1; |
| } else { |
| return s2 == null ? +1 : s1.compareTo(s2); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected T checkPath(Path paramPath) { |
| ValidateUtils.checkNotNull(paramPath, "Missing path argument"); |
| if (paramPath.getClass() != getClass()) { |
| throw new ProviderMismatchException("Path is not of this class: " + paramPath + "[" + paramPath.getClass().getSimpleName() + "]"); |
| } |
| T t = (T) paramPath; |
| |
| FileSystem fs = t.getFileSystem(); |
| if (fs.provider() != this.fileSystem.provider()) { |
| throw new ProviderMismatchException("Mismatched providers for " + t); |
| } |
| return t; |
| } |
| |
| @Override |
| public int hashCode() { |
| synchronized (this) { |
| if (hashValue == 0) { |
| hashValue = calculatedHashCode(); |
| if (hashValue == 0) { |
| hashValue = 1; |
| } |
| } |
| } |
| |
| return hashValue; |
| } |
| |
| protected int calculatedHashCode() { |
| return Objects.hash(getFileSystem(), root, names); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return (obj instanceof Path) && (compareTo((Path) obj) == 0); |
| } |
| |
| @Override |
| public String toString() { |
| synchronized (this) { |
| if (strValue == null) { |
| strValue = asString(); |
| } |
| } |
| |
| return strValue; |
| } |
| |
| protected String asString() { |
| StringBuilder sb = new StringBuilder(); |
| if (root != null) { |
| sb.append(root); |
| } |
| |
| String separator = getFileSystem().getSeparator(); |
| for (String name : names) { |
| if ((sb.length() > 0) && (sb.charAt(sb.length() - 1) != '/')) { |
| sb.append(separator); |
| } |
| sb.append(name); |
| } |
| |
| return sb.toString(); |
| } |
| } |