# HG changeset patch # User Artem Tikhomirov # Date 1336147162 -7200 # Node ID 072b5f3ed0c84ea98b9cd6faa8383e6ba800bb94 # Parent 6865eb74288350510c77c7b5ab217d5d910d4ca3 Path to tell immediate parent-child relationship; more powerful scope impl; tests for both diff -r 6865eb742883 -r 072b5f3ed0c8 src/org/tmatesoft/hg/internal/PathScope.java --- a/src/org/tmatesoft/hg/internal/PathScope.java Fri Apr 27 20:57:20 2012 +0200 +++ b/src/org/tmatesoft/hg/internal/PathScope.java Fri May 04 17:59:22 2012 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 TMate Software Ltd + * Copyright (c) 2011-2012 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,24 +16,66 @@ */ package org.tmatesoft.hg.internal; +import static org.tmatesoft.hg.util.Path.CompareResult.*; + import java.util.ArrayList; +import org.tmatesoft.hg.util.FileIterator; import org.tmatesoft.hg.util.Path; +import org.tmatesoft.hg.util.Path.CompareResult; /** + * * @author Artem Tikhomirov * @author TMate Software Ltd. */ public class PathScope implements Path.Matcher { private final Path[] files; private final Path[] dirs; - private final boolean recursiveDirs; + private final boolean includeNestedDirs; + private final boolean includeParentDirs; + private final boolean includeDirContent; + + /** + * See {@link PathScope#PathScope(boolean, boolean, Path...)} + */ + public PathScope(boolean recursiveDirs, Path... paths) { + this(true, recursiveDirs, true, paths); + } - public PathScope(boolean recursiveDirs, Path... paths) { + /** + * With matchParentDirs, recursiveDirs and matchDirContent set to false, + * this scope matches only exact paths specified. + *

+ * With matchParentDirs set to true, parent directories for files and folders listed in + * the paths would get accepted as well (handy for {@link FileIterator FileIterators}). + * Note, if supplied path lists a file, parent directory for the file is not matched unless matchParentDirs + * is true. To match file's immediate parent without matching all other parents up to the root, just add file parent + * along with the file to paths. + *

+ * With recursiveDirs set to true, subdirectories (with files) of directories listed in paths would + * be matched as well. Similar to `a/b/**` + *

