This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/master by this push:
     new 60fac61ae2 CAUSEWAY-3711: adds and registers TreePathValueSemantics
60fac61ae2 is described below

commit 60fac61ae2b800e1e2653b3960a5ee971f89478d
Author: andi-huber <[email protected]>
AuthorDate: Sat Mar 30 10:37:16 2024 +0100

    CAUSEWAY-3711: adds and registers TreePathValueSemantics
---
 .../causeway/applib/graph/tree/TreeAdapter.java    |   4 +-
 .../causeway/applib/graph/tree/TreePath.java       | 139 +++++++++++++++---
 .../applib/graph/tree/TreePath_Default.java        | 157 ---------------------
 .../metamodel/CausewayModuleCoreMetamodel.java     |   2 +
 .../_testing/MetaModelContext_forTesting.java      |   2 +
 .../valuesemantics/TreePathValueSemantics.java     | 137 ++++++++++++++++++
 .../model/valuetypes/ValueTypeExample.java         |   2 -
 .../ui/components/tree/_TreeModelTreeAdapter.java  |  34 ++---
 8 files changed, 273 insertions(+), 204 deletions(-)

diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeAdapter.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeAdapter.java
index f6feafa67a..0233dcad21 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeAdapter.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreeAdapter.java
@@ -37,7 +37,9 @@ public interface TreeAdapter<T> {
      * @return number of child tree-nodes of the specified {@code value} 
tree-node (pojo)
      */
     @Domain.Exclude
-    int childCountOf(T value);
+    default int childCountOf(T value) {
+        return Math.toIntExact(childrenOf(value).count());
+    }
 
     /**
      * @param value - tree-node (pojo)
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath.java 
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath.java
index ed2803b93c..e56376d8cb 100644
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath.java
+++ 
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath.java
@@ -19,7 +19,9 @@
 package org.apache.causeway.applib.graph.tree;
 
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.OptionalInt;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -27,12 +29,16 @@ import java.util.stream.Stream;
 
 import org.springframework.lang.Nullable;
 
+import org.apache.causeway.applib.annotation.Value;
 import org.apache.causeway.commons.functional.IndexedConsumer;
 import org.apache.causeway.commons.internal.assertions._Assert;
+import org.apache.causeway.commons.internal.base._Refs;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.commons.internal.primitives._Ints;
 
+import lombok.val;
+
 /**
  * Provides an unambiguous way to address nodes by position within a 
tree-structure. Examples:
  * <ul>
@@ -42,19 +48,52 @@ import 
org.apache.causeway.commons.internal.primitives._Ints;
  * </ul>
  * @since 2.0 {@index}
  */
-public interface TreePath extends Serializable {
+@Value
+public class TreePath implements Serializable {
+    
+    // -- FACTORIES
+
+    public static TreePath of(final int ... canonicalPath) {
+        return new TreePath(canonicalPath);
+    }
+
+    public static TreePath root() {
+        return of(0);
+    }
+    
+    // -- CONSTRUCTION 
+    
+    private static final long serialVersionUID = 530511373409525896L;
+    private final int[] canonicalPath;
+    private final int hashCode;
+    
+    public TreePath(final int[] canonicalPath) {
+        Objects.requireNonNull(canonicalPath, "canonicalPath is required");
+        if(canonicalPath.length<1) {
+            throw new IllegalArgumentException("canonicalPath must not be 
empty");
+        }
+        this.canonicalPath = canonicalPath;
+        this.hashCode = Arrays.hashCode(canonicalPath);
+    }
 
     /**
      * Number of path-elements.
      * @apiNote Root has size = 1.
      */
-    public int size();
+    public int size() {
+        return canonicalPath.length;
+    }
     
     /**
      * @param indexWithinSiblings
      * @return a new TreePath instance composed of this with one canonical 
path entry added
      */
-    public TreePath append(int indexWithinSiblings);
+    public TreePath append(int indexWithinSiblings) {
+        final int[] newCanonicalPath = new int[canonicalPath.length+1];
+        System.arraycopy(canonicalPath, 0, newCanonicalPath, 0, 
canonicalPath.length);
+        newCanonicalPath[canonicalPath.length] = indexWithinSiblings;
+        return new TreePath(newCanonicalPath);
+    }
     
     /**
      * Returns a sub-path containing all the path-elements of this path, 
skipping
@@ -62,40 +101,80 @@ public interface TreePath extends Serializable {
      * @apiNote The first element of the resulting path indicates the sibling 
index 
      *      of the tree-node the subPath corresponds to.
      */
-    public TreePath subPath(int startIndex);
+    public TreePath subPath(int startIndex) {
+        if(startIndex<=0) return this;
+        if(startIndex>=size()) throw new IndexOutOfBoundsException(startIndex);
+        final int newSize = size() - startIndex; 
+        final int[] newCanonicalPath = new int[newSize];
+        System.arraycopy(canonicalPath, startIndex, newCanonicalPath, 0, 
newSize);
+        return new TreePath(newCanonicalPath);
+
+    }
     
     /**
      * Returns a TreePath instance that represents the parent path of this 
TreePath,
      * if this is not the root.
      */
-    public @Nullable TreePath getParentIfAny();
+    public @Nullable TreePath getParentIfAny() {
+        if(isRoot()) {
+            return null;
+        }
+        final int[] newCanonicalPath = new int[canonicalPath.length-1];
+        System.arraycopy(canonicalPath, 0, newCanonicalPath, 0, 
canonicalPath.length-1);
+        return new TreePath(newCanonicalPath);
+
+    }
+    
+    public boolean isRoot() {
+        return canonicalPath.length==1;
+    }
     
-    public boolean isRoot();
-    public boolean startsWith(TreePath other);
+    public boolean startsWith(TreePath other) {
+        if(other.size()>this.size()) return false;
+        // optimization, not strictly required
+        if(other instanceof TreePath) {
+            final int lastIndexToCheck = other.size() - 1;
+            return Arrays.equals(
+                    this.canonicalPath, 0, lastIndexToCheck, 
+                    ((TreePath)other).canonicalPath, 0, lastIndexToCheck);    
+        }
+        return this.stringify("/").startsWith(other.stringify("/"));
+    }
 
-    public IntStream streamPathElements();
+    public IntStream streamPathElements() {
+        return IntStream.of(canonicalPath);
+    }
     
     /**
      * Optionally the 2nd path-element's value, based on presence.
      * It corresponds to the sibling index of the child node this tree-path 
      * (either directly references or) includes.
      */
-    public OptionalInt childIndex();
-
-    public String stringify(String delimiter);
-
-    public Stream<TreePath> streamUpTheHierarchyStartingAtSelf();
-
-    // -- CONSTRUCTION
+    public OptionalInt childIndex() {
+        return size()>=2
+                ? OptionalInt.of(canonicalPath[1])
+                : OptionalInt.empty();
+    }
 
-    public static TreePath of(final int ... canonicalPath) {
-        return new TreePath_Default(canonicalPath);
+    public String stringify(String delimiter) {
+        _Assert.assertTrue(_Strings.isNotEmpty(delimiter), ()->"non-empty 
delimiter required");
+        return delimiter + streamPathElements()
+            .mapToObj(i->""+i)
+            .collect(Collectors.joining(delimiter));
     }
 
-    public static TreePath root() {
-        return of(0);
+    public Stream<TreePath> streamUpTheHierarchyStartingAtSelf() {
+        val hasMore = _Refs.booleanRef(true);
+        return Stream.iterate((TreePath)this, __->hasMore.isTrue(), 
TreePath::getParentIfAny)
+                .filter(x->{
+                    if(x.isRoot()) {
+                        hasMore.setValue(false); // stop the stream only after 
we have included the root
+                    }
+                    return true;
+                });
     }
 
+
     /**
      * Parses stringified tree path of format {@code 
<delimiter>0<delimiter>3<delimiter>1} ...,
      * as returned by {@link TreePath#stringify(String)}.
@@ -127,4 +206,26 @@ public interface TreePath extends Serializable {
         return of(canonicalPath);
     }
 
+    // -- OBJECT CONTRACTS
+
+    @Override
+    public boolean equals(final Object obj) {
+        if(obj instanceof TreePath) {
+            final TreePath other = (TreePath) obj;
+            return Arrays.equals(canonicalPath, other.canonicalPath);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode;
+    }
+
+    @Override
+    public String toString() {
+        return stringify("/");
+    }
+
+
 }
diff --git 
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath_Default.java
 
b/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath_Default.java
deleted file mode 100644
index df37ea3cda..0000000000
--- 
a/api/applib/src/main/java/org/apache/causeway/applib/graph/tree/TreePath_Default.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.causeway.applib.graph.tree;
-
-import java.util.Arrays;
-import java.util.Objects;
-import java.util.OptionalInt;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-
-import org.apache.causeway.commons.internal.assertions._Assert;
-import org.apache.causeway.commons.internal.base._Refs;
-import org.apache.causeway.commons.internal.base._Strings;
-
-import lombok.NonNull;
-import lombok.val;
-
-/**
- * Package private mixin for TreePath.
- */
-class TreePath_Default implements TreePath {
-
-    private static final long serialVersionUID = 530511373409525896L;
-    private final int[] canonicalPath;
-    private final int hashCode;
-
-    TreePath_Default(final int[] canonicalPath) {
-        Objects.requireNonNull(canonicalPath, "canonicalPath is required");
-        if(canonicalPath.length<1) {
-            throw new IllegalArgumentException("canonicalPath must not be 
empty");
-        }
-        this.canonicalPath = canonicalPath;
-        this.hashCode = Arrays.hashCode(canonicalPath);
-    }
-
-    @Override
-    public int size() {
-        return canonicalPath.length;
-    }
-    
-    @Override
-    public TreePath append(final int indexWithinSiblings) {
-        final int[] newCanonicalPath = new int[canonicalPath.length+1];
-        System.arraycopy(canonicalPath, 0, newCanonicalPath, 0, 
canonicalPath.length);
-        newCanonicalPath[canonicalPath.length] = indexWithinSiblings;
-        return new TreePath_Default(newCanonicalPath);
-    }
-
-    @Override
-    public TreePath getParentIfAny() {
-        if(isRoot()) {
-            return null;
-        }
-        final int[] newCanonicalPath = new int[canonicalPath.length-1];
-        System.arraycopy(canonicalPath, 0, newCanonicalPath, 0, 
canonicalPath.length-1);
-        return new TreePath_Default(newCanonicalPath);
-    }
-
-    @Override
-    public boolean isRoot() {
-        return canonicalPath.length==1;
-    }
-    
-    @Override
-    public boolean startsWith(TreePath other) {
-        if(other.size()>this.size()) return false;
-        // optimization, not strictly required
-        if(other instanceof TreePath_Default) {
-            final int lastIndexToCheck = other.size() - 1;
-            return Arrays.equals(
-                    this.canonicalPath, 0, lastIndexToCheck, 
-                    ((TreePath_Default)other).canonicalPath, 0, 
lastIndexToCheck);    
-        }
-        return this.stringify("/").startsWith(other.stringify("/"));
-    }
-    
-    @Override
-    public OptionalInt childIndex() {
-        return size()>=2
-                ? OptionalInt.of(canonicalPath[1])
-                : OptionalInt.empty();
-    }
-    
-    @Override
-    public TreePath subPath(int startIndex) {
-        if(startIndex<=0) return this;
-        if(startIndex>=size()) throw new IndexOutOfBoundsException(startIndex);
-        final int newSize = size() - startIndex; 
-        final int[] newCanonicalPath = new int[newSize];
-        System.arraycopy(canonicalPath, startIndex, newCanonicalPath, 0, 
newSize);
-        return new TreePath_Default(newCanonicalPath);
-    }
-
-    @Override
-    public String stringify(@NonNull final String delimiter) {
-        _Assert.assertTrue(_Strings.isNotEmpty(delimiter), ()->"non-empty 
delimiter required");
-        return delimiter + streamPathElements()
-            .mapToObj(i->""+i)
-            .collect(Collectors.joining(delimiter));
-    }
-
-    @Override
-    public IntStream streamPathElements() {
-        return IntStream.of(canonicalPath);
-    }
-
-    @Override
-    public Stream<TreePath> streamUpTheHierarchyStartingAtSelf() {
-        val hasMore = _Refs.booleanRef(true);
-        return Stream.iterate((TreePath)this, __->hasMore.isTrue(), 
TreePath::getParentIfAny)
-                .filter(x->{
-                    if(x.isRoot()) {
-                        hasMore.setValue(false); // stop the stream only after 
we have included the root
-                    }
-                    return true;
-                });
-    }
-
-    // -- OBJECT CONTRACTS
-
-    @Override
-    public boolean equals(final Object obj) {
-        if(obj instanceof TreePath_Default) {
-            final TreePath_Default other = (TreePath_Default) obj;
-            return Arrays.equals(canonicalPath, other.canonicalPath);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return hashCode;
-    }
-
-    @Override
-    public String toString() {
-        return stringify("/");
-    }
-
-}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
index 8c9c9f251b..9d95c92868 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
@@ -97,6 +97,7 @@ import 
org.apache.causeway.core.metamodel.valuesemantics.PasswordValueSemantics;
 import org.apache.causeway.core.metamodel.valuesemantics.ShortValueSemantics;
 import org.apache.causeway.core.metamodel.valuesemantics.StringValueSemantics;
 import 
org.apache.causeway.core.metamodel.valuesemantics.TreeNodeValueSemantics;
+import 
org.apache.causeway.core.metamodel.valuesemantics.TreePathValueSemantics;
 import org.apache.causeway.core.metamodel.valuesemantics.URLValueSemantics;
 import org.apache.causeway.core.metamodel.valuesemantics.UUIDValueSemantics;
 import 
org.apache.causeway.core.metamodel.valuesemantics.temporal.LocalDateTimeValueSemantics;
@@ -156,6 +157,7 @@ import lombok.NonNull;
         OidDtoValueSemantics.class,
         UUIDValueSemantics.class,
         LocaleValueSemantics.class,
+        TreePathValueSemantics.class,
         TreeNodeValueSemantics.class,
         ChangesDtoValueSemantics.class,
         CommandDtoValueSemantics.class,
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
index 2f2aaa0b00..4174afa7ea 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
@@ -99,6 +99,7 @@ import 
org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.specloader.SpecificationLoader;
 import 
org.apache.causeway.core.metamodel.specloader.SpecificationLoaderDefault;
 import 
org.apache.causeway.core.metamodel.valuesemantics.BigDecimalValueSemantics;
+import 
org.apache.causeway.core.metamodel.valuesemantics.TreePathValueSemantics;
 import org.apache.causeway.core.metamodel.valuesemantics.URLValueSemantics;
 import org.apache.causeway.core.metamodel.valuesemantics.UUIDValueSemantics;
 import 
org.apache.causeway.core.metamodel.valuetypes.ValueSemanticsResolverDefault;
@@ -253,6 +254,7 @@ extends MetaModelContext {
                 new BigDecimalValueSemantics(),
                 new URLValueSemantics(),
                 new UUIDValueSemantics(),
+                new TreePathValueSemantics(),
                 this);
 
         return Stream.concat(
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/TreePathValueSemantics.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/TreePathValueSemantics.java
new file mode 100644
index 0000000000..a9ae584914
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/TreePathValueSemantics.java
@@ -0,0 +1,137 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.core.metamodel.valuesemantics;
+
+import javax.annotation.Priority;
+import javax.inject.Named;
+
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.graph.tree.TreePath;
+import org.apache.causeway.applib.services.bookmark.IdStringifier;
+import org.apache.causeway.applib.value.semantics.Parser;
+import org.apache.causeway.applib.value.semantics.Renderer;
+import org.apache.causeway.applib.value.semantics.ValueDecomposition;
+import org.apache.causeway.applib.value.semantics.ValueSemanticsAbstract;
+import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.schema.common.v2.ValueType;
+
+import lombok.NonNull;
+import lombok.val;
+
+@Component
+@Named("causeway.metamodel.value.TreePathValueSemantics")
+@Priority(PriorityPrecedence.LATE)
+public class TreePathValueSemantics
+extends ValueSemanticsAbstract<TreePath>
+implements
+    Parser<TreePath>,
+    Renderer<TreePath>,
+    IdStringifier.EntityAgnostic<TreePath> {
+
+    @Override
+    public Class<TreePath> getCorrespondingClass() {
+        return TreePath.class;
+    }
+
+    @Override
+    public ValueType getSchemaValueType() {
+        return ValueType.STRING; // this type can be easily converted to 
string and back
+    }
+
+    // -- COMPOSER
+
+    @Override
+    public ValueDecomposition decompose(final TreePath value) {
+        return decomposeAsString(value, 
TreePathValueSemantics::canonicalStringify, ()->null);
+    }
+
+    @Override
+    public TreePath compose(final ValueDecomposition decomposition) {
+        return composeFromString(decomposition, 
TreePathValueSemantics::canonicalDestringify, ()->null);
+    }
+
+    // -- ID STRINGIFIER
+
+    @Override
+    public String enstring(final @NonNull TreePath value) {
+        return canonicalStringify(value);
+    }
+
+    @Override
+    public TreePath destring(final @NonNull String stringified) {
+        return canonicalDestringify(stringified);
+    }
+
+    // -- RENDERER
+
+    @Override
+    public String titlePresentation(final ValueSemanticsProvider.Context 
context, final TreePath value) {
+        return value == null ? "" : canonicalStringify(value);
+    }
+
+    // -- PARSER
+
+    @Override
+    public String parseableTextRepresentation(final 
ValueSemanticsProvider.Context context, final TreePath value) {
+        return canonicalStringify(value);
+    }
+
+    @Override
+    public TreePath parseTextRepresentation(final 
ValueSemanticsProvider.Context context, final String text) {
+        val input = _Strings.blankToNullOrTrim(text);
+        return canonicalDestringify(input);
+    }
+
+    @Override
+    public int typicalLength() {
+        return 40;
+    }
+
+    @Override
+    public int maxLength() {
+        return 1024;
+    }
+
+    @Override
+    public Can<TreePath> getExamples() {
+        return Can.of(
+                TreePath.root(),
+                TreePath.of(0, 1, 2, 3));
+    }
+
+    // -- HELPER
+    
+    private static String canonicalStringify(@Nullable TreePath treePath) {
+        return treePath!=null
+                ? treePath.stringify("/")
+                : null;
+    }
+    
+    private static TreePath canonicalDestringify(@Nullable String input) {
+        return input!=null
+                ? TreePath.parse(input, "/")
+                : null;
+    }
+    
+}
diff --git 
a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/valuetypes/ValueTypeExample.java
 
b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/valuetypes/ValueTypeExample.java
index 39d2d35402..5570d7dc9c 100644
--- 
a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/valuetypes/ValueTypeExample.java
+++ 
b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/model/valuetypes/ValueTypeExample.java
@@ -768,8 +768,6 @@ public abstract class ValueTypeExample<T> {
                 "anotherRoot", TreeAdapterString.class, 
TreeState.rootCollapsed());
 
         private static class TreeAdapterString implements TreeAdapter<String> {
-            @Override public int childCountOf(final String value) {
-                return 0; }
             @Override public Stream<String> childrenOf(final String value) {
                 return Stream.empty(); }
         }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/_TreeModelTreeAdapter.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/_TreeModelTreeAdapter.java
index e27d9b5246..6743e98edd 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/_TreeModelTreeAdapter.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/_TreeModelTreeAdapter.java
@@ -19,7 +19,6 @@
 package org.apache.causeway.viewer.wicket.ui.components.tree;
 
 import java.io.Serializable;
-import java.util.Objects;
 import java.util.function.Function;
 import java.util.stream.Stream;
 
@@ -55,24 +54,8 @@ implements
         this.treeAdapterClass = treeAdapterClass;
     }
 
-//    @Override
-//    public Optional<_TreeNodeMemento> parentOf(final _TreeNodeMemento 
treeModel) {
-//        if(treeModel==null) {
-//            return Optional.empty();
-//        }
-//        val pojoNode = demementify(treeModel);
-//        if(pojoNode==null) {
-//            return Optional.empty();
-//        }
-//        return wrappedTreeAdapter().parentOf(pojoNode)
-//                .map(pojo->mementify(pojo, 
treeModel.getTreePath().getParentIfAny()));
-//    }
-
     @Override
-    public int childCountOf(final _TreeNodeMemento treeModel) {
-        if(treeModel==null) {
-            return 0;
-        }
+    public int childCountOf(final @Nullable _TreeNodeMemento treeModel) {
         val pojoNode = demementify(treeModel);
         if(pojoNode==null) {
             return 0;
@@ -81,10 +64,7 @@ implements
     }
 
     @Override
-    public Stream<_TreeNodeMemento> childrenOf(final _TreeNodeMemento 
treeModel) {
-        if(treeModel==null) {
-            return Stream.empty();
-        }
+    public Stream<_TreeNodeMemento> childrenOf(final @Nullable 
_TreeNodeMemento treeModel) {
         val pojoNode = demementify(treeModel);
         if(pojoNode==null) {
             return Stream.empty();
@@ -93,14 +73,18 @@ implements
                 .map(newPojoToTreeModelMapper(treeModel));
     }
 
+    // -- HELPER
+    
     _TreeNodeMemento mementify(final @NonNull Object pojo, final @NonNull 
TreePath treePath) {
         return new _TreeNodeMemento(
                 treePath,
                 ManagedObject.adaptSingular(getSpecificationLoader(), 
pojo).getBookmark().orElseThrow());
     }
-    private @Nullable Object demementify(final _TreeNodeMemento model) {
-        Objects.requireNonNull(model);
-        return model.getPojo();
+    
+    private @Nullable Object demementify(final @Nullable _TreeNodeMemento 
model) {
+        return model!=null
+                ? model.getPojo()
+                : null;
     }
 
     private Function<Object, _TreeNodeMemento> newPojoToTreeModelMapper(final 
_TreeNodeMemento parent) {

Reply via email to