Skip to content

Commit ec50542

Browse files
committed
CAUSEWAY-4036: properly infer max total digits
1 parent 52f9fec commit ec50542

6 files changed

Lines changed: 153 additions & 241 deletions

File tree

core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/value/semantics/MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation.java

Lines changed: 0 additions & 51 deletions
This file was deleted.

core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactory.java

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.apache.causeway.core.metamodel.facets.TypedFacetHolder;
3131
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacet;
3232
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxIntegerDigitsFacet;
33-
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacet;
3433
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MinFractionalDigitsFacet;
3534
import org.apache.causeway.core.metamodel.specloader.validator.ValidationFailureUtils;
3635

@@ -44,30 +43,29 @@ public ValueSemanticsAnnotationFacetFactory(final MetaModelContext mmc) {
4443

4544
@Override
4645
public void process(final ProcessMethodContext processMethodContext) {
47-
var valueSemanticsIfAny = processMethodContext
48-
.synthesizeOnMethodOrMixinType(
49-
ValueSemantics.class,
50-
() -> ValidationFailureUtils
51-
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), ValueSemantics.class));
46+
var valueSemanticsOpt = processMethodContext
47+
.synthesizeOnMethodOrMixinType(
48+
ValueSemantics.class,
49+
() -> ValidationFailureUtils
50+
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), ValueSemantics.class));
5251

5352
// support for @jakarta.validation.constraints.Digits
54-
var digitsIfAny = processMethodContext
55-
.synthesizeOnMethodOrMixinType(
56-
Digits.class,
57-
() -> ValidationFailureUtils
58-
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), Digits.class));
53+
var digitsOpt = processMethodContext
54+
.synthesizeOnMethodOrMixinType(
55+
Digits.class,
56+
() -> ValidationFailureUtils
57+
.raiseAmbiguousMixinAnnotations(processMethodContext.getFacetHolder(), Digits.class));
5958

60-
processAll(processMethodContext.getFacetHolder(), valueSemanticsIfAny, digitsIfAny);
59+
processAll(processMethodContext.getFacetHolder(), valueSemanticsOpt, digitsOpt);
6160
}
6261

6362
@Override
6463
public void processParams(final ProcessParameterContext processParameterContext) {
65-
var valueSemanticsIfAny = processParameterContext.synthesizeOnParameter(ValueSemantics.class);
66-
64+
var valueSemanticsOpt = processParameterContext.synthesizeOnParameter(ValueSemantics.class);
6765
// support for @jakarta.validation.constraints.Digits
68-
var digitsIfAny = processParameterContext.synthesizeOnParameter(Digits.class);
66+
var digitsOpt = processParameterContext.synthesizeOnParameter(Digits.class);
6967

70-
processAll(processParameterContext.getFacetHolder(), valueSemanticsIfAny, digitsIfAny);
68+
processAll(processParameterContext.getFacetHolder(), valueSemanticsOpt, digitsOpt);
7169
}
7270

7371
// -- HELPER
@@ -98,12 +96,8 @@ private void processDigits(
9896

9997
// max total digits
10098
addFacetIfPresent(
101-
MaxTotalDigitsFacet.strongestConstraint(
102-
MaxTotalDigitsFacetFromValueSemanticsAnnotation
103-
.create(valueSemanticsOpt, facetHolder),
104-
// support for @jakarta.validation.constraints.Digits
105-
MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation
106-
.create(digitsOpt, facetHolder)));
99+
MaxTotalDigitsFacetFromValueSemanticsAnnotation
100+
.create(valueSemanticsOpt, facetHolder));
107101

