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.name = name;
this.parent = parent; this.parent = parent;
subcategoryMap = new LazyLoadingCachingMap<String, CategoryDB>(mgr.lock, CategoryDB.class) { subcategoryMap = new LazyLoadingCachingMap<>(mgr.lock, CategoryDB.class) {
@Override @Override
public Map<String, CategoryDB> loadMap() { public Map<String, CategoryDB> loadMap() {
return buildSubcategoryMap(); return buildSubcategoryMap();
} }
}; };
dataTypeMap = new LazyLoadingCachingMap<String, DataType>(mgr.lock, DataType.class) { dataTypeMap = new LazyLoadingCachingMap<>(mgr.lock, DataType.class) {
@Override @Override
public Map<String, DataType> loadMap() { public Map<String, DataType> loadMap() {
return createDataTypeMap(); return createDataTypeMap();
@ -273,9 +273,6 @@ class CategoryDB extends DatabaseObject implements Category {
if (categoryName == null || categoryName.length() == 0) { if (categoryName == null || categoryName.length() == 0) {
throw new InvalidNameException("Name cannot be null or zero length"); 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; return null;
} }
// try to deal with datatypes that have '/' chars in their name. // Use a category path to parse the datatype path because it knows how to deal with
Category category = getLowestLevelCategory(dataTypePath); // escaped forward slashes.
CategoryPath parsedPath = new CategoryPath(dataTypePath);
CategoryPath categoryPath = parsedPath.getParent();
String dataTypeName = parsedPath.getName();
Category category = getCategory(categoryPath);
if (category != null) { if (category == null) {
CategoryPath categoryPath = category.getCategoryPath(); return null;
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);
} }
return null; return category.getDataType(dataTypeName);
} }
@Override @Override
@ -1273,19 +1273,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
return getDataType(dataTypePath); 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 @Override
public void findEnumValueNames(long value, Set<String> enumValueNames) { public void findEnumValueNames(long value, Set<String> enumValueNames) {
buildEnumValueMap(); buildEnumValueMap();

View file

@ -23,12 +23,6 @@ import ghidra.util.task.TaskMonitor;
* Each data type resides in a given a category. * Each data type resides in a given a category.
*/ */
public interface Category extends Comparable<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. * Get the name of this category.
*/ */

View file

@ -15,7 +15,9 @@
*/ */
package ghidra.program.model.data; 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 * 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 char DELIMITER_CHAR = '/';
public static final String DELIMITER_STRING = "" + 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 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; private final String name;
/** /**
* Create a category path given a 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
* @param path category path string. * 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) { public CategoryPath(String path) {
if (path == null || path.length() == 0 || path.equals(DELIMITER_STRING)) { 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) { else if (path.charAt(0) != DELIMITER_CHAR) {
throw new IllegalArgumentException("Paths must start with " + DELIMITER_STRING); 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); throw new IllegalArgumentException("Paths must not end with " + DELIMITER_STRING);
} }
else if (path.indexOf(ILLEGAL_STRING) >= 0) { else if (path.indexOf(ILLEGAL_STRING) >= 0) {
throw new IllegalArgumentException("Paths must have non-empty elements"); throw new IllegalArgumentException("Paths must have non-empty elements");
} }
else {
int index = path.lastIndexOf(DELIMITER_CHAR); int delimiterIndex = findIndexOfLastNonEscapedDelimiter(path);
this.parentPath = path.substring(0, index); this.parent = new CategoryPath(path.substring(0, delimiterIndex));
this.name = path.substring(index + 1); this.name = unescapeString(path.substring(delimiterIndex + 1));
}
} }
/** private boolean endsWithNonEscapedDelimiter(String string) {
* Create a category path given a parent category and name. return (string.charAt(string.length() - 1) == DELIMITER_CHAR &&
* string.lastIndexOf(ESCAPED_DELIMITER_STRING) != string.length() -
* @param parent parent category this path will reside in. ESCAPED_DELIMITER_STRING.length());
* @param name name of the category within the parent category. }
*/
public CategoryPath(CategoryPath parent, String name) { private int findIndexOfLastNonEscapedDelimiter(String string) {
if (name == null || name.length() == 0 || name.indexOf(DELIMITER_CHAR) >= 0) { int escapedIndex = string.length();
throw new IllegalArgumentException("Bad name: " + name); 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(); return delimiterIndex;
this.name = name;
} }
/** /**
@ -76,56 +168,92 @@ public class CategoryPath implements Comparable<CategoryPath> {
* @return true if this is a root category path * @return true if this is a root category path
*/ */
public boolean isRoot() { 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() { public String getName() {
return name; 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() { public String getPath() {
return parentPath + DELIMITER_CHAR + name; if (isRoot()) {
} return DELIMITER_STRING;
/**
* Return the parent category path.
*/
public CategoryPath getParent() {
if (parentPath.length() == 0 && name.length() == 0) {
return null;
} }
return new CategoryPath(parentPath); if (parent.isRoot()) {
return DELIMITER_CHAR + escapeString(name);
}
return parent.getPath() + DELIMITER_CHAR + escapeString(name);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof CategoryPath) { if (this == obj) {
CategoryPath cp = (CategoryPath) obj; return true;
return cp.parentPath.equals(parentPath) && cp.name.equals(name);
} }
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 @Override
public int hashCode() { 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. * 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. * @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 // True / /apple
// False /apple / // False /apple /
// True /apple /apple/sub // True /apple /apple/sub
@ -133,43 +261,47 @@ public class CategoryPath implements Comparable<CategoryPath> {
// False /app /apple // False /app /apple
// False /pear /apple // False /pear /apple
if (categoryPath.isRoot()) { if (candidateAncestorPath.isRoot()) {
return true; return true;
} }
if (isRoot()) { CategoryPath path = this;
return false; while (!path.isRoot()) {
if (candidateAncestorPath.equals(path)) {
return true;
}
path = path.getParent();
} }
return false;
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;
} }
/**
* Returns array of names in category path.
* @return array of names
*/
public String[] getPathElements() { public String[] getPathElements() {
StringTokenizer tokenizer = new StringTokenizer(getPath(), DELIMITER_STRING); return asArray();
String[] tokens = new String[tokenizer.countTokens()];
for (int i = 0; i < tokens.length; i++) {
tokens[i] = tokenizer.nextToken();
}
return tokens;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object) * @see java.lang.Comparable#compareTo(java.lang.Object)
*/ */
@Override @Override
public int compareTo(CategoryPath otherPath) { public int compareTo(CategoryPath other) {
return getPath().compareTo(otherPath.getPath()); 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) /* (non-Javadoc)
@ -180,4 +312,30 @@ public class CategoryPath implements Comparable<CategoryPath> {
return getPath(); 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. * @param dataTypeName the name of the datatype.
* @throws IllegalArgumentException if a null category path or dataTypeName is given. * @throws IllegalArgumentException if a null category path or dataTypeName is given.
*/ */
public DataTypePath(CategoryPath categoryPath, String datatypeName) { public DataTypePath(CategoryPath categoryPath, String dataTypeName) {
if (datatypeName == null || categoryPath == null) { if (dataTypeName == null || categoryPath == null) {
throw new IllegalArgumentException("null not allowed for categoryPath or datatypeName"); throw new IllegalArgumentException("null not allowed for categoryPath or datatypeName");
} }
this.categoryPath = categoryPath; this.categoryPath = categoryPath;
this.dataTypeName = datatypeName; this.dataTypeName = dataTypeName;
} }
/** /**
* Returns the categoryPath for the datatype represented by this datatype path. * Returns the categoryPath for the datatype represented by this datatype path.
* (ie. the CategoryPath that contains the DataType that this DataTypePath points to). * (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() { public CategoryPath getCategoryPath() {
return categoryPath; return categoryPath;
@ -70,6 +71,7 @@ public class DataTypePath {
/** /**
* Returns the name of the datatype. * Returns the name of the datatype.
* @return the name
*/ */
public String getDataTypeName() { public String getDataTypeName() {
return dataTypeName; return dataTypeName;
@ -79,6 +81,7 @@ public class DataTypePath {
* Returns the full path of this datatype. NOTE: if the datatype name contains any * 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 * "/" characters, then the resulting path string may be ambiguous as to where the
* category path ends and the datatype name begins. * category path ends and the datatype name begins.
* @return the full path
*/ */
public String getPath() { public String getPath() {
StringBuffer buf = new StringBuffer(categoryPath.getPath()); StringBuffer buf = new StringBuffer(categoryPath.getPath());

View file

@ -21,18 +21,17 @@
*/ */
package ghidra.program.model.data; 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 org.junit.Test;
import generic.test.AbstractGenericTest; import generic.test.AbstractGenericTest;
/** /**
* * {@link CategoryPath} tests.
*
* To change the template for this generated type comment go to
* Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
*/ */
public class CategoryPathTest extends AbstractGenericTest { public class CategoryPathTest extends AbstractGenericTest {
@ -41,109 +40,275 @@ public class CategoryPathTest extends AbstractGenericTest {
} }
@Test @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); CategoryPath c = new CategoryPath(null);
Assert.assertEquals("/", c.getPath()); assertEquals("/", c.getPath());
Assert.assertEquals("", c.getName()); assertEquals("", c.getName());
assertTrue(c.isRoot());
}
c = new CategoryPath(""); @Test
Assert.assertEquals("/", c.getPath()); public void testConstructorRoot3() {
Assert.assertEquals("", c.getName()); CategoryPath c = new CategoryPath("");
assertEquals("/", c.getPath());
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
c = new CategoryPath("/"); @Test
Assert.assertEquals("/", c.getPath()); public void testConstructorRoot4() {
Assert.assertEquals("", c.getName()); CategoryPath c = new CategoryPath("/");
assertEquals("/", c.getPath());
assertEquals("", c.getName());
assertTrue(c.isRoot());
}
c = new CategoryPath("/apple"); @Test
Assert.assertEquals("/apple", c.getPath()); public void testConstructorBasicString1() {
Assert.assertEquals("apple", c.getName()); CategoryPath c = new CategoryPath("/apple");
assertEquals("/apple", c.getPath());
assertEquals("apple", c.getName());
}
c = new CategoryPath("/apple/pear"); @Test
Assert.assertEquals("/apple/pear", c.getPath()); public void testConstructorBasicString2() {
Assert.assertEquals("pear", c.getName()); CategoryPath c = new CategoryPath("/apple/pear");
assertEquals("/apple/pear", c.getPath());
assertEquals("pear", c.getName());
}
try { @Test
c = new CategoryPath("//"); public void testConstructorParentVarargsSingle() {
Assert.fail(); CategoryPath c = new CategoryPath("/apple/pear");
} c = new CategoryPath(c, "mango");
catch (IllegalArgumentException e) { assertEquals("/apple/pear/mango", c.getPath());
} assertEquals("mango", c.getName());
try { }
c = new CategoryPath("apple");
Assert.fail(); @Test
} public void testConstructorParentAndList() {
catch (IllegalArgumentException e) { CategoryPath parent = new CategoryPath("/universe/earth");
} List<String> list = new ArrayList<>();
try { list.add("boy");
c = new CategoryPath("/apple/"); list.add("bad");
Assert.fail(); CategoryPath c = new CategoryPath(parent, list);
} assertEquals("/universe/earth/boy/bad", c.getPath());
catch (IllegalArgumentException e) { assertEquals("bad", c.getName());
} }
try {
c = new CategoryPath("/apple//bob"); @Test
Assert.fail(); public void testConstructorParentAndVarargsArray() {
} CategoryPath parent = new CategoryPath("/apple/peaches");
catch (IllegalArgumentException e) { 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) @Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_empty_path_element() { public void testConstructorBadCtorParam_empty_path_element() {
new CategoryPath("//"); new CategoryPath("//");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_empty_path_element_2() { public void testConstructorBadCtorParam_empty_path_element_2() {
new CategoryPath("/apple//bob"); new CategoryPath("/apple//bob");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_missing_leading_slash() { public void testConstructorBadCtorParam_missing_leading_slash() {
new CategoryPath("apple"); new CategoryPath("apple");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testBadCtorParam_bad_trailing_slash() { public void testConstructorBadCtorParam_bad_trailing_slash() {
new CategoryPath("/apple/"); 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 @Test
public void testGetParent() { public void testGetParent() {
CategoryPath c = new CategoryPath(null); CategoryPath c = CategoryPath.ROOT;
assertNull(c.getParent()); assertNull(c.getParent());
c = new CategoryPath("/aaa/bbb/ccc"); c = new CategoryPath("/aaa/bbb/ccc");
c = c.getParent(); c = c.getParent();
Assert.assertEquals("/aaa/bbb", c.getPath()); assertEquals("/aaa/bbb", c.getPath());
} }
@Test @Test
public void testIsAncestor() { public void testIsAncestorRootRoot() {
assertTrue(CategoryPath.ROOT.isAncestorOrSelf(CategoryPath.ROOT));
Assert.assertTrue(CategoryPath.ROOT.isAncestorOrSelf(CategoryPath.ROOT)); }
@Test
public void testIsAncestorRootApple() {
CategoryPath apple = new CategoryPath("/apple"); CategoryPath apple = new CategoryPath("/apple");
Assert.assertTrue(apple.isAncestorOrSelf(CategoryPath.ROOT)); assertTrue(apple.isAncestorOrSelf(CategoryPath.ROOT));
Assert.assertFalse(CategoryPath.ROOT.isAncestorOrSelf(apple)); assertFalse(CategoryPath.ROOT.isAncestorOrSelf(apple));
}
@Test
public void testIsAncestorAppleSubApple() {
CategoryPath apple = new CategoryPath("/apple");
CategoryPath applesub = new CategoryPath("/apple/sub"); CategoryPath applesub = new CategoryPath("/apple/sub");
Assert.assertTrue(applesub.isAncestorOrSelf(apple)); assertTrue(applesub.isAncestorOrSelf(apple));
Assert.assertTrue(applesub.isAncestorOrSelf(applesub)); assertTrue(applesub.isAncestorOrSelf(applesub));
}
@Test
public void testIsAncestorAppleSubNotApple() {
CategoryPath applesub = new CategoryPath("/apple/sub");
CategoryPath notapple = new CategoryPath("/notapple"); 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"); 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());
} }
} }