+ * With matchDirContent set to true, files right under any directory listed in path would be matched. + * Similar to `a/b/*`. Makes little sense to set to false when recursiceDirs is true, although may still + * be useful in certain scenarios, e.g. PathScope(false, true, false, "a/") matches files under "a/b/*" and "a/b/c/*", but not files "a/*". + * + * @param matchParentDirs true to accept parent dirs of supplied paths + * @param recursiveDirs true to include subdirectories and files of supplied paths + * @param includeDirContent + * @param paths files and folders to match + */ + public PathScope(boolean matchParentDirs, boolean recursiveDirs, boolean matchDirContent, Path... paths) { if (paths == null) { throw new IllegalArgumentException(); } - this.recursiveDirs = recursiveDirs; + includeParentDirs = matchParentDirs; + includeNestedDirs = recursiveDirs; + includeDirContent = matchDirContent; ArrayList f = new ArrayList(5); ArrayList d = new ArrayList(5); for (Path p : paths) { @@ -49,36 +91,53 @@ public boolean accept(Path path) { if (path.isDirectory()) { - // either equals to or parent of a directory we know about. - // If recursiveDirs, accept also if nested to one of our directories. - // If one of configured files is nested under the path, accept. + // either equals to or a parent of a directory we know about (i.e. configured dir is *nested* in supplied arg). + // Also, accept arg if it happened to be nested into configured dir (i.e. one of them is *parent* for the arg), + // and recursiveDirs is true. for (Path d : dirs) { switch(d.compareWith(path)) { case Same : return true; - case Nested : return true; - case Parent : return recursiveDirs; + case ImmediateChild : + case Nested : return includeParentDirs; // path is parent to one of our locations + case ImmediateParent : + case Parent : return includeNestedDirs; // path is nested in one of our locations } } + if (!includeParentDirs) { + return false; + } + // If one of configured files is nested under the path, and we shall report parents, accept. + // Note, I don't respect includeDirContent here as with file it's easy to add parent to paths explicitly, if needed. + // (if includeDirContent == .f and includeParentDirs == .f, directory than owns a scope file won't get reported) for (Path f : files) { - if (f.compareWith(path) == Path.CompareResult.Nested) { + CompareResult cr = f.compareWith(path); + if (cr == Nested || cr == ImmediateChild) { return true; } } } else { - for (Path d : dirs) { - if (d.compareWith(path) == Path.CompareResult.Parent) { - return true; - } - } for (Path f : files) { if (f.equals(path)) { return true; } } - // either lives in a directory in out scope - // or there's a file that matches the path + // if interested in nested/recursive dirs, shall check if supplied file is under any of our configured locations + if (!includeNestedDirs && !includeDirContent) { + return false; + } + for (Path d : dirs) { + CompareResult cr = d.compareWith(path); + if (includeNestedDirs && cr == Parent) { + // file is nested in one of our locations + return true; + } + if (includeDirContent && cr == ImmediateParent) { + // file is right under one of our directories, and includeDirContents is .t + return true; + } + // try another directory + } } - // TODO Auto-generated method stub return false; } } \ No newline at end of file diff -r 6865eb742883 -r 072b5f3ed0c8 src/org/tmatesoft/hg/util/Path.java --- a/src/org/tmatesoft/hg/util/Path.java Fri Apr 27 20:57:20 2012 +0200 +++ b/src/org/tmatesoft/hg/util/Path.java Fri May 04 17:59:22 2012 +0200 @@ -118,11 +118,11 @@ } public enum CompareResult { - Same, Unrelated, Nested, Parent, /* perhaps, also ImmediateParent, DirectChild? */ + Same, Unrelated, ImmediateChild, Nested, ImmediateParent, Parent /* +CommonParent ?*/ } - /* - * a/file and a/dir ? + /** + * @return one of {@link CompareResult} constants to indicate relations between the paths */ public CompareResult compareWith(Path another) { if (another == null) { @@ -131,14 +131,23 @@ if (another == this || (another.length() == length() && equals(another))) { return CompareResult.Same; } - if (path.startsWith(another.path)) { - return CompareResult.Nested; + // one of the parties can't be parent in parent/nested, the other may be either file or folder + if (another.isDirectory() && path.startsWith(another.path)) { + return isOneSegmentDifference(path, another.path) ? CompareResult.ImmediateChild : CompareResult.Nested; } - if (another.path.startsWith(path)) { - return CompareResult.Parent; + if (isDirectory() && another.path.startsWith(path)) { + return isOneSegmentDifference(another.path, path) ? CompareResult.ImmediateParent : CompareResult.Parent; } return CompareResult.Unrelated; } + + // true if p1 is only one segment larger than p2 + private static boolean isOneSegmentDifference(String p1, String p2) { + assert p1.startsWith(p2); + String p1Tail= p1.substring(p2.length()); + int slashLoc = p1Tail.indexOf('/'); + return slashLoc == -1 || slashLoc == p1Tail.length() - 1; + } public static Path create(CharSequence path) { if (path == null) { diff -r 6865eb742883 -r 072b5f3ed0c8 test/org/tmatesoft/hg/test/TestAuxUtilities.java --- a/test/org/tmatesoft/hg/test/TestAuxUtilities.java Fri Apr 27 20:57:20 2012 +0200 +++ b/test/org/tmatesoft/hg/test/TestAuxUtilities.java Fri May 04 17:59:22 2012 +0200 @@ -17,16 +17,19 @@ package org.tmatesoft.hg.test; import static org.tmatesoft.hg.repo.HgRepository.TIP; +import static org.tmatesoft.hg.util.Path.CompareResult.*; import java.io.IOException; import java.nio.ByteBuffer; import org.junit.Assert; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.tmatesoft.hg.core.HgCatCommand; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.ArrayHelper; +import org.tmatesoft.hg.internal.PathScope; import org.tmatesoft.hg.repo.HgChangelog; import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgDataFile; @@ -50,6 +53,9 @@ */ public class TestAuxUtilities { + @Rule + public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); + @Test public void testArrayHelper() { String[] initial = {"d", "w", "k", "b", "c", "i", "a", "r", "e", "h" }; @@ -290,6 +296,84 @@ Assert.assertTrue(s.equals(r2)); } + @Test + public void testPathScope() { + // XXX whether PathScope shall accept paths that are leading towards configured elements + Path[] scope = new Path[] { + Path.create("a/"), + Path.create("b/c"), + Path.create("d/e/f/") + }; + // + // accept specified path, with files and folders below + PathScope ps1 = new PathScope(true, scope); + // folders + errorCollector.assertTrue(ps1.accept(Path.create("a/"))); // == scope[0] + errorCollector.assertTrue(ps1.accept(Path.create("a/d/"))); // scope[0] is parent and recursiveDir = true + errorCollector.assertTrue(ps1.accept(Path.create("a/d/e/"))); // scope[0] is parent and recursiveDir = true + errorCollector.assertTrue(!ps1.accept(Path.create("b/d/"))); // unrelated to any preconfigured + errorCollector.assertTrue(ps1.accept(Path.create("b/"))); // arg is parent to scope[1] + errorCollector.assertTrue(ps1.accept(Path.create("d/"))); // arg is parent to scope[2] + errorCollector.assertTrue(ps1.accept(Path.create("d/e/"))); // arg is parent to scope[2] + errorCollector.assertTrue(!ps1.accept(Path.create("d/g/"))); // unrelated to any preconfigured + // files + errorCollector.assertTrue(ps1.accept(Path.create("a/d"))); // "a/" is parent + errorCollector.assertTrue(ps1.accept(Path.create("a/d/f"))); // "a/" is still a parent + errorCollector.assertTrue(ps1.accept(Path.create("b/c"))); // == + errorCollector.assertTrue(!ps1.accept(Path.create("b/d"))); // file, != + // + // accept only specified files, folders and their direct children, allow navigate to them from above (FileIterator contract) + PathScope ps2 = new PathScope(true, false, true, scope); + // folders + errorCollector.assertTrue(!ps2.accept(Path.create("a/b/c/"))); // recursiveDirs = false + errorCollector.assertTrue(ps2.accept(Path.create("b/"))); // arg is parent to scope[1] (IOW, scope[1] is nested under arg) + errorCollector.assertTrue(ps2.accept(Path.create("d/"))); // scope[2] is nested under arg + errorCollector.assertTrue(ps2.accept(Path.create("d/e/"))); // scope[2] is nested under arg + errorCollector.assertTrue(!ps2.accept(Path.create("d/f/"))); + errorCollector.assertTrue(!ps2.accept(Path.create("b/f/"))); + // files + errorCollector.assertTrue(!ps2.accept(Path.create("a/b/c"))); // file, no exact match + errorCollector.assertTrue(ps2.accept(Path.create("d/e/f/g"))); // file under scope[2] + errorCollector.assertTrue(!ps2.accept(Path.create("b/e"))); // unrelated file + + // matchParentDirs == false + PathScope ps3 = new PathScope(false, true, true, Path.create("a/b/")); // match any dir/file under a/b/, but not above + errorCollector.assertTrue(!ps3.accept(Path.create("a/"))); + errorCollector.assertTrue(ps3.accept(Path.create("a/b/c/d"))); + errorCollector.assertTrue(ps3.accept(Path.create("a/b/c"))); + errorCollector.assertTrue(!ps3.accept(Path.create("b/"))); + errorCollector.assertTrue(!ps3.accept(Path.create("d/"))); + errorCollector.assertTrue(!ps3.accept(Path.create("d/e/"))); + + // match nested but not direct dir + PathScope ps4 = new PathScope(false, true, false, Path.create("a/b/")); // match any dir/file *deep* under a/b/, + errorCollector.assertTrue(!ps4.accept(Path.create("a/"))); + errorCollector.assertTrue(!ps4.accept(Path.create("a/b/c"))); + errorCollector.assertTrue(ps4.accept(Path.create("a/b/c/d"))); + } + + @Test + public void testPathCompareWith() { + Path p1 = Path.create("a/b/"); + Path p2 = Path.create("a/b/c"); + Path p3 = Path.create("a/b"); // file with the same name as dir + Path p4 = Path.create("a/b/c/d/"); + Path p5 = Path.create("d/"); + + errorCollector.assertEquals(Same, p1.compareWith(p1)); + errorCollector.assertEquals(Same, p1.compareWith(Path.create(p1.toString()))); + errorCollector.assertEquals(Unrelated, p1.compareWith(null)); + errorCollector.assertEquals(Unrelated, p1.compareWith(p5)); + // + errorCollector.assertEquals(Parent, p1.compareWith(p4)); + errorCollector.assertEquals(Nested, p4.compareWith(p1)); + errorCollector.assertEquals(ImmediateParent, p1.compareWith(p2)); + errorCollector.assertEquals(ImmediateChild, p2.compareWith(p1)); + // + errorCollector.assertEquals(Unrelated, p2.compareWith(p3)); + errorCollector.assertEquals(Unrelated, p3.compareWith(p2)); + } + public static void main(String[] args) throws Exception { new TestAuxUtilities().testRepositoryConfig();