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/11/08 09:01:12 UTC

[isis] branch master updated: ISIS-2871: implement ValueSemanticsAnnotationFacetFactory

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


The following commit(s) were added to refs/heads/master by this push:
     new b2439bf  ISIS-2871: implement ValueSemanticsAnnotationFacetFactory
b2439bf is described below

commit b2439bf2721c0bc39f8e3e180c2da17d7128d92b
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Nov 8 10:01:02 2021 +0100

    ISIS-2871: implement ValueSemanticsAnnotationFacetFactory
---
 .../isis/applib/annotation/ValueSemantics.java     |  36 +--
 .../digits/MaxFractionalDigitsFacetAbstract.java   |  18 ++
 .../objectvalue/digits/MaxTotalDigitsFacet.java    |   2 +-
 .../digits/MaxTotalDigitsFacetAbstract.java        |  24 +-
 .../digits/MinIntegerDigitsFacetAbstract.java      |   4 +-
 ...sFacetFromJavaxValidationDigitsAnnotation.java} |   6 +-
 ...rameterFromJavaxValidationDigitsAnnotation.java |  48 ----
 ...sFacetFromJavaxValidationDigitsAnnotation.java} |   6 +-
 ...rameterFromJavaxValidationDigitsAnnotation.java |  49 ----
 .../ValueSemanticsAnnotationFacetFactory.java      |  33 ++-
 .../metamodel/facets/AbstractFacetFactoryTest.java |  52 ++--
 .../ValueSemanticsAnnotationFacetFactoryTest.java  | 275 ++++++++++++++++++---
 ...malFromJdoColumnAnnotationFacetFactoryTest.java |   2 +-
 .../viewer/common/model/feature/ScalarUiModel.java |   2 +-
 .../domainobjects/ObjectPropertyReprRenderer.java  |   2 +-
 15 files changed, 369 insertions(+), 190 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java
index c8986ba..25e5706 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/ValueSemantics.java
@@ -49,24 +49,6 @@ public @interface ValueSemantics {
 
     /**
      * EXPERIMENTAL - considered to be moved to a separate {@code @Digits} annotation<p>
-     * If associated with a {@link Number}, the minimum number of integer digits required for
-     * this number.<br>
-     * default = {@code 1}
-     */
-    int minIntegerDigits()
-            default 1;
-
-    /**
-     * EXPERIMENTAL - considered to be moved to a separate {@code @Digits} annotation<p>
-     * If associated with a {@link BigDecimal}, the minimum number of fractional digits
-     * required for this number.<br>
-     * default = {@code 0}
-     */
-    int minFractionalDigits()
-            default 0;
-
-    /**
-     * EXPERIMENTAL - considered to be moved to a separate {@code @Digits} annotation<p>
      * If associated with a {@link Number}, the maximum number of total digits accepted for
      * this number.<br>
      * Can be omitted, if {@link Column#precision()} is used.<br>
@@ -79,6 +61,15 @@ public @interface ValueSemantics {
 
     /**
      * EXPERIMENTAL - considered to be moved to a separate {@code @Digits} annotation<p>
+     * If associated with a {@link Number}, the minimum number of integer digits required for
+     * this number.<br>
+     * default = {@code 1}
+     */
+    int minIntegerDigits()
+            default 1;
+
+    /**
+     * EXPERIMENTAL - considered to be moved to a separate {@code @Digits} annotation<p>
      * If associated with a {@link BigDecimal}, the maximum number of fractional digits accepted
      * for this number.<br>
      * Can be omitted, if {@link Column#scale()} is used.<br>
@@ -89,4 +80,13 @@ public @interface ValueSemantics {
     int maxFractionalDigits()
             default 30;
 
+    /**
+     * EXPERIMENTAL - considered to be moved to a separate {@code @Digits} annotation<p>
+     * If associated with a {@link BigDecimal}, the minimum number of fractional digits
+     * required for this number.<br>
+     * default = {@code 0}
+     */
+    int minFractionalDigits()
+            default 0;
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxFractionalDigitsFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxFractionalDigitsFacetAbstract.java
index 6a4f3cd..841615c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxFractionalDigitsFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxFractionalDigitsFacetAbstract.java
@@ -18,6 +18,7 @@
  */
 package org.apache.isis.core.metamodel.facets.objectvalue.digits;
 
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -70,4 +71,21 @@ implements MaxFractionalDigitsFacet {
                 : String.valueOf(maxFractionalDigits));
     }
 