108102
// max integer digits
109103
addFacetIfPresent(

core/metamodel/src/main/java/org/apache/causeway/core/metamodel/util/Facets.java

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@
6868
import org.apache.causeway.core.metamodel.facets.object.value.ValueSerializer;
6969
import org.apache.causeway.core.metamodel.facets.objectvalue.daterenderedadjust.DateRenderAdjustFacet;
7070
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacet;
71+
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxIntegerDigitsFacet;
7172
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacet;
7273
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MinFractionalDigitsFacet;
74+
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MinIntegerDigitsFacet;
7375
import org.apache.causeway.core.metamodel.facets.objectvalue.fileaccept.FileAcceptFacet;
7476
import org.apache.causeway.core.metamodel.facets.objectvalue.labelat.LabelAtFacet;
7577
import org.apache.causeway.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet;
@@ -252,6 +254,20 @@ public String labelAtCss(final ObjectFeature feature) {
252254

253255
// -- VALUE SEMANTICS
254256

257+
public OptionalInt maxIntegerDigits(final FacetHolder facetHolder) {
258+
return facetHolder.lookupFacet(MaxIntegerDigitsFacet.class)
259+
.map(MaxIntegerDigitsFacet::maxIntegerDigits)
260+
.map(OptionalInt::of)
261+
.orElseGet(OptionalInt::empty);
262+
}
263+
264+
public OptionalInt minIntegerDigits(final FacetHolder facetHolder) {
265+
return facetHolder.lookupFacet(MinIntegerDigitsFacet.class)
266+
.map(MinIntegerDigitsFacet::minIntegerDigits)
267+
.map(OptionalInt::of)
268+
.orElseGet(OptionalInt::empty);
269+
}
270+
255271
public OptionalInt minFractionalDigits(final FacetHolder facetHolder) {
256272
return facetHolder.lookupFacet(MinFractionalDigitsFacet.class)
257273
.map(MinFractionalDigitsFacet::minFractionalDigits)
@@ -268,23 +284,47 @@ public OptionalInt maxFractionalDigits(final FacetHolder facetHolder) {
268284
.orElseGet(OptionalInt::empty);
269285
}
270286

271-
public OptionalInt maxLength(final FacetHolder facetHolder) {
272-
return facetHolder
273-
.lookupFacet(MaxLengthFacet.class)
274-
.map(MaxLengthFacet::value)
275-
.map(OptionalInt::of)
276-
.orElseGet(OptionalInt::empty);
287+
/**
288+
* Does not consult the combination of maxIntegerDigits and maxFractionalDigits.
289+
*/
290+
public OptionalInt maxTotalDigitsExplicit(final FacetHolder facetHolder) {
291+
return facetHolder.lookupFacet(MaxTotalDigitsFacet.class)
292+
.map(MaxTotalDigitsFacet::maxTotalDigits)
293+
.map(OptionalInt::of)
294+
.orElseGet(OptionalInt::empty);
277295
}
278296

279-
public OptionalInt maxTotalDigits(final FacetHolder facetHolder) {
280-
return facetHolder.lookupFacet(MaxTotalDigitsFacet.class)
281-
.map(MaxTotalDigitsFacet::maxTotalDigits)
282-
.map(OptionalInt::of)
283-
.orElseGet(OptionalInt::empty);
297+
/**
298+
* Consults all available constraints, in contrast to {@link #maxTotalDigits(FacetHolder)}
299+
*/
300+
public OptionalInt maxTotalDigitsInferred(final FacetHolder facetHolder) {
301+
var maxInt = maxIntegerDigits(facetHolder);
302+
if(!maxInt.isPresent())
303+
return maxTotalDigitsExplicit(facetHolder);
304+
305+
var maxFrac = maxFractionalDigits(facetHolder);
306+
if(!maxFrac.isPresent())
307+
return maxTotalDigitsExplicit(facetHolder);
308+
309+
int inferredMaxTot = maxInt.getAsInt() + maxFrac.getAsInt();
310+
311+
var maxTot = maxTotalDigitsExplicit(facetHolder);
312+
if(!maxTot.isPresent())
313+
return OptionalInt.of(inferredMaxTot);
314+
315+
return OptionalInt.of(Math.min(maxTot.getAsInt(), inferredMaxTot));
284316
}
285317

286318
// --
287319

320+
public OptionalInt maxLength(final FacetHolder facetHolder) {
321+
return facetHolder
322+
.lookupFacet(MaxLengthFacet.class)
323+
.map(MaxLengthFacet::value)
324+
.map(OptionalInt::of)
325+
.orElseGet(OptionalInt::empty);
326+
}
327+
288328
public boolean mixinIsPresent(final ObjectSpecification objectSpec) {
289329
return objectSpec.containsFacet(MixinFacet.class);
290330
}

core/mmtest/src/test/java/org/apache/causeway/core/metamodel/facets/value/semantics/ValueSemanticsAnnotationFacetFactoryTest.java

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,17 @@
2727
import static org.hamcrest.CoreMatchers.is;
2828
import static org.hamcrest.MatcherAssert.assertThat;
2929
import static org.junit.jupiter.api.Assertions.assertNotNull;
30-
import static org.junit.jupiter.api.Assertions.assertTrue;
3130

3231
import org.apache.causeway.applib.annotation.TimeZoneTranslation;
3332
import org.apache.causeway.applib.annotation.ValueSemantics;
3433
import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
3534
import org.apache.causeway.core.metamodel.facets.FacetFactoryTestAbstract;
3635
import org.apache.causeway.core.metamodel.facets.FacetedMethod;
3736
import org.apache.causeway.core.metamodel.facets.objectvalue.daterenderedadjust.DateRenderAdjustFacet;
38-
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxFractionalDigitsFacet;
39-
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxIntegerDigitsFacet;
40-
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MaxTotalDigitsFacet;
41-
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MinFractionalDigitsFacet;
42-
import org.apache.causeway.core.metamodel.facets.objectvalue.digits.MinIntegerDigitsFacet;
4337
import org.apache.causeway.core.metamodel.facets.objectvalue.temporalformat.DateFormatStyleFacet;
4438
import org.apache.causeway.core.metamodel.facets.objectvalue.temporalformat.TimeFormatStyleFacet;
4539
import org.apache.causeway.core.metamodel.facets.objectvalue.temporalformat.TimeZoneTranslationFacet;
40+
import org.apache.causeway.core.metamodel.util.Facets;
4641

4742
class ValueSemanticsAnnotationFacetFactoryTest
4843
extends FacetFactoryTestAbstract {
@@ -60,7 +55,8 @@ class Order {
6055
// when
6156
newFacetFactory().process(processMethodContext);
6257
// then
63-
assertMaxTotalDigits(facetedMethod, 5);
58+
assertMaxTotalDigitsExplicit(facetedMethod, 5);
59+
assertMaxTotalDigitsInferred(facetedMethod, 5);
6460
assertDefaultMaxIntegerDigits(facetedMethod);
6561
assertDefaultMinIntegerDigits(facetedMethod);
6662
assertDefaultMaxFractionalDigits(facetedMethod);
@@ -81,7 +77,8 @@ public void updateCost(
8177
// when
8278
newFacetFactory().processParams(processParameterContext);
8379
// then
84-
assertMaxTotalDigits(facetedMethodParameter, 5);
80+
assertMaxTotalDigitsExplicit(facetedMethodParameter, 5);
81+
assertMaxTotalDigitsInferred(facetedMethodParameter, 5);
8582
assertDefaultMaxIntegerDigits(facetedMethodParameter);
8683
assertDefaultMinIntegerDigits(facetedMethodParameter);
8784
assertDefaultMaxFractionalDigits(facetedMethodParameter);
@@ -226,7 +223,8 @@ class Order {
226223
// when
227224
newFacetFactory().process(processMethodContext);
228225
// then
229-
assertDigitsFacets(facetedMethod, 18, 4);
226+
assertMaxIntegerDigits(facetedMethod, 14);
227+
assertMaxFractionalDigits(facetedMethod, 4);
230228
});
231229
}
232230

@@ -243,7 +241,8 @@ public void updateCost(
243241
// when
244242
newFacetFactory().processParams(processParameterContext);
245243
// then
246-
assertDigitsFacets(facetedMethodParameter, 18, 4);
244+
assertMaxIntegerDigits(facetedMethodParameter, 14);
245+
assertMaxFractionalDigits(facetedMethodParameter, 4);
247246
});
248247
}
249248

@@ -275,15 +274,19 @@ class Order {
275274
actionScenario(Order.class, "maxTotalA", (processMethodContext, facetHolder, facetedMethod) -> {
276275
// when
277276
newFacetFactory().process(processMethodContext);
278-
// then - lowest bound wins
279-
assertMaxTotalDigits(facetedMethod, 18);
277+
// then - constraints apply individually
278+
assertMaxTotalDigitsExplicit(facetedMethod, 19);
279+
assertMaxTotalDigitsInferred(facetedMethod, 18);
280+
assertMaxIntegerDigits(facetedMethod, 14);
281+
assertMaxFractionalDigits(facetedMethod, 4);
280282
});
281283

282284
actionScenario(Order.class, "maxTotalB", (processMethodContext, facetHolder, facetedMethod) -> {
283285
// when
284286
newFacetFactory().process(processMethodContext);
285287
// then - lowest bound wins
286-
assertMaxTotalDigits(facetedMethod, 17);
288+
assertMaxTotalDigitsExplicit(facetedMethod, 17);
289+
assertMaxTotalDigitsInferred(facetedMethod, 17);
287290
});
288291

289292
actionScenario(Order.class, "maxFracA", (processMethodContext, facetHolder, facetedMethod) -> {
@@ -381,7 +384,7 @@ ValueSemanticsAnnotationFacetFactory newFacetFactory() {
381384
}
382385

383386
private void assertDefaultMaxTotalDigits(final FacetHolder facetedMethod) {
384-
assertMaxTotalDigits(facetedMethod, 0);
387+
assertMaxTotalDigitsExplicit(facetedMethod, 0);
385388
}
386389

387390
private void assertDefaultMaxIntegerDigits(final FacetHolder facetedMethod) {
@@ -400,66 +403,40 @@ private void assertDefaultMinFractionalDigits(final FacetHolder facetedMethod) {
400403
assertMinFractionalDigits(facetedMethod, 0);
401404
}
402405

403-
private void assertMaxTotalDigits(
406+
private void assertMaxTotalDigitsExplicit(
404407
final FacetHolder facetedMethod, final int maxTotalDigits) {
405-
final MaxTotalDigitsFacet facet = facetedMethod.lookupFacet(MaxTotalDigitsFacet.class).orElse(null);
406-
if(facet==null && maxTotalDigits==0)
407-
return; // absence of the facet is semantically equal to maxTotalDigits=0
408-
assertNotNull(facet);
409-
assertThat(facet.maxTotalDigits(), is(maxTotalDigits));
408+
int unlimitted = 0;
409+
assertThat(Facets.maxTotalDigitsExplicit(facetedMethod).orElse(unlimitted), is(maxTotalDigits));
410+
}
411+
412+
private void assertMaxTotalDigitsInferred(
413+
final FacetHolder facetedMethod, final int maxTotalDigits) {
414+
int unlimitted = 0;
415+
assertThat(Facets.maxTotalDigitsInferred(facetedMethod).orElse(unlimitted), is(maxTotalDigits));
410416
}
411417

412418
private void assertMaxIntegerDigits(
413419
final FacetHolder facetedMethod, final int maxIntegerDigits) {
414-
final MaxIntegerDigitsFacet facet = facetedMethod.lookupFacet(MaxIntegerDigitsFacet.class).orElse(null);
415-
if(facet==null && maxIntegerDigits==0)
416-
return; // absence of the facet is semantically equal to maxIntegerDigits=0
417-
assertNotNull(facet);
418-
assertThat(facet.maxIntegerDigits(), is(maxIntegerDigits));
420+
int unlimitted = 0;
421+
assertThat(Facets.maxIntegerDigits(facetedMethod).orElse(unlimitted), is(maxIntegerDigits));
419422
}
420423

421424
private void assertMinIntegerDigits(
422425
final FacetHolder facetedMethod, final int minIntegerDigits) {
423-
final MinIntegerDigitsFacet facet = facetedMethod.lookupFacet(MinIntegerDigitsFacet.class).orElse(null);
424-
if(facet==null && minIntegerDigits==1)
425-
return; // absence of the facet is semantically equal to minIntegerDigits=1
426-
assertNotNull(facet);
427-
assertThat(facet.minIntegerDigits(), is(minIntegerDigits));
426+
int unlimitted = 1;
427+
assertThat(Facets.minIntegerDigits(facetedMethod).orElse(unlimitted), is(minIntegerDigits));
428428
}
429429

430430
private void assertMaxFractionalDigits(
431431
final FacetHolder facetedMethod, final int maxFractionalDigits) {
432-
final MaxFractionalDigitsFacet facet = facetedMethod.lookupFacet(MaxFractionalDigitsFacet.class).orElse(null);
433-
if(facet==null && maxFractionalDigits==-1)
434-
return; // absence of the facet is semantically equal to maxFractionalDigits=-1
435-
assertNotNull(facet);
436-
assertThat(facet.maxFractionalDigits(), is(maxFractionalDigits));
432+
int unlimitted = -1;
433+
assertThat(Facets.maxFractionalDigits(facetedMethod).orElse(unlimitted), is(maxFractionalDigits));
437434
}
438435

439436
private void assertMinFractionalDigits(
440437
final FacetHolder facetedMethod, final int minFractionalDigits) {
441-
final MinFractionalDigitsFacet facet = facetedMethod.lookupFacet(MinFractionalDigitsFacet.class).orElse(null);
442-
if(facet==null && minFractionalDigits==0)
443-
return; // absence of the facet is semantically equal to minFractionalDigits=0
444-
assertNotNull(facet);
445-
assertThat(facet.minFractionalDigits(), is(minFractionalDigits));
446-
}
447-
448-
private void assertDigitsFacets(
449-
final FacetHolder facetedMethod, final int maxTotalDigits, final int maxFractionalDigits) {
450-
if(maxTotalDigits>=0) {
451-
final MaxTotalDigitsFacet facet = facetedMethod.lookupFacet(MaxTotalDigitsFacet.class).orElse(null);
452-
assertNotNull(facet);
453-
assertTrue(facet instanceof MaxTotalDigitsFacetFromJavaxValidationDigitsAnnotation);
454-
assertThat(facet.maxTotalDigits(), is(maxTotalDigits));
455-
}
456-
457-
if(maxFractionalDigits>=0) {
458-
final MaxFractionalDigitsFacet facet = facetedMethod.lookupFacet(MaxFractionalDigitsFacet.class).orElse(null);
459-
assertNotNull(facet);
460-
assertTrue(facet instanceof MaxFractionalDigitsFacetFromJakartaDigitsAnnotation);
461-
assertThat(facet.maxFractionalDigits(), is(maxFractionalDigits));
462-
}
438+
int unlimitted = 0;
439+
assertThat(Facets.minFractionalDigits(facetedMethod).orElse(unlimitted), is(minFractionalDigits));
463440
}
464441

465442
private void assertDateRenderAdjustDays(

viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/valuerender/JsonValueConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ record InferredFromFacets(
100100
@Override
101101
public OptionalInt maxTotalDigits(final @Nullable ManagedObject value) {
102102
return streamFacetHolders(value)
103-
.map(Facets::maxTotalDigits)
103+
.map(Facets::maxTotalDigitsInferred)
104104
.findFirst()
105105
.orElseGet(OptionalInt::empty);
106106
}

0 commit comments

Comments
 (0)