Skip to content

Commit

Permalink
Path based access to datanode children (#343)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsiq-karold committed May 28, 2019
1 parent de0f83f commit 1d66e2f
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ class GroovyDataNode implements DataNodeExpectations, DataNode {
}

@Override
DataNode get(String name) {
return new GroovyDataNode(node.get(name))
DataNode get(String pathOrName) {
return new GroovyDataNode(node.get(pathOrName))
}

@Override
boolean has(String name) {
return node.has(name)
boolean has(String pathOrName) {
return node.has(pathOrName)
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
public interface DataNode extends DataNodeExpectations, Iterable<DataNode> {
DataNodeId id();

DataNode get(String name);
DataNode get(String pathOrName);

boolean has(String name);
boolean has(String pathOrName);

DataNode get(int idx);

Expand All @@ -50,6 +50,10 @@ public interface DataNode extends DataNodeExpectations, Iterable<DataNode> {

Map<String, DataNode> asMap();

default boolean isNull() {
return false;
}

default boolean isBinary() {
return getTraceableValue() != null &&
getTraceableValue().getValue() != null &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public DataNodeId id() {
}

@Override
public DataNode get(String name) {
return new NullDataNode(id.child(name));
public DataNode get(String pathOrName) {
return new NullDataNode(id.child(pathOrName));
}

@Override
public boolean has(String name) {
public boolean has(String pathOrName) {
return false;
}

Expand Down Expand Up @@ -95,6 +95,11 @@ public Map<String, DataNode> asMap() {
return Collections.emptyMap();
}

@Override
public boolean isNull() {
return true;
}

@Override
public String toString() {
return "[null node]@" + id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,66 @@ public DataNodeId id() {
}

@Override
public DataNode get(String name) {
public DataNode get(String nameOrPath) {
if (isList()) {
return getAsCollectFromList(name);
return getAsCollectFromList(nameOrPath);
}

int dotIdx = nameOrPath.indexOf('.');
if (dotIdx == -1) {
// simple name
return getChild(nameOrPath);
}

String rootName = nameOrPath.substring(0, dotIdx);
DataNode root = getChild(rootName);
String pathUnderRoot = nameOrPath.substring(dotIdx + 1);

return root.get(pathUnderRoot);
}

private DataNode getChild(String name) {
int openBraceIdx = name.indexOf('[');
int closeBraceIdx = name.indexOf(']');
if (openBraceIdx != -1 && closeBraceIdx != -1) {
return getIndexedChild(name, openBraceIdx, closeBraceIdx);
} else if (openBraceIdx != -1 || closeBraceIdx != -1) {
throw new IllegalArgumentException("Requested name " + name + " is not a simple name nor does it contain a properly formatted index");
}

// simple name
return (children != null && children.containsKey(name)) ?
children.get(name):
children.get(name) :
new NullDataNode(id.child(name));
}

private DataNode getIndexedChild(String name, int openBraceIdx, int closeBraceIdx) {
int additionalOpenIdx = name.indexOf('[', openBraceIdx + 1);
int additionalCloseId = name.indexOf(']', closeBraceIdx + 1);

if (additionalOpenIdx != -1 || additionalCloseId != -1 || openBraceIdx > closeBraceIdx) {
throw new IllegalArgumentException("Requested name " + name + " contains mismatched indexing brackets");
}

String indexStr = name.substring(openBraceIdx + 1, closeBraceIdx);
try {
int idx = Integer.valueOf(indexStr);
String nameWithoutIndex = name.substring(0, openBraceIdx);
DataNode node = get(nameWithoutIndex);

if (idx < 0) {
idx = node.numberOfElements() + idx;
}

return node.get(idx);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Requested index " + indexStr + " of name " + name.substring(0, openBraceIdx) + " is not an integer");
}
}

@Override
public boolean has(String name) {
return children.containsKey(name);
public boolean has(String pathOrName) {
return !get(pathOrName).isNull();
}

@Override
Expand Down Expand Up @@ -152,14 +199,14 @@ public String toString() {
return "{" + children.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).collect(joining(", ")) + "}";
}

private DataNode getAsCollectFromList(String name) {
if (values.stream().noneMatch(v -> v.has(name))) {
return new NullDataNode(id.child(name));
private DataNode getAsCollectFromList(String nameOrPath) {
if (values.stream().noneMatch(v -> v.has(nameOrPath))) {
return new NullDataNode(id.child(nameOrPath));
}

return new StructuredDataNode(id.child(name),
return new StructuredDataNode(id.child(nameOrPath),
values.stream()
.map(n -> n.get(name))
.map(n -> n.get(nameOrPath))
.collect(Collectors.toList()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,79 @@ class StructuredDataNodeTest {
List<Map<String, Object>> list = node.get('key1').get()
list.should == [[name: 'name1'], [name: 'name2']]
}

@Test
void "should access underlying value when provided path"() {
def node = DataNodeBuilder.fromMap(new DataNodeId("body"), [
key1: [name: 'name1'],
key2: [name: 'name2'],
])

String name = node.get('key1.name').get()
name.should == 'name1'
}

@Test
void "should access array element via path"() {
def node = DataNodeBuilder.fromMap(new DataNodeId("body"), [
key1: [[name: 'name1'], [name: 'name2']],
root: [child: [[foo: 'boo'], [foo: 'bar']]]
])

Map<String, String> firstElement = node.get('key1[0]').get()
firstElement.should == [name: 'name1']

String firstName = node.get('key1[0].name').get()
firstName.should == 'name1'

String lastName = node.get('key1[-1].name').get()
lastName.should == 'name2'

String nested = node.get('root.child[1].foo')
nested.should == 'bar'
}

@Test
void "should collect over list with path get"() {
def node = DataNodeBuilder.fromMap(new DataNodeId("body"), [
key1: [[name: 'name1'], [name: 'name2']]
])

List<String> names = node.get("key1.name").get()
names.should == ['name1', 'name2']
}

@Test
void "should collect over list with path"() {
def node = DataNodeBuilder.fromList(new DataNodeId("body"), [
[key: [name: 'name1']],
[key: [name: 'name2']]
])

List<String> names = node.get("key.name").get()
names.should == ['name1', 'name2']
}

@Test
void "should check full path in has"() {
def node = DataNodeBuilder.fromMap(new DataNodeId("body"), [
key1: 'name1',
key2: [name: 'name2']
])

boolean has = node.has('key1')
has.should == true

has = node.has('key2')
has.should == true

has = node.has('key2.name')
has.should == true

has = node.has('key2.foo')
has.should == false

has = node.has('key2.name.foo')
has.should == false
}
}

0 comments on commit 1d66e2f

Please sign in to comment.