This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch v3
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/v3 by this push:
new 2769770974b CAUSEWAY-2297: work on simplified tree model (part 16)
2769770974b is described below
commit 2769770974baadf99f3adadeacae909e93b4d202
Author: andi-huber <[email protected]>
AuthorDate: Sun Dec 15 14:51:34 2024 +0100
CAUSEWAY-2297: work on simplified tree model (part 16)
- test fixes; tree traversal works now
- TODO: support associations of type Map as child nodes
- TODO: support properties as child nodes
---
.../causeway/applib/graph/tree/TreeNode.java | 20 ++++++
.../graph/tree/TreeNode_iteratorBreadthFirst.java | 12 ++--
.../graph/tree/TreeNode_iteratorDepthFirst.java | 19 +++---
.../facets/object/navchild/ObjectTreeAdapter.java | 8 +--
.../metamodel/spec/impl/ObjectMemberContainer.java | 48 +++++++-------
.../facets/object/navchild/TreeTraversalTest.java | 76 ++++++++++++++++------
6 files changed, 117 insertions(+), 66 deletions(-)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode.java
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode.java
index b7fac25280f..8217d168bc3 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode.java
@@ -101,6 +101,26 @@ implements Vertex<T> {
return new TreeNode<T>(null, TreePath.root(), rootValue, treeAdapter,
sharedState);
}
+ // -- CONTRACT
+
+ @Override
+ public final String toString() {
+ return "TreeNode[%s, value=%s]".formatted(treePath, value);
+ }
+
+ @Override
+ public final boolean equals(final Object obj) {
+ return obj instanceof TreeNode other
+ ? Objects.equals(this.treePath, other.treePath)
+ && Objects.equals(this.value, other.value)
+ : false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(treePath);
+ }
+
// --
public TreeNode<T> rootNode() {
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorBreadthFirst.java
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorBreadthFirst.java
index ff8aae2a3d2..77ae756913a 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorBreadthFirst.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorBreadthFirst.java
@@ -28,8 +28,8 @@ class TreeNode_iteratorBreadthFirst<T> implements
Iterator<TreeNode<T>> {
private Deque<TreeNode<T>> deque = new ArrayDeque<>();
private TreeNode<T> next;
- TreeNode_iteratorBreadthFirst(TreeNode<T> treeNode) {
- next = treeNode;
+ TreeNode_iteratorBreadthFirst(final TreeNode<T> treeNode) {
+ this.next = treeNode;
}
@Override
@@ -42,17 +42,17 @@ class TreeNode_iteratorBreadthFirst<T> implements
Iterator<TreeNode<T>> {
if(next==null) {
throw new NoSuchElementException("Iterator has run out of
elements.");
}
- final TreeNode<T> result = next;
- next = fetchNext(next);
+ var result = next;
+ this.next = fetchNext(next);
return result;
}
// -- HELPER
- private TreeNode<T> fetchNext(TreeNode<T> current) {
+ private TreeNode<T> fetchNext(final TreeNode<T> current) {
if(!current.isLeaf()) {
current.streamChildren()
- .forEach(deque::offerLast);
+ .forEach(deque::offerLast);
}
return deque.pollFirst();
}
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorDepthFirst.java
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorDepthFirst.java
index f66768d0db6..ac2a269968d 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorDepthFirst.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeNode_iteratorDepthFirst.java
@@ -24,11 +24,11 @@ import java.util.Stack;
class TreeNode_iteratorDepthFirst<T> implements Iterator<TreeNode<T>> {
- private Stack<TreeNode<T>> stack = new Stack<>();
+ private final Stack<TreeNode<T>> stack = new Stack<>();
private TreeNode<T> next;
- TreeNode_iteratorDepthFirst(TreeNode<T> treeNode) {
- next = treeNode;
+ TreeNode_iteratorDepthFirst(final TreeNode<T> treeNode) {
+ this.next = treeNode;
}
@Override
@@ -41,26 +41,25 @@ class TreeNode_iteratorDepthFirst<T> implements
Iterator<TreeNode<T>> {
if(next==null) {
throw new NoSuchElementException("Iterator has run out of
elements.");
}
- final TreeNode<T> result = next;
- next = fetchNext(next);
+ var result = next;
+ this.next = fetchNext(next);
return result;
}
// -- HELPER
- private TreeNode<T> fetchNext(TreeNode<T> current) {
+ private TreeNode<T> fetchNext(final TreeNode<T> current) {
if(!current.isLeaf()) {
pushChildrenToStackInReverseOrder(current);
}
return stack.isEmpty() ? null : stack.pop();
}
- private Stack<TreeNode<T>> fifo = new Stack<>(); // declared as field only
to reduce heap pollution
-
- private void pushChildrenToStackInReverseOrder(TreeNode<T> node) {
+ private final Stack<TreeNode<T>> fifo = new Stack<>(); // declared as
field only to reduce heap pollution
+ private void pushChildrenToStackInReverseOrder(final TreeNode<T> node) {
node.streamChildren()
- .forEach(fifo::push);
+ .forEach(fifo::push);
while(!fifo.isEmpty()) {
stack.push(fifo.pop());
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/navchild/ObjectTreeAdapter.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/navchild/ObjectTreeAdapter.java
index 130abfd9411..2563175d797 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/navchild/ObjectTreeAdapter.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/navchild/ObjectTreeAdapter.java
@@ -22,11 +22,10 @@ import java.util.Optional;
import java.util.stream.Stream;
import org.apache.causeway.applib.graph.tree.TreeAdapter;
-import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
-public record ObjectTreeAdapter(SpecificationLoader specLoader)
-implements
+public record ObjectTreeAdapter(SpecificationLoader specLoader)
+implements
TreeAdapter<Object> {
@Override
@@ -46,8 +45,7 @@ implements
private <T> Optional<NavigableSubtreeFacet> treeNodeFacet(final T node) {
return specLoader().loadSpecification(node.getClass())
- .lookupFacet(NavigableSubtreeFacet.class)
-
.map(treeNodeFacet->_Casts.<NavigableSubtreeFacet>uncheckedCast(treeNodeFacet));
+ .lookupFacet(NavigableSubtreeFacet.class);
}
}
\ No newline at end of file
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectMemberContainer.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectMemberContainer.java
index 236f4decb32..e6420489902 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectMemberContainer.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectMemberContainer.java
@@ -82,24 +82,24 @@ extends
return actionStream
- // as of contributing super-classes same actions might appear more
than once (overriding)
- .filter(action->{
- if(action.isMixedIn()) {
- return true; // do not filter mixedIn actions based on
signature
- }
- var isUnique = actionSignatures
-
.add(action.getFeatureIdentifier().getMemberNameAndParameterClassNamesIdentityString());
- return isUnique;
- })
-
- // ensure we don't emit duplicates
- .filter(action->{
- var isUnique = actionIds.add(action.getId());
- if(!isUnique) {
- onActionOverloaded.accept(action);
- }
- return isUnique;
- });
+ // as of contributing super-classes same actions might appear more
than once (overriding)
+ .filter(action->{
+ if(action.isMixedIn()) {
+ return true; // do not filter mixedIn actions based on
signature
+ }
+ var isUnique = actionSignatures
+
.add(action.getFeatureIdentifier().getMemberNameAndParameterClassNamesIdentityString());
+ return isUnique;
+ })
+
+ // ensure we don't emit duplicates
+ .filter(action->{
+ var isUnique = actionIds.add(action.getId());
+ if(!isUnique) {
+ onActionOverloaded.accept(action);
+ }
+ return isUnique;
+ });
}
// -- ASSOCIATIONS
@@ -109,9 +109,7 @@ extends
var declaredAssociation = getDeclaredAssociation(id, mixedIn); // no
inheritance considered
- if(declaredAssociation.isPresent()) {
- return declaredAssociation;
- }
+ if(declaredAssociation.isPresent()) return declaredAssociation;
return isTypeHierarchyRoot()
? Optional.empty() // stop searching
@@ -121,17 +119,15 @@ extends
@Override
default Stream<ObjectAssociation> streamAssociations(final MixedIn
mixedIn) {
- if(isTypeHierarchyRoot()) {
- return streamDeclaredAssociations(mixedIn); // stop going deeper
- }
+ if(isTypeHierarchyRoot()) return streamDeclaredAssociations(mixedIn);
// stop going deeper
var ids = _Sets.<String>newHashSet();
return Stream.concat(
streamDeclaredAssociations(mixedIn),
superclass().streamAssociations(mixedIn)
- )
- .filter(association->ids.add(association.getId())); // ensure we don't
emit duplicates
+ )
+ .filter(association->ids.add(association.getId())); // ensure we
don't emit duplicates
}
}
diff --git
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/navchild/TreeTraversalTest.java
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/navchild/TreeTraversalTest.java
index e7c4e7df2d5..e3d01e2dfad 100644
---
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/navchild/TreeTraversalTest.java
+++
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/facets/object/navchild/TreeTraversalTest.java
@@ -24,18 +24,25 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.causeway.applib.graph.tree.TreeNode;
+import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.core.metamodel._testing.MetaModelContext_forTesting;
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.facets.FacetFactoryTestAbstract;
+import org.apache.causeway.core.metamodel.facets.object.navchild._TreeSample.A;
+import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
class TreeTraversalTest
extends FacetFactoryTestAbstract {
- MetaModelContext mmc;
- ObjectTreeAdapter treeAdapter;
+ private MetaModelContext mmc;
+ private ObjectTreeAdapter treeAdapter;
+ private A a;
+ private TreeNode<Object> tree;
@BeforeEach
void setUp() {
@@ -43,12 +50,25 @@ extends FacetFactoryTestAbstract {
.enablePostprocessors(true)
.build();
treeAdapter = new ObjectTreeAdapter(mmc.getSpecificationLoader());
+ // sample tree, that we then traverse
+ a = _TreeSample.sampleA();
+ tree = TreeNode.root(a, treeAdapter);
+
+ //TODO[causeway-core-metamodel-CAUSEWAY-2297]
ObjectSpecification#streamAssociations seems to have the side effect
+ // of initializing the various members with the
NavigableSubtreeSequenceFacet,
+ // which otherwise does not happen (is this test specific? if so then
good, but we should fix that)
+ var specs = Can.of(_TreeSample.A.class, _TreeSample.B.class,
_TreeSample.C.class, _TreeSample.D.class)
+ .map(mmc.getSpecificationLoader()::specForType)
+ .map(opt->opt.orElse(null));
+
specs.forEach(spec->spec.streamAssociations(MixedIn.EXCLUDED).forEach(assoc->{}));
}
@Test
void preconditions() {
var specLoader = mmc.getSpecificationLoader();
var specA = specLoader.loadSpecification(_TreeSample.A.class);
+ // including this one to test presence of NavigableSubtreeFacet w/o
the side-effect of calling the getAssociation method
+ var specB = specLoader.loadSpecification(_TreeSample.B.class);
// first: members must have the NavigableSubtreeSequenceFacet
var assocAB = specA.getAssociationElseFail("childrenB");
@@ -57,38 +77,56 @@ extends FacetFactoryTestAbstract {
// second: post-processor should generate NavigableSubtreeFacet
assertTrue(specA.containsFacet(NavigableSubtreeFacet.class));
+ assertTrue(specB.containsFacet(NavigableSubtreeFacet.class));
+
+ // tree sanity checks
+ assertEquals(a, tree.value());
+ assertNotNull(tree.rootNode());
+ assertEquals(tree, tree.rootNode());
+ assertTrue(tree.isRoot());
+ assertFalse(tree.isLeaf());
+
+ // node a is expected to have 2 children
+ var navigableSubtreeFacet =
specA.getFacet(NavigableSubtreeFacet.class);
+ assertEquals(2, navigableSubtreeFacet.childCountOf(a));
+ assertEquals(2, navigableSubtreeFacet.childrenOf(a).toList().size());
+ assertEquals(2, treeAdapter.childCountOf(a));
+ assertEquals(2, treeAdapter.childrenOf(a).toList().size());
+ assertEquals(2, tree.childCount());
+ assertEquals(2, tree.streamChildren().toList().size());
+
+ var firstChildOfA =
treeAdapter.childrenOf(a).findFirst().orElseThrow();
+
+ // first child node of a is expected to have 3 children
+ assertEquals(3, treeAdapter.childCountOf(firstChildOfA));
+ assertEquals(3, treeAdapter.childrenOf(firstChildOfA).toList().size());
+
+ //TODO[causeway-core-metamodel-CAUSEWAY-2297] add map support ->
expected 17
+ // count all nodes
+ assertEquals(9, Can.ofIterable(tree::iteratorDepthFirst).size());
+ assertEquals(9, Can.ofIterable(tree::iteratorBreadthFirst).size());
}
- //TODO[causeway-core-metamodel-CAUSEWAY-2297] make test work
- //@Test
+ @Test
void depthFirstTraversal() {
- // instantiate a tree, that we later traverse
- var a = _TreeSample.sampleA();
-
- // traverse the tree
- var tree = TreeNode.root(a, treeAdapter);
-
var nodeNames = tree.streamDepthFirst()
.map(TreeNode::value)
.map(_TreeSample::nameOf)
.collect(Collectors.joining(", "));
assertEquals(
- "a, b1, d1, d2, d3, b2, d1, d2, d3, c1, d1, d2, d3, c2, d1,
d2, d3",
+ "a, b1, d1, d2, d3, b2, d1, d2, d3",
+ //TODO[causeway-core-metamodel-CAUSEWAY-2297] add map support
+ //"a, b1, d1, d2, d3, b2, d1, d2, d3, c1, d1, d2, d3, c2, d1,
d2, d3",
nodeNames);
}
- //TODO[causeway-core-metamodel-CAUSEWAY-2297] make test work
- //@Test
+ @Test
void leafToRootTraversal() {
- // instantiate a tree and pick an arbitrary leaf value,
- // from which we later traverse up to the root
- var a = _TreeSample.sampleA();
+ // pick an arbitrary leaf value,
+ // from which we then traverse up to the root
var d = a.childrenB().getFirstElseFail().childrenD().getLastElseFail();
- // traverse the tree
- var tree = TreeNode.root(a, treeAdapter);
-
// find d's node
var leafNode = tree.streamDepthFirst()
.filter((final TreeNode<Object>
treeNode)->d.equals(treeNode.value()))