Skip to content

Commit fdd4628

Browse files
committed
CAUSEWAY-4036: polishing digit constraining API
Task-Url: https://issues.apache.org/jira/browse/CAUSEWAY-4036
1 parent 4d00401 commit fdd4628

3 files changed

Lines changed: 76 additions & 43 deletions

File tree

api/applib/src/main/java/org/apache/causeway/applib/annotation/ValueSemantics.java

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.annotation.RetentionPolicy;
2525
import java.lang.annotation.Target;
2626
import java.math.BigDecimal;
27+
import java.math.BigInteger;
2728
import java.time.format.FormatStyle;
2829
import java.util.Locale;
2930

@@ -67,39 +68,71 @@ String provider()
6768
// -- NUMBER CONSTRAINTS
6869

6970
/**
70-
* If associated with a {@link Number}, the maximum number of total digits accepted for
71-
* this number.<br>
72-
* Can be omitted, if {@link Column#precision()} is used.<br>
73-
* default = {@code 65}
71+
* If associated with {@link BigDecimal}, {@link BigInteger},
72+
* or any Java integer type (long, int, short, byte),
73+
* the maximum number of total digits accepted for input (editing).
74+
*
75+
* <p> But input is not constrained for double/float, since those
76+
* types have fixed intrinsic precision and their bit representation does not
77+
* directly correspond to decimal digits. Further more, double/float may support
78+
* scientific notation for input (as well as display), where the notion of 'total digits' is
79+
* no longer viable.
80+
*
81+
* <p> When {@link Column#precision()} {@code >0} is used,
82+
* while {@link ValueSemantics} is not used,
83+
* then {@link Column#precision()} is undersood as an alias for this annotation attribute.
84+
*
85+
* <p> default = {@code 65}
86+
*
7487
* @apiNote SQL's DECIMAL(precision, scale) has max-precision=65 and max-scale=30
7588
* @see Column#precision()
7689
*/
7790
int maxTotalDigits()
7891
default 65;
7992

8093
/**
81-
* If associated with a {@link Number}, the minimum number of integer digits required for
82-
* this number.<br>
83-
* default = {@code 1}
94+
* If associated with any Java number type {@link BigDecimal}, {@link BigInteger},
95+
* long, int, short, byte, double or float,
96+
* the minimum number of integer digits required for
97+
* input (editing).
98+
*
99+
* <p> For double/float specifically, requires their decimal representation, to satisfy this requirement.
100+
* Those types may support scientific notation for input (as well as display), where the notion of 'integer digits'
101+
* is still viable.
102+
*
103+
* <p> default = {@code 1}
84104
*/
85105
int minIntegerDigits()
86106
default 1;
87107

88108
/**
89-
* If associated with a {@link BigDecimal}, the maximum number of fractional digits accepted
90-
* for this number.<br>
91-
* Can be omitted, if {@link Column#scale()} is used.<br>
92-
* default = {@code 30}
109+
* If associated with any non-integer {@link Number} type,
110+
* the maximum number of fractional decimal digits displayed.
111+
*
112+
* <p> If associated with a {@link BigDecimal} specifically,
113+
* also governs the maximum number of fractional digits accepted for input (editing).
114+
*
115+
* <p> But input is not constrained for double/float, since those
116+
* types have fixed intrinsic precision and their bit representation does not
117+
* directly correspond to decimal digits.
118+
*
119+
* <p> When {@link Column#scale()} {@code >0} is used on a {@link BigDecimal},
120+
* while {@link ValueSemantics} is not used,
121+
* then {@link Column#scale()} is undersood as an alias for this annotation attribute.
122+
*
123+
* <p> default = {@code 30}
124+
*
93125
* @apiNote SQL's DECIMAL(precision, scale) has max-precision=65 and max-scale=30
94126
* @see Column#scale()
95127
*/
96128
int maxFractionalDigits()
97129
default 30;
98130

