From e36a150b212f044e7d50625f277bb707b45c9bec Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 20 Apr 2017 15:17:51 +0200 Subject: [PATCH] rework unique name builder to produce better qualified names --- .../fileEditor/UniqueNameBuilderTest.java | 23 ++- .../openapi/util/io/UniqueNameBuilder.java | 159 ++++++++++++------ 2 files changed, 130 insertions(+), 52 deletions(-) diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/UniqueNameBuilderTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/UniqueNameBuilderTest.java index a5229626f0ea..55ebc77297a0 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/UniqueNameBuilderTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/UniqueNameBuilderTest.java @@ -21,6 +21,25 @@ public class UniqueNameBuilderTest extends TestCase { builder.addPath("B", "/Users/yole/idea/bar/buy/index.html"); assertEquals("foo/\u2026/index.html", builder.getShortPath("A")); } + + public void testThreeLevel() { + UniqueNameBuilder builder = new UniqueNameBuilder<>("", "/", 100); + builder.addPath("A", "/Users/yole/idea/foo/before/somedir/index.html"); + builder.addPath("A2", "/Users/yole/idea/foo/after/somedir/index.html"); + builder.addPath("A3", "/Users/yole/fabrique/foo/before/somedir/index.html"); + builder.addPath("B", "/Users/yole/idea/bar/before/somedir/index.html"); + builder.addPath("B2", "/Users/yole/idea/bar/after/somedir/index.html"); + builder.addPath("B3", "/Users/yole/fabrique/bar/after/somedir/index.html"); + + assertEquals("idea/foo/before/\u2026/index.html", builder.getShortPath("A")); + + assertEquals("idea/foo/after/\u2026/index.html", builder.getShortPath("A2")); + assertEquals("fabrique/foo/before/\u2026/index.html", builder.getShortPath("A3")); + + assertEquals("idea/bar/before/\u2026/index.html", builder.getShortPath("B")); + assertEquals("idea/bar/after/\u2026/index.html", builder.getShortPath("B2")); + assertEquals("fabrique/bar/after/\u2026/index.html", builder.getShortPath("B3")); + } public void testSeparator() { UniqueNameBuilder builder = new UniqueNameBuilder<>("", "\\", 100); @@ -56,7 +75,9 @@ public class UniqueNameBuilderTest extends TestCase { builder.addPath("A", "/Users/yole/idea/pycharm/download/index.html"); builder.addPath("B", "/Users/yole/idea/pycharm/documentation/index.html"); builder.addPath("C", "/Users/yole/idea/fabrique/download/index.html"); - assertEquals("pycharm/\u2026/index.html", builder.getShortPath("A")); + assertEquals("pycharm/download/index.html", builder.getShortPath("A")); + assertEquals("pycharm/documentation/index.html", builder.getShortPath("B")); + assertEquals("fabrique/download/index.html", builder.getShortPath("C")); } public void testShortenNamesUnique2() { diff --git a/platform/util/src/com/intellij/openapi/util/io/UniqueNameBuilder.java b/platform/util/src/com/intellij/openapi/util/io/UniqueNameBuilder.java index c7d2237dd273..70f4d985cdd9 100644 --- a/platform/util/src/com/intellij/openapi/util/io/UniqueNameBuilder.java +++ b/platform/util/src/com/intellij/openapi/util/io/UniqueNameBuilder.java @@ -17,15 +17,16 @@ package com.intellij.openapi.util.io; import com.intellij.openapi.util.text.StringUtil; import gnu.trove.THashMap; -import gnu.trove.TIntObjectHashMap; +import java.util.Iterator; import java.util.Map; +import java.util.NoSuchElementException; /** * @author yole */ public class UniqueNameBuilder { - public static final char INTERNAL_PATH_DELIMITER = '/'; + private static final String VFS_SEPARATOR = "/"; private final Map myPaths = new THashMap(); private final String mySeparator; private final int myMaxLength; @@ -42,80 +43,97 @@ public class UniqueNameBuilder { } private static class Node { - final char myChar; - final TIntObjectHashMap myChildren; + final String myText; + final THashMap myChildren; final Node myParentNode; + int myNestedChildrenCount; - Node(char ch, Node parentNode) { - myChar = ch; + Node(String text, Node parentNode) { + myText = text; myParentNode = parentNode; - myChildren = new TIntObjectHashMap(1); + myChildren = new THashMap(1); + } + + Node findOrAddChild(String word) { + Node node = myChildren.get(word); + if (node == null) myChildren.put(word, node = new Node(word, this)); + return node; } } - private final Node myRootNode = new Node('\0', null); + private final Node myRootNode = new Node("", null); - public void addPath(T key, String value) { - value = StringUtil.trimStart(value, myRoot); - myPaths.put(key, value); + // Build a trie from path components starting from end + // E.g. following try will be build from example + // |<-------[/fabrique] <- [/idea] + // /idea/pycharm/download/index.html | + // /idea/fabrique/download/index.html [RootNode] <- [/index.html] <- [/download] <- [/pycharm] <- [/idea] + // /idea/pycharm/documentation/index.html | + // |<------[/documentation] <- [/pycharm] <- [/idea] + public void addPath(T key, String path) { + path = StringUtil.trimStart(path, myRoot); + myPaths.put(key, path); Node current = myRootNode; + Iterator pathComponentsIterator = new PathComponentsIterator(path); - for (int i = value.length() - 1; i >= 0; --i) { - char ch = value.charAt(i); - Node node = current.myChildren.get(ch); - if (node == null) current.myChildren.put(ch, node = new Node(ch, current)); - current = node; + while(pathComponentsIterator.hasNext()) { + String word = pathComponentsIterator.next(); + current = current.findOrAddChild(word); } + for(Node c = current; c != null; c = c.myParentNode) ++c.myNestedChildrenCount; } public String getShortPath(T key) { String path = myPaths.get(key); if (path == null) return key.toString(); - Node current = myRootNode, firstDirNodeWithSingleChildAfterNodeWithManyChildren = null; - Node firstDirNode = null; + Node current = myRootNode; + Node firstNodeWithBranches = null; + Node firstNodeBeforeNodeWithBranches = null; + Node fileNameNode = null; - boolean searchingForManyChildren = current.myChildren.size() == 1; - for(int i = path.length() - 1; i >= 0; --i) { - Node node = current.myChildren.get(path.charAt(i)); - if (node == null) return path; - if (firstDirNode == null && node.myChar == INTERNAL_PATH_DELIMITER) { - firstDirNode = node; + Iterator pathComponentsIterator = new PathComponentsIterator(path); + + while(pathComponentsIterator.hasNext()) { + String pathComponent = pathComponentsIterator.next(); + current = current.findOrAddChild(pathComponent); + + if (fileNameNode == null) fileNameNode = current; + if (firstNodeBeforeNodeWithBranches == null && + firstNodeWithBranches != null && + current.myChildren.size() <= 1) { + if(current.myParentNode.myNestedChildrenCount - current.myParentNode.myChildren.size() < 1) { + firstNodeBeforeNodeWithBranches = current; + } } - if (searchingForManyChildren && node.myChildren.size() > 1) { - searchingForManyChildren = false; - } else if (!searchingForManyChildren && - firstDirNodeWithSingleChildAfterNodeWithManyChildren == null && - node.myChildren.size() == 1 && node.myChar == INTERNAL_PATH_DELIMITER) { - firstDirNodeWithSingleChildAfterNodeWithManyChildren = node; + + if (current.myChildren.size() != 1 && firstNodeWithBranches == null) { + firstNodeWithBranches = current; } - current = node; } - - if (firstDirNodeWithSingleChildAfterNodeWithManyChildren == null) { - firstDirNodeWithSingleChildAfterNodeWithManyChildren = current; - } - - final boolean skipDirs = firstDirNodeWithSingleChildAfterNodeWithManyChildren != firstDirNode; - StringBuilder b = new StringBuilder(); - final Node firstCharacterOfDirectoryName = firstDirNodeWithSingleChildAfterNodeWithManyChildren != current || current.myChar== - INTERNAL_PATH_DELIMITER - ? firstDirNodeWithSingleChildAfterNodeWithManyChildren.myParentNode // firstDirNodeWithSingleChildAfterNodeWithManyChildren.myChar == / - : firstDirNodeWithSingleChildAfterNodeWithManyChildren; - for(Node n = firstCharacterOfDirectoryName; n != myRootNode; ) { - if (n.myChar == INTERNAL_PATH_DELIMITER) b.append(mySeparator); - else b.append(n.myChar); + if (firstNodeBeforeNodeWithBranches == null) { + firstNodeBeforeNodeWithBranches = current; + } - if (skipDirs && n.myChar == INTERNAL_PATH_DELIMITER && n != firstDirNode) { - // Skip intermediate path content which is the same till file name - n = n.myParentNode; - while(n != firstDirNode) n = n.myParentNode; - b.append("\u2026").append(mySeparator); + boolean skipFirstSeparator = true; + for(Node c = firstNodeBeforeNodeWithBranches; c!= myRootNode; c = c.myParentNode) { + if (c != fileNameNode && c != firstNodeBeforeNodeWithBranches && c.myParentNode.myChildren.size() == 1) { + b.append(mySeparator); + b.append("\u2026"); + + while(c.myParentNode != fileNameNode && c.myParentNode.myChildren.size() == 1) c = c.myParentNode; + } else { + if (c.myText.startsWith(VFS_SEPARATOR)) { + if (!skipFirstSeparator) b.append(mySeparator); + skipFirstSeparator = false; + b.append(c.myText, VFS_SEPARATOR.length(), c.myText.length()); + } else { + b.append(c.myText); + } } - n = n.myParentNode; } return b.toString(); } @@ -123,4 +141,43 @@ public class UniqueNameBuilder { public String getSeparator() { return mySeparator; } + + private static class PathComponentsIterator implements Iterator { + private final String myPath; + private int myLastPos; + private int mySeparatorPos; + + PathComponentsIterator(String path) { + myPath = path; + myLastPos = path.length(); + mySeparatorPos = path.lastIndexOf(VFS_SEPARATOR); + } + + @Override + public boolean hasNext() { + return myLastPos != 0; + } + + @Override + public String next() { + if (myLastPos == 0) throw new NoSuchElementException(); + String pathComponent; + + if (mySeparatorPos != -1) { + pathComponent = myPath.substring(mySeparatorPos, myLastPos); + myLastPos = mySeparatorPos; + mySeparatorPos = myPath.lastIndexOf(VFS_SEPARATOR, myLastPos - 1); + } else { + pathComponent = myPath.substring(0, myLastPos); + if (!pathComponent.startsWith(VFS_SEPARATOR)) pathComponent = VFS_SEPARATOR + pathComponent; + myLastPos = 0; + } + return pathComponent; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } }