tikhomirov@64: /* tikhomirov@64: * Copyright (c) 2011 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@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@229: Same, Unrelated, Nested, Parent, /* perhaps, also ImmediateParent, DirectChild? */ tikhomirov@229: } tikhomirov@229: tikhomirov@229: /* tikhomirov@229: * a/file and a/dir ? 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@229: if (path.startsWith(another.path)) { tikhomirov@229: return CompareResult.Nested; tikhomirov@229: } tikhomirov@229: if (another.path.startsWith(path)) { tikhomirov@229: return CompareResult.Parent; tikhomirov@229: } tikhomirov@229: return CompareResult.Unrelated; tikhomirov@229: } 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@292: if (p.indexOf('\\') != -1) { tikhomirov@77: throw new IllegalArgumentException(); 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@141: Path path(String p); tikhomirov@141: } tikhomirov@141: tikhomirov@141: /** tikhomirov@141: * Straightforward {@link Source} implementation that creates new Path instance for each supplied string tikhomirov@141: */ tikhomirov@141: public static class SimpleSource implements Source { tikhomirov@141: private final PathRewrite normalizer; tikhomirov@141: tikhomirov@141: public SimpleSource(PathRewrite pathRewrite) { tikhomirov@141: if (pathRewrite == null) { tikhomirov@141: throw new IllegalArgumentException(); tikhomirov@141: } tikhomirov@141: normalizer = pathRewrite; tikhomirov@141: } tikhomirov@141: tikhomirov@141: public Path path(String p) { tikhomirov@141: return Path.create(normalizer.rewrite(p)); tikhomirov@141: } tikhomirov@64: } tikhomirov@64: }