99131
/**
100-
* If associated with a {@link BigDecimal}, the minimum number of fractional digits
101-
* required for this number.<br>
102-
* default = {@code 0}
132+
* If associated with any non-integer {@link Number} type,
133+
* the minimum number of fractional digits displayed.
134+
*
135+
* <p> default = {@code 0}
103136
*/
104137
int minFractionalDigits()
105138
default 0;
@@ -133,8 +166,10 @@ TimePrecision timePrecision()
133166
* If associated with a temporal value,
134167
* that has time-zone or time-offset information,
135168
* the rendering mode, as to whether to transform the rendered value
136-
* to the user's local/current time-zone or not.<br>
137-
* default = {@link TimeZoneTranslation#TO_LOCAL_TIMEZONE}
169+
* to the user's local/current time-zone or not.
170+
*
171+
* <p>default = {@link TimeZoneTranslation#TO_LOCAL_TIMEZONE}
172+
*
138173
* @see TimeZoneTranslation
139174
*/
140175
TimeZoneTranslation timeZoneTranslation()
@@ -151,26 +186,20 @@ TimeZoneTranslation timeZoneTranslation()
151186
* after the actually stored date.
152187
* For negative <i>n</i> its days before respectively.
153188
*
154-
* <p>
155-
* This is intended to be used so that an exclusive end date of an interval
189+
* <p>This is intended to be used so that an exclusive end date of an interval
156190
* can be rendered as 1 day before the actual value stored.
157-
* </p>
158191
*
159-
* <p>
160-
* For example:
161-
* </p>
192+
* <p> For example:
162193
* <pre>
163194
* public LocalDate getStartDate() { ... }
164195
*
165196
* &#64;ValueSemantics(dateRenderAdjustDays = ValueSemantics.AS_DAY_BEFORE)
166197
* public LocalDate getEndDate() { ... }
167198
* </pre>
168199
*
169-
* <p>
170-
* Here, the interval of the [1-may-2013,1-jun-2013) would be rendered as the dates
200+
* <p>Here, the interval of the [1-may-2013,1-jun-2013) would be rendered as the dates
171201
* 1-may-2013 for the start date but using 31-may-2013 (the day before) for the end date. What is stored
172202
* In the domain object, itself, however, the value stored is 1-jun-2013.
173-
* </p>
174203
*/
175204
int dateRenderAdjustDays()
176205
default 0;

api/applib/src/main/java/org/apache/causeway/applib/value/semantics/NumericValueSemantics.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ private DecimalFormatEx getNumberFormat(
177177
* based on {@link #grouping()}
178178
*/
179179
protected void configureDecimalFormat(
180-
final Context context, final DecimalFormat format, final FormatUsageFor usedFor) {
180+
final ValueSemanticsProvider.@Nullable Context context,
181+
final DecimalFormat format,
182+
final FormatUsageFor usedFor) {
181183
}
182184

183185
protected Optional<BigInteger> parseInteger(

core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BigDecimalValueSemantics.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.math.BigDecimal;
2222
import java.text.DecimalFormat;
2323
import java.util.Optional;
24-
import java.util.OptionalInt;
2524
import java.util.function.UnaryOperator;
2625

2726
import jakarta.inject.Inject;
@@ -123,34 +122,37 @@ public int typicalLength() {
123122
public void configureDecimalFormat(
124123
final Context context, final DecimalFormat format, final FormatUsageFor usedFor) {
125124

125+
// we skip this when PARSING,
126+
// because we want to firstly parse any number value into a BigDecimal,
127+
// no matter the minimumFractionDigits, which can always be filled up with '0' digits later
128+
if(usedFor.isParsing())
129+
return;
130+
126131
if(context==null)
127132
return;
128133

129-
var specificationLoader = MetaModelContext.instanceElseFail().getSpecificationLoader();
130-
var feature = specificationLoader.loadFeature(context.featureIdentifier())
131-
.orElse(null);
134+
var feature = MetaModelContext.instanceElseFail().getSpecificationLoader()
135+
.loadFeature(context.featureIdentifier())
136+
.orElse(null);
132137
if(feature==null)
133138
return;
134139

135140
// evaluate any facets that provide the MaximumFractionDigits
136141
Facets.maxFractionalDigits(feature)
137-
.ifPresent(newValue -> format.setMaximumFractionDigits(newValue));
142+
.ifPresent(format::setMaximumFractionDigits);
138143

139144
var bigDecimalConfig = causewayConfiguration.valueTypes().bigDecimal();
140-
// we skip this when PARSING,
141-
// because we want to firstly parse any number value into a BigDecimal,
142-
// no matter the minimumFractionDigits, which can always be filled up with '0' digits later
143-
if(!usedFor.isParsing() || bigDecimalConfig.editing().preserveScale()) {
145+
if(bigDecimalConfig.editing().preserveScale()) {
144146

145147
// if there is a facet specifying minFractionalDigits (ie the scale), then apply it
146-
OptionalInt optionalInt = Facets.minFractionalDigits(feature);
147-
if (optionalInt.isPresent()) {
148-
format.setMinimumFractionDigits(optionalInt.getAsInt());
149-
} else {
150-
// otherwise, apply a minScale if configured.
151-
Optional.ofNullable(bigDecimalConfig.display().minScale())
152-
.ifPresent(format::setMinimumFractionDigits);
153-
}
148+
Facets.minFractionalDigits(feature)
149+
.ifPresentOrElse(
150+
format::setMinimumFractionDigits,
151+
()->{
152+
// otherwise, apply a minScale if configured.
153+
Optional.ofNullable(bigDecimalConfig.display().minScale())
154+
.ifPresent(format::setMinimumFractionDigits);
155+
});
154156
}
155157
}
156158

0 commit comments

Comments
 (0)