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/02 13:47:37 UTC
[isis] branch master updated: ISIS-2741: intermediate - work on
MaximumFractionDigits to be provided via meta meta-model
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 7325189 ISIS-2741: intermediate - work on MaximumFractionDigits to be provided via meta meta-model
7325189 is described below
commit 7325189d518b9282a6572ff75948bb45c0425bc2
Author: andi-huber <ah...@apache.org>
AuthorDate: Tue Nov 2 14:47:19 2021 +0100
ISIS-2741: intermediate - work on MaximumFractionDigits to be provided
via meta meta-model
- not sure yet how to do this consistently
---
.../applib/adapters/ValueSemanticsAbstract.java | 4 +
.../applib/adapters/ValueSemanticsProvider.java | 10 ++
.../_testing/InteractionService_forTesting.java | 83 ++++++---
.../scalars/ConverterBasedOnValueSemantics.java | 6 +-
.../jdkmath/BigDecimalConverter_roundtrip.java | 195 +++++++++++++++++++++
5 files changed, 267 insertions(+), 31 deletions(-)
diff --git a/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsAbstract.java b/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsAbstract.java
index 22f1c96..fe09cc8 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsAbstract.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsAbstract.java
@@ -121,6 +121,9 @@ implements ValueSemanticsProvider<T> {
}
val format = getNumberFormat(context);
format.setParseBigDecimal(true);
+ if(context.getMaximumFractionDigits()>-1) {
+ format.setMaximumFractionDigits(context.getMaximumFractionDigits());
+ }
val position = new ParsePosition(0);
try {
@@ -132,6 +135,7 @@ implements ValueSemanticsProvider<T> {
}
return number;
} catch (final NumberFormatException | ParseException e) {
+ System.err.printf("suppressed message %s%n", e.getMessage());
throw new TextEntryParseException("Not a decimal value " + input, e);
}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsProvider.java b/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsProvider.java
index f62d12b..955a65e 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsProvider.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/adapters/ValueSemanticsProvider.java
@@ -50,8 +50,18 @@ public interface ValueSemanticsProvider<T> {
@lombok.Value(staticConstructor = "of")
class Context {
+
Identifier identifier;
InteractionContext interactionContext;
+ /**
+ * Optional. Used for for decimal values, when a fixed amount of fractional digits is specified, otherwise {@code -1}
+ */
+ public int getMaximumFractionDigits() {
+ // FIXME[ISIS-2741] provide some means to recover the meta-model member from feature-id,
+ // so we can evaluate facets that provide the MaximumFractionDigits
+ // - as an alternative move this to the Parser<T>
+ return -1;
+ }
}
Class<T> getCorrespondingClass();
diff --git a/core/security/src/main/java/org/apache/isis/core/security/_testing/InteractionService_forTesting.java b/core/security/src/main/java/org/apache/isis/core/security/_testing/InteractionService_forTesting.java
index 8210088..c9b1d93 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/_testing/InteractionService_forTesting.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/_testing/InteractionService_forTesting.java
@@ -19,6 +19,7 @@
package org.apache.isis.core.security._testing;
import java.util.Optional;
+import java.util.Stack;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.function.Function;
@@ -33,6 +34,7 @@ import org.apache.isis.applib.services.user.UserMemento;
import org.apache.isis.commons.functional.ThrowingRunnable;
import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
/**
@@ -42,39 +44,29 @@ import lombok.SneakyThrows;
public class InteractionService_forTesting
implements InteractionService {
- private InteractionLayer interactionLayer = null;
+ private Stack<InteractionLayer> interactionLayers = new Stack<>();
@Override
public InteractionLayer openInteraction() {
final UserMemento userMemento = UserMemento.system();
- final UUID uuid = UUID.randomUUID();
- final Interaction interaction = new Interaction() {
- @Override public <T> T putAttribute(Class<? super T> type, T value) { return null; }
- @Override public <T> T computeAttributeIfAbsent(Class<? super T> type, Function<Class<?>, ? extends T> mappingFunction) { return null; }
- @Override public <T> T getAttribute(Class<T> type) { return null; }
- @Override public void removeAttribute(Class<?> type) { }
- @Override public UUID getInteractionId() { return uuid; }
- @Override public Command getCommand() { return null; }
- @Override public Execution<?, ?> getCurrentExecution() { return null; }
- @Override public Execution<?, ?> getPriorExecution() { return null; }
- };
- interactionLayer = new InteractionLayer(interaction, InteractionContext.ofUserWithSystemDefaults(userMemento));
- return null;
+ return openInteraction(InteractionContext.ofUserWithSystemDefaults(userMemento));
}
@Override
- public InteractionLayer openInteraction(@NonNull InteractionContext interactionContext) {
- return openInteraction();
+ public InteractionLayer openInteraction(@NonNull final InteractionContext interactionContext) {
+ final Interaction interaction = new Interaction_forTesting();
+ return interactionLayers.push(
+ new InteractionLayer(interaction, interactionContext));
}
@Override
public void closeInteractionLayers() {
- interactionLayer = null;
+ interactionLayers.clear();
}
@Override
public boolean isInInteraction() {
- return interactionLayer != null;
+ return interactionLayers.size()>0;
}
@Override public Optional<UUID> getInteractionId() {
@@ -84,32 +76,67 @@ implements InteractionService {
}
@Override public Optional<InteractionLayer> currentInteractionLayer() {
- return Optional.ofNullable(this.interactionLayer);
+ return interactionLayers.isEmpty()
+ ? Optional.empty()
+ : Optional.of(interactionLayers.peek());
}
@Override public int getInteractionLayerCount() {
- return isInInteraction() ? 1 : 0;
+ return interactionLayers.size();
}
@Override @SneakyThrows
- public <R> R call(@NonNull InteractionContext interactionContext, @NonNull Callable<R> callable) {
- return callable.call();
+ public <R> R call(@NonNull final InteractionContext interactionContext, @NonNull final Callable<R> callable) {
+ try {
+ openInteraction(interactionContext);
+ return callable.call();
+ } finally {
+ interactionLayers.pop();
+ }
}
@Override @SneakyThrows
- public void run(@NonNull InteractionContext interactionContext, @NonNull ThrowingRunnable runnable) {
- runnable.run();
+ public void run(@NonNull final InteractionContext interactionContext, @NonNull final ThrowingRunnable runnable) {
+ try {
+ openInteraction(interactionContext);
+ runnable.run();
+ } finally {
+ interactionLayers.pop();
+ }
}
@Override @SneakyThrows
- public void runAnonymous(@NonNull ThrowingRunnable runnable) {
- runnable.run();
+ public void runAnonymous(@NonNull final ThrowingRunnable runnable) {
+ try {
+ openInteraction();
+ runnable.run();
+ } finally {
+ interactionLayers.pop();
+ }
}
@Override @SneakyThrows
- public <R> R callAnonymous(@NonNull Callable<R> callable) {
- return callable.call();
+ public <R> R callAnonymous(@NonNull final Callable<R> callable) {
+ try {
+ openInteraction();
+ return callable.call();
+ } finally {
+ interactionLayers.pop();
+ }
}
+ @RequiredArgsConstructor
+ static class Interaction_forTesting implements Interaction {
+ private final UUID uuid = UUID.randomUUID();
+ @Override public <T> T putAttribute(final Class<? super T> type, final T value) { return null; }
+ @Override public <T> T computeAttributeIfAbsent(final Class<? super T> type, final Function<Class<?>, ? extends T> mappingFunction) { return null; }
+ @Override public <T> T getAttribute(final Class<T> type) { return null; }
+ @Override public void removeAttribute(final Class<?> type) { }
+ @Override public UUID getInteractionId() { return uuid; }
+ @Override public Command getCommand() { return null; }
+ @Override public Execution<?, ?> getCurrentExecution() { return null; }
+ @Override public Execution<?, ?> getPriorExecution() { return null; }
+ };
+
}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ConverterBasedOnValueSemantics.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ConverterBasedOnValueSemantics.java
index 8f0b61a..4c1bb5f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ConverterBasedOnValueSemantics.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/ConverterBasedOnValueSemantics.java
@@ -65,7 +65,7 @@ implements
* @see IConverter#convertToObject(String, Locale)
*/
@Override
- public T convertToObject(final String text, final Locale locale) throws ConversionException {
+ public final T convertToObject(final String text, final Locale locale) throws ConversionException {
val feature = feature();
val valueFacet = valueFacet();
@@ -82,7 +82,7 @@ implements
* @see IConverter#convertToString(String, Locale)
*/
@Override
- public String convertToString(final T value, final Locale locale) {
+ public final String convertToString(final T value, final Locale locale) {
val feature = feature();
val valueFacet = valueFacet();
@@ -126,7 +126,7 @@ implements
// -- DEPENDENCIES
@Override
- public IsisAppCommonContext getCommonContext() {
+ public final IsisAppCommonContext getCommonContext() {
return commonContext = CommonContextUtils.computeIfAbsent(commonContext);
}
diff --git a/viewers/wicket/ui/src/test/java/org/apache/isis/viewer/wicket/ui/components/scalars/jdkmath/BigDecimalConverter_roundtrip.java b/viewers/wicket/ui/src/test/java/org/apache/isis/viewer/wicket/ui/components/scalars/jdkmath/BigDecimalConverter_roundtrip.java
new file mode 100644
index 0000000..8b70978
--- /dev/null
+++ b/viewers/wicket/ui/src/test/java/org/apache/isis/viewer/wicket/ui/components/scalars/jdkmath/BigDecimalConverter_roundtrip.java
@@ -0,0 +1,195 @@
+/*
+ * 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.viewer.wicket.ui.components.scalars.jdkmath;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+
+import javax.validation.constraints.Digits;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.exceptions.recoverable.TextEntryParseException;
+import org.apache.isis.applib.services.iactnlayer.InteractionContext;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.core.config.valuetypes.ValueSemanticsRegistry;
+import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
+import org.apache.isis.core.metamodel.context.MetaModelContext;
+import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
+import org.apache.isis.core.metamodel.valuesemantics.BigDecimalValueSemantics;
+import org.apache.isis.core.security._testing.InteractionService_forTesting;
+import org.apache.isis.viewer.wicket.ui.components.scalars.ConverterBasedOnValueSemantics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.val;
+
+class BigDecimalConverter_roundtrip {
+
+ final BigDecimal bd_123_45_scale2 = new BigDecimal("123.45").setScale(2);
+ final BigDecimal bd_123_4500_scale2 = new BigDecimal("123.4500").setScale(2);
+
+ final BigDecimal bd_789123_45_scale2 = new BigDecimal("789123.45").setScale(2);
+
+ final BigDecimal bd_123_45_scale4 = new BigDecimal("123.45").setScale(4);
+ final BigDecimal bd_123_4500_scale4 = new BigDecimal("123.4500").setScale(4);
+
+ private InteractionService interactionService;
+ private MetaModelContext mmc;
+
+ private static class BigDecimalConverterForFeature
+ extends ConverterBasedOnValueSemantics<BigDecimal> {
+
+ private static final long serialVersionUID = 1L;
+
+ protected BigDecimalConverterForFeature(final ObjectFeature propOrParam) {
+ super(propOrParam);
+ }
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+
+ mmc = MetaModelContext_forTesting.builder()
+ .valueSemantic(new BigDecimalValueSemantics())
+ .interactionProvider(interactionService = new InteractionService_forTesting())
+ .build();
+
+ // pre-requisites for testing
+ val reg = mmc.getServiceRegistry().lookupServiceElseFail(ValueSemanticsRegistry.class);
+ assertNotNull(reg.selectValueSemantics(BigDecimal.class));
+ assertTrue(reg.selectValueSemantics(BigDecimal.class).isNotEmpty());
+ assertNotNull(mmc.getServiceRegistry().lookupServiceElseFail(InteractionService.class));
+ assertNotNull(mmc.getInteractionProvider());
+ }
+
+ @Test
+ void scale2_english() {
+ assertRoundtrip(CustomerScale2.class, bd_123_45_scale2, "123.45", "123.45", Locale.ENGLISH);
+ }
+
+ @Test @Disabled //FIXME[ISIS-2741] scale not picked up yet
+ void scale4_english() {
+ assertRoundtrip(CustomerScale4.class, bd_123_45_scale4, "123.4500", "123.4500", Locale.ENGLISH);
+ }
+
+ @Test @Disabled //FIXME[ISIS-2741] scale not picked up yet
+ void scaleNull_english() {
+ assertRoundtrip(Customer.class, bd_123_45_scale2, "123.45", "123.45", Locale.ENGLISH);
+ assertRoundtrip(Customer.class, bd_123_45_scale4, "123.4500", "123.4500", Locale.ENGLISH);
+ }
+
+ @Test
+ void scale2_italian() {
+ assertRoundtrip(Customer.class, bd_123_45_scale2, "123,45", "123,45", Locale.ITALIAN);
+ }
+
+ @Test
+ void scale2_english_withThousandSeparators() {
+ assertRoundtrip(CustomerScale2.class, bd_789123_45_scale2, "789,123.45", "789,123.45", Locale.ENGLISH);
+ }
+
+ @Test
+ void scale2_english_withoutThousandSeparators() {
+ assertRoundtrip(CustomerScale2.class, bd_789123_45_scale2, "789123.45", "789,123.45", Locale.ENGLISH);
+ }
+
+ @Test @Disabled //FIXME[ISIS-2741] scale not picked up yet
+ void scale2_english_tooLargeScale() {
+ assertParseError(CustomerScale2.class, "123.454", Locale.ENGLISH,
+ "No more than 2 digits can be entered after the decimal place");
+ }
+
+ // -- HELPER
+
+ private void assertRoundtrip(final Class<?> type, final BigDecimal expected, final String valueAsText, final String expectedText, final Locale locale) {
+
+ val converter = newConverter(type);
+
+ interactionService.run(InteractionContext.builder()
+ .locale(locale)
+ .build(), ()->{
+
+ // when
+ final BigDecimal actual = converter.convertToObject(valueAsText, null);
+ assertNumberEquals(expected, actual);
+
+ // when
+ final String actualStr = converter.convertToString(actual, null);
+ assertEquals(expectedText, actualStr);
+
+ });
+ }
+
+ private void assertParseError(final Class<?> type, final String valueAsText, final Locale locale, final String expectedMessage) {
+
+ val converter = newConverter(type);
+
+ interactionService.run(InteractionContext.builder()
+ .locale(locale)
+ .build(), ()->{
+ // when
+ val ex = assertThrows(TextEntryParseException.class, ()->
+ converter.convertToObject(valueAsText, null));
+ assertEquals(expectedMessage, ex.getMessage());
+ });
+ }
+
+ private static void assertNumberEquals(final BigDecimal a, final BigDecimal b) {
+ assertEquals(a, b);
+ }
+
+ // -- SCENARIOS
+
+ @DomainObject
+ static class Customer {
+ @Property @Getter @Setter
+ private BigDecimal value;
+ }
+
+ @DomainObject
+ static class CustomerScale2 {
+ @Property @Getter @Setter
+ @Digits(fraction = 2, integer = 20)
+ private BigDecimal value;
+ }
+
+ @DomainObject
+ static class CustomerScale4 {
+ @Property @Getter @Setter
+ @Digits(fraction = 4, integer = 20)
+ private BigDecimal value;
+ }
+
+ private BigDecimalConverterForFeature newConverter(final Class<?> type) {
+ val customerSpec = mmc.getSpecificationLoader().specForTypeElseFail(type);
+ val prop = customerSpec.getPropertyElseFail("value");
+ return new BigDecimalConverterForFeature(prop);
+ }
+
+}