+    /**
+     * If equal, first argument wins over second.
+     */
+    public static Optional<MaxFractionalDigitsFacet> minimum(
+            final Optional<MaxFractionalDigitsFacet> a,
+            final Optional<MaxFractionalDigitsFacet> b) {
+        if(b.isEmpty()) {
+            return a;
+        }
+        if(a.isEmpty()) {
+            return b;
+        }
+        return a.get().getMaxFractionalDigits() <= b.get().getMaxFractionalDigits()
+                ? a
+                : b;
+    }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacet.java
index dca2c9b..3c7edde 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacet.java
@@ -41,6 +41,6 @@ extends Facet {
      * eg. as provided by {@link Digits#fraction()}
      * and {@link ValueSemantics#maxTotalDigits()}
      */
-    int maxTotalDigits();
+    int getMaxTotalDigits();
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacetAbstract.java
index 474053a..2d832f8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MaxTotalDigitsFacetAbstract.java
@@ -18,6 +18,7 @@
  */
 package org.apache.isis.core.metamodel.facets.objectvalue.digits;
 
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -26,7 +27,6 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 
 import lombok.Getter;
 import lombok.NonNull;
-import lombok.experimental.Accessors;
 
 public abstract class MaxTotalDigitsFacetAbstract
 extends FacetAbstract
@@ -36,7 +36,7 @@ implements MaxTotalDigitsFacet {
         return MaxTotalDigitsFacet.class;
     }
 
-    @Getter(onMethod_ = {@Override}) @Accessors(fluent = true)
+    @Getter(onMethod_ = {@Override})
     private final int maxTotalDigits;
 
     protected MaxTotalDigitsFacetAbstract(
@@ -58,8 +58,8 @@ implements MaxTotalDigitsFacet {
     public boolean semanticEquals(@NonNull final Facet other) {
         return other instanceof MaxTotalDigitsFacet
                 ? Integer.compare(
-                        this.maxTotalDigits(),
-                        ((MaxTotalDigitsFacet)other).maxTotalDigits()) == 0
+                        this.getMaxTotalDigits(),
+                        ((MaxTotalDigitsFacet)other).getMaxTotalDigits()) == 0
                 : false;
     }
 
@@ -71,4 +71,20 @@ implements MaxTotalDigitsFacet {
                 : String.valueOf(maxTotalDigits));
     }
 
+    /**
+     * If equal, first argument wins over second.
+     */
+    public static Optional<MaxTotalDigitsFacet> minimum(
+            final Optional<MaxTotalDigitsFacet> a,
+            final Optional<MaxTotalDigitsFacet> b) {
+        if(b.isEmpty()) {
+            return a;
+        }
+        if(a.isEmpty()) {
+            return b;
+        }
+        return a.get().getMaxTotalDigits() <= b.get().getMaxTotalDigits()
+                ? a
+                : b;
+    }
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MinIntegerDigitsFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MinIntegerDigitsFacetAbstract.java
index 787ff20..7a03bd4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MinIntegerDigitsFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/digits/MinIntegerDigitsFacetAbstract.java
@@ -55,10 +55,10 @@ implements MinIntegerDigitsFacet {
 
     @Override
     public boolean semanticEquals(@NonNull final Facet other) {
-        return other instanceof MinFractionalDigitsFacet
+        return other instanceof MinIntegerDigitsFacet
                 ? Integer.compare(
                         this.getMinIntegerDigits(),
-                        ((MinFractionalDigitsFacet)other).getMinFractionalDigits()) == 0
+                        ((MinIntegerDigitsFacet)other).getMinIntegerDigits()) == 0
                 : false;
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation.java
similarity index 86%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation.java
index 352c2dd..0395bd0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation.java
@@ -26,7 +26,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacetAbstract;
 
-public class MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation
+public class MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation
 extends MaxFractionalDigitsFacetAbstract {
 
      public static Optional<MaxFractionalDigitsFacet> create(
@@ -35,12 +35,12 @@ extends MaxFractionalDigitsFacetAbstract {
 
          return digitsIfAny
          .map(digits->{
-             return new MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation(
+             return new MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation(
                      digits.fraction(), holder);
          });
     }
 
-    private MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation(
+    private MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation(
             final int maxFractionalDigits, final FacetHolder holder) {
         super(maxFractionalDigits, holder);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation.java
deleted file mode 100644
index a7ac711..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation.java
+++ /dev/null
@@ -1,48 +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.isis.core.metamodel.facets.value.semantics;
-
-import java.util.Optional;
-
-import javax.validation.constraints.Digits;
-
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacet;
-import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacetAbstract;
-
-public class MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation
-extends MaxFractionalDigitsFacetAbstract {
-
-     public static Optional<MaxFractionalDigitsFacet> create(
-             final Optional<Digits> digitsIfAny,
-             final FacetHolder holder) {
-
-         return digitsIfAny
-         .map(digits->{
-             return new MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation(
-                     digits.fraction(), holder);
-         });
-    }
-
-    private MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation(
-            final int maxFractionalDigits, final FacetHolder holder) {
-        super(maxFractionalDigits, holder);
-    }
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation.java
similarity index 87%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation.java
index d3b7ec4..9a36fea 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation.java
@@ -26,7 +26,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacetAbstract;
 
-public class MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation
+public class MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation
 extends MaxTotalDigitsFacetAbstract {
 
     public static Optional<MaxTotalDigitsFacet> create(
@@ -35,12 +35,12 @@ extends MaxTotalDigitsFacetAbstract {
 
         return digitsIfAny
         .map(digits->{
-            return new MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation(
+            return new MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation(
                     digits.integer() + digits.fraction(), holder);
         });
    }
 
-   private MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation(
+   private MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation(
            final int maxTotalDigits, final FacetHolder holder) {
        super(maxTotalDigits, holder);
    }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation.java
deleted file mode 100644
index 5c18a67..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation.java
+++ /dev/null
@@ -1,49 +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.isis.core.metamodel.facets.value.semantics;
-
-import java.util.Optional;
-
-import javax.validation.constraints.Digits;
-
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacet;
-import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacetAbstract;
-
-public class MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation
-extends MaxTotalDigitsFacetAbstract {
-
-    public static Optional<MaxTotalDigitsFacet> create(
-            final Optional<Digits> digitsIfAny,
-            final FacetHolder holder) {
-
-        return digitsIfAny
-        .map(digits->{
-            return new MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation(
-                    digits.integer() + digits.fraction(), holder);
-        });
-   }
-
-   private MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation(
-           final int maxTotalDigits, final FacetHolder holder) {
-       super(maxTotalDigits, holder);
-   }
-
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactory.java
index 3bc035f..ea2aee0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactory.java
@@ -29,6 +29,8 @@ import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.TypedHolderAbstract;
+import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacetAbstract;
+import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacetAbstract;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 
 import lombok.val;
@@ -49,7 +51,7 @@ extends FacetFactoryAbstract {
                         () -> MetaModelValidatorForAmbiguousMixinAnnotations
                             .addValidationFailure(processMethodContext.getFacetHolder(), ValueSemantics.class));
 
-        // support for @javax.validataion.Digits
+        // support for @javax.validation.constraints.Digits
         val digitsIfAny = BigDecimal.class == processMethodContext.getMethod().getReturnType()
                 ? processMethodContext
                     .synthesizeOnMethodOrMixinType(
@@ -65,7 +67,7 @@ extends FacetFactoryAbstract {
     public void processParams(final ProcessParameterContext processParameterContext) {
         val valueSemanticsIfAny = processParameterContext.synthesizeOnParameter(ValueSemantics.class);
 
-        // support for @javax.validataion.Digits
+        // support for @javax.validation.constraints.Digits
         val digitsIfAny = BigDecimal.class == processParameterContext.getParameterType()
                 ? processParameterContext.synthesizeOnParameter(Digits.class)
                 : Optional.<Digits>empty();
@@ -100,14 +102,31 @@ extends FacetFactoryAbstract {
             final Optional<Digits> digitsIfAny){
 
         addFacetIfPresent(
-                MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation
-                .create(digitsIfAny, facetHolder));
+                MaxTotalDigitsFacetAbstract.minimum(
+                        MaxTotalDigitsFacetFromValueSemanticsAnnotation
+                        .create(valueSemanticsIfAny, facetHolder),
+                        // support for @javax.validation.constraints.Digits
+                        MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation
+                        .create(digitsIfAny, facetHolder)
+                        ));
 
         addFacetIfPresent(
-                MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation
-                .create(digitsIfAny, facetHolder));
+                MinIntegerDigitsFacetFromValueSemanticsAnnotation
+                .create(valueSemanticsIfAny, facetHolder));
+
+        addFacetIfPresent(
+                MaxFractionalDigitsFacetAbstract.minimum(
+                        MaxFractionalDigitsFacetFromValueSemanticsAnnotation
+                        .create(valueSemanticsIfAny, facetHolder),
+                        // support for @javax.validation.constraints.Digits
+                        MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation
+                        .create(digitsIfAny, facetHolder)
+                        ));
+
+        addFacetIfPresent(
+                MinFractionalDigitsFacetFromValueSemanticsAnnotation
+                .create(valueSemanticsIfAny, facetHolder));
 
-        //FIXME[ISIS-2871] actually process @ValueSemantics(min/max...) and merge with the above
     }
 
 }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/AbstractFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/AbstractFacetFactoryTest.java
index 54d8a2c..0dec4da 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/AbstractFacetFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/AbstractFacetFactoryTest.java
@@ -25,12 +25,12 @@ import org.jmock.Expectations;
 import org.junit.Rule;
 
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.annotation.Introspection.IntrospectionPolicy;
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.services.iactn.InteractionProvider;
 import org.apache.isis.applib.services.iactnlayer.InteractionContext;
 import org.apache.isis.commons.collections.ImmutableEnumSet;
-import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
 import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel._testing.MethodRemover_forTesting;
@@ -38,6 +38,7 @@ import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderAbstract;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext;
 import org.apache.isis.core.metamodel.facets.actions.layout.ActionLayoutFacetFactory;
 import org.apache.isis.core.metamodel.facets.collections.layout.CollectionLayoutFacetFactory;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.PropertyLayoutFacetFactory;
@@ -138,14 +139,38 @@ public abstract class AbstractFacetFactoryTest extends TestCase {
         return Utils.contains(featureTypes, featureType);
     }
 
-    protected static Method findMethod(final Class<?> type, final String methodName, final Class<?>[] methodTypes) {
-        return Utils.findMethod(type, methodName, methodTypes);
+    protected static Method findMethod(final Class<?> type, final String methodName, final Class<?>[] signature) {
+        return Utils.findMethod(type, methodName, signature);
     }
 
     protected Method findMethod(final Class<?> type, final String methodName) {
         return Utils.findMethod(type, methodName);
     }
 
+    protected void processMethod(
+            final FacetFactory facetFactory,
+            final Class<?> type,
+            final String methodName,
+            final Class<?>[] signature) {
+
+        facetFactory.process(ProcessMethodContext
+                .forTesting(type, null,
+                        findMethod(type, methodName, signature),
+                        methodRemover, facetedMethod));
+    }
+
+    protected void processParams(
+            final FacetFactory facetFactory,
+            final Class<?> type,
+            final String methodName,
+            final Class<?>[] signature) {
+
+        facetFactory.processParams(new FacetFactory
+                .ProcessParameterContext(type, IntrospectionPolicy.ANNOTATION_OPTIONAL,
+                        findMethod(type, methodName, signature),
+                        null, facetedMethodParameter));
+    }
+
     protected void assertNoMethodsRemoved() {
         assertTrue(methodRemover.getRemovedMethodMethodCalls().isEmpty());
         assertTrue(methodRemover.getRemoveMethodArgsCalls().isEmpty());
@@ -154,30 +179,15 @@ public abstract class AbstractFacetFactoryTest extends TestCase {
     // -- FACTORIES
 
     protected static PropertyLayoutFacetFactory createPropertyLayoutFacetFactory(final MetaModelContext mmc) {
-        return new PropertyLayoutFacetFactory(mmc) {
-            @Override
-            public IsisConfiguration getConfiguration() {
-                return new IsisConfiguration(null);
-            }
-        };
+        return new PropertyLayoutFacetFactory(mmc);
     }
 
     protected static CollectionLayoutFacetFactory createCollectionLayoutFacetFactory(final MetaModelContext mmc) {
-        return new CollectionLayoutFacetFactory(mmc) {
-            @Override
-            public IsisConfiguration getConfiguration() {
-                return new IsisConfiguration(null);
-            }
-        };
+        return new CollectionLayoutFacetFactory(mmc);
     }
 
     protected static ActionLayoutFacetFactory createActionLayoutFacetFactory(final MetaModelContext mmc) {
-        return new ActionLayoutFacetFactory(mmc) {
-            @Override
-            public IsisConfiguration getConfiguration() {
-                return new IsisConfiguration(null);
-            }
-        };
+        return new ActionLayoutFacetFactory(mmc);
     }
 
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactoryTest.java
index 8a11e1c..85c230e 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactoryTest.java
@@ -1,77 +1,290 @@
 package org.apache.isis.core.metamodel.facets.value.semantics;
 
-import java.lang.reflect.Method;
 import java.math.BigDecimal;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import org.apache.isis.applib.annotation.Introspection.IntrospectionPolicy;
+import org.apache.isis.applib.annotation.ValueSemantics;
+import org.apache.isis.commons.internal._Constants;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
-import org.apache.isis.core.metamodel.facets.FacetFactory;
-import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext;
 import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.digits.MinFractionalDigitsFacet;
+import org.apache.isis.core.metamodel.facets.objectvalue.digits.MinIntegerDigitsFacet;
 
+@SuppressWarnings("unused")
 public class ValueSemanticsAnnotationFacetFactoryTest
 extends AbstractFacetFactoryTest {
 
-    public void testAnnotationPickedUpOnProperty() {
-        final ValueSemanticsAnnotationFacetFactory facetFactory =
-                new ValueSemanticsAnnotationFacetFactory(metaModelContext);
+    // -- MAX TOTAL DIGITS
 
+    public void testMaxTotalPickedUpOnProperty() {
+        // given
         class Order {
-            @javax.validation.constraints.Digits(integer=14, fraction=4)
-            public BigDecimal getCost() {
-                return null;
-            }
+            @ValueSemantics(maxTotalDigits = 5)
+            public BigDecimal getCost() { return null; }
         }
-        final Method method = findMethod(Order.class, "getCost");
+        // when
+        processMethod(newFacetFactory(), Order.class, "getCost", _Constants.emptyClasses);
+        // then
+        assertMaxTotalDigits(facetedMethod, 5);
+        assertDefaultMinIntegerDigits(facetedMethod);
+        assertDefaultMaxFractionalDigits(facetedMethod);
+        assertDefaultMinFractionalDigits(facetedMethod);
+    }
 
-        facetFactory.process(ProcessMethodContext
-                .forTesting(Order.class, null, method, methodRemover, facetedMethod));
+    public void testMaxTotalPickedUpOnActionParameter() {
+        // given
+        class Order {
+            public void updateCost(
+                    @ValueSemantics(maxTotalDigits = 5)
+                    final BigDecimal cost) { }
+        }
+        // when
+        processParams(newFacetFactory(), Order.class, "updateCost", new Class[] { BigDecimal.class });
+        // then
+        assertMaxTotalDigits(facetedMethodParameter, 5);
+        assertDefaultMinIntegerDigits(facetedMethodParameter);
+        assertDefaultMaxFractionalDigits(facetedMethodParameter);
+        assertDefaultMinFractionalDigits(facetedMethodParameter);
+    }
+
+    // -- MIN INTEGER DIGITS
+
+    public void testMinIntegerPickedUpOnProperty() {
+        // given
+        class Order {
+            @ValueSemantics(minIntegerDigits = 5)
+            public BigDecimal getCost() { return null; }
+        }
+        // when
+        processMethod(newFacetFactory(), Order.class, "getCost", _Constants.emptyClasses);
+        // then
+        assertDefaultMaxTotalDigits(facetedMethod);
+        assertMinIntegerDigits(facetedMethod, 5);
+        assertDefaultMaxFractionalDigits(facetedMethod);
+        assertDefaultMinFractionalDigits(facetedMethod);
+    }
 
-        assertBigDecimalSemantics(facetedMethod, 18, 4);
+    public void testMinIntegerPickedUpOnActionParameter() {
+        // given
+        class Order {
+            public void updateCost(
+                    @ValueSemantics(minIntegerDigits = 5)
+                    final BigDecimal cost) { }
+        }
+        // when
+        processParams(newFacetFactory(), Order.class, "updateCost", new Class[] { BigDecimal.class });
+        // then
+        assertDefaultMaxTotalDigits(facetedMethodParameter);
+        assertMinIntegerDigits(facetedMethodParameter, 5);
+        assertDefaultMaxFractionalDigits(facetedMethodParameter);
+        assertDefaultMinFractionalDigits(facetedMethodParameter);
     }
 
-    public void testAnnotationPickedUpOnActionParameter() {
-        final ValueSemanticsAnnotationFacetFactory facetFactory =
-                new ValueSemanticsAnnotationFacetFactory(metaModelContext);
+    // -- MAX FRACTIONAL DIGITS
+
+    public void testMaxFracionalPickedUpOnProperty() {
+        // given
+        class Order {
+            @ValueSemantics(maxFractionalDigits = 5)
+            public BigDecimal getCost() { return null; }
+        }
+        // when
+        processMethod(newFacetFactory(), Order.class, "getCost", _Constants.emptyClasses);
+        // then
+        assertDefaultMaxTotalDigits(facetedMethod);
+        assertDefaultMinIntegerDigits(facetedMethod);
+        assertMaxFractionalDigits(facetedMethod, 5);
+        assertDefaultMinFractionalDigits(facetedMethod);
+    }
+
+    public void testMaxFracionalPickedUpOnActionParameter() {
+        // given
+        class Order {
+            public void updateCost(
+                    @ValueSemantics(maxFractionalDigits = 5)
+                    final BigDecimal cost) { }
+        }
+        // when
+        processParams(newFacetFactory(), Order.class, "updateCost", new Class[] { BigDecimal.class });
+        // then
+        assertDefaultMaxTotalDigits(facetedMethodParameter);
+        assertDefaultMinIntegerDigits(facetedMethodParameter);
+        assertMaxFractionalDigits(facetedMethodParameter, 5);
+        assertDefaultMinFractionalDigits(facetedMethodParameter);
+    }
+
+    // -- MIN FRACTIONAL DIGITS
+
+    public void testMinFracionalPickedUpOnProperty() {
+        // given
+        class Order {
+            @ValueSemantics(minFractionalDigits = 5)
+            public BigDecimal getCost() { return null; }
+        }
+        // when
+        processMethod(newFacetFactory(), Order.class, "getCost", _Constants.emptyClasses);
+        // then
+        assertDefaultMaxTotalDigits(facetedMethod);
+        assertDefaultMinIntegerDigits(facetedMethod);
+        assertDefaultMaxFractionalDigits(facetedMethod);
+        assertMinFractionalDigits(facetedMethod, 5);
+    }
+
+    public void testMinFracionalPickedUpOnActionParameter() {
+        // given
+        class Order {
+            public void updateCost(
+                    @ValueSemantics(minFractionalDigits = 5)
+                    final BigDecimal cost) { }
+        }
+        // when
+        processParams(newFacetFactory(), Order.class, "updateCost", new Class[] { BigDecimal.class });
+        // then
+        assertDefaultMaxTotalDigits(facetedMethodParameter);
+        assertDefaultMinIntegerDigits(facetedMethodParameter);
+        assertDefaultMaxFractionalDigits(facetedMethodParameter);
+        assertMinFractionalDigits(facetedMethodParameter, 5);
+    }
+
+    // -- DIGITS ANNOTATION
+
+    public void testDigitsAnnotationPickedUpOnProperty() {
+        // given
+        class Order {
+            @javax.validation.constraints.Digits(integer=14, fraction=4)
+            public BigDecimal getCost() { return null; }
+        }
+        // when
+        processMethod(newFacetFactory(), Order.class, "getCost", _Constants.emptyClasses);
+        // then
+        assertDigitsFacets(facetedMethod, 18, 4);
+    }
 
+    public void testDigitsAnnotationPickedUpOnActionParameter() {
+        // given
         class Order {
-            @SuppressWarnings("unused")
             public void updateCost(
                     @javax.validation.constraints.Digits(integer=14, fraction=4)
-                    final BigDecimal cost) {
-            }
+                    final BigDecimal cost) { }
+        }
+        // when
+        processParams(newFacetFactory(), Order.class, "updateCost", new Class[] { BigDecimal.class });
+        // then
+        assertDigitsFacets(facetedMethodParameter, 18, 4);
+    }
+
+    // -- CONTRAINT MERGERS
+
+    public void testMultipleAnnotationsMergedOnProperty() {
+        // given
+        class Order {
+
+            @javax.validation.constraints.Digits(integer=14, fraction=4)
+            @ValueSemantics(maxTotalDigits = 19)
+            public BigDecimal maxTotalA() { return null; }
+
+            @javax.validation.constraints.Digits(integer=14, fraction=5)
+            @ValueSemantics(maxTotalDigits = 17)
+            public BigDecimal maxTotalB() { return null; }
+
+            @javax.validation.constraints.Digits(integer=14, fraction=4)
+            @ValueSemantics(maxFractionalDigits = 5)
+            public BigDecimal maxFracA() { return null; }
+
+            @javax.validation.constraints.Digits(integer=14, fraction=5)
+            @ValueSemantics(maxFractionalDigits = 4)
+            public BigDecimal maxFracB() { return null; }
+
         }
-        final Method method = findMethod(Order.class, "updateCost", new Class[] { BigDecimal.class });
 
-        facetFactory.processParams(new FacetFactory
-                .ProcessParameterContext(Customer.class, IntrospectionPolicy.ANNOTATION_OPTIONAL, method, null, facetedMethodParameter));
+        // when
+        processMethod(newFacetFactory(), Order.class, "maxTotalA", _Constants.emptyClasses);
+        // then - lowest bound wins
+        assertMaxTotalDigits(facetedMethod, 18);
 
-        assertBigDecimalSemantics(facetedMethodParameter, 18, 4);
+        // when
+        processMethod(newFacetFactory(), Order.class, "maxTotalB", _Constants.emptyClasses);
+        // then - lowest bound wins
+        assertMaxTotalDigits(facetedMethod, 17);
 
+        // when
+        processMethod(newFacetFactory(), Order.class, "maxFracA", _Constants.emptyClasses);
+        // then - lowest bound wins
+        assertMaxFractionalDigits(facetedMethod, 4);
+
+        // when
+        processMethod(newFacetFactory(), Order.class, "maxFracB", _Constants.emptyClasses);
+        // then - lowest bound wins
+        assertMaxFractionalDigits(facetedMethod, 4);
     }
 
     // -- HELPER
 
-    private void assertBigDecimalSemantics(
+    ValueSemanticsAnnotationFacetFactory newFacetFactory() {
+        return new ValueSemanticsAnnotationFacetFactory(metaModelContext);
+    }
+
+    private void assertDefaultMaxTotalDigits(final FacetHolder facetedMethod) {
+        assertMaxTotalDigits(facetedMethod, 65);
+    }
+
+    private void assertDefaultMinIntegerDigits(final FacetHolder facetedMethod) {
+        assertMinIntegerDigits(facetedMethod, 1);
+    }
+
+    private void assertDefaultMaxFractionalDigits(final FacetHolder facetedMethod) {
+        assertMaxFractionalDigits(facetedMethod, 30);
+    }
+
+    private void assertDefaultMinFractionalDigits(final FacetHolder facetedMethod) {
+        assertMinFractionalDigits(facetedMethod, 0);
+    }
+
+    private void assertMaxTotalDigits(
+            final FacetHolder facetedMethod, final int maxTotalDigits) {
+        final MaxTotalDigitsFacet facet = facetedMethod.getFacet(MaxTotalDigitsFacet.class);
+        assertNotNull(facet);
+        assertThat(facet.getMaxTotalDigits(), is(maxTotalDigits));
+    }
+
+    private void assertMinIntegerDigits(
+            final FacetHolder facetedMethod, final int minIntegerDigits) {
+        final MinIntegerDigitsFacet facet = facetedMethod.getFacet(MinIntegerDigitsFacet.class);
+        assertNotNull(facet);
+        assertThat(facet.getMinIntegerDigits(), is(minIntegerDigits));
+    }
+
+    private void assertMaxFractionalDigits(
+            final FacetHolder facetedMethod, final int maxFractionalDigits) {
+        final MaxFractionalDigitsFacet facet = facetedMethod.getFacet(MaxFractionalDigitsFacet.class);
+        assertNotNull(facet);
+        assertThat(facet.getMaxFractionalDigits(), is(maxFractionalDigits));
+    }
+
+    private void assertMinFractionalDigits(
+            final FacetHolder facetedMethod, final int minFractionalDigits) {
+        final MinFractionalDigitsFacet facet = facetedMethod.getFacet(MinFractionalDigitsFacet.class);
+        assertNotNull(facet);
+        assertThat(facet.getMinFractionalDigits(), is(minFractionalDigits));
+    }
+
+    private void assertDigitsFacets(
             final FacetHolder facetedMethod, final int maxTotalDigits, final int maxFractionalDigits) {
         if(maxTotalDigits>=0) {
             final MaxTotalDigitsFacet facet = facetedMethod.getFacet(MaxTotalDigitsFacet.class);
             assertNotNull(facet);
-            assertTrue(facet instanceof MaxTotalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation
-                    ||facet instanceof MaxTotalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation);
-            assertThat(facet.maxTotalDigits(), is(maxTotalDigits));
+            assertTrue(facet instanceof MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation);
+            assertThat(facet.getMaxTotalDigits(), is(maxTotalDigits));
         }
 
         if(maxFractionalDigits>=0) {
             final MaxFractionalDigitsFacet facet = facetedMethod.getFacet(MaxFractionalDigitsFacet.class);
             assertNotNull(facet);
-            assertTrue(facet instanceof MaxFractionalDigitsFacetOnPropertyFromJavaxValidationDigitsAnnotation
-                    ||facet instanceof MaxFractionalDigitsFacetOnParameterFromJavaxValidationDigitsAnnotation);
+            assertTrue(facet instanceof MaxFractionalDigitsFacetFromJavaxValidationDigitsAnnotation);
             assertThat(facet.getMaxFractionalDigits(), is(maxFractionalDigits));
         }
     }
diff --git a/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/facets/prop/column/BigDecimalFromJdoColumnAnnotationFacetFactoryTest.java b/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/facets/prop/column/BigDecimalFromJdoColumnAnnotationFacetFactoryTest.java
index def2f7e..2006bfd 100644
--- a/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/facets/prop/column/BigDecimalFromJdoColumnAnnotationFacetFactoryTest.java
+++ b/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/facets/prop/column/BigDecimalFromJdoColumnAnnotationFacetFactoryTest.java
@@ -106,7 +106,7 @@ extends AbstractFacetFactoryTest {
             final MaxTotalDigitsFacet facet = facetedMethod.getFacet(MaxTotalDigitsFacet.class);
             assertNotNull(facet);
             assertTrue(facet instanceof MaxTotalDigitsFacetFromJdoColumnAnnotation);
-            assertThat(facet.maxTotalDigits(), is(maxTotalDigits));
+            assertThat(facet.getMaxTotalDigits(), is(maxTotalDigits));
         } else {
             assertNull(facetedMethod.getFacet(MaxTotalDigitsFacet.class));
         }
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/feature/ScalarUiModel.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/feature/ScalarUiModel.java
index 57c1b46..08991fc 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/feature/ScalarUiModel.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/feature/ScalarUiModel.java
@@ -57,7 +57,7 @@ public interface ScalarUiModel {
      */
     default Integer getLength() {
         return getMetaModel().lookupFacet(MaxTotalDigitsFacet.class)
-                .map(MaxTotalDigitsFacet::maxTotalDigits)
+                .map(MaxTotalDigitsFacet::getMaxTotalDigits)
                 .orElse(null);
     }
 
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
index e5d3bec..fbb15dd 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
@@ -104,7 +104,7 @@ extends AbstractObjectMemberReprRenderer<OneToOneAssociation> {
                         valueAdapterIfAny != null ? valueAdapterIfAny.getSpecification() : null);
 
                 final int totalDigits = lookupFacet(MaxTotalDigitsFacet.class, facetHolders)
-                        .map(MaxTotalDigitsFacet::maxTotalDigits)
+                        .map(MaxTotalDigitsFacet::getMaxTotalDigits)
                         .orElse(-1);
 
                 final int scale = lookupFacet(MaxFractionalDigitsFacet.class, facetHolders)