You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/09/17 10:03:03 UTC
[isis] branch master updated: ISIS-2871: viewers should not
directly deal with value semantics
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push:
new 95d594e ISIS-2871: viewers should not directly deal with value semantics
95d594e is described below
commit 95d594ee5228bff2dd9e4fefd24867ca2dcbc007
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Sep 17 12:02:50 2021 +0200
ISIS-2871: viewers should not directly deal with value semantics
instead provide a layer of abstraction for that in the meta-model
---
.../org/apache/isis/commons/binding/Bindable.java | 4 +
.../internal/binding/_BindableAbstract.java | 56 ++++-
.../commons/internal/binding/BindableTest.java | 225 +++++++++++++--------
.../parseable/ParseableFacetUsingParser.java | 6 +-
.../title/parser/TitleFacetUsingRenderer.java | 10 +-
.../metamodel/facets/object/value/ValueFacet.java | 26 ++-
.../facets/object/value/ValueFacetAbstract.java | 92 +++++++--
.../interactions/managed/ManagedMember.java | 3 +-
.../interactions/managed/ManagedProperty.java | 3 +-
.../interactions/managed/ManagedValue.java | 4 +-
.../managed/ParameterNegotiationModel.java | 31 ++-
.../managed/PropertyNegotiationModel.java | 38 +++-
.../AsciiDocValueSemanticsWithPreprocessing.java | 3 +-
.../viewer/wicket/model/models/ScalarModel.java | 41 ++--
.../wicket/model/models/ScalarParameterModel.java | 10 +-
.../wicket/model/models/ScalarPropertyModel.java | 16 +-
16 files changed, 421 insertions(+), 147 deletions(-)
diff --git a/commons/src/main/java/org/apache/isis/commons/binding/Bindable.java b/commons/src/main/java/org/apache/isis/commons/binding/Bindable.java
index 75aba72..86f8f85 100644
--- a/commons/src/main/java/org/apache/isis/commons/binding/Bindable.java
+++ b/commons/src/main/java/org/apache/isis/commons/binding/Bindable.java
@@ -18,6 +18,8 @@
*/
package org.apache.isis.commons.binding;
+import java.util.function.Function;
+
/**
* @param <T>
*/
@@ -33,4 +35,6 @@ public interface Bindable<T> extends Observable<T>, Writable<T> {
void unbindBidirectional(Bindable<T> other);
+ <R> Bindable<R> mapToBindable(Function<T, R> forwardMapper, Function<R, T> reverseMapper);
+
}
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/binding/_BindableAbstract.java b/commons/src/main/java/org/apache/isis/commons/internal/binding/_BindableAbstract.java
index 5df7126..d1075cf 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/binding/_BindableAbstract.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/binding/_BindableAbstract.java
@@ -19,6 +19,8 @@
package org.apache.isis.commons.internal.binding;
import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
import org.apache.isis.commons.binding.Bindable;
import org.apache.isis.commons.binding.ChangeListener;
@@ -50,37 +52,37 @@ public abstract class _BindableAbstract<T> implements Bindable<T> {
public _BindableAbstract() {
}
- public _BindableAbstract(T initialValue) {
+ public _BindableAbstract(final T initialValue) {
this.value = initialValue;
}
@Override
- public void addListener(InvalidationListener listener) {
+ public void addListener(final InvalidationListener listener) {
util = InternalUtil.addListener(util, this, listener);
}
@Override
- public void removeListener(InvalidationListener listener) {
+ public void removeListener(final InvalidationListener listener) {
util = InternalUtil.removeListener(util, listener);
}
@Override
- public void addListener(ChangeListener<? super T> listener) {
+ public void addListener(final ChangeListener<? super T> listener) {
util = InternalUtil.addListener(util, this, listener);
}
@Override
- public void removeListener(ChangeListener<? super T> listener) {
+ public void removeListener(final ChangeListener<? super T> listener) {
util = InternalUtil.removeListener(util, listener);
}
@Override
- public void bindBidirectional(Bindable<T> other) {
+ public void bindBidirectional(final Bindable<T> other) {
InternalBidirectionalBinding.bind(this, other);
}
@Override
- public void unbindBidirectional(Bindable<T> other) {
+ public void unbindBidirectional(final Bindable<T> other) {
InternalBidirectionalBinding.unbind(this, other);
}
@@ -93,7 +95,7 @@ public abstract class _BindableAbstract<T> implements Bindable<T> {
}
@Override
- public void setValue(T newValue) {
+ public void setValue(final T newValue) {
if (isBound()) {
throw _Exceptions.unrecoverable("Cannot set value on a bound bindable.");
}
@@ -137,6 +139,38 @@ public abstract class _BindableAbstract<T> implements Bindable<T> {
protected void onInvalidated() {
}
+ // -- COMPOSITION
+
+ @Override
+ public <R> Bindable<R> mapToBindable(
+ final Function<T, R> forwardMapper,
+ final Function<R, T> reverseMapper) {
+
+ final var isForwardUpdating = new AtomicBoolean();
+ final var isReverseUpdating = new AtomicBoolean();
+
+ final var newBindable = _Bindables.<R>forValue(forwardMapper.apply(getValue()));
+ addListener((e,o,n)->{
+ if(isReverseUpdating.get()) {
+ return;
+ }
+ isForwardUpdating.set(true);
+ newBindable.setValue(forwardMapper.apply(n));
+ isForwardUpdating.set(false);
+ });
+
+ newBindable.addListener((e,o,n)->{
+ if(isForwardUpdating.get()) {
+ return;
+ }
+ isReverseUpdating.set(true);
+ setValue(reverseMapper.apply(n));
+ isReverseUpdating.set(false);
+ });
+
+ return newBindable;
+ }
+
// -- HELPER
private void markInvalid() {
@@ -152,12 +186,12 @@ public abstract class _BindableAbstract<T> implements Bindable<T> {
private final WeakReference<_BindableAbstract<?>> wref;
- public WeakInvalidationListener(_BindableAbstract<?> ref) {
+ public WeakInvalidationListener(final _BindableAbstract<?> ref) {
this.wref = new WeakReference<_BindableAbstract<?>>(ref);
}
@Override
- public void invalidated(Observable<?> observable) {
+ public void invalidated(final Observable<?> observable) {
_BindableAbstract<?> ref = wref.get();
if (ref == null) {
observable.removeListener(this);
@@ -171,4 +205,6 @@ public abstract class _BindableAbstract<T> implements Bindable<T> {
return wref.get() == null;
}
}
+
+
}
diff --git a/commons/src/test/java/org/apache/isis/commons/internal/binding/BindableTest.java b/commons/src/test/java/org/apache/isis/commons/internal/binding/BindableTest.java
index 23e3028..517b605 100644
--- a/commons/src/test/java/org/apache/isis/commons/internal/binding/BindableTest.java
+++ b/commons/src/test/java/org/apache/isis/commons/internal/binding/BindableTest.java
@@ -38,99 +38,99 @@ class BindableTest {
val primaryChangeCounter = new LongAdder();
val secondaryChangeCounter = new LongAdder();
-
- val primary = _Bindables.forValue("hello");
+
+ val primary = _Bindables.forValue("hello");
val secondary = _Bindables.forValue("");
-
+
primary.addListener((e,o,n)->primaryChangeCounter.increment());
secondary.addListener((e,o,n)->secondaryChangeCounter.increment());
assertFalse(primary.isBound());
assertFalse(secondary.isBound());
-
+
// setup unidirectional binding, implicitly propagates value PRIMARY -> SECONDARY
-
+
secondary.bind(primary);
-
+
assertFalse(primary.isBound()); // not bound
assertTrue(secondary.isBound()); // bound
-
+
assertEquals("hello", primary.getValue());
assertEquals("hello", secondary.getValue());
-
+
assertEquals(0, primaryChangeCounter.intValue());
assertEquals(1, secondaryChangeCounter.intValue()); // expected 1 after bidirectional binding established
-
+
// propagate value PRIMARY -> SECONDARY
-
+
primary.setValue("there");
-
+
assertEquals("there", primary.getValue());
assertEquals("there", secondary.getValue());
-
+
assertEquals(1, primaryChangeCounter.intValue());
assertEquals(2, secondaryChangeCounter.intValue());
-
- // propagate value SECONDARY -> PRIMARY
-
+
+ // propagate value SECONDARY -> PRIMARY
+
assertThrows(Exception.class, ()->secondary.setValue("world")); // a bound value cannot be set
-
+
assertEquals("there", primary.getValue());
assertEquals("there", secondary.getValue());
-
+
assertEquals(1, primaryChangeCounter.intValue());
assertEquals(2, secondaryChangeCounter.intValue());
-
+
}
-
+
@Test
void bidirectionalBinding() {
-
+
val primaryChangeCounter = new LongAdder();
val secondaryChangeCounter = new LongAdder();
-
- val primary = _Bindables.forValue("hello");
+
+ val primary = _Bindables.forValue("hello");
val secondary = _Bindables.forValue("");
-
+
primary.addListener((e,o,n)->primaryChangeCounter.increment());
secondary.addListener((e,o,n)->secondaryChangeCounter.increment());
-
+
assertFalse(primary.isBound());
assertFalse(secondary.isBound());
-
+
// setup bidirectional binding, implicitly propagates value PRIMARY -> SECONDARY
-
+
secondary.bindBidirectional(primary);
-
+
assertFalse(primary.isBound()); // bidirectional binding is separated from unidirectional binding
assertFalse(secondary.isBound()); // bidirectional binding is separated from unidirectional binding
-
+
assertEquals("hello", primary.getValue());
assertEquals("hello", secondary.getValue());
-
+
assertEquals(0, primaryChangeCounter.intValue());
assertEquals(1, secondaryChangeCounter.intValue()); // expected 1 after bidirectional binding established
-
+
// propagate value PRIMARY -> SECONDARY
-
+
primary.setValue("there");
-
+
assertEquals("there", primary.getValue());
assertEquals("there", secondary.getValue());
-
+
assertEquals(1, primaryChangeCounter.intValue());
assertEquals(2, secondaryChangeCounter.intValue());
-
- // propagate value SECONDARY -> PRIMARY
-
+
+ // propagate value SECONDARY -> PRIMARY
+
secondary.setValue("world");
-
+
assertEquals("world", primary.getValue());
assertEquals("world", secondary.getValue());
-
+
assertEquals(2, primaryChangeCounter.intValue());
assertEquals(3, secondaryChangeCounter.intValue());
-
+
}
@Test
@@ -138,100 +138,165 @@ class BindableTest {
val primaryChangeCounter = new LongAdder();
val secondaryChangeCounter = new LongAdder();
-
- val primary = _Bindables.forValue(Can.<String>of("hello"));
+
+ val primary = _Bindables.forValue(Can.<String>of("hello"));
val secondary = _Bindables.forValue(Can.<String>empty());
-
+
primary.addListener((e,o,n)->primaryChangeCounter.increment());
secondary.addListener((e,o,n)->secondaryChangeCounter.increment());
assertFalse(primary.isBound());
assertFalse(secondary.isBound());
-
+
// setup unidirectional binding, implicitly propagates value PRIMARY -> SECONDARY
-
+
secondary.bind(primary);
-
+
assertFalse(primary.isBound()); // not bound
assertTrue(secondary.isBound()); // bound
-
+
assertEquals(Can.<String>of("hello"), primary.getValue());
assertEquals(Can.<String>of("hello"), secondary.getValue());
-
+
assertEquals(0, primaryChangeCounter.intValue());
assertEquals(1, secondaryChangeCounter.intValue()); // expected 1 after bidirectional binding established
-
+
// propagate value PRIMARY -> SECONDARY
-
+
primary.setValue(primary.getValue().add("there"));
-
+
assertEquals(Can.<String>of("hello", "there"), primary.getValue());
assertEquals(Can.<String>of("hello", "there"), secondary.getValue());
-
+
assertEquals(1, primaryChangeCounter.intValue());
assertEquals(2, secondaryChangeCounter.intValue());
-
- // propagate value SECONDARY -> PRIMARY
-
+
+ // propagate value SECONDARY -> PRIMARY
+
assertThrows(Exception.class, ()->secondary.setValue(Can.empty())); // a bound value cannot be set
-
+
assertEquals(Can.<String>of("hello", "there"), primary.getValue());
assertEquals(Can.<String>of("hello", "there"), secondary.getValue());
-
+
assertEquals(1, primaryChangeCounter.intValue());
assertEquals(2, secondaryChangeCounter.intValue());
-
+
}
-
+
@Test
void bidirectionalBinding_onCan() {
-
+
val primaryChangeCounter = new LongAdder();
val secondaryChangeCounter = new LongAdder();
-
- val primary = _Bindables.forValue(Can.<String>of("hello"));
+
+ val primary = _Bindables.forValue(Can.<String>of("hello"));
val secondary = _Bindables.forValue(Can.<String>empty());
-
+
primary.addListener((e,o,n)->primaryChangeCounter.increment());
secondary.addListener((e,o,n)->secondaryChangeCounter.increment());
-
+
assertFalse(primary.isBound());
assertFalse(secondary.isBound());
-
+
// setup bidirectional binding, implicitly propagates value PRIMARY -> SECONDARY
-
+
secondary.bindBidirectional(primary);
-
+
assertFalse(primary.isBound()); // bidirectional binding is separated from unidirectional binding
assertFalse(secondary.isBound()); // bidirectional binding is separated from unidirectional binding
-
+
assertEquals(Can.<String>of("hello"), primary.getValue());
assertEquals(Can.<String>of("hello"), secondary.getValue());
-
+
assertEquals(0, primaryChangeCounter.intValue());
assertEquals(1, secondaryChangeCounter.intValue()); // expected 1 after bidirectional binding established
-
+
// propagate value PRIMARY -> SECONDARY
-
+
primary.setValue(primary.getValue().add("there"));
-
+
assertEquals(Can.<String>of("hello", "there"), primary.getValue());
assertEquals(Can.<String>of("hello", "there"), secondary.getValue());
-
+
assertEquals(1, primaryChangeCounter.intValue());
assertEquals(2, secondaryChangeCounter.intValue());
-
- // propagate value SECONDARY -> PRIMARY
-
+
+ // propagate value SECONDARY -> PRIMARY
+
primary.setValue(primary.getValue().add("world"));
-
+
assertEquals(Can.<String>of("hello", "there", "world"), primary.getValue());
assertEquals(Can.<String>of("hello", "there", "world"), secondary.getValue());
-
+
assertEquals(2, primaryChangeCounter.intValue());
assertEquals(3, secondaryChangeCounter.intValue());
-
+
}
-
-
+
+ @Test
+ void bidirectionalBinding_withSynchronizedView() {
+
+ val primaryChangeCounter = new LongAdder();
+ val secondaryChangeCounter = new LongAdder();
+
+ val primary = _Bindables.forValue(Integer.valueOf(3));
+ val secondary = _Bindables.forValue(Integer.valueOf(0));
+
+ val view = primary.<String>mapToBindable(i->""+i, Integer::valueOf);
+
+ primary.addListener((e,o,n)->primaryChangeCounter.increment());
+ secondary.addListener((e,o,n)->secondaryChangeCounter.increment());
+
+ assertFalse(primary.isBound());
+ assertFalse(secondary.isBound());
+
+ // setup bidirectional binding, implicitly propagates value PRIMARY -> SECONDARY
+
+ secondary.bindBidirectional(primary);
+
+ assertFalse(primary.isBound()); // bidirectional binding is separated from unidirectional binding
+ assertFalse(secondary.isBound()); // bidirectional binding is separated from unidirectional binding
+
+ assertEquals(3, primary.getValue());
+ assertEquals(3, secondary.getValue());
+ assertEquals("3", view.getValue());
+
+ assertEquals(0, primaryChangeCounter.intValue());
+ assertEquals(1, secondaryChangeCounter.intValue()); // expected 1 after bidirectional binding established
+
+ // propagate value PRIMARY -> SECONDARY
+
+ primary.setValue(99);
+
+ assertEquals(99, primary.getValue());
+ assertEquals(99, secondary.getValue());
+ assertEquals("99", view.getValue());
+
+ assertEquals(1, primaryChangeCounter.intValue());
+ assertEquals(2, secondaryChangeCounter.intValue());
+
+ // propagate value SECONDARY -> PRIMARY
+
+ secondary.setValue(42);
+
+ assertEquals(42, primary.getValue());
+ assertEquals(42, secondary.getValue());
+ assertEquals("42", view.getValue());
+
+ assertEquals(2, primaryChangeCounter.intValue());
+ assertEquals(3, secondaryChangeCounter.intValue());
+
+ // propagate value VIEW -> PRIMARY -> SECONDARY
+
+ view.setValue("64");
+
+ assertEquals(64, primary.getValue());
+ assertEquals(64, secondary.getValue());
+ assertEquals("64", view.getValue());
+
+ assertEquals(3, primaryChangeCounter.intValue());
+ assertEquals(4, secondaryChangeCounter.intValue());
+
+ }
+
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/parseable/ParseableFacetUsingParser.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/parseable/ParseableFacetUsingParser.java
index 0f4a004..f4cc772 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/parseable/ParseableFacetUsingParser.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/parseable/ParseableFacetUsingParser.java
@@ -106,7 +106,7 @@ implements ParseableFacet {
}
try {
- final Object parsed = parser.parseTextRepresentation(parserContext(), entry);
+ final Object parsed = parser.parseTextRepresentation(valueSemanticsContext(), entry);
if (parsed == null) {
return null;
}
@@ -138,10 +138,10 @@ implements ParseableFacet {
public String parseableTextRepresentation(final ManagedObject contextAdapter) {
final Object pojo = UnwrapUtil.single(contextAdapter);
- return parser.parseableTextRepresentation(parserContext(), _Casts.uncheckedCast(pojo));
+ return parser.parseableTextRepresentation(valueSemanticsContext(), _Casts.uncheckedCast(pojo));
}
- private ValueSemanticsProvider.Context parserContext() {
+ private ValueSemanticsProvider.Context valueSemanticsContext() {
val iaProvider = super.getInteractionProvider();
if(iaProvider==null) {
return null; // JUnit context
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/parser/TitleFacetUsingRenderer.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/parser/TitleFacetUsingRenderer.java
index 56cb416..9023cda 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/parser/TitleFacetUsingRenderer.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/parser/TitleFacetUsingRenderer.java
@@ -39,13 +39,13 @@ implements TitleFacet {
private final @NonNull Renderer<?> renderer;
- public static TitleFacetUsingRenderer create(final Renderer<?> parser, final FacetHolder holder) {
- return new TitleFacetUsingRenderer(parser, holder);
+ public static TitleFacetUsingRenderer create(final Renderer<?> renderer, final FacetHolder holder) {
+ return new TitleFacetUsingRenderer(renderer, holder);
}
- private TitleFacetUsingRenderer(final Renderer<?> parser, final FacetHolder holder) {
+ private TitleFacetUsingRenderer(final Renderer<?> renderer, final FacetHolder holder) {
super(TitleFacet.class, holder, Precedence.LOW);
- this.renderer = parser;
+ this.renderer = renderer;
}
@Override
@@ -70,7 +70,7 @@ implements TitleFacet {
@Override
public void visitAttributes(final BiConsumer<String, Object> visitor) {
super.visitAttributes(visitor);
- visitor.accept("parser", renderer.toString());
+ visitor.accept("renderer", renderer.toString());
}
private ValueSemanticsProvider.Context valueSemanticsContext() {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java
index b4cf352..9a2183d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java
@@ -18,8 +18,16 @@
*/
package org.apache.isis.core.metamodel.facets.object.value;
+import java.util.Optional;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.adapters.Parser;
+import org.apache.isis.applib.adapters.ValueSemanticsProvider;
import org.apache.isis.applib.id.LogicalType;
+import org.apache.isis.commons.collections.Can;
import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
/**
* Indicates that this class has value semantics.
@@ -30,8 +38,24 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
* convenient term for a number of mostly optional semantics all of which are
* defined elsewhere.
*/
-public interface ValueFacet extends Facet {
+public interface ValueFacet<T> extends Facet {
LogicalType getValueType();
+ Can<ValueSemanticsProvider<T>> getValueSemantics();
+
+ Optional<Parser<T>> selectParserForParameter(final ObjectActionParameter param);
+ Optional<Parser<T>> selectParserForProperty(final OneToOneAssociation prop);
+
+ Parser<T> fallbackParser(Identifier featureIdentifier);
+
+ default Parser<T> selectParserForParameterElseFallback(final ObjectActionParameter param) {
+ return selectParserForParameter(param)
+ .orElseGet(()->fallbackParser(param.getFeatureIdentifier()));
+ }
+
+ default Parser<T> selectParserForPropertyElseFallback(final OneToOneAssociation prop) {
+ return selectParserForProperty(prop)
+ .orElseGet(()->fallbackParser(prop.getFeatureIdentifier()));
+ }
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
index 85c9118..f2d44e9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
@@ -18,44 +18,49 @@
*/
package org.apache.isis.core.metamodel.facets.object.value;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.adapters.Parser;
import org.apache.isis.applib.adapters.ValueSemanticsProvider;
+import org.apache.isis.applib.adapters.ValueSemanticsProvider.Context;
import org.apache.isis.applib.id.LogicalType;
import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.base._NullSafe;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
-public abstract class ValueFacetAbstract
+public abstract class ValueFacetAbstract<T>
extends FacetAbstract
-implements ValueFacet {
+implements ValueFacet<T> {
private static final Class<? extends Facet> type() {
return ValueFacet.class;
}
- private final Can<ValueSemanticsProvider<?>> semanticsProviders;
+ @Getter(onMethod_ = {@Override})
+ private final Can<ValueSemanticsProvider<T>> valueSemantics;
protected ValueFacetAbstract(
- final Can<ValueSemanticsProvider<?>> semanticsProviders,
+ final Can<ValueSemanticsProvider<T>> valueSemantics,
final FacetHolder holder,
final Facet.Precedence precedence) {
super(type(), holder, precedence);
-
- this.semanticsProviders = semanticsProviders;
-
- // we now figure add all the facets supported. Note that we do not use
- // FacetUtil.addFacet,
- // because we need to add them explicitly to our delegate facetholder
- // but have the
- // facets themselves reference this value's holder.
-
- super.getFacetHolder().addFacet(this); // add just ValueFacet.class
-
+ this.valueSemantics = valueSemantics;
}
protected boolean hasSemanticsProvider() {
- return !this.semanticsProviders.isEmpty();
+ return !this.valueSemantics.isEmpty();
}
@Override
@@ -63,4 +68,59 @@ implements ValueFacet {
return getFacetHolder().getFeatureIdentifier().getLogicalType();
}
+ @Override
+ public Optional<Parser<T>> selectParserForParameter(final ObjectActionParameter param) {
+ return streamParsers(s->true) // TODO filter by qualifier if any
+ .findFirst();
+ }
+
+ @Override
+ public Optional<Parser<T>> selectParserForProperty(final OneToOneAssociation prop) {
+ return streamParsers(s->true) // TODO filter by qualifier if any
+ .findFirst();
+ }
+
+ @Override
+ public Parser<T> fallbackParser(final Identifier featureIdentifier) {
+ return new ReadonlyMissingParserMessageParser<T>(String
+ .format("Could not find a parser for type %s "
+ + "in the context of %s",
+ getValueType(),
+ featureIdentifier));
+ }
+
+ // -- HELPER
+
+ private Stream<Parser<T>> streamParsers(
+ final Predicate<ValueSemanticsProvider<T>> semanticsFilter) {
+ return getValueSemantics()
+ .stream()
+ .filter(semanticsFilter)
+ .map(ValueSemanticsProvider::getParser)
+ .filter(_NullSafe::isPresent);
+ }
+
+ @RequiredArgsConstructor
+ private final static class ReadonlyMissingParserMessageParser<T>
+ implements Parser<T> {
+
+ private final String message;
+
+ @Override
+ public String parseableTextRepresentation(final Context context, final T value) {
+ return message;
+ }
+
+ @Override
+ public T parseTextRepresentation(final Context context, final String text) {
+ throw _Exceptions.unsupportedOperation(message);
+ }
+
+ @Override
+ public int typicalLength() {
+ return 60;
+ }
+
+ }
+
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedMember.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedMember.java
index 0925fd1..f71af75 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedMember.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedMember.java
@@ -43,7 +43,8 @@ import lombok.extern.log4j.Log4j2;
@Log4j2
@RequiredArgsConstructor
-public abstract class ManagedMember implements ManagedFeature {
+public abstract class ManagedMember
+implements ManagedFeature {
// only used to create failure messages
@RequiredArgsConstructor
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedProperty.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedProperty.java
index beb70a5..08e5940 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedProperty.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedProperty.java
@@ -39,7 +39,8 @@ import lombok.val;
import lombok.extern.log4j.Log4j2;
@Log4j2
-public final class ManagedProperty extends ManagedMember {
+public final class ManagedProperty
+extends ManagedMember {
// -- FACTORIES
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedValue.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedValue.java
index 03532e8..df52198 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedValue.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ManagedValue.java
@@ -27,10 +27,12 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
public interface ManagedValue {
ObjectSpecification getSpecification();
+
Bindable<ManagedObject> getValue();
+ Bindable<String> getValueAsParsableText();
+
Observable<String> getValidationMessage();
Bindable<String> getSearchArgument();
Observable<Can<ManagedObject>> getChoices();
-
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ParameterNegotiationModel.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ParameterNegotiationModel.java
index bc66533..781b526 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ParameterNegotiationModel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/ParameterNegotiationModel.java
@@ -18,14 +18,19 @@
*/
package org.apache.isis.core.metamodel.interactions.managed;
+import java.util.Optional;
import java.util.stream.IntStream;
import org.springframework.lang.Nullable;
import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.adapters.Parser;
+import org.apache.isis.applib.adapters.ValueSemanticsProvider;
import org.apache.isis.commons.binding.Bindable;
import org.apache.isis.commons.binding.Observable;
import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.assertions._Assert;
+import org.apache.isis.commons.internal.base._NullSafe;
import org.apache.isis.commons.internal.binding._BindableAbstract;
import org.apache.isis.commons.internal.binding._Bindables;
import org.apache.isis.commons.internal.binding._Observables;
@@ -34,6 +39,7 @@ import org.apache.isis.commons.internal.exceptions._Exceptions;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.consent.InteractionResult;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
import org.apache.isis.core.metamodel.spec.ManagedObject;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
@@ -221,13 +227,17 @@ public class ParameterNegotiationModel {
@Getter @NonNull private final _BindableAbstract<String> bindableParamSearchArgument;
@Getter @NonNull private final LazyObservable<Can<ManagedObject>> observableParamChoices;
+ private final @NonNull Bindable<String> bindableParamAsText;
+
private ParameterModel(
final int paramNr,
final @NonNull ParameterNegotiationModel negotiationModel,
final @NonNull ManagedObject initialValue) {
+ final var action = negotiationModel.getHead().getMetaModel();
+
this.paramNr = paramNr;
- this.metaModel = negotiationModel.getHead().getMetaModel().getParameters().getElseFail(paramNr);
+ this.metaModel = action.getParameters().getElseFail(paramNr);
this.negotiationModel = negotiationModel;
bindableParamValue = _Bindables.forValue(initialValue);
@@ -235,6 +245,20 @@ public class ParameterNegotiationModel {
getNegotiationModel().onNewParamValue();
});
+ // value types should have associated parsers/formatters via value semantics
+ bindableParamAsText = action.getReturnType().lookupFacet(ValueFacet.class)
+ .map(valueFacet->valueFacet.selectParserForParameterElseFallback(metaModel))
+ .map(parser->bindableParamValue
+ .mapToBindable(
+ value->parser.parseableTextRepresentation(null, value.getPojo()),
+ text->ManagedObject.of(null, parser.parseTextRepresentation(null, text)))
+ )
+ .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",
+ action.getReturnType().getLogicalType()))
+ );
// has either autoComplete, choices, or none
observableParamChoices = metaModel.hasAutoComplete()
@@ -298,6 +322,11 @@ public class ParameterNegotiationModel {
}
@Override
+ public Bindable<String> getValueAsParsableText() {
+ return bindableParamAsText;
+ }
+
+ @Override
public Observable<String> getValidationMessage() {
return observableParamValidation;
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/PropertyNegotiationModel.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/PropertyNegotiationModel.java
index f6d89ee..91f03da 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/PropertyNegotiationModel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/interactions/managed/PropertyNegotiationModel.java
@@ -26,6 +26,7 @@ import org.apache.isis.commons.internal.binding._Bindables;
import org.apache.isis.commons.internal.binding._Observables;
import org.apache.isis.commons.internal.binding._Observables.LazyObservable;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
import org.apache.isis.core.metamodel.spec.ManagedObject;
import org.apache.isis.core.metamodel.spec.ManagedObjects;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
@@ -35,16 +36,16 @@ import lombok.val;
public class PropertyNegotiationModel implements ManagedValue {
- @NonNull private final _BindableAbstract<ManagedObject> proposedValue;
- @NonNull private final LazyObservable<String> validation;
- @NonNull private final _BindableAbstract<String> searchArgument;
- @NonNull private final LazyObservable<Can<ManagedObject>> choices;
+ private final @NonNull _BindableAbstract<ManagedObject> proposedValue;
+ private final @NonNull LazyObservable<String> validation;
+ private final @NonNull _BindableAbstract<String> searchArgument;
+ private final @NonNull LazyObservable<Can<ManagedObject>> choices;
+ private final @NonNull ManagedProperty managedProperty;
+ private final @NonNull Bindable<String> proposedValueAsText;
- @NonNull private final ManagedProperty managedProperty;
-
- PropertyNegotiationModel(ManagedProperty managedProperty) {
+ PropertyNegotiationModel(final ManagedProperty managedProperty) {
this.managedProperty = managedProperty;
- val propMeta = managedProperty.getMetaModel();
+ final var propMeta = managedProperty.getMetaModel();
validationFeedbackActive = _Bindables.forValue(false);
@@ -58,6 +59,21 @@ public class PropertyNegotiationModel implements ManagedValue {
invalidateChoicesAndValidation();
});
+ // value types should have associated parsers/formatters via value semantics
+ proposedValueAsText = propMeta.getOnType().lookupFacet(ValueFacet.class)
+ .map(valueFacet->valueFacet.selectParserForPropertyElseFallback(propMeta))
+ .map(parser->proposedValue
+ .mapToBindable(
+ value->parser.parseableTextRepresentation(null, value.getPojo()),
+ text->ManagedObject.of(null, parser.parseTextRepresentation(null, text)))
+ )
+ .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",
+ propMeta.getOnType().getLogicalType()))
+ );
+
// has either autoComplete, choices, or none
choices = propMeta.hasAutoComplete()
? _Observables.forFactory(()->
@@ -98,6 +114,11 @@ public class PropertyNegotiationModel implements ManagedValue {
}
@Override
+ public Bindable<String> getValueAsParsableText() {
+ return proposedValueAsText;
+ }
+
+ @Override
public Observable<String> getValidationMessage() {
return validation;
}
@@ -139,5 +160,4 @@ public class PropertyNegotiationModel implements ManagedValue {
}
-
}
diff --git a/examples/demo/domain/src/main/java/demoapp/dom/_infra/resources/AsciiDocValueSemanticsWithPreprocessing.java b/examples/demo/domain/src/main/java/demoapp/dom/_infra/resources/AsciiDocValueSemanticsWithPreprocessing.java
index d60ca07..388802c 100644
--- a/examples/demo/domain/src/main/java/demoapp/dom/_infra/resources/AsciiDocValueSemanticsWithPreprocessing.java
+++ b/examples/demo/domain/src/main/java/demoapp/dom/_infra/resources/AsciiDocValueSemanticsWithPreprocessing.java
@@ -11,7 +11,8 @@ import org.apache.isis.valuetypes.asciidoc.metamodel.semantics.AsciiDocValueSema
@Named("demo.AsciiDocValueSemantics")
@Qualifier("adoc-pre-processor")
public class AsciiDocValueSemanticsWithPreprocessing
-extends AsciiDocValueSemantics {
+//extends AsciiDocValueSemantics
+{
//FIXME add pre-processing stuff
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
index 94c8c2d..11fdbbf 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarModel.java
@@ -21,15 +21,20 @@ package org.apache.isis.viewer.wicket.model.models;
import java.util.List;
import java.util.Optional;
+import org.apache.isis.applib.adapters.ValueSemanticsProvider;
import org.apache.isis.applib.annotation.PromptStyle;
import org.apache.isis.applib.id.LogicalType;
import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.base._Either;
import org.apache.isis.commons.internal.base._NullSafe;
import org.apache.isis.commons.internal.collections._Lists;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
import org.apache.isis.core.metamodel.facets.object.promptStyle.PromptStyleFacet;
+import org.apache.isis.core.metamodel.interactions.managed.ManagedParameter;
+import org.apache.isis.core.metamodel.interactions.managed.ManagedProperty;
+import org.apache.isis.core.metamodel.interactions.managed.ManagedValue;
import org.apache.isis.core.metamodel.spec.ManagedObject;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
@@ -90,7 +95,9 @@ implements HasRenderingHints, ScalarUiModel, LinksProvider, FormExecutorContext
* object, with the {@link #getObject() value of this model} to be default
* value (if any) of that action parameter.
*/
- protected ScalarModel(final EntityModel parentEntityModel, final ActionParameterMemento apm) {
+ protected ScalarModel(
+ final EntityModel parentEntityModel,
+ final ActionParameterMemento apm) {
super(parentEntityModel.getCommonContext());
@@ -186,27 +193,35 @@ implements HasRenderingHints, ScalarUiModel, LinksProvider, FormExecutorContext
}
val spec = adapter.getSpecification();
if(spec.isValue()) {
- val parseableFacet = getTypeOfSpecification().getFacet(ParseableFacet.class);
- if (parseableFacet == null) {
- throw new RuntimeException("unable to find a parser for " + spec.getFullIdentifier());
- }
- return parseableFacet.parseableTextRepresentation(adapter);
+ managedValue().getValueAsParsableText().getValue();
+
+// val parseableFacet = getTypeOfSpecification().getFacet(ParseableFacet.class);
+// if (parseableFacet == null) {
+// throw new RuntimeException("unable to find a parser for " + spec.getFullIdentifier());
+// }
+// return parseableFacet.parseableTextRepresentation(adapter);
}
return adapter.titleString();
}
public void setObjectAsString(final String enteredText) {
// parse text to get adapter
- val parseableFacet = getTypeOfSpecification().getFacet(ParseableFacet.class);
- if (parseableFacet == null) {
- throw new RuntimeException("unable to find a parser for " + getTypeOfSpecification().getFullIdentifier());
- }
- ManagedObject adapter = parseableFacet.parseTextEntry(getObject(), enteredText,
- InteractionInitiatedBy.USER);
- setObject(adapter);
+ managedValue().getValueAsParsableText().setValue(enteredText);
+ setObject(managedValue().getValue().getValue());
+
+// val parseableFacet = getTypeOfSpecification().getFacet(ParseableFacet.class);
+// if (parseableFacet == null) {
+// throw new RuntimeException("unable to find a parser for " + getTypeOfSpecification().getFullIdentifier());
+// }
+// ManagedObject adapter = parseableFacet.parseTextEntry(getObject(), enteredText,
+// InteractionInitiatedBy.USER);
+//
+// setObject(adapter);
}
+ public abstract ManagedValue managedValue();
+
public abstract boolean whetherHidden();
public abstract String whetherDisabled();
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarParameterModel.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarParameterModel.java
index fa789f1..29df55e 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarParameterModel.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarParameterModel.java
@@ -24,6 +24,7 @@ import org.apache.isis.applib.annotation.Where;
import org.apache.isis.commons.collections.Can;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
+import org.apache.isis.core.metamodel.interactions.managed.ManagedValue;
import org.apache.isis.core.metamodel.interactions.managed.ParameterNegotiationModel;
import org.apache.isis.core.metamodel.spec.ManagedObject;
import org.apache.isis.core.metamodel.spec.ManagedObjects;
@@ -54,7 +55,9 @@ implements ParameterUiModel {
* object, with the {@link #getObject() value of this model} to be default
* value (if any) of that action parameter.
*/
- public ScalarParameterModel(final EntityModel parentEntityModel, final ActionParameterMemento paramMemento) {
+ public ScalarParameterModel(
+ final EntityModel parentEntityModel,
+ final ActionParameterMemento paramMemento) {
super(parentEntityModel, paramMemento);
this.paramMemento = paramMemento;
}
@@ -150,6 +153,11 @@ implements ParameterUiModel {
super.setObject(paramValue);
}
+ @Override
+ public ManagedValue managedValue() {
+ return getPendingParameterModel().getParamModels().getElseFail(paramMemento.getNumber());
+ }
+
// -- HELPER
private ManagedObject toNonNull(@Nullable ManagedObject adapter) {
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarPropertyModel.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarPropertyModel.java
index f7fff17..15d419a 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarPropertyModel.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ScalarPropertyModel.java
@@ -21,6 +21,8 @@ package org.apache.isis.viewer.wicket.model.models;
import org.apache.isis.commons.collections.Can;
import org.apache.isis.core.metamodel.interactions.managed.InteractionVeto;
import org.apache.isis.core.metamodel.interactions.managed.ManagedProperty;
+import org.apache.isis.core.metamodel.interactions.managed.ManagedValue;
+import org.apache.isis.core.metamodel.interactions.managed.PropertyNegotiationModel;
import org.apache.isis.core.metamodel.spec.ManagedObject;
import org.apache.isis.core.metamodel.spec.ManagedObjects;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
@@ -48,21 +50,21 @@ implements PropertyUiModel {
public ScalarPropertyModel(
final EntityModel parentEntityModel,
final PropertyMemento propertyMemento,
- final EntityModel.Mode mode,
+ final EntityModel.Mode editingOrViewing,
final EntityModel.RenderingHint renderingHint) {
- super(parentEntityModel, propertyMemento, mode, renderingHint);
+ super(parentEntityModel, propertyMemento, editingOrViewing, renderingHint);
this.propertyMemento = propertyMemento;
reset();
}
public ScalarPropertyModel copyHaving(
- final EntityModel.Mode mode,
+ final EntityModel.Mode editingOrViewing,
final EntityModel.RenderingHint renderingHint) {
return new ScalarPropertyModel(
getParentUiModel(),
propertyMemento,
- mode,
+ editingOrViewing,
renderingHint);
}
@@ -163,6 +165,7 @@ implements PropertyUiModel {
*
* @return adapter, which may be different from the original
*/
+ @Deprecated // use managedValue() instead
public ManagedObject applyValue() {
val proposedNewValue = getObject();
getManagedProperty().modifyProperty(proposedNewValue);
@@ -170,6 +173,11 @@ implements PropertyUiModel {
}
@Override
+ public ManagedValue managedValue() {
+ return getManagedProperty().startNegotiation();
+ }
+
+ @Override
protected Can<ObjectAction> calcAssociatedActions() {
return getManagedProperty().getAssociatedActions();
}