You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/03/03 18:39:11 UTC

[isis] 06/09: ISIS-2553: refactor ApplicationFeatureId to have only a single constructor

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/isis.git

commit 9e3d54e24a6146ee5b587a5fe2057943673e5193
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Mar 3 17:38:51 2021 +0100

    ISIS-2553: refactor ApplicationFeatureId to have only a single
    constructor
---
 .../services/appfeat/ApplicationFeatureId.java     | 100 ++++++++-------------
 .../services/appfeat/ApplicationFeatureSort.java   |  84 +++++++++--------
 .../isis/commons/internal/base/_Strings.java       |  33 ++++++-
 .../services/appfeat/ApplicationFeatureIdTest.java |  27 ------
 .../appfeat/ApplicationFeatureTypeTest.java        |  32 +++----
 .../model/app/user/UserPermissionViewModel.java    |  11 ++-
 .../dom/role/ApplicationRole_addPermission.java    |   2 +-
 7 files changed, 133 insertions(+), 156 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureId.java b/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureId.java
index c94a1f1..20152bf 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureId.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureId.java
@@ -21,7 +21,6 @@ package org.apache.isis.applib.services.appfeat;
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Iterator;
 import java.util.List;
 
 import static java.util.Comparator.comparing;
@@ -40,6 +39,7 @@ import org.apache.isis.applib.util.TitleBuffer;
 import org.apache.isis.applib.util.ToString;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
 
 import lombok.Getter;
 import lombok.NonNull;
@@ -47,11 +47,11 @@ import lombok.Setter;
 import lombok.val;
 
 /**
- * Value type representing a package, class or member.
+ * Value type representing a namespace, type or member.
  * <p>
  * This value is {@link Comparable}, the implementation of which considers 
- * {@link #getType() (feature) type}, {@link #getNamespace() logical package name}, 
- * {@link #getTypeSimpleName() class name} and {@link #getMemberName() member name}.
+ * {@link #getSort() (feature) sort}, {@link #getNamespace() namespace}, 
+ * {@link #getTypeSimpleName() type simple name} and {@link #getMemberName() member name}.
  * 
  */
 @Value
@@ -64,8 +64,7 @@ implements
 
     // -- CONSTANTS
 
-    public static final ApplicationFeatureId NAMESPACE_DEFAULT = 
-            new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE, "default");
+    public static final ApplicationFeatureId NAMESPACE_DEFAULT = newNamespace("default");
 
     // -- FACTORY METHODS
 
@@ -84,10 +83,10 @@ implements
     }
     
     public static ApplicationFeatureId newFeature(
-            final ApplicationFeatureSort featureType, 
-            final String qualifiedName) {
+            final @NonNull ApplicationFeatureSort featureSort, 
+            final @NonNull String qualifiedName) {
         
-        switch (featureType) {
+        switch (featureSort) {
         case NAMESPACE:
             return newNamespace(qualifiedName);
         case TYPE:
@@ -95,7 +94,7 @@ implements
         case MEMBER:
             return newMember(qualifiedName);
         }
-        throw new IllegalArgumentException("Unknown feature type " + featureType);
+        throw _Exceptions.illegalArgument("Unknown feature sort '%s'", featureSort);
     }
 
     public static ApplicationFeatureId newFeature(
@@ -113,45 +112,35 @@ implements
     }
 
     public static ApplicationFeatureId newNamespace(final String namespace) {
-        final ApplicationFeatureId featureId = new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE);
-        featureId.setNamespace(namespace);
-        return featureId;
+        val feat = new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE);
+        ApplicationFeatureSort.initNamespace(feat, namespace);
+        return feat;
     }
 
     public static ApplicationFeatureId newType(final String logicalTypeName) {
-        return new ApplicationFeatureId(ApplicationFeatureSort.TYPE, logicalTypeName);
+        val feat = new ApplicationFeatureId(ApplicationFeatureSort.TYPE);
+        ApplicationFeatureSort.initType(feat, logicalTypeName);
+        return feat;
     }
 
     public static ApplicationFeatureId newMember(final String logicalTypeName, final String memberName) {
         final ApplicationFeatureId featureId = new ApplicationFeatureId(ApplicationFeatureSort.MEMBER);
-        ApplicationFeatureSort.TYPE.init(featureId, logicalTypeName);
+        ApplicationFeatureSort.initType(featureId, logicalTypeName);
         featureId.sort = ApplicationFeatureSort.MEMBER;
         featureId.setMemberName(memberName);
         return featureId;
     }
 
