diff -r b5a20bd41f6a parsing.api/src/org/netbeans/modules/parsing/impl/indexing/FileObjectCrawler.java --- a/parsing.api/src/org/netbeans/modules/parsing/impl/indexing/FileObjectCrawler.java Fri Jan 11 12:49:06 2013 +0100 +++ b/parsing.api/src/org/netbeans/modules/parsing/impl/indexing/FileObjectCrawler.java Mon Jan 14 17:46:41 2013 +0100 @@ -53,6 +53,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -114,18 +115,35 @@ if (files != null) { if (files.length > 1) { Map> clusters = new HashMap>(); - for(FileObject f : files) { + Map relPaths = new HashMap(); + OUTER: for(FileObject f : files) { FileObject parent = f.getParent(); Set cluster = clusters.get(parent); if (cluster == null) { + StringBuilder currentRelPath = getRelativePath(root, parent); + + for (Iterator> it = relPaths.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); + switch (computeStartsRelation(e.getValue(), currentRelPath)) { + case FIRST_STARTS_WITH_SECOND: + //we are currently processing a parent folder of something that was already added, skip the old value: + clusters.remove(e.getKey()); + it.remove(); + break; + case SECOND_STARTS_WITH_FIRST: + //something existing is a parent folder of what we are processing now - ignore the current request + continue OUTER; + } + } cluster = new HashSet(); clusters.put(parent, cluster); + relPaths.put(parent, currentRelPath); } cluster.add(f); } for(FileObject parent : clusters.keySet()) { Set cluster = clusters.get(parent); - StringBuilder relativePath = getRelativePath(root, parent); + StringBuilder relativePath = relPaths.get(parent); if (relativePath != null) { finished = collect( cluster.toArray(new FileObject[cluster.size()]), @@ -274,6 +292,21 @@ return null; } } + + private static enum SubstringKind { + FIRST_STARTS_WITH_SECOND, + SECOND_STARTS_WITH_FIRST, + UNRELATED; + } + private SubstringKind computeStartsRelation(StringBuilder first, StringBuilder second) { + int end = Math.min(first.length(), second.length()); + + for (int i = 0; i < end; i++) { + if (first.charAt(i) != second.charAt(i)) return SubstringKind.UNRELATED; + } + + return first.length() > second.length() ? SubstringKind.FIRST_STARTS_WITH_SECOND : SubstringKind.SECOND_STARTS_WITH_FIRST; + } private boolean isVisible (final @NonNull FileObject fo) { try { diff -r b5a20bd41f6a parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/indexing/FileObjectCrawlerTest.java --- a/parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/indexing/FileObjectCrawlerTest.java Fri Jan 11 12:49:06 2013 +0100 +++ b/parsing.api/test/unit/src/org/netbeans/modules/parsing/impl/indexing/FileObjectCrawlerTest.java Mon Jan 14 17:46:41 2013 +0100 @@ -46,14 +46,16 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.regex.Pattern; +import static junit.framework.Assert.assertNull; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.junit.NbTestCase; import org.netbeans.modules.parsing.spi.indexing.Indexable; @@ -303,17 +305,49 @@ "folder2/data3.txt"); } + public void testDuplicateResults1() throws IOException { + File root = new File(getWorkDir(), "src"); + String [] paths = new String [] { + "org/pckg1/file1.txt", + "org/pckg1/pckg2/file1.txt", + "org/pckg1/pckg2/file2.txt", + }; + + populateFolderStructure(root, paths); + + FileObject rootFO = FileUtil.toFileObject(root); + FileObjectCrawler crawler = new FileObjectCrawler(rootFO, new FileObject[] {rootFO.getFileObject("org/pckg1/pckg2/file1.txt"), rootFO.getFileObject("org/pckg1/pckg2")}, EnumSet.of(Crawler.TimeStampAction.UPDATE), null, CR, SuspendSupport.NOP); + assertCollectedFiles("Wrong files collected", crawler.getResources(), new String[] {"org/pckg1/pckg2/file1.txt", "org/pckg1/pckg2/file2.txt"}); + } + + public void testDuplicateResults2() throws IOException { + File root = new File(getWorkDir(), "src"); + String [] paths = new String [] { + "org/pckg1/file1.txt", + "org/pckg1/pckg2/file1.txt", + "org/pckg1/pckg2/file2.txt", + }; + + populateFolderStructure(root, paths); + + FileObject rootFO = FileUtil.toFileObject(root); + FileObjectCrawler crawler = new FileObjectCrawler(rootFO, new FileObject[] {rootFO.getFileObject("org/pckg1/pckg2/file1.txt"), rootFO.getFileObject("org/pckg1/pckg2/file2.txt")}, EnumSet.of(Crawler.TimeStampAction.UPDATE), null, CR, SuspendSupport.NOP); + assertCollectedFiles("Wrong files collected", crawler.getResources(), new String[] {"org/pckg1/pckg2/file1.txt", "org/pckg1/pckg2/file2.txt"}); + } + protected void assertCollectedFiles(String message, Collection resources, String... expectedPaths) throws IOException { - Set collectedPaths = new HashSet(); + List collectedPaths = new ArrayList(); for(Indexable ii : resources) { collectedPaths.add(ii.getRelativePath()); } - Set expectedPathsFiltered = new HashSet(); + List expectedPathsFiltered = new ArrayList(); for(String path : expectedPaths) { if (!path.endsWith("/")) { // crawler only collects files expectedPathsFiltered.add(path); } } + Collections.sort(collectedPaths); + Collections.sort(expectedPathsFiltered); assertEquals(message, expectedPathsFiltered, collectedPaths); }