Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions core/src/main/java/org/kohsuke/stapler/export/NamedPathPruner.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,44 @@ private NamedPathPruner(Tree tree) {
public @Override Range getRange() {
return tree.range;
}

/**
* Parses the given specification and returns a hierarchical tree representation.
* <p>
* Each node is represented as a key-value pair in the returned {@code TreeMap}.
* The value for a key:
* <ul>
* <li>Is another {@code TreeMap<String, Object>} for non-leaf nodes.</li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires the caller to cast, and even worse to use unchecked conversions. I think it would be better to expose Tree or some interface it implements.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimally

interface Something extends Map<String, Something> {}

might work

* <li>Is an empty {@code TreeMap<String, Object>} for leaf nodes.</li>
* </ul>
*
* @param spec the textual specification
* @return a hierarchical tree represented as {@code TreeMap<String, Object>}.
*/
public static TreeMap<String, Object> parseAsTreeMap(String spec) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this cannot just be typed as a Map?

Tree root = parse(spec);
return toTreeMapRepresentation(root);
}

/**
* Recursively converts the internal {@code Tree} structure into a {@code TreeMap<String, Object>}.
* <p>
* Leaf nodes are represented by an empty {@code TreeMap} (e.g., {@code new TreeMap<String, Object>()}).
*
* @param tree the internal {@code Tree} structure to convert
* @return a {@code TreeMap<String, Object>} representing the tree
*/
private static TreeMap<String, Object> toTreeMapRepresentation(Tree tree) {
TreeMap<String, Object> result = new TreeMap<>();

for (Map.Entry<String, Tree> entry : tree.children.entrySet()) {
String key = entry.getKey();
Tree childTree = entry.getValue();
TreeMap<String, ?> subtree =
childTree.children.isEmpty() ? new TreeMap<>() : toTreeMapRepresentation(childTree);
result.put(key, subtree);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
import java.util.function.Function;
import junit.framework.TestCase;

public class NamedPathPrunerTest extends TestCase {
Expand All @@ -23,21 +25,26 @@ public void testParse() {
assertEquals(
"{a={}, b={c={}, d={}}, e={}}",
NamedPathPruner.parse("a,b[c,d]{,10},e").toString());
assertParseError("");
assertParseError("a,");
assertParseError(",b");
assertParseError("a[");
assertParseError("a[b,c");
assertParseError("a[]");
assertParseError("a[b,,]");
assertParseError("a]");
assertParseError("a{}");
assertParseError("a{b}");

checkParseFailureCases(NamedPathPruner::parse);
}

private void checkParseFailureCases(Function<String, ?> parseFunction) {
assertParseError(parseFunction, "");
assertParseError(parseFunction, "a,");
assertParseError(parseFunction, ",b");
assertParseError(parseFunction, "a[");
assertParseError(parseFunction, "a[b,c");
assertParseError(parseFunction, "a[]");
assertParseError(parseFunction, "a[b,,]");
assertParseError(parseFunction, "a]");
assertParseError(parseFunction, "a{}");
assertParseError(parseFunction, "a{b}");
}

private static void assertParseError(String spec) {
private static void assertParseError(Function<String, ?> parseFunction, String spec) {
try {
NamedPathPruner.parse(spec);
parseFunction.apply(spec);
fail();
} catch (IllegalArgumentException x) {
// pass
Expand Down Expand Up @@ -132,4 +139,23 @@ private static void assertResult(String expected, Object bean, String spec) thro
model.writeTo(bean, new NamedPathPruner(spec), Flavor.JSON.createDataWriter(bean, w, config));
assertEquals(expected, w.toString().replace("\\\"", "").replace("\"", ""));
}

/**
* Tests the {@link NamedPathPruner#parseAsTreeMap} method.
* Although the success test cases are same as {@link #testParse}, here using strongly typed return type {@code TreeMap<String, Object>}
* to ensure the return type is correct.
*/
public void testParseAsTreeMap() {
TreeMap<String, Object> result;
result = NamedPathPruner.parseAsTreeMap("a,b[c]");
assertEquals("{a={}, b={c={}}}", result.toString());
result = NamedPathPruner.parseAsTreeMap("a,b[c,d]");
assertEquals("{a={}, b={c={}, d={}}}", result.toString());
result = NamedPathPruner.parseAsTreeMap("a,b[c,d],e");
assertEquals("{a={}, b={c={}, d={}}, e={}}", result.toString());
result = NamedPathPruner.parseAsTreeMap("a,b[c,d]{,10},e");
assertEquals("{a={}, b={c={}, d={}}, e={}}", result.toString());

checkParseFailureCases(NamedPathPruner::parseAsTreeMap);
}
}