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();
     }