-    public static ApplicationFeatureId newMember(final String fullyQualifiedName) {
-        return new ApplicationFeatureId(ApplicationFeatureSort.MEMBER, fullyQualifiedName);
+    public static ApplicationFeatureId newMember(String fqn) {
+        val feat = new ApplicationFeatureId(ApplicationFeatureSort.MEMBER);
+        ApplicationFeatureSort.initMember(feat, fqn);
+        return feat;
     }
-
+    
     // -- CONSTRUCTOR
 
-    private ApplicationFeatureId(final String asString) {
-        final Iterator<String> iterator = _Strings.splitThenStream(asString, ":").iterator();
-        final ApplicationFeatureSort type = ApplicationFeatureSort.valueOf(iterator.next());
-        type.init(this, iterator.next());
-    }
-
-    /**
-     * Must be called by {@link ApplicationFeatureSort#init(ApplicationFeatureId, String)} 
-     * immediately afterwards to fully initialize.
-     */
-    ApplicationFeatureId(final ApplicationFeatureSort type) {
-        this.sort = type;
-    }
-
-    public ApplicationFeatureId(final ApplicationFeatureSort type, final String fullyQualifiedName) {
-        type.init(this, fullyQualifiedName);
+    private ApplicationFeatureId(final ApplicationFeatureSort sort) {
+        this.sort = sort;
     }
 
     // -- TITLE
@@ -245,27 +234,29 @@ implements
     // -- ENCODING / DECODING
 
     @Programmatic
-    public String asString() {
+    public String stringify() {
         return sort.name() + ":" + getFullyQualifiedName();
     }
 
-    @Programmatic
-    public String asEncodedString() {
-        return _Strings.base64UrlEncode(asString());
-    }
-    
     /**
-     * Round-trip with {@link #asString()}
+     * Round-trip with {@link #stringify()}
      */
-    public static ApplicationFeatureId parse(final String asString) {
-        return new ApplicationFeatureId(asString);
+    public static ApplicationFeatureId parse(final String stringified) {
+        return _Strings.splitThenApplyRequireNonEmpty(stringified, ":", (sort, fqn)->
+            newFeature(ApplicationFeatureSort.valueOf(sort), fqn))
+        .orElseThrow(()->_Exceptions.illegalArgument("cannot parse feature-id '%s'", stringified));
+    }
+    
+    @Programmatic
+    public String asEncodedString() {
+        return _Strings.base64UrlEncode(stringify());
     }
 
     /**
      * Round-trip with {@link #asEncodedString()}
      */
     public static ApplicationFeatureId parseEncoded(final String encodedString) {
-        return new ApplicationFeatureId(_Strings.base64UrlDecode(encodedString));
+        return parse(_Strings.base64UrlDecode(encodedString));
     }
 
     // -- pathIds, parentIds
@@ -368,26 +359,7 @@ implements
         return newFeature(namespace, this.getTypeSimpleName(), this.getMemberName()); 
     }
 
-    @Deprecated // duplicate
-    public static ApplicationFeatureId createNamespace(String namespace) {
-        val feat = new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE);
-        ApplicationFeatureSort.NAMESPACE.init(feat, namespace);
-        return feat;
-    }
-    
-    @Deprecated // duplicate
-    public static ApplicationFeatureId createType(String logicalTypeName) {
-        val feat = new ApplicationFeatureId(ApplicationFeatureSort.TYPE);
-        ApplicationFeatureSort.TYPE.init(feat, logicalTypeName);
-        return feat;
-    }
-    
-    @Deprecated // duplicate
-    public static ApplicationFeatureId createMember(String fqn) {
-        val feat = new ApplicationFeatureId(ApplicationFeatureSort.MEMBER);
-        ApplicationFeatureSort.MEMBER.init(feat, fqn);
-        return feat;
-    }
+
 
 
 }
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureSort.java b/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureSort.java
index 352a380..074e382 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureSort.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/appfeat/ApplicationFeatureSort.java
@@ -23,55 +23,21 @@ import org.apache.isis.commons.internal.base._Strings;
 public enum ApplicationFeatureSort {
     
     /** 
-     * logical namespace, leading part of the <i>object type</i> (aka logical type)
+     * logical namespace
      */
-    NAMESPACE {
-        @Override
-        void init(final ApplicationFeatureId feature, final String fullyQualifiedName) {
-            feature.setNamespace(fullyQualifiedName);
-            feature.setTypeSimpleName(null);
-            feature.setMemberName(null);
-            feature.sort = this;
-        }
-    },
+    NAMESPACE,
     
     /** 
-     * logical type, simple name of the <i>object type</i> (aka logical type)
+     * {@code namespace + "." + typeSimpleName} 
+     * makes up the fully qualified logical type
      */
-    TYPE {
-        @Override
-        void init(final ApplicationFeatureId feature, final String fullyQualifiedName) {
-            final int i = fullyQualifiedName.lastIndexOf(".");
-            if(i != -1) {
-                feature.setNamespace(fullyQualifiedName.substring(0, i));
-                feature.setTypeSimpleName(fullyQualifiedName.substring(i+1));
-            } else {
-                feature.setNamespace("");
-                feature.setTypeSimpleName(fullyQualifiedName);
-            }
-            feature.setMemberName(null);
-            feature.sort = this;
-        }
-    },
+    TYPE,
     
     /** 
      * {@code namespace + "." + typeSimpleName + "." + memberName} 
-     * make up the fully qualified logical member name
+     * makes up the fully qualified logical member
      */
-    MEMBER {
-        @Override
-        void init(final ApplicationFeatureId feature, final String fullyQualifiedName) {
-            final int i = fullyQualifiedName.lastIndexOf("#");
-            if(i == -1) {
-                throw new IllegalArgumentException("Malformed, expected a '#': " + fullyQualifiedName);
-            }
-            final String className = fullyQualifiedName.substring(0, i);
-            final String memberName = fullyQualifiedName.substring(i+1);
-            TYPE.init(feature, className);
-            feature.setMemberName(memberName);
-            feature.sort = this;
-        }
-    };
+    MEMBER;
 
     public boolean hideClassName() {
         return this == ApplicationFeatureSort.NAMESPACE;
@@ -81,8 +47,6 @@ public enum ApplicationFeatureSort {
         return this == ApplicationFeatureSort.NAMESPACE || this == ApplicationFeatureSort.TYPE;
     }
 
-    abstract void init(ApplicationFeatureId applicationFeatureId, String fullyQualifiedName);
-
     public static void ensurePackage(final ApplicationFeatureId feature) {
         if(feature.sort != ApplicationFeatureSort.NAMESPACE) {
             throw new IllegalStateException("Can only be called for a package; " + feature.toString());
@@ -112,5 +76,39 @@ public enum ApplicationFeatureSort {
         return _Strings.capitalize(name());
     }
     
+    // -- REFACTORING
+    
+    static void initNamespace(final ApplicationFeatureId feature, final String fullyQualifiedName) {
+        feature.setNamespace(fullyQualifiedName);
+        feature.setTypeSimpleName(null);
+        feature.setMemberName(null);
+        feature.sort = NAMESPACE;
+    }
+    
+    static void initType(final ApplicationFeatureId feature, final String fullyQualifiedName) {
+        final int i = fullyQualifiedName.lastIndexOf(".");
+        if(i != -1) {
+            feature.setNamespace(fullyQualifiedName.substring(0, i));
+            feature.setTypeSimpleName(fullyQualifiedName.substring(i+1));
+        } else {
+            feature.setNamespace("");
+            feature.setTypeSimpleName(fullyQualifiedName);
+        }
+        feature.setMemberName(null);
+        feature.sort = TYPE;
+    }
+    
+    static void initMember(final ApplicationFeatureId feature, final String fullyQualifiedName) {
+        final int i = fullyQualifiedName.lastIndexOf("#");
+        if(i == -1) {
+            throw new IllegalArgumentException("Malformed, expected a '#': " + fullyQualifiedName);
+        }
+        final String className = fullyQualifiedName.substring(0, i);
+        final String memberName = fullyQualifiedName.substring(i+1);
+        initType(feature, className);
+        feature.setMemberName(memberName);
+        feature.sort = MEMBER;
+    }
+    
 
 }
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
index ec8c8dd..cac0c85 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
@@ -31,6 +31,7 @@ import java.util.Scanner;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
@@ -456,7 +457,37 @@ public final class _Strings {
         requires(delimiterPattern, "delimiterPattern");
         return delimiterPattern.splitAsStream(input);
     }
-    
+
+    /**
+     * Optionally applies {@code onNonEmptySplit} function, based on whether both split parts 
+     * <i>lhs</i> and <i>rhs</i> are non empty Strings. 
+     * @param <T>
+     * @param input
+     * @param separator
+     * @param onNonEmptySplit
+     */
+    public static <T> Optional<T> splitThenApplyRequireNonEmpty(
+            @Nullable final String input, 
+            final String separator, 
+            final BiFunction<String, String, T> onNonEmptySplit) {
+        
+        if(isEmpty(input)) {
+            return Optional.empty();
+        }
+        // we have a non-empty string
+        final int p = input.indexOf(separator);
+        if(p<1){
+            // separator not found or
+            // empty lhs in string
+            return Optional.empty();
+        }
+        final int q = p + separator.length();
+        if(q==input.length()) {
+            // empty rhs
+            return Optional.empty();
+        }
+        return Optional.ofNullable(onNonEmptySplit.apply(input.substring(0, p), input.substring(q)));
+    }
 
     public static void splitThenAccept(
             @Nullable final String input, 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureIdTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureIdTest.java
index 1d76bdc..5f1da3d 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureIdTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureIdTest.java
@@ -118,33 +118,6 @@ public class ApplicationFeatureIdTest {
 
     }
 
-    public static class Constructor_AFT_String extends ApplicationFeatureIdTest {
-
-        @Test
-        public void whenPackage() throws Exception {
-            // when
-            val applicationFeatureId = new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE, "com.mycompany");
-            // then
-            assertThat(applicationFeatureId, is(ApplicationFeatureId.newNamespace("com.mycompany")));
-        }
-
-        @Test
-        public void whenClass() throws Exception {
-            // when
-            val applicationFeatureId = new ApplicationFeatureId(ApplicationFeatureSort.TYPE, "com.mycompany.Bar");
-            // then
-            assertThat(applicationFeatureId, is(ApplicationFeatureId.newType("com.mycompany.Bar")));
-        }
-
-        @Test
-        public void whenMember() throws Exception {
-            // when
-            val applicationFeatureId = new ApplicationFeatureId(ApplicationFeatureSort.MEMBER, "com.mycompany.Bar#foo");
-            // then
-            assertThat(applicationFeatureId, is(ApplicationFeatureId.newMember("com.mycompany.Bar","foo")));
-        }
-    }
-
     public static class NewFeature_AFT_String extends ApplicationFeatureIdTest {
 
         @Test
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureTypeTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureTypeTest.java
index f30f90c..1da0471 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureTypeTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureTypeTest.java
@@ -60,7 +60,7 @@ public class ApplicationFeatureTypeTest {
         @Test
         public void givenPackage() throws Exception {
 
-            val applicationFeatureId = ApplicationFeatureId.createNamespace("com.mycompany"); 
+            val applicationFeatureId = ApplicationFeatureId.newNamespace("com.mycompany"); 
 
             assertThat(applicationFeatureId.getNamespace(), is("com.mycompany"));
             assertThat(applicationFeatureId.getTypeSimpleName(), is(nullValue()));
@@ -70,7 +70,7 @@ public class ApplicationFeatureTypeTest {
         @Test
         public void givenClass() throws Exception {
 
-            val applicationFeatureId = ApplicationFeatureId.createType("com.mycompany.Bar");
+            val applicationFeatureId = ApplicationFeatureId.newType("com.mycompany.Bar");
 
             assertThat(applicationFeatureId.getNamespace(), is("com.mycompany"));
             assertThat(applicationFeatureId.getTypeSimpleName(), is("Bar"));
@@ -80,7 +80,7 @@ public class ApplicationFeatureTypeTest {
         @Test
         public void givenMember() throws Exception {
 
-            val applicationFeatureId = ApplicationFeatureId.createMember("com.mycompany.Bar#foo");
+            val applicationFeatureId = ApplicationFeatureId.newMember("com.mycompany.Bar#foo");
             
             assertThat(applicationFeatureId.getNamespace(), is("com.mycompany"));
             assertThat(applicationFeatureId.getTypeSimpleName(), is("Bar"));
@@ -91,7 +91,7 @@ public class ApplicationFeatureTypeTest {
 
             expectedException.expect(IllegalArgumentException.class);
             ApplicationFeatureId
-                    .createMember("com.mycompany.BarISMISSINGTHEHASHSYMBOL");
+                    .newMember("com.mycompany.BarISMISSINGTHEHASHSYMBOL");
         }
     }
 
@@ -102,17 +102,17 @@ public class ApplicationFeatureTypeTest {
 
         @Test
         public void whenPackage() throws Exception {
-            ApplicationFeatureSort.ensurePackage(new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE, "xxx"));
+            ApplicationFeatureSort.ensurePackage(ApplicationFeatureId.newFeature(ApplicationFeatureSort.NAMESPACE, "xxx"));
         }
         @Test
         public void whenClass() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensurePackage(new ApplicationFeatureId(ApplicationFeatureSort.TYPE, "xxx"));
+            ApplicationFeatureSort.ensurePackage(ApplicationFeatureId.newFeature(ApplicationFeatureSort.TYPE, "xxx"));
         }
         @Test
         public void whenMember() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensurePackage(new ApplicationFeatureId(ApplicationFeatureSort.MEMBER, "xxx#x"));
+            ApplicationFeatureSort.ensurePackage(ApplicationFeatureId.newFeature(ApplicationFeatureSort.MEMBER, "xxx#x"));
         }
     }
 
@@ -123,16 +123,16 @@ public class ApplicationFeatureTypeTest {
 
         @Test
         public void whenPackage() throws Exception {
-            ApplicationFeatureSort.ensurePackageOrClass(new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE, "xxx"));
+            ApplicationFeatureSort.ensurePackageOrClass(ApplicationFeatureId.newFeature(ApplicationFeatureSort.NAMESPACE, "xxx"));
         }
         @Test
         public void whenClass() throws Exception {
-            ApplicationFeatureSort.ensurePackageOrClass(new ApplicationFeatureId(ApplicationFeatureSort.TYPE, "xxx"));
+            ApplicationFeatureSort.ensurePackageOrClass(ApplicationFeatureId.newFeature(ApplicationFeatureSort.TYPE, "xxx"));
         }
         @Test
         public void whenMember() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensurePackageOrClass(new ApplicationFeatureId(ApplicationFeatureSort.MEMBER, "xxx#x"));
+            ApplicationFeatureSort.ensurePackageOrClass(ApplicationFeatureId.newFeature(ApplicationFeatureSort.MEMBER, "xxx#x"));
         }
 
     }
@@ -144,16 +144,16 @@ public class ApplicationFeatureTypeTest {
         @Test
         public void whenPackage() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensureClass(new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE, "xxx"));
+            ApplicationFeatureSort.ensureClass(ApplicationFeatureId.newFeature(ApplicationFeatureSort.NAMESPACE, "xxx"));
         }
         @Test
         public void whenClass() throws Exception {
-            ApplicationFeatureSort.ensureClass(new ApplicationFeatureId(ApplicationFeatureSort.TYPE, "xxx"));
+            ApplicationFeatureSort.ensureClass(ApplicationFeatureId.newFeature(ApplicationFeatureSort.TYPE, "xxx"));
         }
         @Test
         public void whenMember() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensureClass(new ApplicationFeatureId(ApplicationFeatureSort.MEMBER, "xxx#x"));
+            ApplicationFeatureSort.ensureClass(ApplicationFeatureId.newFeature(ApplicationFeatureSort.MEMBER, "xxx#x"));
         }
 
     }
@@ -165,16 +165,16 @@ public class ApplicationFeatureTypeTest {
         @Test
         public void whenPackage() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensureMember(new ApplicationFeatureId(ApplicationFeatureSort.NAMESPACE, "xxx"));
+            ApplicationFeatureSort.ensureMember(ApplicationFeatureId.newFeature(ApplicationFeatureSort.NAMESPACE, "xxx"));
         }
         @Test
         public void whenClass() throws Exception {
             expectedException.expect(IllegalStateException.class);
-            ApplicationFeatureSort.ensureMember(new ApplicationFeatureId(ApplicationFeatureSort.TYPE, "xxx"));
+            ApplicationFeatureSort.ensureMember(ApplicationFeatureId.newFeature(ApplicationFeatureSort.TYPE, "xxx"));
         }
         @Test
         public void whenMember() throws Exception {
-            ApplicationFeatureSort.ensureMember(new ApplicationFeatureId(ApplicationFeatureSort.MEMBER, "xxx#x"));
+            ApplicationFeatureSort.ensureMember(ApplicationFeatureId.newFeature(ApplicationFeatureSort.MEMBER, "xxx#x"));
         }
     }
 
diff --git a/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/app/user/UserPermissionViewModel.java b/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/app/user/UserPermissionViewModel.java
index 9b5e25f..cf82d24 100644
--- a/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/app/user/UserPermissionViewModel.java
+++ b/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/app/user/UserPermissionViewModel.java
@@ -186,7 +186,9 @@ public class UserPermissionViewModel implements ViewModel {
         final String viewingEvaluationCauseFeatureIdType = iterator.next();
         final ApplicationFeatureSort viewingEvaluationFeatureIdType =  !viewingEvaluationCauseFeatureIdType.isEmpty() ? ApplicationFeatureSort.valueOf(viewingEvaluationCauseFeatureIdType) : null;
         final String viewingEvaluationFeatureFqn = iterator.next();
-        this.viewingFeatureId = viewingEvaluationFeatureIdType != null? new ApplicationFeatureId(viewingEvaluationFeatureIdType,viewingEvaluationFeatureFqn) : null;
+        this.viewingFeatureId = viewingEvaluationFeatureIdType != null
+                ? ApplicationFeatureId.newFeature(viewingEvaluationFeatureIdType, viewingEvaluationFeatureFqn) 
+                : null;
 
         final String viewingEvaluationCauseRule = iterator.next();
         this.viewingRule = !viewingEvaluationCauseRule.isEmpty()? ApplicationPermissionRule.valueOf(viewingEvaluationCauseRule): null;
@@ -198,7 +200,9 @@ public class UserPermissionViewModel implements ViewModel {
         final String changingEvaluationCauseFeatureIdType = iterator.next();
         final ApplicationFeatureSort changingEvaluationFeatureIdType =  !changingEvaluationCauseFeatureIdType.isEmpty() ? ApplicationFeatureSort.valueOf(changingEvaluationCauseFeatureIdType) : null;
         final String changingEvaluationFeatureFqn = iterator.next();
-        this.changingFeatureId = changingEvaluationFeatureIdType != null? new ApplicationFeatureId(changingEvaluationFeatureIdType,changingEvaluationFeatureFqn) : null;
+        this.changingFeatureId = changingEvaluationFeatureIdType != null
+                ? ApplicationFeatureId.newFeature(changingEvaluationFeatureIdType, changingEvaluationFeatureFqn) 
+                : null;
 
         final String changingEvaluationCauseRule = iterator.next();
         this.changingRule = !changingEvaluationCauseRule.isEmpty()? ApplicationPermissionRule.valueOf(changingEvaluationCauseRule): null;
@@ -206,11 +210,10 @@ public class UserPermissionViewModel implements ViewModel {
         this.changingMode = !changingEvaluationCauseMode.isEmpty()? ApplicationPermissionMode.valueOf(changingEvaluationCauseMode): null;
 
         final ApplicationFeatureSort type = ApplicationFeatureSort.valueOf(iterator.next());
-        this.featureId = new ApplicationFeatureId(type, iterator.next());
+        this.featureId = ApplicationFeatureId.newFeature(type, iterator.next());
     }
 
 
-
     @Programmatic
     public String asEncodedString() {
         return asEncodedString(getFeatureId(), getUsername(), newEvaluation(viewingGranted, viewingFeatureId, viewingRule, viewingMode), newEvaluation(changingGranted, changingFeatureId, changingRule, changingMode));
diff --git a/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/dom/role/ApplicationRole_addPermission.java b/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/dom/role/ApplicationRole_addPermission.java
index d5293ee..8fa78bc 100644
--- a/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/dom/role/ApplicationRole_addPermission.java
+++ b/extensions/security/secman/model/src/main/java/org/apache/isis/extensions/secman/model/dom/role/ApplicationRole_addPermission.java
@@ -112,7 +112,7 @@ public class ApplicationRole_addPermission {
         return idsByName.entrySet().stream()
         .filter(entry->matches(entry.getKey(), entry.getValue(), search))
         .map(Map.Entry::getValue)
-        .map(ApplicationFeatureId::asString)
+        .map(ApplicationFeatureId::stringify)
         .collect(Collectors.toCollection(TreeSet::new));
     }