This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new 1ce1db080b5 CAUSEWAY-3859: Java record refactoring (part 22)
1ce1db080b5 is described below
commit 1ce1db080b5e1026fe707ce54cb1fcc523705452
Author: Andi Huber <[email protected]>
AuthorDate: Thu Feb 20 08:33:48 2025 +0100
CAUSEWAY-3859: Java record refactoring (part 22)
---
.../internal/binding/_BindableAbstract.java | 12 +-
.../interactions/managed/ManagedParameter.java | 47 +---
.../managed/ParameterNegotiationModel.java | 261 ++++++++++++---------
.../managed/PendingParamsSnapshot.java | 2 +-
.../interactions/managed/_BindingUtil.java | 92 ++++----
.../core/metamodel/object/MmDebugUtils.java | 2 +-
.../tabular/simple/DataTableSerializationTest.java | 59 +++--
.../interaction/DomainObjectTesterFactory.java | 22 +-
8 files changed, 266 insertions(+), 231 deletions(-)
diff --git
a/commons/src/main/java/org/apache/causeway/commons/internal/binding/_BindableAbstract.java
b/commons/src/main/java/org/apache/causeway/commons/internal/binding/_BindableAbstract.java
index e0425df0e31..63f91fa7d48 100644
---
a/commons/src/main/java/org/apache/causeway/commons/internal/binding/_BindableAbstract.java
+++
b/commons/src/main/java/org/apache/causeway/commons/internal/binding/_BindableAbstract.java
@@ -45,7 +45,7 @@
public abstract class _BindableAbstract<T> implements Bindable<T> {
private T value;
- private Observable<? extends T> observable = null;;
+ private Observable<? extends T> observable = null;
private InvalidationListener invalidationListener = null;
private boolean valid = true;
private InternalUtil<T> util = null;
@@ -178,9 +178,8 @@ public <R> Bindable<R> mapToBindable(
var newBindable =
_Bindables.<R>forValue(forwardMapper.apply(getValue()));
addListener((e,o,n)->{
- if(isReverseUpdating.get()) {
- return;
- }
+ if(isReverseUpdating.get()) return;
+
try {
isForwardUpdating.set(true);
newBindable.setValue(forwardMapper.apply(n));
@@ -190,9 +189,8 @@ public <R> Bindable<R> mapToBindable(
});
newBindable.addListener((e,o,n)->{
- if(isForwardUpdating.get()) {
- return;
- }
+ if(isForwardUpdating.get()) return;
+
try {
isReverseUpdating.set(true);
setValue(reverseMapper.apply(n));
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedParameter.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedParameter.java
index 6c7e285fd9a..8e359d6e960 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedParameter.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedParameter.java
@@ -20,49 +20,26 @@
import java.util.Optional;
+import org.jspecify.annotations.NonNull;
+
import org.apache.causeway.commons.collections.Can;
-import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
-import org.apache.causeway.core.metamodel.consent.Veto;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
-import org.jspecify.annotations.NonNull;
-import lombok.extern.log4j.Log4j2;
-
-@Log4j2
-public abstract class ManagedParameter
-implements
+public interface ManagedParameter
+extends
ManagedValue,
ManagedFeature {
-
- public abstract int getParamNr();
- @Override public abstract ObjectActionParameter getMetaModel();
- public abstract ParameterNegotiationModel getNegotiationModel();
-
+
+ ObjectActionParameter metaModel();
+ @Override default ObjectActionParameter getMetaModel() { return
metaModel(); }
+
+ int paramIndex();
+ ParameterNegotiationModel negotiationModel();
+
/**
- * @param params
* @return non-empty if not usable/editable (meaning if read-only)
*/
- public final Optional<InteractionVeto> checkUsability(final @NonNull
Can<ManagedObject> params) {
-
- try {
- var head = getNegotiationModel().getHead();
-
- var usabilityConsent =
- getMetaModel()
- .isUsable(head, params, InteractionInitiatedBy.USER);
-
- return usabilityConsent.isVetoed()
- ? Optional.of(InteractionVeto.readonly(usabilityConsent))
- : Optional.empty();
-
- } catch (final Exception ex) {
-
- log.warn(ex.getLocalizedMessage(), ex);
- return Optional.of(InteractionVeto.readonly(new Veto("failure
during usability evaluation")));
-
- }
-
- }
+ Optional<InteractionVeto> checkUsability(@NonNull Can<ManagedObject>
params);
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
index 5d1f3e81dcd..ea19b022518 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ParameterNegotiationModel.java
@@ -24,12 +24,14 @@
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
+import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.apache.causeway.applib.Identifier;
import org.apache.causeway.commons.binding.Bindable;
import org.apache.causeway.commons.binding.Observable;
import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.base._Lazy;
import org.apache.causeway.commons.internal.binding._BindableAbstract;
import org.apache.causeway.commons.internal.binding._Bindables;
import org.apache.causeway.commons.internal.binding._Bindables.BooleanBindable;
@@ -38,6 +40,7 @@
import org.apache.causeway.core.metamodel.consent.Consent;
import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.causeway.core.metamodel.consent.InteractionResult;
+import org.apache.causeway.core.metamodel.consent.Veto;
import
org.apache.causeway.core.metamodel.interactions.managed._BindingUtil.TargetFormat;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.object.ManagedObjects;
@@ -45,8 +48,7 @@
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
-import lombok.Getter;
-import org.jspecify.annotations.NonNull;
+import lombok.extern.log4j.Log4j2;
/**
* Model used to negotiate the parameter values of an action by means of an UI
dialog.
@@ -133,7 +135,7 @@ public void setParamValues(final @NonNull
Can<ManagedObject> paramValues) {
var valueIterator = paramValues.iterator();
paramModels.forEach(paramModel->{
if(!valueIterator.hasNext()) return;
- paramModel.getBindableParamValue().setValue(valueIterator.next());
+ paramModel.bindableParamValue().setValue(valueIterator.next());
});
}
@@ -148,19 +150,19 @@ public void setParamValues(final @NonNull
Can<ManagedObject> paramValues) {
}
@NonNull public Bindable<ManagedObject> getBindableParamValue(final int
paramNr) {
- return paramModels.getElseFail(paramNr).getBindableParamValue();
+ return paramModels.getElseFail(paramNr).bindableParamValue();
}
@NonNull public BooleanBindable getBindableParamValueDirtyFlag(final int
paramNr) {
- return
paramModels.getElseFail(paramNr).getBindableParamValueDirtyFlag();
+ return paramModels.getElseFail(paramNr).bindableParamValueDirtyFlag();
}
@NonNull public Observable<Can<ManagedObject>>
getObservableParamChoices(final int paramNr) {
- return paramModels.getElseFail(paramNr).getObservableParamChoices();
+ return paramModels.getElseFail(paramNr).observableParamChoices();
}
@NonNull public Observable<String> getObservableParamValidation(final int
paramNr) {
- return paramModels.getElseFail(paramNr).getObservableParamValidation();
+ return paramModels.getElseFail(paramNr).observableParamValidation();
}
/**
@@ -168,20 +170,26 @@ public void setParamValues(final @NonNull
Can<ManagedObject> paramValues) {
* (Ignoring the {@link ParameterModel#isValidationFeedbackActive()} flag.)
* @apiNote introduced for [CAUSEWAY-3753] - not sure why required.
*/
- @NonNull public String validateImmediately(final int paramNr) {
- return paramModels.getElseFail(paramNr).validateImmediately();
+ @NonNull public String validateImmediately(final int paramIndex) {
+ return getHead().getMetaModel().getParameterByIndex(paramIndex)
+ .isValid(
+ getHead(),
+ getParamValues(),
+ InteractionInitiatedBy.USER)
+ .getReasonAsString()
+ .orElse(null);
}
@NonNull public Bindable<String> getBindableParamSearchArgument(final int
paramNr) {
- return
paramModels.getElseFail(paramNr).getBindableParamSearchArgument();
+ return paramModels.getElseFail(paramNr).bindableParamSearchArgument();
}
@NonNull public Observable<Consent> getObservableVisibilityConsent(final
int paramNr) {
- return
paramModels.getElseFail(paramNr).getObservableVisibilityConsent();
+ return paramModels.getElseFail(paramNr).observableVisibilityConsent();
}
@NonNull public Observable<Consent> getObservableUsabilityConsent(final
int paramNr) {
- return
paramModels.getElseFail(paramNr).getObservableUsabilityConsent();
+ return paramModels.getElseFail(paramNr).observableUsabilityConsent();
}
@NonNull public Consent getVisibilityConsent(final int paramNr) {
@@ -232,13 +240,13 @@ public void setParamValue(final int paramIndex, final
@Nullable ManagedObject ne
if (ManagedObjects.isNullOrUnspecifiedOrEmpty(newParamValue)) {
clearParamValue(paramIndex);
} else {
-
paramModels.getElseFail(paramIndex).getBindableParamValue().setValue(newParamValue);
+
paramModels.getElseFail(paramIndex).bindableParamValue().setValue(newParamValue);
}
}
public void clearParamValue(final int paramIndex) {
var emptyValue = adaptParamValuePojo(paramIndex, null);
-
paramModels.getElseFail(paramIndex).getBindableParamValue().setValue(emptyValue);
+
paramModels.getElseFail(paramIndex).bindableParamValue().setValue(emptyValue);
}
/**
@@ -247,7 +255,7 @@ public void clearParamValue(final int paramIndex) {
* and returns the new parameter argument value also wrapped as
{@link ManagedObject}
*/
public void updateParamValue(final int paramIndex, final @NonNull
UnaryOperator<ManagedObject> updater) {
- var bindableParamValue =
paramModels.getElseFail(paramIndex).getBindableParamValue();
+ var bindableParamValue =
paramModels.getElseFail(paramIndex).bindableParamValue();
var newParamValue = updater.apply(bindableParamValue.getValue());
if (ManagedObjects.isNullOrUnspecifiedOrEmpty(newParamValue)) {
clearParamValue(paramIndex);
@@ -320,92 +328,127 @@ public PendingParamsSnapshot createSnapshotModel() {
}
/**
- * Returns a copy, but with a single param value replaced.
+ * Returns a copy, but with a single param value replaced.
*/
- public ParameterNegotiationModel withParamValue(int parameterIndex,
@NonNull ManagedObject paramValue) {
+ public ParameterNegotiationModel withParamValue(final int parameterIndex,
@NonNull final ManagedObject paramValue) {
return ParameterNegotiationModel.of(managedAction,
getParamValues().replace(parameterIndex, paramValue));
}
-
+
// -- INTERNAL HOLDER OF PARAMETER BINDABLES
- private static class ParameterModel extends ManagedParameter {
-
- @Getter(onMethod_ = {@Override}) private final int paramNr;
- @Getter(onMethod_ = {@Override}) @NonNull private final
ObjectActionParameter metaModel;
- @Getter(onMethod_ = {@Override}) @NonNull private final
ParameterNegotiationModel negotiationModel;
- @Getter @NonNull private final _BindableAbstract<ManagedObject>
bindableParamValue;
- @Getter @NonNull private final BooleanBindable
bindableParamValueDirtyFlag;
- @Getter @NonNull private final LazyObservable<String>
observableParamValidation;
- @Getter @NonNull private final _BindableAbstract<String>
bindableParamSearchArgument;
- @Getter @NonNull private final LazyObservable<Can<ManagedObject>>
observableParamChoices;
- @Getter @NonNull private final LazyObservable<Consent>
observableVisibilityConsent;
- @Getter @NonNull private final LazyObservable<Consent>
observableUsabilityConsent;
- private Observable<String> bindableParamAsTitle;
- private Observable<String> bindableParamAsHtml;
- private Bindable<String> bindableParamAsParsableText;
+ @Log4j2
+ private record ParameterModel(
+ int paramIndex,
+ @NonNull ObjectActionParameter metaModel,
+ @NonNull ParameterNegotiationModel negotiationModel,
+ @NonNull _BindableAbstract<ManagedObject> bindableParamValue,
+ @NonNull BooleanBindable bindableParamValueDirtyFlag,
+ @NonNull _BindableAbstract<String> bindableParamSearchArgument,
+ @NonNull LazyObservable<String> observableParamValidation,
+ @NonNull LazyObservable<Can<ManagedObject>> observableParamChoices,
+ @NonNull LazyObservable<Consent> observableVisibilityConsent,
+ @NonNull LazyObservable<Consent> observableUsabilityConsent,
+ @NonNull Observable<String> observableParamAsTitle,
+ @NonNull Observable<String> observableParamAsHtml,
+ @NonNull _Lazy<Bindable<String>> bindableParamAsParsableTextLazy
+ ) implements ManagedParameter {
private ParameterModel(
- final int paramNr,
- final @NonNull ParameterNegotiationModel negotiationModel,
- final @NonNull ManagedObject initialValue) {
-
- var action = negotiationModel.getHead().getMetaModel();
+ final int paramIndex,
+ final @NonNull ParameterNegotiationModel negotiationModel,
+ final @NonNull ManagedObject initialValue) {
+ this(paramIndex,
negotiationModel.getHead().getMetaModel().getParameterByIndex(paramIndex),
negotiationModel,
+ // bindableParamValue
+ _Bindables.forValue(initialValue),
+ // bindableParamValueDirtyFlag
+ _Bindables.forBoolean(false),
+ // bindableParamSearchArgument
+ _Bindables.forValue(null),
+ // unused in canonical constructor ..
+ null, null, null, null, null, null, null);
+ }
- this.paramNr = paramNr;
- this.metaModel = action.getParameters().getElseFail(paramNr);
+ // canonical constructor
+ ParameterModel(
+ final int paramIndex,
+ @NonNull final ObjectActionParameter metaModel,
+ @NonNull final ParameterNegotiationModel negotiationModel,
+ @NonNull final _BindableAbstract<ManagedObject> bindableParamValue,
+ @NonNull final BooleanBindable bindableParamValueDirtyFlag,
+ @NonNull final _BindableAbstract<String>
bindableParamSearchArgument,
+ // unused ..
+ final LazyObservable<String> observableParamValidation,
+ final LazyObservable<Can<ManagedObject>> observableParamChoices,
+ final LazyObservable<Consent> observableVisibilityConsent,
+ final LazyObservable<Consent> observableUsabilityConsent,
+ final Observable<String> observableParamAsTitle,
+ final Observable<String> observableParamAsHtml,
+ final _Lazy<Bindable<String>> bindableParamAsParsableTextLazy
+ ) {
+ this.paramIndex = paramIndex;
+ this.metaModel = metaModel;
this.negotiationModel = negotiationModel;
- bindableParamValue = _Bindables.forValue(initialValue);
- bindableParamValueDirtyFlag = _Bindables.forBoolean(false);
+ this.bindableParamValue = bindableParamValue;
+ this.bindableParamValueDirtyFlag = bindableParamValueDirtyFlag;
+ this.bindableParamSearchArgument = bindableParamSearchArgument;
- //bindableParamValue.setValueRefiner(MmEntityUtil::refetch); no
longer used
-
bindableParamValue.setValueGuard(MmAssertionUtils.assertInstanceOf(metaModel.getElementType()));
- bindableParamValue.addListener((event, oldValue, newValue)->{
+
bindableParamValue().setValueGuard(MmAssertionUtils.assertInstanceOf(metaModel().getElementType()));
+ bindableParamValue().addListener((event, oldValue, newValue)->{
if(newValue==null) {
// lift null to empty ...
- bindableParamValue.setValue(metaModel.getEmpty()); //
triggers this event again
+ bindableParamValue().setValue(metaModel().getEmpty()); //
triggers this event again
return;
}
- getNegotiationModel().onNewParamValue();
- bindableParamValueDirtyFlag.setValue(true); // set dirty
whenever an update event happens
+ negotiationModel().onNewParamValue();
+ bindableParamValueDirtyFlag().setValue(true); // set dirty
whenever an update event happens
});
// has either autoComplete, choices, or none
- observableParamChoices = metaModel.hasAutoComplete()
- ? _Observables.lazy(()->
- getMetaModel().getAutoComplete(
- getNegotiationModel(),
- getBindableParamSearchArgument().getValue(),
- InteractionInitiatedBy.USER))
- : metaModel.hasChoices()
+ this.observableParamChoices = metaModel().hasAutoComplete()
? _Observables.lazy(()->
- getMetaModel().getChoices(getNegotiationModel(),
InteractionInitiatedBy.USER))
- : _Observables.lazy(Can::empty);
+ metaModel().getAutoComplete(
+ negotiationModel(),
+ bindableParamSearchArgument().getValue(),
+ InteractionInitiatedBy.USER))
+ : metaModel().hasChoices()
+ ? _Observables.lazy(()->
+ getMetaModel().getChoices(negotiationModel(),
InteractionInitiatedBy.USER))
+ : _Observables.lazy(Can::empty);
// if has autoComplete, then activate the search argument
- bindableParamSearchArgument = _Bindables.forValue(null);
- if(metaModel.hasAutoComplete()) {
- bindableParamSearchArgument.addListener((e,o,n)->{
- observableParamChoices.invalidate();
+ if(metaModel().hasAutoComplete()) {
+ this.bindableParamSearchArgument.addListener((e,o,n)->{
+ observableParamChoices().invalidate();
});
}
// validate this parameter, but only when validationFeedback has
been activated
- observableParamValidation = _Observables.lazy(()->
+ this.observableParamValidation = _Observables.lazy(()->
isValidationFeedbackActive()
- ? validateImmediately()
+ ? negotiationModel().validateImmediately(paramIndex)
: (String)null);
- observableVisibilityConsent = _Observables.lazy(()->
- metaModel.isVisible(
- negotiationModel.getHead(),
- negotiationModel.getParamValues(),
+ this.observableVisibilityConsent = _Observables.lazy(()->
+ metaModel().isVisible(
+ negotiationModel().getHead(),
+ negotiationModel().getParamValues(),
InteractionInitiatedBy.USER));
- observableUsabilityConsent = _Observables.lazy(()->
- metaModel.isUsable(
- negotiationModel.getHead(),
- negotiationModel.getParamValues(),
+ this.observableUsabilityConsent = _Observables.lazy(()->
+ metaModel().isUsable(
+ negotiationModel().getHead(),
+ negotiationModel().getParamValues(),
InteractionInitiatedBy.USER));
+
+ // value types should have associated rederers via value semantics
+ this.observableParamAsTitle = _BindingUtil
+ .bindAsFormated(TargetFormat.TITLE, metaModel(),
bindableParamValue());
+ this.observableParamAsHtml = _BindingUtil
+ .bindAsFormated(TargetFormat.HTML, metaModel(),
bindableParamValue());
+ // value types should have associated parsers/formatters via value
semantics
+ // except for composite value types, which might have not
+ this.bindableParamAsParsableTextLazy =
_Lazy.threadSafe(()->(Bindable<String>) _BindingUtil
+ .bindAsFormated(TargetFormat.PARSABLE_TEXT, metaModel(),
bindableParamValue()));
}
public void invalidateChoicesAndValidation() {
@@ -419,7 +462,7 @@ public void invalidateVisibilityAndUsability() {
}
private boolean isValidationFeedbackActive() {
- return
getNegotiationModel().getObservableValidationFeedbackActive().getValue();
+ return
negotiationModel().getObservableValidationFeedbackActive().getValue();
}
// -- MANAGED PARAMETER
@@ -452,22 +495,12 @@ public Bindable<ManagedObject> getValue() {
@Override
public Observable<String> getValueAsTitle() {
- if(bindableParamAsTitle==null) {
- // value types should have associated rederers via value
semantics
- bindableParamAsTitle = _BindingUtil
- .bindAsFormated(TargetFormat.TITLE, metaModel,
bindableParamValue);
- }
- return bindableParamAsTitle;
+ return observableParamAsTitle;
}
@Override
public Observable<String> getValueAsHtml() {
- if(bindableParamAsHtml==null) {
- // value types should have associated rederers via value
semantics
- bindableParamAsHtml = _BindingUtil
- .bindAsFormated(TargetFormat.HTML, metaModel,
bindableParamValue);
- }
- return bindableParamAsHtml;
+ return observableParamAsHtml;
}
@Override
@@ -477,13 +510,7 @@ public boolean isValueAsParsableTextSupported() {
@Override
public Bindable<String> getValueAsParsableText() {
- if(bindableParamAsParsableText==null) {
- // value types should have associated parsers/formatters via
value semantics
- // except for composite value types, which might have not
- bindableParamAsParsableText = (Bindable<String>) _BindingUtil
- .bindAsFormated(TargetFormat.PARSABLE_TEXT, metaModel,
bindableParamValue);
- }
- return bindableParamAsParsableText;
+ return bindableParamAsParsableTextLazy.get();
}
@Override
@@ -501,20 +528,44 @@ public Observable<Can<ManagedObject>> getChoices() {
return observableParamChoices;
}
- // -- HELPER
-
- /**
- * Calls the underlying action parameter validation logic, for pending
arguments.
- * (Ignoring the {@link #isValidationFeedbackActive()} flag.)
- */
- private String validateImmediately() {
- return metaModel
- .isValid(
- getNegotiationModel().getHead(),
- getNegotiationModel().getParamValues(),
- InteractionInitiatedBy.USER)
- .getReasonAsString()
- .orElse(null);
+ @Override
+ public Optional<InteractionVeto> checkUsability(@NonNull final
Can<ManagedObject> params) {
+
+ try {
+ var head = negotiationModel().getHead();
+
+ var usabilityConsent =
+ getMetaModel()
+ .isUsable(head, params, InteractionInitiatedBy.USER);
+
+ return usabilityConsent.isVetoed()
+ ? Optional.of(InteractionVeto.readonly(usabilityConsent))
+ : Optional.empty();
+
+ } catch (final Exception ex) {
+ log.warn(ex.getLocalizedMessage(), ex);
+ return Optional.of(InteractionVeto.readonly(new Veto("failure
during usability evaluation")));
+ }
+
+ }
+
+ // -- OBJECT CONTRACT
+
+ @Override
+ public final boolean equals(final Object obj) {
+ return obj instanceof ParameterModel other
+ ? Objects.equals(this.getIdentifier(), other.getIdentifier())
+ : false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hashCode(getIdentifier());
+ }
+
+ @Override
+ public final String toString() {
+ return "ParameterModel[id=%s]".formatted(getIdentifier());
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/PendingParamsSnapshot.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/PendingParamsSnapshot.java
index e2fd4a0f8eb..dabe115db39 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/PendingParamsSnapshot.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/PendingParamsSnapshot.java
@@ -144,7 +144,7 @@ private Can<Bookmark> bookmark(
()->String.format("Framework Bug: cardinality
constraint mismatch on parameter %s",
paramModel.getMetaModel().getFeatureIdentifier()));
if(isPlural) {
- cardinalityConstraints[paramModel.getParamNr()] =
+ cardinalityConstraints[paramModel.paramIndex()] =
Objects.requireNonNull(((PackedManagedObject)paramValue).getLogicalType());
}
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/_BindingUtil.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/_BindingUtil.java
index f9a7b8b0e0b..f370462a077 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/_BindingUtil.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/_BindingUtil.java
@@ -18,22 +18,23 @@
*/
package org.apache.causeway.core.metamodel.interactions.managed;
+import org.jspecify.annotations.NonNull;
+
import org.apache.causeway.applib.value.semantics.Parser;
import org.apache.causeway.applib.value.semantics.Renderer;
import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
+import org.apache.causeway.commons.binding.Bindable;
import org.apache.causeway.commons.binding.Observable;
import org.apache.causeway.commons.functional.Either;
import org.apache.causeway.commons.internal.binding._BindableAbstract;
import org.apache.causeway.commons.internal.binding._Bindables;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.core.metamodel.facetapi.FeatureType;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.object.MmUnwrapUtils;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
-import org.jspecify.annotations.NonNull;
import lombok.experimental.UtilityClass;
@UtilityClass
@@ -52,6 +53,10 @@ boolean requiresRenderer() {
}
}
+ /**
+ * For {@link TargetFormat#PARSABLE_TEXT} returns a {@link Bindable},
+ * otherwise just an {@link Observable}.
+ */
@SuppressWarnings({ "rawtypes" })
Observable<String> bindAsFormated(
final @NonNull TargetFormat format,
@@ -62,50 +67,53 @@ Observable<String> bindAsFormated(
// value types should have associated parsers/formatters via value
semantics
return spec.valueFacet()
- .map(valueFacet->{
- var eitherRendererOrParser = format.requiresRenderer()
- ? Either.<Renderer,
Parser>left(valueFacet.selectRendererForParamOrPropOrCollOrElseFallback(prop))
- : Either.<Renderer,
Parser>right(valueFacet.selectParserForAttributeOrElseFallback(prop));
- var ctx = valueFacet.createValueSemanticsContext(prop);
-
- return bindAsFormated(format, spec, bindablePropertyValue,
eitherRendererOrParser, ctx);
- })
- .orElseGet(()->
- // fallback Bindable that is floating free (unbound)
- // writing to it has no effect on the domain
- _Bindables.forValue(String.format("Could not find a ValueFacet for
type %s",
- spec.logicalType()))
- );
-
+ .map(valueFacet->{
+ var eitherRendererOrParser = format.requiresRenderer()
+ ? Either.<Renderer,
Parser>left(valueFacet.selectRendererForParamOrPropOrCollOrElseFallback(prop))
+ : Either.<Renderer,
Parser>right(valueFacet.selectParserForAttributeOrElseFallback(prop));
+ var ctx = valueFacet.createValueSemanticsContext(prop);
+
+ return bindAsFormated(format, spec, bindablePropertyValue,
eitherRendererOrParser, ctx);
+ })
+ .orElseGet(()->
+ // fallback Bindable that is floating free (unbound)
+ // writing to it has no effect on the domain
+ _Bindables.forValue(String.format("Could not find a ValueFacet
for type %s",
+ spec.logicalType()))
+ );
}
+ /**
+ * For {@link TargetFormat#PARSABLE_TEXT} returns a {@link Bindable},
+ * otherwise just an {@link Observable}.
+ */
@SuppressWarnings({ "rawtypes" })
Observable<String> bindAsFormated(
final @NonNull TargetFormat format,
final @NonNull ObjectActionParameter param,
final @NonNull _BindableAbstract<ManagedObject>
bindableParamValue) {
- guardAgainstNonScalarParam(param);
+ // non-scalar action parameters are neither parseable nor renderable
+ if(param.isPlural()) return _Bindables.forValue("multiple parameters");
var spec = param.getElementType();
// value types should have associated parsers/formatters via value
semantics
return spec.valueFacet()
- .map(valueFacet->{
- var eitherRendererOrParser = format.requiresRenderer()
- ? Either.<Renderer,
Parser>left(valueFacet.selectRendererForParamOrPropOrCollOrElseFallback(param))
- : Either.<Renderer,
Parser>right(valueFacet.selectParserForAttributeOrElseFallback(param));
- var ctx = valueFacet.createValueSemanticsContext(param);
-
- return bindAsFormated(format, spec, bindableParamValue,
eitherRendererOrParser, ctx);
- })
- .orElseGet(()->
- // fallback Bindable that is floating free (unbound)
- // writing to it has no effect on the domain
- _Bindables.forValue(String.format("Could not find a ValueFacet for
type %s",
- spec.logicalType()))
- );
-
+ .map(valueFacet->{
+ var eitherRendererOrParser = format.requiresRenderer()
+ ? Either.<Renderer,
Parser>left(valueFacet.selectRendererForParamOrPropOrCollOrElseFallback(param))
+ : Either.<Renderer,
Parser>right(valueFacet.selectParserForAttributeOrElseFallback(param));
+ var ctx = valueFacet.createValueSemanticsContext(param);
+
+ return bindAsFormated(format, spec, bindableParamValue,
eitherRendererOrParser, ctx);
+ })
+ .orElseGet(()->
+ // fallback Bindable that is floating free (unbound)
+ // writing to it has no effect on the domain
+ _Bindables.forValue(String.format("Could not find a ValueFacet
for type %s",
+ spec.logicalType()))
+ );
}
// -- PREDICATES
@@ -118,7 +126,7 @@ boolean hasParser(final @NonNull OneToOneAssociation prop) {
}
boolean hasParser(final @NonNull ObjectActionParameter param) {
- return isNonScalarParam(param)
+ return param.isPlural()
? false
: param.getElementType()
.valueFacet()
@@ -128,18 +136,10 @@ boolean hasParser(final @NonNull ObjectActionParameter
param) {
// -- HELPER
- private boolean isNonScalarParam(final @NonNull ObjectActionParameter
param) {
- return param.getFeatureType() == FeatureType.ACTION_PARAMETER_PLURAL;
- }
-
- private void guardAgainstNonScalarParam(final @NonNull
ObjectActionParameter param) {
- if(isNonScalarParam(param)) {
- throw _Exceptions.illegalArgument(
- "Non-scalar action parameters are neither parseable nor
renderable: %s",
- param.getFeatureIdentifier());
- }
- }
-
+ /**
+ * For {@link TargetFormat#PARSABLE_TEXT} returns a {@link Bindable},
+ * otherwise just an {@link Observable}.
+ */
@SuppressWarnings({ "unchecked", "rawtypes" })
private Observable<String> bindAsFormated(
final @NonNull TargetFormat format,
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
index c2a1a486f19..d9367698a51 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmDebugUtils.java
@@ -50,7 +50,7 @@ public String formatted() {
}
String formatted(final ManagedParameter managedParam) {
return String.format("- param[index=%d,name=%s]: %s",
- managedParam.getParamNr(),
+ managedParam.paramIndex(),
managedParam.getFriendlyName(),
formatPendingValue(managedParam.getValue().getValue()));
}
diff --git
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
index 137a28c007e..11f0d68c840 100644
---
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
+++
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
@@ -21,7 +21,8 @@
import jakarta.inject.Named;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -38,6 +39,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
+import lombok.SneakyThrows;
class DataTableSerializationTest implements HasMetaModelContext {
@@ -49,52 +51,59 @@ final void setUp() throws Exception {
.build();
}
- @Named("DataTableSerializationTest.Customer")
+ @Named("DataTableSerializationTest.CustomerClass")
@AllArgsConstructor
- public static class Customer implements ViewModel {
+ public static class CustomerClass implements ViewModel {
- @Property
- @Getter @Setter
- private String memento;
+ @Property @Getter @Setter private String memento;
- @Override
- public String viewModelMemento() {
- return memento;
- }
+ @Override public String viewModelMemento() { return memento; }
+ }
+
+ @Named("DataTableSerializationTest.CustomerRecord")
+ public record CustomerRecord(@Property String memento) implements
ViewModel {
+ @Override public String viewModelMemento() { return memento; }
}
- @Test
- void roundtripOnEmptyTable() {
- var original = DataTable.forDomainType(Customer.class);
+
+ @ParameterizedTest
+ @ValueSource(classes = {CustomerClass.class, CustomerRecord.class})
+ void roundtripOnEmptyTable(Class<? extends ViewModel> viewmodelClass) {
+ var original = DataTable.forDomainType(viewmodelClass);
var afterRoundtrip = _SerializationTester.roundtrip(original);
assertNotNull(afterRoundtrip);
assertEquals(
- "DataTableSerializationTest.Customer",
+ "DataTableSerializationTest." + viewmodelClass.getSimpleName(),
afterRoundtrip.elementType().logicalTypeName());
assertEquals(0, afterRoundtrip.getElementCount());
assertEquals(1, afterRoundtrip.dataColumns().size());
assertEquals(0, afterRoundtrip.dataRows().size());
}
- @Test
- void roundtripOnPopulatedTable() {
- var original = DataTable.forDomainType(Customer.class)
- .withDataElementPojos(Can.of(
- getObjectManager().adapt(new Customer("cus-1")),
- getObjectManager().adapt(new Customer("cus-2"))
- ));
+ @ParameterizedTest
+ @ValueSource(classes = {CustomerClass.class, CustomerRecord.class})
+ void roundtripOnPopulatedTable(Class<? extends ViewModel> viewmodelClass) {
+ var original = DataTable.forDomainType(viewmodelClass)
+ .withDataElementPojos(Can.of("cus-1", "cus-2")
+ .map(name->newInstance(viewmodelClass, name))
+ .map(getObjectManager()::adapt));
var afterRoundtrip = _SerializationTester.roundtrip(original);
assertNotNull(afterRoundtrip);
assertEquals(2, afterRoundtrip.dataRows().size());
- var cus1 = (Customer)
afterRoundtrip.dataRows().getElseFail(0).rowElement().getPojo();
- var cus2 = (Customer)
afterRoundtrip.dataRows().getElseFail(1).rowElement().getPojo();
+ var cus1 = (ViewModel)
afterRoundtrip.dataRows().getElseFail(0).rowElement().getPojo();
+ var cus2 = (ViewModel)
afterRoundtrip.dataRows().getElseFail(1).rowElement().getPojo();
- assertEquals("cus-1", cus1.getMemento());
- assertEquals("cus-2", cus2.getMemento());
+ assertEquals("cus-1", cus1.viewModelMemento());
+ assertEquals("cus-2", cus2.viewModelMemento());
+ }
+
+ @SneakyThrows
+ private static ViewModel newInstance(Class<? extends ViewModel>
viewmodelClass, String memento) {
+ return viewmodelClass.getConstructor(new
Class<?>[]{String.class}).newInstance(memento);
}
}
diff --git
a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java
b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java
index 8caf6f371ae..2a76ec182e8 100644
---
a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java
+++
b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/util/interaction/DomainObjectTesterFactory.java
@@ -367,7 +367,7 @@ public void assertInvocationResult(
pendingArgs.getParamModels()
.forEach(param->{
pojoReplacers
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(replacer->updatePojo(param,
replacer));
});
@@ -397,7 +397,7 @@ public Object invokeWithPojos(final List<Object>
pojoArgList) {
pendingArgs.getParamModels()
.forEach(param->{
pojoVector
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(pojo->updatePojo(param, __->pojo));
});
@@ -431,7 +431,7 @@ public void assertInvocationResultNoRules(
pendingArgs.getParamModels()
.forEach(param->{
pojoReplacers
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(replacer->updatePojo(param, replacer));
});
@@ -463,7 +463,7 @@ public void assertParameterValues(
pendingArgs.getParamModels()
.forEach(param->{
pojoTests
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(pojoTest->
pojoTest.accept(
MmUnwrapUtils.single(param.getValue().getValue())
@@ -491,7 +491,7 @@ public <X> void assertParameterChoices(
startParameterNegotiation(checkRules).getParamModels()
.forEach(param->{
pojoTests
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(pojoTest->
pojoTest.accept(
(List<X>) choicesFor(param)
@@ -526,10 +526,10 @@ public void assertParameterVisibility(
pendingArgs.getParamModels()
.forEach(param->{
- var consent =
pendingArgs.getVisibilityConsent(param.getParamNr());
+ var consent =
pendingArgs.getVisibilityConsent(param.paramIndex());
visibilityTests
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(visibilityTest->
visibilityTest.accept(consent.isAllowed()));
});
@@ -554,10 +554,10 @@ public void assertParameterUsability(
pendingArgs.getParamModels()
.forEach(param->{
- var consent =
pendingArgs.getUsabilityConsent(param.getParamNr());
+ var consent =
pendingArgs.getUsabilityConsent(param.paramIndex());
usabilityTests
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(usabilityTest->
usabilityTest.accept(consent.getReasonAsString().orElse(null)));
});
@@ -583,7 +583,7 @@ public void assertValidationMessage(
var objManager =
param.getMetaModel().getObjectManager();
pojoArgMappers
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(argMapper->
param.getValue().setValue(
objManager
@@ -640,7 +640,7 @@ public DataTableTester tableTester(
pendingArgs.getParamModels()
.forEach(param->{
pojoReplacers
- .get(param.getParamNr())
+ .get(param.paramIndex())
.ifPresent(replacer->updatePojo(param,
replacer));
});