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()))

Reply via email to