Merge remote-tracking branch 'origin/GT-2961_ghizard_CategoryPath_delimiter_elimination'

This commit is contained in:
Ryan Kurtz 2019-07-16 08:13:09 -04:00
commit d95fd43762
6 changed files with 479 additions and 175 deletions

View File

@ -57,13 +57,13 @@ class CategoryDB extends DatabaseObject implements Category {
this.name = name;
this.parent = parent;
subcategoryMap = new LazyLoadingCachingMap<String, CategoryDB>(mgr.lock, CategoryDB.class) {
subcategoryMap = new LazyLoadingCachingMap<>(mgr.lock, CategoryDB.class) {
@Override
public Map<String, CategoryDB> loadMap() {
return buildSubcategoryMap();
}
};
dataTypeMap = new LazyLoadingCachingMap<String, DataType>(mgr.lock, DataType.class) {
dataTypeMap = new LazyLoadingCachingMap<>(mgr.lock, DataType.class) {
@Override
public Map<String, DataType> loadMap() {
return createDataTypeMap();
@ -273,9 +273,6 @@ class CategoryDB extends DatabaseObject implements Category {
if (categoryName == null || categoryName.length() == 0) {
throw new InvalidNameException("Name cannot be null or zero length");
}
if (categoryName.indexOf(DELIMITER_CHAR) >= 0) {
throw new InvalidNameException("Bad name: " + categoryName);
}
}
/**

View File

@ -1255,17 +1255,17 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return null;
}
// try to deal with datatypes that have '/' chars in their name.
Category category = getLowestLevelCategory(dataTypePath);
// Use a category path to parse the datatype path because it knows how to deal with
// escaped forward slashes.
CategoryPath parsedPath = new CategoryPath(dataTypePath);
CategoryPath categoryPath = parsedPath.getParent();
String dataTypeName = parsedPath.getName();
Category category = getCategory(categoryPath);
if (category != null) {
CategoryPath categoryPath = category.getCategoryPath();
String path = categoryPath.getPath();
int dataTypeNameStartIndex = path.endsWith("/") ? path.length() : path.length() + 1; // +1 to get past the last '/'
String dataTypeName = dataTypePath.substring(dataTypeNameStartIndex);
return category.getDataType(dataTypeName);
if (category == null) {
return null;
}
return null;
return category.getDataType(dataTypeName);
}
@Override
@ -1273,19 +1273,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return getDataType(dataTypePath);
}
private Category getLowestLevelCategory(String dataTypePath) {
CategoryPath pathParser = new CategoryPath(dataTypePath); // Use a category path to parse the path.
while (pathParser != null) {
CategoryPath path = pathParser.getParent();
Category category = getCategory(path);
if (category != null) {
return category;
}
pathParser = path;
}
return null;
}
@Override
public void findEnumValueNames(long value, Set<String> enumValueNames) {
buildEnumValueMap();

View File

@ -23,12 +23,6 @@ import ghidra.util.task.TaskMonitor;
* Each data type resides in a given a category.
*/
public interface Category extends Comparable<Category> {
public static final char DELIMITER_CHAR = '/'; // delimeter between categories
public static final String NAME_DELIMITER = "/"; // delimiter between names
public static final String DELIMITER_STRING = "" + DELIMITER_CHAR; // delimeter between categories
/**
* Get the name of this category.
*/

View File

@ -15,7 +15,9 @@
*/
package ghidra.program.model.data;
import java.util.StringTokenizer;
import java.util.*;
import org.apache.commons.collections4.CollectionUtils;
/**
* A category path is the full path to a particular data type
@ -24,51 +26,141 @@ public class CategoryPath implements Comparable<CategoryPath> {
public static final char DELIMITER_CHAR = '/';
public static final String DELIMITER_STRING = "" + DELIMITER_CHAR;
public static final String ESCAPED_DELIMITER_STRING = "\\" + DELIMITER_STRING;
public static final CategoryPath ROOT = new CategoryPath(null);
public static final CategoryPath ROOT = new CategoryPath();
private static final String ILLEGAL_STRING = DELIMITER_STRING + DELIMITER_STRING;
private static final int DIFF = ESCAPED_DELIMITER_STRING.length() - DELIMITER_STRING.length();
private final String parentPath;
// parent can only be null for ROOT
private final CategoryPath parent;
private final String name;
/**
* Create a category path given a string.
*
* @param path category path string.
* Converts a non-escaped String into an escaped string suitable for being passed in as a
* component of a single category path string to the constructor that takes a single
* escaped category path string. The user is responsible for constructing the single
* category path string from the escaped components.
* @param nonEscapedString String that might need escaping for characters used for delimiting
* @return escaped String
* @see #unescapeString(String)
*/
public static String escapeString(String nonEscapedString) {
return nonEscapedString.replace(DELIMITER_STRING, ESCAPED_DELIMITER_STRING);
}
/**
* Converts an escaped String suitable for being passed in as a component of a single category
* path string into an non-escaped string.
* @param escapedString String that might need unescaping for characters used for delimiting
* @return non-escaped String
* @see #escapeString(String)
*/
public static String unescapeString(String escapedString) {
return escapedString.replace(ESCAPED_DELIMITER_STRING, DELIMITER_STRING);
}
/**
* Constructor for internal creation of ROOT.
*/
private CategoryPath() {
// parent can only be null for ROOT
parent = null;
name = "";
}
/**
* Construct a CategoryPath from a parent and a hierarchical array of strings where each
* string is the name of a category in the category path.
*
* @param parent the parent CategoryPath. Choose {@code ROOT} if needed.
* @param subPathElements the array of names of sub-categories of the parent.
* @throws IllegalArgumentException if the given array is null or empty.
*/
public CategoryPath(CategoryPath parent, String... subPathElements) {
this(parent, Arrays.asList(subPathElements));
}
/**
* Construct a CategoryPath from a parent and a hierarchical list of strings where each
* string is the name of a category in the category path.
*
* @param parent the parent CategoryPath. Choose {@code ROOT} if needed.
* @param subPathElements the hierarchical array of sub-categories of the parent.
* @throws IllegalArgumentException if the given list is null or empty.
*/
public CategoryPath(CategoryPath parent, List<String> subPathElements) {
Objects.requireNonNull(parent);
if (CollectionUtils.isEmpty(subPathElements)) {
throw new IllegalArgumentException(
"Category list must contain at least one string name!");
}
name = subPathElements.get(subPathElements.size() - 1);
if (subPathElements.size() == 1) {
this.parent = parent;
}
else {
this.parent =
new CategoryPath(parent, subPathElements.subList(0, subPathElements.size() - 1));
}
}
/**
* Creates a category path given a forward-slash-delimited string (e.g., {@code "/aa/bb"}).
* If an individual path component has one or more '/' characters in it, then it can be
* <I><B>escaped</B></I> using the {@link #escapeString(String)} utility method. The
* {@link #unescapeString(String)} method can be used to unescape an individual component.
* <P>
* <B>Refrain</B> from using this constructor in production code, and instead use one of the
* other constructors that does not require escaping. Situations where using this constructor
* is OK is in simple cases where a literal is passed in, such as in testing methods or in
* scripts.
* @param path category path string, delimited with '/' characters where individual components
* may have '/' characters escaped. Must start with the '/' character.
*/
// NOTE: We purposefully did not create a constructor that takes varags only, as that
// constructor, called with a single argument that would not be escaped, would conflict with
// this constructor, which requires an escaped argument.
public CategoryPath(String path) {
if (path == null || path.length() == 0 || path.equals(DELIMITER_STRING)) {
this.parentPath = this.name = "";
// parent can only be null for ROOT
parent = null;
name = "";
return;
}
else if (path.charAt(0) != DELIMITER_CHAR) {
throw new IllegalArgumentException("Paths must start with " + DELIMITER_STRING);
}
else if (path.charAt(path.length() - 1) == DELIMITER_CHAR) {
else if (endsWithNonEscapedDelimiter(path)) {
throw new IllegalArgumentException("Paths must not end with " + DELIMITER_STRING);
}
else if (path.indexOf(ILLEGAL_STRING) >= 0) {
throw new IllegalArgumentException("Paths must have non-empty elements");
}
else {
int index = path.lastIndexOf(DELIMITER_CHAR);
this.parentPath = path.substring(0, index);
this.name = path.substring(index + 1);
}
int delimiterIndex = findIndexOfLastNonEscapedDelimiter(path);
this.parent = new CategoryPath(path.substring(0, delimiterIndex));
this.name = unescapeString(path.substring(delimiterIndex + 1));
}
/**
* Create a category path given a parent category and name.
*
* @param parent parent category this path will reside in.
* @param name name of the category within the parent category.
*/
public CategoryPath(CategoryPath parent, String name) {
if (name == null || name.length() == 0 || name.indexOf(DELIMITER_CHAR) >= 0) {
throw new IllegalArgumentException("Bad name: " + name);
private boolean endsWithNonEscapedDelimiter(String string) {
return (string.charAt(string.length() - 1) == DELIMITER_CHAR &&
string.lastIndexOf(ESCAPED_DELIMITER_STRING) != string.length() -
ESCAPED_DELIMITER_STRING.length());
}
private int findIndexOfLastNonEscapedDelimiter(String string) {
int escapedIndex = string.length();
int delimiterIndex = escapedIndex;
while (delimiterIndex > 0) {
escapedIndex = string.lastIndexOf(ESCAPED_DELIMITER_STRING, escapedIndex - 1);
delimiterIndex = string.lastIndexOf(DELIMITER_CHAR, delimiterIndex - 1);
if (delimiterIndex != escapedIndex + DIFF) {
break;
}
}
this.parentPath = parent.isRoot() ? "" : parent.getPath();
this.name = name;
return delimiterIndex;
}
/**
@ -76,56 +168,92 @@ public class CategoryPath implements Comparable<CategoryPath> {
* @return true if this is a root category path
*/
public boolean isRoot() {
return parentPath.length() == 0 && name.length() == 0;
// parent can only be null for ROOT
return parent == null;
}
/**
* Return the name of this category path
* Return the parent category path.
* @return the parent
*/
public CategoryPath getParent() {
return parent;
}
/**
* Return the terminating name of this category path.
* @return the name
*/
public String getName() {
return name;
}
/**
* Return the full path to the category including the category name as a string.
* Return the {@link String} representation of this category path including the category name,
* where components are delimited with a forward slash. Any component that contains a forward
* slash will be have the forward slash characters escaped.
* @return the full category path
*/
public String getPath() {
return parentPath + DELIMITER_CHAR + name;
}
/**
* Return the parent category path.
*/
public CategoryPath getParent() {
if (parentPath.length() == 0 && name.length() == 0) {
return null;
if (isRoot()) {
return DELIMITER_STRING;
}
return new CategoryPath(parentPath);
if (parent.isRoot()) {
return DELIMITER_CHAR + escapeString(name);
}
return parent.getPath() + DELIMITER_CHAR + escapeString(name);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CategoryPath) {
CategoryPath cp = (CategoryPath) obj;
return cp.parentPath.equals(parentPath) && cp.name.equals(name);
if (this == obj) {
return true;
}
return false;
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CategoryPath other = (CategoryPath) obj;
if (name == null) {
if (other.name != null) {
return false;
}
}
else if (!name.equals(other.name)) {
return false;
}
if (parent == null) {
if (other.parent != null) {
return false;
}
}
else if (!parent.equals(other.parent)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return parentPath.hashCode() + name.hashCode();
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((parent == null) ? 0 : parent.hashCode());
return result;
}
/**
* Tests if the specified categoryPath is the same as, or an ancestor of, this category path.
* @param categoryPath the category path to be checked.
* @param candidateAncestorPath the category path to be checked.
* @return true if the given path is the same as, or an ancestor of, this category path.
*/
public boolean isAncestorOrSelf(CategoryPath categoryPath) {
public boolean isAncestorOrSelf(CategoryPath candidateAncestorPath) {
// Result categoryPath This
// Result categoryPath This
// ------ --------------------- ------------------------
// True / /
// True / /apple
// False /apple /
// True /apple /apple/sub
@ -133,43 +261,47 @@ public class CategoryPath implements Comparable<CategoryPath> {
// False /app /apple
// False /pear /apple
if (categoryPath.isRoot()) {
if (candidateAncestorPath.isRoot()) {
return true;
}
if (isRoot()) {
return false;
CategoryPath path = this;
while (!path.isRoot()) {
if (candidateAncestorPath.equals(path)) {
return true;
}
path = path.getParent();
}
String otherCategory = categoryPath.getPath();
String myCategory = getPath();
if (!myCategory.startsWith(otherCategory)) {
return false;
}
if (myCategory.length() == otherCategory.length()) {
// categoryPath is the same as this
return true;
}
return myCategory.charAt(otherCategory.length()) == DELIMITER_CHAR;
return false;
}
/**
* Returns array of names in category path.
* @return array of names
*/
public String[] getPathElements() {
StringTokenizer tokenizer = new StringTokenizer(getPath(), DELIMITER_STRING);
String[] tokens = new String[tokenizer.countTokens()];
for (int i = 0; i < tokens.length; i++) {
tokens[i] = tokenizer.nextToken();
}
return tokens;
return asArray();
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(CategoryPath otherPath) {
return getPath().compareTo(otherPath.getPath());
public int compareTo(CategoryPath other) {
if (isRoot() && other.isRoot()) {
return 0;
}
if (isRoot() || other.isRoot()) {
return isRoot() ? -1 : 1;
}
int result = parent.compareTo(other.getParent());
if (result == 0) {
result = name.compareTo(other.getName());
}
return result;
}
/* (non-Javadoc)
@ -180,4 +312,30 @@ public class CategoryPath implements Comparable<CategoryPath> {
return getPath();
}
/**
* Returns a hierarchical list of names of the categories in the category path, starting with
* the name just below the {@code ROOT} category.
*
* @return a hierarchical list of names of the category in the category path.
*/
public List<String> asList() {
if (isRoot()) {
return new ArrayList<>();
}
List<String> list = parent.asList();
list.add(name);
return list;
}
/**
* Returns a hierarchical array of names of the categories in the category path, starting with
* the name just below the {@code ROOT} category.
*
* @return a hierarchical array of names of the categories in the category path.
*/
public String[] asArray() {
List<String> list = asList();
return list.toArray(new String[list.size()]);
}
}

View File

@ -39,19 +39,20 @@ public class DataTypePath {
* @param dataTypeName the name of the datatype.
* @throws IllegalArgumentException if a null category path or dataTypeName is given.
*/
public DataTypePath(CategoryPath categoryPath, String datatypeName) {
if (datatypeName == null || categoryPath == null) {
public DataTypePath(CategoryPath categoryPath, String dataTypeName) {
if (dataTypeName == null || categoryPath == null) {
throw new IllegalArgumentException("null not allowed for categoryPath or datatypeName");
}
this.categoryPath = categoryPath;
this.dataTypeName = datatypeName;
this.dataTypeName = dataTypeName;
}
/**
* Returns the categoryPath for the datatype represented by this datatype path.
* (ie. the CategoryPath that contains the DataType that this DataTypePath points to).
*
* @return the parent {@link CategoryPath} of the {@link DataType} that this DataTypePath points to.
* @return the parent {@link CategoryPath} of the {@link DataType} that this DataTypePath
* points to.
*/
public CategoryPath getCategoryPath() {
return categoryPath;
@ -70,6 +71,7 @@ public class DataTypePath {
/**
* Returns the name of the datatype.
* @return the name
*/
public String getDataTypeName() {
return dataTypeName;
@ -79,6 +81,7 @@ public class DataTypePath {
* Returns the full path of this datatype. NOTE: if the datatype name contains any
* "/" characters, then the resulting path string may be ambiguous as to where the
* category path ends and the datatype name begins.
* @return the full path
*/
public String getPath() {
StringBuffer buf = new StringBuffer(categoryPath.getPath());

View File

@ -21,18 +21,17 @@
*/
package ghidra.program.model.data;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import generic.test.AbstractGenericTest;
/**
*
*
* To change the template for this generated type comment go to
* Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
* {@link CategoryPath} tests.
*/
public class CategoryPathTest extends AbstractGenericTest {
@ -41,109 +40,275 @@ public class CategoryPathTest extends AbstractGenericTest {
}
@Test
public void testConstructor() {
public void testEscapeStringEmpty() {
String orig = "";
String escaped = CategoryPath.escapeString(orig);
String unescaped = CategoryPath.unescapeString(escaped);
assertEquals(orig, unescaped);
assertEquals("", escaped);
}
@Test
public void testEscapeString1() {
String orig = "/";
String escaped = CategoryPath.escapeString(orig);
String unescaped = CategoryPath.unescapeString(escaped);
assertEquals(orig, unescaped);
assertEquals("\\/", escaped);
}
@Test
public void testEscapeString2() {
String orig = "//";
String escaped = CategoryPath.escapeString(orig);
String unescaped = CategoryPath.unescapeString(escaped);
assertEquals(orig, unescaped);
assertEquals("\\/\\/", escaped);
}
@Test
public void testConstructorRoot1() {
CategoryPath c = CategoryPath.ROOT;
assertEquals("/", c.getPath());
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
@Test
public void testConstructorRoot2() {
CategoryPath c = new CategoryPath(null);
Assert.assertEquals("/", c.getPath());
Assert.assertEquals("", c.getName());
assertEquals("/", c.getPath());
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
c = new CategoryPath("");
Assert.assertEquals("/", c.getPath());
Assert.assertEquals("", c.getName());
@Test
public void testConstructorRoot3() {
CategoryPath c = new CategoryPath("");
assertEquals("/", c.getPath());
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
c = new CategoryPath("/");
Assert.assertEquals("/", c.getPath());
Assert.assertEquals("", c.getName());
@Test
public void testConstructorRoot4() {
CategoryPath c = new CategoryPath("/");
assertEquals("/", c.getPath());
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
c = new CategoryPath("/apple");
Assert.assertEquals("/apple", c.getPath());
Assert.assertEquals("apple", c.getName());
@Test
public void testConstructorBasicString1() {
CategoryPath c = new CategoryPath("/apple");
assertEquals("/apple", c.getPath());
assertEquals("apple", c.getName());
}
c = new CategoryPath("/apple/pear");
Assert.assertEquals("/apple/pear", c.getPath());
Assert.assertEquals("pear", c.getName());
@Test
public void testConstructorBasicString2() {
CategoryPath c = new CategoryPath("/apple/pear");
assertEquals("/apple/pear", c.getPath());
assertEquals("pear", c.getName());
}
try {
c = new CategoryPath("//");
Assert.fail();
}
catch (IllegalArgumentException e) {
}
try {
c = new CategoryPath("apple");
Assert.fail();
}
catch (IllegalArgumentException e) {
}
try {
c = new CategoryPath("/apple/");
Assert.fail();
}
catch (IllegalArgumentException e) {
}
try {
c = new CategoryPath("/apple//bob");
Assert.fail();
}
catch (IllegalArgumentException e) {
}
@Test
public void testConstructorParentVarargsSingle() {
CategoryPath c = new CategoryPath("/apple/pear");
c = new CategoryPath(c, "mango");
assertEquals("/apple/pear/mango", c.getPath());
assertEquals("mango", c.getName());
}
@Test
public void testConstructorParentAndList() {
CategoryPath parent = new CategoryPath("/universe/earth");
List<String> list = new ArrayList<>();
list.add("boy");
list.add("bad");
CategoryPath c = new CategoryPath(parent, list);
assertEquals("/universe/earth/boy/bad", c.getPath());
assertEquals("bad", c.getName());
}
@Test
public void testConstructorParentAndVarargsArray() {
CategoryPath parent = new CategoryPath("/apple/peaches");
CategoryPath c = new CategoryPath(parent, new String[] { "pumpkin", "pie" });
assertEquals("pie", c.getName());
c = c.getParent();
assertEquals("pumpkin", c.getName());
c = c.getParent();
assertEquals("peaches", c.getName());
c = c.getParent();
assertEquals("apple", c.getName());
c = c.getParent();
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
@Test
public void testConstructorParentAndVarargs() {
CategoryPath parent = new CategoryPath("/apple/peaches");
CategoryPath c = new CategoryPath(parent, "pumpkin", "pie");
assertEquals("pie", c.getName());
c = c.getParent();
assertEquals("pumpkin", c.getName());
c = c.getParent();
assertEquals("peaches", c.getName());
c = c.getParent();
assertEquals("apple", c.getName());
c = c.getParent();
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
@Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_empty_path_element() {
public void testConstructorBadCtorParam_empty_path_element() {
new CategoryPath("//");
}
@Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_empty_path_element_2() {
public void testConstructorBadCtorParam_empty_path_element_2() {
new CategoryPath("/apple//bob");
}
@Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_missing_leading_slash() {
public void testConstructorBadCtorParam_missing_leading_slash() {
new CategoryPath("apple");
}
@Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_bad_trailing_slash() {
public void testConstructorBadCtorParam_bad_trailing_slash() {
new CategoryPath("/apple/");
}
@Test
public void testOtherConstructor() {
CategoryPath a = new CategoryPath("/aaa");
CategoryPath b = new CategoryPath(a, "bbb");
Assert.assertEquals("/aaa/bbb", b.getPath());
Assert.assertEquals("bbb", b.getName());
}
@Test
public void testGetParent() {
CategoryPath c = new CategoryPath(null);
CategoryPath c = CategoryPath.ROOT;
assertNull(c.getParent());
c = new CategoryPath("/aaa/bbb/ccc");
c = c.getParent();
Assert.assertEquals("/aaa/bbb", c.getPath());
assertEquals("/aaa/bbb", c.getPath());
}
@Test
public void testIsAncestor() {
Assert.assertTrue(CategoryPath.ROOT.isAncestorOrSelf(CategoryPath.ROOT));
public void testIsAncestorRootRoot() {
assertTrue(CategoryPath.ROOT.isAncestorOrSelf(CategoryPath.ROOT));
}
@Test
public void testIsAncestorRootApple() {
CategoryPath apple = new CategoryPath("/apple");
Assert.assertTrue(apple.isAncestorOrSelf(CategoryPath.ROOT));
Assert.assertFalse(CategoryPath.ROOT.isAncestorOrSelf(apple));
assertTrue(apple.isAncestorOrSelf(CategoryPath.ROOT));
assertFalse(CategoryPath.ROOT.isAncestorOrSelf(apple));
}
@Test
public void testIsAncestorAppleSubApple() {
CategoryPath apple = new CategoryPath("/apple");
CategoryPath applesub = new CategoryPath("/apple/sub");
Assert.assertTrue(applesub.isAncestorOrSelf(apple));
Assert.assertTrue(applesub.isAncestorOrSelf(applesub));
assertTrue(applesub.isAncestorOrSelf(apple));
assertTrue(applesub.isAncestorOrSelf(applesub));
}
@Test
public void testIsAncestorAppleSubNotApple() {
CategoryPath applesub = new CategoryPath("/apple/sub");
CategoryPath notapple = new CategoryPath("/notapple");
Assert.assertFalse(applesub.isAncestorOrSelf(notapple));
assertFalse(applesub.isAncestorOrSelf(notapple));
}
@Test
public void testIsAncestorAppleSubApp() {
CategoryPath applesub = new CategoryPath("/apple/sub");
CategoryPath app = new CategoryPath("/app");
Assert.assertFalse(applesub.isAncestorOrSelf(app));
assertFalse(applesub.isAncestorOrSelf(app));
}
@Test
public void testToArray() {
CategoryPath path = new CategoryPath("/aaa/bbb/bob");
String[] names = path.asArray();
assertEquals("aaa", names[0]);
assertEquals("bbb", names[1]);
assertEquals("bob", names[2]);
}
@Test
public void testToList() {
CategoryPath path = new CategoryPath("/aaa/bbb/bob");
List<String> names = path.asList();
assertEquals("aaa", names.get(0));
assertEquals("bbb", names.get(1));
assertEquals("bob", names.get(2));
}
@Test
public void testConstructorDelimeterEscape1() {
CategoryPath path = new CategoryPath("/aaa/bbb/\\/bob");
List<String> names = path.asList();
assertEquals("aaa", names.get(0));
assertEquals("bbb", names.get(1));
assertEquals("/bob", names.get(2));
assertEquals("/aaa/bbb/\\/bob", path.getPath());
}
@Test
public void testConstructorDelimeterEscape2() {
// Should not complain about terminating slash
CategoryPath path = new CategoryPath("/aaa/bbb/bob\\/");
List<String> names = path.asList();
assertEquals("aaa", names.get(0));
assertEquals("bbb", names.get(1));
assertEquals("bob/", names.get(2));
assertEquals("/aaa/bbb/bob\\/", path.getPath());
}
@Test
public void testConstructorDelimeterEscape3() {
CategoryPath path = new CategoryPath("/\\/aaa/bbb/bob");
List<String> names = path.asList();
assertEquals("/aaa", names.get(0));
assertEquals("bbb", names.get(1));
assertEquals("bob", names.get(2));
assertEquals("/\\/aaa/bbb/bob", path.getPath());
}
@Test
public void testConstructorDelimeterEscape4() {
CategoryPath path = new CategoryPath("/\\/\\/aaa/bbb/bob");
List<String> names = path.asList();
assertEquals("//aaa", names.get(0));
assertEquals("bbb", names.get(1));
assertEquals("bob", names.get(2));
assertEquals("/\\/\\/aaa/bbb/bob", path.getPath());
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings("unused")
public void testDelimeterEscapeAtRoot() {
CategoryPath path = new CategoryPath("\\//aaa/bbb/bob");
}
@Test
public void testConstructorParentVarargsNestedDelimiter1() {
CategoryPath c = new CategoryPath("/apple/pear");
// nested delimiter sequence should be ignored on constructor and getName(), but output on
// getPath().
c = new CategoryPath(c, "man/go");
assertEquals("/apple/pear/man\\/go", c.getPath());
assertEquals("man/go", c.getName());
}
@Test
public void testConstructorParentVarargsNestedEscape1() {
CategoryPath c = new CategoryPath("/apple/pear");
// nested escape sequence should be ignored on constructor and getName(), but output on
// getPath().
c = new CategoryPath(c, "man\\/go");
assertEquals("/apple/pear/man\\\\/go", c.getPath());
assertEquals("man\\/go", c.getName());
}
}