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
commit b741654e834aad29c038bb152c48066410fc9bfc Author: Andi Huber <[email protected]> AuthorDate: Fri Jun 27 08:05:45 2025 +0200 CAUSEWAY-2297: immutable InteractionResult (refactor) --- .../applib/events/InteractionEventTest.java | 22 +-- core/metamodel/src/main/java/module-info.java | 1 + .../core/metamodel/consent/ConsentAbstract.java | 2 +- .../core/metamodel/consent/InteractionResult.java | 149 ++++++--------------- .../metamodel/consent/InteractionResultSet.java | 13 +- .../metamodel/interactions/InteractionUtils.java | 82 ++++++------ .../interactions/managed/ManagedFeature.java | 3 +- .../interactions/managed/ManagedMember.java | 8 +- .../interactions/managed/ManagedParameter.java | 11 +- .../managed/ParameterNegotiationModel.java | 7 +- .../core/metamodel/object/MmVisibilityUtils.java | 2 +- .../metamodel/consent/InteractionResultTest.java | 59 ++++---- .../handlers/DomainObjectInvocationHandler.java | 56 +++----- 13 files changed, 164 insertions(+), 251 deletions(-) diff --git a/api/applib/src/test/java/org/apache/causeway/applib/events/InteractionEventTest.java b/api/applib/src/test/java/org/apache/causeway/applib/events/InteractionEventTest.java index cca86b18d5b..c7d2dcf37b9 100644 --- a/api/applib/src/test/java/org/apache/causeway/applib/events/InteractionEventTest.java +++ b/api/applib/src/test/java/org/apache/causeway/applib/events/InteractionEventTest.java @@ -42,7 +42,7 @@ class InteractionEventTest { private static class CustomerOrder {} @BeforeEach - public void setUp() { + void setUp() { source = new Object(); identifier = Identifier.actionIdentifier( LogicalType.fqcn(CustomerOrder.class), @@ -51,7 +51,7 @@ public void setUp() { } @Test - public void getIdentifier() { + void getIdentifier() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -59,7 +59,7 @@ public void getIdentifier() { } @Test - public void getSource() { + void getSource() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -67,7 +67,7 @@ public void getSource() { } @Test - public void getClassName() { + void getClassName() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -75,7 +75,7 @@ public void getClassName() { } @Test - public void getClassNaturalName() { + void getClassNaturalName() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -83,7 +83,7 @@ public void getClassNaturalName() { } @Test - public void getMember() { + void getMember() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -91,7 +91,7 @@ public void getMember() { } @Test - public void getMemberNaturalName() { + void getMemberNaturalName() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -99,7 +99,7 @@ public void getMemberNaturalName() { } @Test - public void shouldInitiallyNotVeto() { + void shouldInitiallyNotVeto() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -107,7 +107,7 @@ public void shouldInitiallyNotVeto() { } @Test - public void afterAdvisedShouldVeto() { + void afterAdvisedShouldVeto() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -116,7 +116,7 @@ public void afterAdvisedShouldVeto() { } @Test - public void afterAdvisedShouldReturnReason() { + void afterAdvisedShouldReturnReason() { interactionEvent = new InteractionEvent(source, identifier) { }; @@ -125,7 +125,7 @@ public void afterAdvisedShouldReturnReason() { } @Test - public void afterAdvisedShouldReturnAdvisorClass() { + void afterAdvisedShouldReturnAdvisorClass() { interactionEvent = new InteractionEvent(source, identifier) { }; diff --git a/core/metamodel/src/main/java/module-info.java b/core/metamodel/src/main/java/module-info.java index aaae8756b6f..50a01928912 100644 --- a/core/metamodel/src/main/java/module-info.java +++ b/core/metamodel/src/main/java/module-info.java @@ -171,6 +171,7 @@ requires spring.context; requires spring.core; requires spring.boot.autoconfigure; + requires org.jspecify; //JUnit testing stuff, not required as long this module is an 'open' one // opens org.apache.causeway.core.metamodel.services to spring.core; diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/ConsentAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/ConsentAbstract.java index 733db18abbb..fa4abf02d24 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/ConsentAbstract.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/ConsentAbstract.java @@ -53,7 +53,7 @@ public static Consent allowIf(final boolean allowed) { private static VetoReason determineReason(final InteractionResult interactionResult) { return interactionResult == null ? null - : interactionResult.getReason().orElse(null); + : interactionResult.vetoReason().orElse(null); } /** diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResult.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResult.java index caa453b377f..6f2a155842b 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResult.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResult.java @@ -19,131 +19,68 @@ package org.apache.causeway.core.metamodel.consent; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.apache.causeway.applib.services.wrapper.events.InteractionEvent; +import org.apache.causeway.commons.collections.Can; import org.apache.causeway.core.metamodel.consent.Consent.VetoReason; -public class InteractionResult { - - /** - * Initially {@link #ADVISING}; when call - * {@link InteractionResult#getInteractionEvent()}, flips over into - * {@link #ADVISED}. - * - * <p> - * Subsequent attempts to - * {@link InteractionResult#advise(String, InteractionAdvisor)} will then be - * disallowed. - */ - enum State { - ADVISING, ADVISED - } - - private final InteractionEvent interactionEvent; - private final List<Consent.VetoReason> reasonBuf = new ArrayList<>(); - private final List<InteractionAdvisor> advisors = new ArrayList<InteractionAdvisor>(); - - private State state = State.ADVISING; - - public InteractionResult(final InteractionEvent interactionEvent) { - this.interactionEvent = interactionEvent; - } - - /** - * Returns the contained {@link InteractionEvent}, if necessary updated with - * the {@link #advise(org.apache.causeway.core.metamodel.consent.Consent.VetoReason, InteractionAdvisor) advice} of the - * interactions. - * - * <p> - * That is, if still {@link State#ADVISING advising}, then copies over the - * details from this result into the contained {@link InteractionEvent}, and - * flips into {@link State#ADVISED advised (done)}. - */ - public InteractionEvent getInteractionEvent() { - if (state == State.ADVISING) { - final String nullableReasonString = getReason().map(VetoReason::string).orElse(null); - interactionEvent.advised(nullableReasonString, getAdvisorClass()); - state = State.ADVISED; +public record InteractionResult( + InteractionEvent interactionEvent, + Optional<Consent.VetoReason> vetoReason, + /** + * Any {@link InteractionAdvisor} advisors that have appended veto reasons. + */ + Can<InteractionAdvisor> advisors) { + + public static Builder builder(final InteractionEvent interactionEvent) { return new Builder(interactionEvent); } + public record Builder( + InteractionEvent interactionEvent, + List<Consent.VetoReason> reasonBuf, + List<InteractionAdvisor> advisors) { + public Builder(InteractionEvent interactionEvent) { + this(interactionEvent, new ArrayList<>(), new ArrayList<>()); } - return interactionEvent; - } - - private Class<?> getAdvisorClass() { - final InteractionAdvisor advisor = getAdvisor(); - return advisor != null ? advisor.getClass() : null; - } - - public void advise(final Consent.VetoReason reason, final InteractionAdvisor facet) { - if (state == State.ADVISED) { - throw new IllegalStateException("Cannot append since have called getInteractionEvent"); + public void addAdvise(final VetoReason reason, final InteractionAdvisor facet) { + reasonBuf.add(Objects.requireNonNull(reason)); + advisors.add(Objects.requireNonNull(facet)); } - if (reason == null) { - return; + public InteractionResult build() { + Optional<Consent.VetoReason> reason = reasonBuf.stream().reduce(Consent.VetoReason::reduce); + return new InteractionResult(interactionEvent, reason, Can.ofCollection(advisors)); } - advisors.add(facet); - reasonBuf.add(reason); } - public boolean isVetoing() { - return !isNotVetoing(); + // canonical constructor + public InteractionResult( + InteractionEvent interactionEvent, + Optional<Consent.VetoReason> vetoReason, + Can<InteractionAdvisor> advisors) { + this.interactionEvent = Objects.requireNonNull(interactionEvent); + this.vetoReason = Objects.requireNonNull(vetoReason); + this.advisors = Objects.requireNonNull(advisors); + + vetoReason.ifPresent(reason->{ + InteractionAdvisor advisor = advisors.stream().findFirst().orElseThrow(); + interactionEvent.advised(reason.string(), advisor.getClass()); + }); } - public boolean isNotVetoing() { - return reasonBuf.size() == 0; - } - - /** - * Returns the first of the {@link #getAdvisorFacets()} that has been - * {@link #advise(org.apache.causeway.core.metamodel.consent.Consent.VetoReason, InteractionAdvisor) advised} , or <tt>null</tt> if - * none yet. - * - * @see #getAdvisorFacets() - */ - public InteractionAdvisor getAdvisor() { - return advisors.size() >= 1 ? advisors.get(0) : null; - } - - /** - * Returns all {@link InteractionAdvisor advisor} (facet)s that have - * {@link #advise(org.apache.causeway.core.metamodel.consent.Consent.VetoReason, InteractionAdvisor) append}ed reasons to the - * buffer. - * - * @see #getAdvisor() - */ - public List<InteractionAdvisor> getAdvisorFacets() { - return Collections.unmodifiableList(advisors); - } + public boolean isAllowing() { return vetoReason.isEmpty(); } + public boolean isVetoing() { return vetoReason.isPresent(); } public Consent createConsent() { - if (isNotVetoing()) { - return new Allow(this); - } else { - return new Veto(this); - } - } - - /** - * Gets the reason as currently known, but does not change the state. - * <p> - * If {@link #isNotVetoing()}, then returns <tt>Optional.empty()</tt>. - */ - public Optional<Consent.VetoReason> getReason() { - return reasonBuf.stream().reduce(Consent.VetoReason::reduce); + return isAllowing() + ? new Allow(this) + : new Veto(this); } @Override public String toString() { - return String.format("%s: %s: %s (%d facets advised)", - interactionEvent, state, toStringInterpret(), advisors.size()); - } - - private String toStringInterpret() { - return isNotVetoing() - ? "allowed" - : "vetoed"; + return String.format("%s: %s (%d facets advised)", + interactionEvent, isAllowing() ? "allowed" : "vetoed", advisors.size()); } } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java index c8716d4029f..60abb129980 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/consent/InteractionResultSet.java @@ -39,9 +39,8 @@ public InteractionResultSet add(final InteractionResult result) { } /** - * Empty only if all the {@link #add(InteractionResult) contained} - * {@link InteractionResult}s are also - * {@link InteractionResult#isNotVetoing() empty}. + * Allowing only if all the {@link #add(InteractionResult) contained} + * {@link InteractionResult}s are also {@link InteractionResult#isAllowing()}. */ public boolean isAllowed() { return !isVetoed(); @@ -54,9 +53,7 @@ public boolean isAllowed() { */ public boolean isVetoed() { for (final InteractionResult result : results) { - if (result.isVetoing()) { - return true; - } + if (result.isVetoing()) return true; } return false; } @@ -84,9 +81,7 @@ public Consent createConsent() { */ public InteractionResult getInteractionResult() { for (final InteractionResult result : results) { - if (!result.isNotVetoing()) { - return result; - } + if (!result.isAllowing()) return result; } return firstResult != null ? firstResult : null; } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java index 68d144dcab6..4c95dd4f0a3 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/InteractionUtils.java @@ -24,6 +24,7 @@ import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.Identifier; +import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.config.environment.DeploymentType; import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants; @@ -49,7 +50,7 @@ public final class InteractionUtils { public InteractionResult isVisibleResult(final FacetHolder facetHolder, final VisibilityContext context) { - var iaResult = new InteractionResult(context.createInteractionEvent()); + var builder = InteractionResult.builder(context.createInteractionEvent()); // depending on the ifHiddenPolicy, we may do no vetoing here (instead, it moves into the usability check). var ifHiddenPolicy = context.renderPolicy().ifHiddenPolicy(); @@ -58,12 +59,9 @@ public InteractionResult isVisibleResult(final FacetHolder facetHolder, final Vi facetHolder.streamFacets(HidingInteractionAdvisor.class) .filter(advisor->compatible(advisor, context)) .forEach(advisor->{ - var hidingReasonString = advisor.hides(context); - var hidingReason = Optional.ofNullable(hidingReasonString) - .map(Consent.VetoReason::explicit) - .orElse(null); - - iaResult.advise(hidingReason, advisor); + _Strings.nonEmpty(advisor.hides(context)) + .map(Consent.VetoReason::explicit) + .ifPresent(hidingReason->builder.addAdvise(hidingReason, advisor)); }); break; case SHOW_AS_DISABLED: @@ -72,12 +70,12 @@ public InteractionResult isVisibleResult(final FacetHolder facetHolder, final Vi break; } - return iaResult; + return builder.build(); } public InteractionResult isUsableResult(final FacetHolder facetHolder, final UsabilityContext context) { - var isResult = new InteractionResult(context.createInteractionEvent()); + var builder = InteractionResult.builder(context.createInteractionEvent()); // depending on the ifHiddenPolicy, we additionally may disable using a hidden advisor var ifHiddenPolicy = context.renderPolicy().ifHiddenPolicy(); @@ -88,53 +86,49 @@ public InteractionResult isUsableResult(final FacetHolder facetHolder, final Usa case SHOW_AS_DISABLED_WITH_DIAGNOSTICS: var visibilityContext = context.asVisibilityContext(); facetHolder.streamFacets(HidingInteractionAdvisor.class) - .filter(advisor->compatible(advisor, context)) - .forEach(advisor->{ - String hidingReasonString = advisor.hides(visibilityContext); - Consent.VetoReason hidingReason = Optional.ofNullable(hidingReasonString) - .map(Consent.VetoReason::explicit) - .orElse(null); - if(hidingReason != null - && ifHiddenPolicy.isShowAsDisabledWithDiagnostics()) { - hidingReason = VetoUtil.withAdvisorAsDiagnostic(hidingReason, advisor); - } - isResult.advise(hidingReason, advisor); - }); + .filter(advisor->compatible(advisor, context)) + .forEach(advisor->{ + _Strings.nonEmpty(advisor.hides(visibilityContext)) + .map(Consent.VetoReason::explicit) + .ifPresent(hidingReason->{ + if(ifHiddenPolicy.isShowAsDisabledWithDiagnostics()) { + hidingReason = VetoUtil.withAdvisorAsDiagnostic(hidingReason, advisor); + } + builder.addAdvise(hidingReason, advisor); + }); + }); break; } var ifDisabledPolicy = context.renderPolicy().ifDisabledPolicy(); facetHolder.streamFacets(DisablingInteractionAdvisor.class) - .filter(advisor->compatible(advisor, context)) - .forEach(advisor->{ - Consent.VetoReason disablingReason = advisor.disables(context).orElse(null); - if(disablingReason != null - && ifDisabledPolicy.isShowAsDisabledWithDiagnostics()) { - disablingReason = VetoUtil.withAdvisorAsDiagnostic(disablingReason, advisor); - } - isResult.advise(disablingReason, advisor); - }); - - return isResult; + .filter(advisor->compatible(advisor, context)) + .forEach(advisor->{ + advisor.disables(context) + .ifPresent(disablingReason->{ + if(ifDisabledPolicy.isShowAsDisabledWithDiagnostics()) { + disablingReason = VetoUtil.withAdvisorAsDiagnostic(disablingReason, advisor); + } + builder.addAdvise(disablingReason, advisor); + }); + }); + + return builder.build(); } public InteractionResult isValidResult(final FacetHolder facetHolder, final ValidityContext context) { - var iaResult = new InteractionResult(context.createInteractionEvent()); + var builder = InteractionResult.builder(context.createInteractionEvent()); facetHolder.streamFacets(ValidatingInteractionAdvisor.class) .filter(advisor->compatible(advisor, context)) .forEach(advisor->{ - var invalidatingReasonString = - guardAgainstEmptyReasonString(advisor.invalidates(context), context.identifier()); - - var invalidatingReason = Optional.ofNullable(invalidatingReasonString) - .map(Consent.VetoReason::explicit) - .orElse(null); - iaResult.advise(invalidatingReason, advisor); + guardAgainstEmptyReasonString(advisor.invalidates(context), context.identifier()) + .map(Consent.VetoReason::explicit) + .ifPresent(invalidatingReason->builder.addAdvise(invalidatingReason, advisor)); }); - return iaResult; + return builder.build(); } public InteractionResultSet isValidResultSet( @@ -158,7 +152,7 @@ public RenderPolicy renderPolicy(final ManagedObject ownerAdapter) { * we should generate a message, * explaining what was going wrong and hinting developers at a possible resolution */ - private String guardAgainstEmptyReasonString( + private Optional<String> guardAgainstEmptyReasonString( final @Nullable String reason, final @NonNull Identifier identifier) { if("".equals(reason)) { var msg = ProgrammingModelConstants.MessageTemplate.INVALID_USE_OF_VALIDATION_SUPPORT_METHOD.builder() @@ -166,9 +160,9 @@ private String guardAgainstEmptyReasonString( .addVariable("memberName", identifier.memberLogicalName()) .buildMessage(); log.error(msg); - return msg; + return Optional.of(msg); } - return reason; + return Optional.ofNullable(reason); } private static boolean compatible(final InteractionAdvisor advisor, final InteractionContext ic) { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedFeature.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedFeature.java index 3f6cb957341..883af795091 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedFeature.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedFeature.java @@ -28,7 +28,8 @@ import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.ObjectFeature; -public interface ManagedFeature { +sealed public interface ManagedFeature +permits ManagedMember, ManagedParameter { Identifier getIdentifier(); diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedMember.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedMember.java index 9c6d29da56b..b782d649867 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedMember.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/interactions/managed/ManagedMember.java @@ -20,6 +20,8 @@ import java.util.Optional; +import org.jspecify.annotations.NonNull; + import org.apache.causeway.applib.Identifier; import org.apache.causeway.applib.annotation.Where; import org.apache.causeway.commons.internal.base._Casts; @@ -30,15 +32,15 @@ import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; import lombok.Getter; -import org.jspecify.annotations.NonNull; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.log4j.Log4j2; @Log4j2 @RequiredArgsConstructor -public abstract class ManagedMember -implements ManagedFeature { +public sealed abstract class ManagedMember +implements ManagedFeature +permits ManagedAction, ManagedCollection, ManagedProperty { /** * Some representations may vary according to whether the member is to be represented for read 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 8e359d6e960..f3478560709 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 @@ -26,17 +26,18 @@ import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter; -public interface ManagedParameter +public sealed interface ManagedParameter extends ManagedValue, - ManagedFeature { - + ManagedFeature +permits ParameterNegotiationModel.ParameterModel { + ObjectActionParameter metaModel(); @Override default ObjectActionParameter getMetaModel() { return metaModel(); } - + int paramIndex(); ParameterNegotiationModel negotiationModel(); - + /** * @return non-empty if not usable/editable (meaning if read-only) */ 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 ed4de33f9d3..6dd112dc317 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 @@ -97,7 +97,7 @@ private ParameterNegotiationModel( } // -- ACTION SPECIFIC - + public ObjectAction act() { return managedAction.getAction(); } @@ -120,7 +120,7 @@ public Can<ManagedObject> getParamValues() { InteractionHead interactionHead() { return managedAction.interactionHead(); } - + public ActionInteractionHead actionInteractionHead() { return managedAction.actionInteractionHead(); } @@ -344,8 +344,7 @@ public ParameterNegotiationModel withParamValue(final int parameterIndex, @NonNu // -- INTERNAL HOLDER OF PARAMETER BINDABLES - @Log4j2 - private record ParameterModel( + @Log4j2 record ParameterModel( int paramIndex, @NonNull ObjectActionParameter metaModel, @NonNull ParameterNegotiationModel negotiationModel, diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmVisibilityUtils.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmVisibilityUtils.java index 8b01347329d..4a27cffc8c7 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmVisibilityUtils.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmVisibilityUtils.java @@ -109,7 +109,7 @@ public static boolean isVisible( Where.OBJECT_FORMS); return InteractionUtils.isVisibleResult(spec, visibilityContext) - .isNotVetoing(); + .isAllowing(); } private static VisibilityContext createVisibleInteractionContext( diff --git a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/consent/InteractionResultTest.java b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/consent/InteractionResultTest.java index 23217ecdada..007b0290755 100644 --- a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/consent/InteractionResultTest.java +++ b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/consent/InteractionResultTest.java @@ -18,77 +18,76 @@ */ package org.apache.causeway.core.metamodel.consent; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.apache.causeway.applib.services.wrapper.events.InteractionEvent; import org.apache.causeway.core.metamodel.consent.Consent.VetoReason; class InteractionResultTest { - private InteractionResult result; - - @BeforeEach - public void setUp() throws Exception { - result = new InteractionResult(null); - } - - @AfterEach - public void tearDown() throws Exception { - result = null; - } + private InteractionResult.Builder builder = InteractionResult.builder(Mockito.mock(InteractionEvent.class)); @Test - public void shouldHaveNullReasonWhenJustInstantiated() { + void shouldHaveNullReasonWhenJustInstantiated() { + var result = builder.build(); assertEquals(null, extractReason(result)); } @Test - public void shouldBeEmptyWhenJustInstantiated() { + void shouldBeEmptyWhenJustInstantiated() { + var result = builder.build(); assertFalse(result.isVetoing()); - assertTrue(result.isNotVetoing()); + assertTrue(result.isAllowing()); } @Test - public void shouldHaveNonNullReasonWhenAdvisedWithNonNull() { - result.advise(vetoReason("foo"), InteractionAdvisor.forTesting()); + void shouldHaveNonNullReasonWhenAdvisedWithNonNull() { + advise(vetoReason("foo"), InteractionAdvisor.forTesting()); + var result = builder.build(); assertEquals("foo", extractReason(result)); } @Test - public void shouldConcatenateAdviseWhenAdvisedWithNonNull() { - result.advise(vetoReason("foo"), InteractionAdvisor.forTesting()); - result.advise(vetoReason("bar"), InteractionAdvisor.forTesting()); + void shouldConcatenateAdviseWhenAdvisedWithNonNull() { + advise(vetoReason("foo"), InteractionAdvisor.forTesting()); + advise(vetoReason("bar"), InteractionAdvisor.forTesting()); + var result = builder.build(); assertEquals("foo; bar", extractReason(result)); } @Test - public void shouldNotBeEmptyWhenAdvisedWithNonNull() { - result.advise(vetoReason("foo"), InteractionAdvisor.forTesting()); + void shouldNotBeEmptyWhenAdvisedWithNonNull() { + advise(vetoReason("foo"), InteractionAdvisor.forTesting()); + var result = builder.build(); assertTrue(result.isVetoing()); - assertFalse(result.isNotVetoing()); + assertFalse(result.isAllowing()); } @Test - public void shouldBeEmptyWhenAdvisedWithNull() { - result.advise(null, InteractionAdvisor.forTesting()); - assertTrue(result.isNotVetoing()); - assertFalse(result.isVetoing()); - assertEquals(null, extractReason(result)); + void shouldThrowWhenAdvisedWithNull() { + assertThrowsExactly(NullPointerException.class, ()->advise(null, InteractionAdvisor.forTesting())); + assertThrowsExactly(NullPointerException.class, ()->advise(vetoReason("foo"), null)); } // -- HELPER + private void advise(VetoReason vetoReason, InteractionAdvisor forTesting) { + builder.addAdvise(vetoReason, forTesting); + } + + static Consent.VetoReason vetoReason(final String reasonString) { return Consent.VetoReason.explicit(reasonString); } static String extractReason(final InteractionResult result) { - return result.getReason().map(VetoReason::string).orElse(null); + return result.vetoReason().map(VetoReason::string).orElse(null); } } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java index 29af1c124fd..2b1f34deea9 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java @@ -100,7 +100,6 @@ public Object invoke(WrapperInvocation wrapperInvocation) throws Throwable { final Object target = wrapperInvocation.origin().pojo(); final Method method = wrapperInvocation.method(); - final ManagedObject managedMixee = wrapperInvocation.origin().managedMixee(); if (classMetaData().isObjectMethod(method) || isEnhancedEntityMethod(method)) { @@ -133,52 +132,48 @@ public Object invoke(WrapperInvocation wrapperInvocation) throws Throwable { var objectMember = targetAdapter.objSpec().getMemberElseFail(resolvedMethod); var intent = ImperativeFacet.getIntent(objectMember, resolvedMethod); if(intent == Intent.CHECK_IF_HIDDEN || intent == Intent.CHECK_IF_DISABLED) { - throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'", objectMember.getId())); + throw _Exceptions.unsupportedOperation("Cannot invoke supporting method '%s'", objectMember.getId()); } if (intent == Intent.DEFAULTS || intent == Intent.CHOICES_OR_AUTOCOMPLETE) { return method.invoke(target, wrapperInvocation.args()); } - if (objectMember.isOneToOneAssociation()) { + if (objectMember instanceof OneToOneAssociation prop) { if (intent == Intent.CHECK_IF_VALID || intent == Intent.MODIFY_PROPERTY_SUPPORTING) { - throw new UnsupportedOperationException(String.format("Cannot invoke supporting method for '%s'; use only property accessor/mutator", objectMember.getId())); + throw _Exceptions.unsupportedOperation("Cannot invoke supporting method for '%s'; use only property accessor/mutator", objectMember.getId()); } - final OneToOneAssociation otoa = (OneToOneAssociation) objectMember; - if (intent == Intent.ACCESSOR) { - return handleGetterMethodOnProperty(wrapperInvocation, targetAdapter, otoa); + return handleGetterMethodOnProperty(wrapperInvocation, targetAdapter, prop); } if (intent == Intent.MODIFY_PROPERTY || intent == Intent.INITIALIZATION) { - return handleSetterMethodOnProperty(wrapperInvocation, targetAdapter, otoa); + return handleSetterMethodOnProperty(wrapperInvocation, targetAdapter, prop); } } - if (objectMember.isOneToManyAssociation()) { + if (objectMember instanceof OneToManyAssociation coll) { if (intent == Intent.CHECK_IF_VALID) { - throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only collection accessor/mutator", objectMember.getId())); + throw _Exceptions.unsupportedOperation("Cannot invoke supporting method '%s'; use only collection accessor/mutator", objectMember.getId()); } - final OneToManyAssociation otma = (OneToManyAssociation) objectMember; if (intent == Intent.ACCESSOR) { - return handleGetterMethodOnCollection(wrapperInvocation, targetAdapter, otma, objectMember.getId()); + return handleGetterMethodOnCollection(wrapperInvocation, targetAdapter, coll, objectMember.getId()); } } if (objectMember instanceof ObjectAction objectAction) { if (intent == Intent.CHECK_IF_VALID) { - throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only the 'invoke' method", objectMember.getId())); + throw _Exceptions.unsupportedOperation("Cannot invoke supporting method '%s'; use only the 'invoke' method", objectMember.getId()); } if(targetAdapter.objSpec().isMixin()) { + final ManagedObject managedMixee = wrapperInvocation.origin().managedMixee(); if (managedMixee == null) { - throw _Exceptions.illegalState( - "Missing the required managedMixee for action '%s'", - objectAction.getId()); + throw _Exceptions.illegalState("Missing the required managedMixee for action '%s'", objectAction.getId()); } MmAssertionUtils.assertIsBookmarkSupported(managedMixee); @@ -197,8 +192,7 @@ public Object invoke(WrapperInvocation wrapperInvocation) throws Throwable { return handleGetterMethodOnCollection(wrapperInvocation, managedMixee, (OneToManyAssociation)mixinMember, objectMember.getId()); } } else { - throw _Exceptions.illegalState(String.format( - "Could not locate mixin member for action '%s' on spec '%s'", objectAction.getId(), targetAdapter.objSpec())); + throw _Exceptions.illegalState("Could not locate mixin member for action '%s' on spec '%s'", objectAction.getId(), targetAdapter.objSpec()); } } @@ -206,16 +200,15 @@ public Object invoke(WrapperInvocation wrapperInvocation) throws Throwable { return handleActionMethod(wrapperInvocation, targetAdapter, objectAction); } - throw new UnsupportedOperationException(String.format("Unknown member type '%s'", objectMember)); + throw _Exceptions.unsupportedOperation("Unknown member type '%s'", objectMember); } private static ObjectMember determineMixinMember( final ManagedObject domainObjectAdapter, final ObjectAction objectAction) { - if(domainObjectAdapter == null) { - return null; - } + if(domainObjectAdapter == null) return null; + var specification = domainObjectAdapter.objSpec(); var objectActions = specification.streamAnyActions(MixedIn.INCLUDED); var objectAssociations = specification.streamAssociations(MixedIn.INCLUDED); @@ -368,8 +361,7 @@ private Object handleGetterMethodOnCollection( return mapViewObject; } - var msg = String.format("Collection type '%s' not supported by framework", currentReferencedObj.getClass().getName()); - throw new IllegalArgumentException(msg); + throw _Exceptions.illegalArgument("Collection type '%s' not supported by framework", currentReferencedObj.getClass().getName()); }, ()->new ExceptionLogger("getter " + collection.getId(), targetAdapter)); } @@ -481,7 +473,7 @@ private void checkUsability( // -- NOTIFY LISTENERS private void notifyListenersAndVetoIfRequired(final InteractionResult interactionResult) { - var interactionEvent = interactionResult.getInteractionEvent(); + var interactionEvent = interactionResult.interactionEvent(); mmc().getWrapperFactory().notifyListeners(interactionEvent); if (interactionEvent.isVeto()) { @@ -578,8 +570,7 @@ private Object handleException( private Object singleArgUnderlyingElseNull(final Object[] args, final String name) { if (args.length != 1) { - throw new IllegalArgumentException(String.format( - "Invoking '%s' should only have a single argument", name)); + throw _Exceptions.illegalArgument("Invoking '%s' should only have a single argument", name); } var argumentObj = underlying(args[0]); return argumentObj; @@ -587,8 +578,7 @@ private Object singleArgUnderlyingElseNull(final Object[] args, final String nam private void zeroArgsElseThrow(final Object[] args, final String name) { if (!_NullSafe.isEmpty(args)) { - throw new IllegalArgumentException(String.format( - "Invoking '%s' should have no arguments", name)); + throw _Exceptions.illegalArgument("Invoking '%s' should have no arguments", name); } } @@ -598,13 +588,7 @@ String msg(Exception ex) { String id = mo.isBookmarkMemoized() ? mo.getBookmarkElseFail().identifier() : "<bookmark not memoized>"; - var buf = new StringBuilder("Failed to execute ").append(" ").append(what).append(" "); - buf.append(" on '") - .append(logicalType.logicalName()) - .append(":") - .append(id) - .append("'"); - return buf.toString(); + return "Failed to execute %s on '%s:%s'".formatted(what, logicalType.logicalName(), id); } }
