tikhomirov@64: /* tikhomirov@634: * Copyright (c) 2011-2013 TMate Software Ltd tikhomirov@64: * tikhomirov@64: * This program is free software; you can redistribute it and/or modify tikhomirov@64: * it under the terms of the GNU General Public License as published by tikhomirov@64: * the Free Software Foundation; version 2 of the License. tikhomirov@64: * tikhomirov@64: * This program is distributed in the hope that it will be useful, tikhomirov@64: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@64: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@64: * GNU General Public License for more details. tikhomirov@64: * tikhomirov@64: * For information on how to redistribute this software under tikhomirov@64: * the terms of a license other than GNU General Public License tikhomirov@102: * contact TMate Software at support@hg4j.com tikhomirov@64: */ tikhomirov@133: package org.tmatesoft.hg.util; tikhomirov@64: tikhomirov@229: import java.util.Collection; tikhomirov@339: import java.util.Iterator; tikhomirov@339: import java.util.NoSuchElementException; tikhomirov@229: tikhomirov@634: import org.tmatesoft.hg.internal.Internals; tikhomirov@634: tikhomirov@64: /** tikhomirov@64: * Identify repository files (not String nor io.File). Convenient for pattern matching. Memory-friendly. tikhomirov@64: * tikhomirov@64: * @author Artem Tikhomirov tikhomirov@64: * @author TMate Software Ltd. tikhomirov@64: */ tikhomirov@64: public final class Path implements CharSequence, Comparable/*Cloneable? - although clone for paths make no sense*/{ tikhomirov@64: // private String[] segments; tikhomirov@64: // private int flags; // dir, unparsed tikhomirov@64: private String path; tikhomirov@64: tikhomirov@64: /*package-local*/Path(String p) { tikhomirov@64: path = p; tikhomirov@64: } tikhomirov@64: tikhomirov@148: /** tikhomirov@148: * Check if this is directory's path. tikhomirov@148: * Note, this method doesn't perform any file system operation. tikhomirov@148: * tikhomirov@148: * @return true when this path points to a directory tikhomirov@148: */ tikhomirov@148: public boolean isDirectory() { tikhomirov@148: // XXX simple logic for now. Later we may decide to have an explicit factory method to create directory paths tikhomirov@148: return path.charAt(path.length() - 1) == '/'; tikhomirov@148: } tikhomirov@148: tikhomirov@64: public int length() { tikhomirov@64: return path.length(); tikhomirov@64: } tikhomirov@64: tikhomirov@64: public char charAt(int index) { tikhomirov@64: return path.charAt(index); tikhomirov@64: } tikhomirov@64: tikhomirov@64: public CharSequence subSequence(int start, int end) { tikhomirov@64: // new Path if start-end matches boundaries of any subpath tikhomirov@64: return path.substring(start, end); tikhomirov@64: } tikhomirov@64: tikhomirov@64: @Override tikhomirov@64: public String toString() { tikhomirov@64: return path; // CharSequence demands toString() impl tikhomirov@64: } tikhomirov@339: tikhomirov@339: public Iterable segments() { tikhomirov@339: class SegSeq implements Iterable, Iterator { tikhomirov@339: private int pos; // first char to return tikhomirov@339: tikhomirov@339: public Iterator iterator() { tikhomirov@339: reset(); tikhomirov@339: return this; tikhomirov@339: } tikhomirov@339: public boolean hasNext() { tikhomirov@339: return pos < path.length(); tikhomirov@339: } tikhomirov@339: public String next() { tikhomirov@339: if (pos >= path.length()) { tikhomirov@339: throw new NoSuchElementException(); tikhomirov@339: } tikhomirov@339: int x = path.indexOf('/', pos); tikhomirov@339: if (x == -1) { tikhomirov@339: String rv = path.substring(pos); tikhomirov@339: pos = path.length(); tikhomirov@339: return rv; tikhomirov@339: } else { tikhomirov@339: String rv = path.substring(pos, x); tikhomirov@339: pos = x+1; tikhomirov@339: return rv; tikhomirov@339: } tikhomirov@339: } tikhomirov@339: public void remove() { tikhomirov@339: throw new UnsupportedOperationException(); tikhomirov@339: } tikhomirov@339: tikhomirov@339: private void reset() { tikhomirov@339: pos = 0; tikhomirov@339: } tikhomirov@339: }; tikhomirov@339: return new SegSeq(); tikhomirov@339: } tikhomirov@64: tikhomirov@64: public int compareTo(Path o) { tikhomirov@64: return path.compareTo(o.path); tikhomirov@64: } tikhomirov@64: tikhomirov@64: @Override tikhomirov@64: public boolean equals(Object obj) { tikhomirov@64: if (obj != null && getClass() == obj.getClass()) { tikhomirov@64: return this == obj || path.equals(((Path) obj).path); tikhomirov@64: } tikhomirov@64: return false; tikhomirov@64: } tikhomirov@64: @Override tikhomirov@64: public int hashCode() { tikhomirov@64: return path.hashCode(); tikhomirov@64: } tikhomirov@229: tikhomirov@229: public enum CompareResult { tikhomirov@443: Same, Unrelated, ImmediateChild, Nested, ImmediateParent, Parent /* +CommonParent ?*/ tikhomirov@229: } tikhomirov@229: tikhomirov@443: /** tikhomirov@443: * @return one of {@link CompareResult} constants to indicate relations between the paths tikhomirov@229: */ tikhomirov@229: public CompareResult compareWith(Path another) { tikhomirov@229: if (another == null) { tikhomirov@229: return CompareResult.Unrelated; // XXX perhaps, IAE? tikhomirov@229: } tikhomirov@229: if (another == this || (another.length() == length() && equals(another))) { tikhomirov@229: return CompareResult.Same; tikhomirov@229: } tikhomirov@443: // one of the parties can't be parent in parent/nested, the other may be either file or folder tikhomirov@443: if (another.isDirectory() && path.startsWith(another.path)) { tikhomirov@443: return isOneSegmentDifference(path, another.path) ? CompareResult.ImmediateChild : CompareResult.Nested; tikhomirov@229: } tikhomirov@443: if (isDirectory() && another.path.startsWith(path)) { tikhomirov@443: return isOneSegmentDifference(another.path, path) ? CompareResult.ImmediateParent : CompareResult.Parent; tikhomirov@229: } tikhomirov@229: return CompareResult.Unrelated; tikhomirov@229: } tikhomirov@443: tikhomirov@443: // true if p1 is only one segment larger than p2 tikhomirov@443: private static boolean isOneSegmentDifference(String p1, String p2) { tikhomirov@443: assert p1.startsWith(p2); tikhomirov@443: String p1Tail= p1.substring(p2.length()); tikhomirov@443: int slashLoc = p1Tail.indexOf('/'); tikhomirov@443: return slashLoc == -1 || slashLoc == p1Tail.length() - 1; tikhomirov@443: } tikhomirov@634: tikhomirov@634: private static final boolean runningOnWindows; tikhomirov@634: tikhomirov@634: static { tikhomirov@634: runningOnWindows = Internals.runningOnWindows(); tikhomirov@634: } tikhomirov@64: tikhomirov@292: public static Path create(CharSequence path) { tikhomirov@64: if (path == null) { tikhomirov@64: throw new IllegalArgumentException(); tikhomirov@64: } tikhomirov@292: if (path instanceof Path) { tikhomirov@292: Path o = (Path) path; tikhomirov@292: return o; tikhomirov@292: } tikhomirov@292: String p = path.toString(); tikhomirov@634: if (runningOnWindows && p.indexOf('\\') != -1) { tikhomirov@623: throw new IllegalArgumentException(String.format("Path '%s' contains illegal char at %d", p, p.indexOf('\\'))); tikhomirov@77: } tikhomirov@292: Path rv = new Path(p); tikhomirov@64: return rv; tikhomirov@64: } tikhomirov@133: tikhomirov@133: /** tikhomirov@133: * Path filter. tikhomirov@133: */ tikhomirov@64: public interface Matcher { tikhomirov@141: boolean accept(Path path); tikhomirov@229: tikhomirov@229: final class Any implements Matcher { tikhomirov@229: public boolean accept(Path path) { return true; } tikhomirov@229: } tikhomirov@229: class Composite implements Matcher { tikhomirov@229: private final Path.Matcher[] elements; tikhomirov@229: tikhomirov@229: public Composite(Collection matchers) { tikhomirov@229: elements = matchers.toArray(new Path.Matcher[matchers.size()]); tikhomirov@229: } tikhomirov@229: tikhomirov@229: public boolean accept(Path path) { tikhomirov@229: for (Path.Matcher m : elements) { tikhomirov@229: if (m.accept(path)) { tikhomirov@229: return true; tikhomirov@229: } tikhomirov@229: } tikhomirov@229: return false; tikhomirov@229: } tikhomirov@229: } tikhomirov@141: } tikhomirov@141: tikhomirov@141: /** tikhomirov@141: * Factory for paths tikhomirov@141: */ tikhomirov@141: public interface Source { tikhomirov@431: Path path(CharSequence p); tikhomirov@141: } tikhomirov@431: tikhomirov@141: /** tikhomirov@141: * Straightforward {@link Source} implementation that creates new Path instance for each supplied string tikhomirov@431: * and optionally piping through a converter to get e.g. cached instance tikhomirov@141: */ tikhomirov@141: public static class SimpleSource implements Source { tikhomirov@141: private final PathRewrite normalizer; tikhomirov@431: private final Convertor convertor; tikhomirov@431: tikhomirov@431: public SimpleSource() { tikhomirov@431: this(new PathRewrite.Empty(), null); tikhomirov@431: } tikhomirov@141: tikhomirov@141: public SimpleSource(PathRewrite pathRewrite) { tikhomirov@431: this(pathRewrite, null); tikhomirov@141: } tikhomirov@141: tikhomirov@431: public SimpleSource(PathRewrite pathRewrite, Convertor pathConvertor) { tikhomirov@431: normalizer = pathRewrite; tikhomirov@431: convertor = pathConvertor; tikhomirov@431: } tikhomirov@431: tikhomirov@431: public Path path(CharSequence p) { tikhomirov@431: Path rv = Path.create(normalizer.rewrite(p)); tikhomirov@431: if (convertor != null) { tikhomirov@431: return convertor.mangle(rv); tikhomirov@431: } tikhomirov@431: return rv; tikhomirov@141: } tikhomirov@64: } tikhomirov@64: }