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 2019/05/03 08:10:22 UTC

[isis] branch v2 updated: ISIS-2112: backporting changes from IoC_spring branch

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/v2 by this push:
     new 2221ecc  ISIS-2112: backporting changes from IoC_spring branch
2221ecc is described below

commit 2221ecc3699d5e980ecfb0e8e0d2e0357f9d8696
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri May 3 10:10:13 2019 +0200

    ISIS-2112: backporting changes from IoC_spring branch
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2112
---
 .../apache/isis/commons/internal/_Constants.java   |   6 +
 .../isis/commons/internal/base/_Blackhole.java     |  80 +++++++++
 .../apache/isis/commons/internal/base/_Casts.java  |  60 +++++++
 .../apache/isis/commons/internal/base/_Either.java | 138 +++++++++++++++
 .../apache/isis/commons/internal/base/_Lazy.java   |   2 +
 .../isis/commons/internal/base/_NullSafe.java      |  24 ++-
 .../isis/commons/internal/base/_Strings.java       |  25 +++
 .../commons/internal/base/_Strings_FastSplit.java  |  48 ++++++
 .../internal/base/_Strings_NaturalNames.java       |   3 -
 .../apache/isis/commons/internal/base/_Timing.java |  28 ++++
 .../isis/commons/internal/context/_Context.java    | 150 +++++++++++++----
 .../internal/context/_Context_ThreadLocal.java     | 113 +++++++++++++
 .../context/_Context_ThreadLocal_Singleton.java    |  99 +++++++++++
 .../apache/isis/commons/internal/debug/_Probe.java |  27 ++-
 .../commons/internal/exceptions/_Exceptions.java   |  25 +++
 .../apache/isis/core/commons/collections/Bin.java  | 185 +++++++++++++++++++++
 .../isis/core/commons/collections/Bin_Empty.java   |  45 +++++
 .../core/commons/collections/Bin_Multiple.java     |  45 +++++
 .../core/commons/collections/Bin_Singleton.java    |  44 +++++
 .../isis/core/commons/collections/Cardinality.java |  25 +++
 .../isis/commons/internal/context/ContextTest.java |   4 +-
 .../core/runtime/system/context/IsisContext.java   |   2 +-
 22 files changed, 1134 insertions(+), 44 deletions(-)

diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/_Constants.java b/core/commons/src/main/java/org/apache/isis/commons/internal/_Constants.java
index 879b4c1..6b95a52 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/_Constants.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/_Constants.java
@@ -21,6 +21,7 @@ package org.apache.isis.commons.internal;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.lang.annotation.Annotation;
 
 /**
  * <h1>- internal use only -</h1>
@@ -60,6 +61,11 @@ public final class _Constants {
     public static final byte[] emptyBytes = new byte[0];
 
     /**
+     * empty array of Annotation
+     */
+    public static final Annotation[] emptyAnnotations = new Annotation[0];
+
+    /**
      * writer that does nothing
      */
     public static final Writer nopWriter = new Writer() {
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Blackhole.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Blackhole.java
new file mode 100644
index 0000000..3b73f74
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Blackhole.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.commons.internal.base;
+
+import java.lang.ref.WeakReference;
+import java.util.Random;
+
+/**
+ * <h1>- internal use only -</h1>
+ * <p>
+ * "consumes" the values, conceiving no information to JIT whether the value is actually used afterwards. 
+ * This can save from the dead-code elimination of the computations resulting in the given values.
+ * </p>
+ * <p>
+ * This is a poor man's version of JMH's Blackhole (org.openjdk.jmh.infra.Blackhole).
+ * </p>
+ * <p>
+ * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
+ * These may be changed or removed without notice!
+ * </p>
+ *
+ * @since 2.0.0-M2
+ */
+public final class _Blackhole {
+    
+    /**
+     * Consume object. This call provides a side effect preventing JIT to eliminate dependent computations.
+     *
+     * @param obj object to consume.
+     */
+    public static void consume(Object obj) {
+
+        int tlrMask = internal.tlrMask; // volatile read
+        int tlr = (internal.tlr = (internal.tlr * 1664525 + 1013904223));
+
+        if ((tlr & tlrMask) == 0) {
+            // SHOULD ALMOST NEVER HAPPEN
+            internal.obj1 = new WeakReference<>(obj);
+            internal.tlrMask = (tlrMask << 1) + 1;
+        }
+
+    }
+    
+    // -- HELPER
+    
+    public final static class _Blackhole_Internal {
+        
+        public int tlr;
+        public volatile int tlrMask;
+        public volatile Object obj1;
+        
+        public _Blackhole_Internal() {
+            Random r = new Random(System.nanoTime());
+            tlr = r.nextInt();
+            tlrMask = 1;
+            obj1 = new Object();
+        }
+        
+    }
+    
+    private final static _Blackhole_Internal internal = new _Blackhole_Internal();
+
+}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Casts.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Casts.java
index a8365a9..ac56798 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Casts.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Casts.java
@@ -21,6 +21,7 @@ package org.apache.isis.commons.internal.base;
 
 import static org.apache.isis.commons.internal.base._With.requires;
 
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
 import javax.annotation.Nullable;
@@ -63,4 +64,63 @@ public final class _Casts {
 
     }
 
+    /**
+     * Dependent on whether left or right can be cast to {@code cls}, the appropriate functional 
+     * interface is chosen to produce the result.
+     * @param left
+     * @param right
+     * @param cls
+     * @param onBothCast
+     * @param onLeftCast
+     * @param onRightCast
+     * @param onNonCast
+     * @return
+     */
+    public static <T, R, U, V> R castThenApply(
+    		@Nullable U left, 
+    		@Nullable V right,
+    		Class<T> cls, 
+    		BiFunction<T, T, R> onBothCast,
+    		BiFunction<T, V, R> onLeftCast,
+    		BiFunction<U, T, R> onRightCast,
+    		BiFunction<U, V, R> onNonCast) {
+
+    	requires(cls, "cls");
+    	
+    	T left_casted=null, right_casted=null;
+    	boolean left_not_casted=false, right_not_casted=false;
+    	
+    	if(left==null) {
+    		left_casted = null;
+    	} else if(cls.isAssignableFrom(left.getClass())) {
+    		left_casted = cls.cast(left);
+    	} else {
+    		left_not_casted = true;
+    	}
+    	
+    	if(right==null) {
+    		right_casted = null;
+    	} else if(cls.isAssignableFrom(right.getClass())) {
+    		right_casted = cls.cast(right);
+    	} else {
+    		right_not_casted = true;
+    	}
+    	
+    	if(left_not_casted && right_not_casted) {
+    		return onNonCast.apply(left, right);
+    	}
+
+    	if(!left_not_casted && !right_not_casted) {
+    		return onBothCast.apply(left_casted, right_casted);
+    	}
+    	
+    	if(left_not_casted) {
+    		return onRightCast.apply(left, right_casted);
+    	}
+    	
+    	return onLeftCast.apply(left_casted, right);
+    	
+    }
+    
+    
 }
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Either.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Either.java
new file mode 100644
index 0000000..5a8a9be
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Either.java
@@ -0,0 +1,138 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.commons.internal.base;
+
+import static org.apache.isis.commons.internal.base._With.requires;
+
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+/**
+ * <h1>- internal use only -</h1>
+ * <p>
+ *      A holder of either a left or right reference, only one of both can be present.
+ * </p>
+ * <p>
+ * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
+ * These may be changed or removed without notice!
+ * </p>
+ *
+ * @since 2.0.0-M3
+ */
+@RequiredArgsConstructor(access=AccessLevel.PRIVATE, staticName="of")
+@ToString @EqualsAndHashCode
+public final class _Either<L, R> {
+
+    private final L left;
+    private final R right;
+    private final boolean isLeft;
+    
+    // -- FACTORIES
+    
+    public static <L, R> _Either<L, R> left(L left) {
+    	requires(left, "left");
+        return of(left, null, true);
+    }
+    
+    public static <L, R> _Either<L, R> right(R right) {
+    	requires(right, "right");
+        return of(null, right, false);
+    }
+    
+    public static <L, R> _Either<L, R> leftNullable(@Nullable L left) {
+        return of(left, null, true);
+    }
+    
+    public static <L, R> _Either<L, R> rightNullable(@Nullable R right) {
+        return of(null, right, false);
+    }
+    
+    // -- ACCESSORS
+    
+    public Optional<L> left() {
+    	return Optional.ofNullable(left); 
+    }
+    
+    public Optional<R> right() {
+    	return Optional.ofNullable(right); 
+    }
+    
+    public L leftIfAny() {
+    	return left; 
+    }
+    
+    public R rightIfAny() {
+    	return right; 
+    }
+    
+    // -- PREDICATES
+    
+    public boolean isLeft() {
+    	return isLeft;
+    }
+    
+    public boolean isRight() {
+    	return !isLeft;
+    }
+    
+    public boolean isPresentLeft() {
+    	return left!=null;
+    }
+    
+    public boolean isPresentRight() {
+    	return right!=null;
+    }
+    
+    // -- COMPOSITION
+    
+    public <X, Y> _Either<X, Y> map(Function<L, X> leftMapper, Function<R, Y> rightMapper){
+    	return isLeft()
+    			? left(leftMapper.apply(left))
+    					: right(rightMapper.apply(right));
+    }
+    
+    public <X, Y> _Either<X, Y> mapNullable(Function<L, X> leftMapper, Function<R, Y> rightMapper){
+    	return isLeft()
+    			? leftNullable(leftMapper.apply(left))
+    					: rightNullable(rightMapper.apply(right));
+    }
+    
+    // -- REDUCTION
+    
+    public <X> X get(BiFunction<L, R, X> reduction){
+    	return reduction.apply(left, right);
+    }
+    
+    public <X> X get(Function<L, X> leftMapper, Function<R, X> rightMapper){
+    	return isLeft()
+    			? leftMapper.apply(left)
+    					: rightMapper.apply(right);
+    }
+    
+    
+}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Lazy.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Lazy.java
index d105093..e163801 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Lazy.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Lazy.java
@@ -52,6 +52,8 @@ public interface _Lazy<T> extends Supplier<T> {
     /**
      * Evaluates this lazy value and memoizes it, when called the first time
      * after initialization or clear().
+     * <p>
+     * Postcondition when memoization throws an exception: isMemoized()->true and get()->null  
      */
     @Override
     public T get();
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
index 5b63a99..d53eba5 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
@@ -29,6 +29,8 @@ import java.util.Spliterators;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import javax.annotation.Nullable;
+
 /**
  * <h1>- internal use only -</h1>
  * <p>
@@ -56,7 +58,7 @@ public final class _NullSafe {
      * @param array
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final T[] array) {
+    public static <T> Stream<T> stream(@Nullable final T[] array) {
         return array!=null ? Stream.of(array) : Stream.empty();
     }
 
@@ -67,7 +69,7 @@ public final class _NullSafe {
      * @param nullable
      * @return non-null stream object
      */
-    public static <T> Stream<T> streamNullable(final T nullable) {
+    public static <T> Stream<T> streamNullable(@Nullable final T nullable) {
         return nullable != null ? Stream.of(nullable) : Stream.empty();
     }
 
@@ -77,7 +79,7 @@ public final class _NullSafe {
      * @param coll
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final Collection<T> coll){
+    public static <T> Stream<T> stream(@Nullable final Collection<T> coll){
         return coll!=null ? coll.stream() : Stream.empty();
     }
 
@@ -87,7 +89,7 @@ public final class _NullSafe {
      * @param iterable
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final Iterable<T> iterable){
+    public static <T> Stream<T> stream(@Nullable final Iterable<T> iterable){
         return iterable!=null ? stream(iterable.iterator()) : Stream.empty();
     }
 
@@ -97,7 +99,7 @@ public final class _NullSafe {
      * @param iterator
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final Iterator<T> iterator){
+    public static <T> Stream<T> stream(@Nullable final Iterator<T> iterator){
         return iterator!=null
                 ? StreamSupport.stream(
                         Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), 
@@ -106,6 +108,16 @@ public final class _NullSafe {
     }
     
     /**
+     * If {@code stream} is {@code null} returns the empty stream,
+     * otherwise returns the stream argument.
+     * @param stream
+     * @return non-null stream object
+     */
+    public static <T> Stream<T> stream(@Nullable final Stream<T> stream) {
+        return stream!=null ? stream : Stream.empty();
+    }
+    
+    /**
      * If {@code enumeration} is {@code null} returns the empty stream,
      * otherwise returns a stream of the enumeration's elements.
      * @param enumeration
@@ -232,4 +244,6 @@ public final class _NullSafe {
 
 
 
+
+
 }
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
index 151cfc7..7e6226f 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
@@ -33,6 +33,8 @@ import java.util.Optional;
 import java.util.Scanner;
 import java.util.Spliterator;
 import java.util.Spliterators;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.UnaryOperator;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
@@ -365,6 +367,29 @@ public final class _Strings {
         }
         return delimiterPattern.splitAsStream(input);
     }
+    
+
+    public static void splitThenAccept(
+    		@Nullable final String input, 
+    		final String separator, 
+    		final BiConsumer<String, String> onNonEmptySplit,
+    		final Consumer<String> onNonEmptyLhs,
+    		final Consumer<String> onNonEmptyRhs) {
+    	
+    	_Strings_FastSplit.splitThenAccept(input, separator, onNonEmptySplit, onNonEmptyLhs, onNonEmptyRhs);
+    }
+    
+    public static void splitThenAcceptEmptyAsNull(
+    		@Nullable final String input, 
+    		final String separator, 
+    		final BiConsumer<String, String> onSplit) {
+    	
+    	_Strings_FastSplit.splitThenAccept(input, separator, onSplit, 
+    			lhs->onSplit.accept(lhs, null), 
+    			rhs->onSplit.accept(null, rhs));
+    }
+    
+    
 
     // -- REPLACEMENT OPERATORS
 
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_FastSplit.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_FastSplit.java
new file mode 100644
index 0000000..1eac561
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_FastSplit.java
@@ -0,0 +1,48 @@
+package org.apache.isis.commons.internal.base;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import javax.annotation.Nullable;
+
+class _Strings_FastSplit {
+
+	public static void splitThenAccept(
+    		@Nullable final String input, 
+    		final String separator, 
+    		BiConsumer<String, String> onNonEmptySplit,
+    		Consumer<String> onNonEmptyLhs,
+    		Consumer<String> onNonEmptyRhs) {
+    	
+        if(_Strings.isEmpty(input)) {
+        	// skip
+            return;
+        }
+        
+        // we have a non-empty string
+        
+        final int p = input.indexOf(separator);
+        if(p<1){
+			if(p==-1) {
+				// separator not found
+				onNonEmptyLhs.accept(input);
+				return;
+			}
+			if(p==0) {
+				// empty lhs in string
+				if(input.length()>separator.length()) {
+					onNonEmptyRhs.accept(input);
+				}
+				return;
+			}
+		}
+        final int q = p + separator.length();
+		if(q==input.length()) {
+			// empty rhs
+			onNonEmptyLhs.accept(input.substring(0, p));
+			return;
+		}
+		onNonEmptySplit.accept(input.substring(0, p), input.substring(q));
+    }
+	
+}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_NaturalNames.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_NaturalNames.java
index 46110c3..0f1dd27 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_NaturalNames.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_NaturalNames.java
@@ -33,9 +33,6 @@ final class _Strings_NaturalNames {
      * Returns a word spaced version of the specified name, so there are spaces
      * between the words, where each word starts with a capital letter. E.g.,
      * "NextAvailableDate" is returned as "Next Available Date".
-     * <p>
-     * TODO [ahuber] handleNestedClassNames=false was only used by FixtureScript,
-     * can we remove this parameter and always handleNestedClassNames?
      *
      * @param name
      * @param handleNestedClassNames whether to handle any nested class names, eg 'Foo$Bar'
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Timing.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Timing.java
index 5150822..99cdeaa 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Timing.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Timing.java
@@ -19,6 +19,9 @@
 
 package org.apache.isis.commons.internal.base;
 
+import java.util.Locale;
+import java.util.function.Supplier;
+
 /**
  * <h1>- internal use only -</h1>
  * <p>
@@ -100,4 +103,29 @@ public final class _Timing {
         
     }
 
+    public static StopWatch run(Runnable runnable) {
+        final StopWatch watch = now();
+        runnable.run();
+        return watch.stop();
+    }
+    
+    public static void runVerbose(String label, Runnable runnable) {
+        final StopWatch watch = run(runnable);
+        info(String.format(Locale.US, "Running '%s' took %.2f ms", label, watch.getMillis()));
+    }
+    
+    public static <T> T callVerbose(String label, Supplier<T> callable) {
+        final StopWatch watch = now();
+        T result = callable.get();
+        watch.stop();
+        info(String.format(Locale.US, "Calling '%s' took %.2f ms", label, watch.getMillis()));
+        return result;
+    }
+
+    // -- HELPER
+    
+    private static void info(String msg) {
+        System.out.println(msg);
+    }
+
 }
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java
index 8a20dc1..b30bc65 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java
@@ -19,22 +19,24 @@
 
 package org.apache.isis.commons.internal.context;
 
-import java.util.HashMap;
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
+import static org.apache.isis.commons.internal.base._With.ifPresentElseGet;
+import static org.apache.isis.commons.internal.base._With.ifPresentElseThrow;
+import static org.apache.isis.commons.internal.base._With.requires;
+
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
 import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.core.commons.collections.Bin;
 import org.apache.isis.core.plugins.environment.IsisSystemEnvironment;
 import org.apache.isis.core.plugins.environment.IsisSystemEnvironmentPlugin;
 
-import static org.apache.isis.commons.internal.base._NullSafe.stream;
-import static org.apache.isis.commons.internal.base._With.ifPresentElseGet;
-import static org.apache.isis.commons.internal.base._With.ifPresentElseThrow;
-import static org.apache.isis.commons.internal.base._With.requires;
-
 /**
  * <h1>- internal use only -</h1>
  * <p>
@@ -58,7 +60,10 @@ public final class _Context {
      * the first one wins.<br/>
      * If synchronization is required it should happen elsewhere, not here!<br/>
      */
-    private final static Map<String, Object> singletonMap = new HashMap<>();
+    private final static Map<Class<?>, Object> singletonMap = new ConcurrentHashMap<>();
+    
+    private final static Object $LOCK = new Object[0];
+    
 
     /**
      * Puts a singleton instance onto the current context.
@@ -72,11 +77,11 @@ public final class _Context {
         requires(singleton, "singleton");
 
         // let writes to the map be atomic
-        synchronized (singletonMap) {
-            if(singletonMap.containsKey(toKey(type)))
+        synchronized ($LOCK) {
+            if(singletonMap.containsKey(type))
                 throw new IllegalStateException(
                         "there is already a singleton of type '"+type+"' on this context.");
-            singletonMap.put(toKey(type), singleton);
+            singletonMap.put(type, singleton);
         }
     }
 
@@ -93,12 +98,12 @@ public final class _Context {
         requires(singleton, "singleton");
 
         // let writes to the map be atomic
-        synchronized (singletonMap) {
-            if(singletonMap.containsKey(toKey(type))) {
+        synchronized ($LOCK) {
+            if(singletonMap.containsKey(type)) {
                 if(!override)
                     return false;
             }
-            singletonMap.put(toKey(type), singleton);
+            singletonMap.put(type, singleton);
             return true;
         }
     }
@@ -110,7 +115,7 @@ public final class _Context {
      * @return null, if there is no such instance
      */
     public static <T> T getIfAny(Class<? super T> type) {
-        return _Casts.uncheckedCast(singletonMap.get(toKey(type)));
+        return _Casts.uncheckedCast(singletonMap.get(type));
     }
 
     /**
@@ -124,20 +129,34 @@ public final class _Context {
         requires(type, "type");
         requires(factory, "factory");
 
-        final String key = toKey(type);
+        final T existingIfAny = _Casts.uncheckedCast(singletonMap.get(type));
+        if(existingIfAny!=null) {
+            return existingIfAny;
+        }
+        
+        // Note: we don't want to do this inside the synchronized block
+        final T t = factory.apply(type);
+        
+        // we don't store null to the map
+        if(t==null) {
+        	return null;
+//        	throw _Exceptions.unrecoverable(String.format("factory to compute new value for type '%s' "
+//        			+ "returned 'null', which is not allowed", type));
+        }
         
         // let writes to the map be atomic
-        synchronized (singletonMap) {
+        synchronized ($LOCK) {
             
             // Note: cannot just use 'singletonMap.computeIfAbsent(toKey(type), __->factory.apply(type));'
             // here because it does not allow for modification of singletonMap inside the factory call
+        	// Also we do need a second check for existing key, since it might have been changed by another
+        	// thread since.
+        	final T existingIfAny2 = _Casts.uncheckedCast(singletonMap.get(type));
+            if(existingIfAny2!=null) {
+                return existingIfAny2;
+            }        	
             
-            final T existingIfAny =  _Casts.uncheckedCast(singletonMap.get(key));
-            if(existingIfAny!=null) {
-                return existingIfAny;
-            }
-            final T t = factory.apply(type);
-            singletonMap.put(key, t);
+            singletonMap.put(type, t);
             return t;
         }
     }
@@ -178,20 +197,34 @@ public final class _Context {
      * @return
      * @throws Exception
      */
-    public static <T, E extends Exception> T getOrThrow(
+    public static <T, E extends Exception> T getElseThrow (
             Class<? super T> type,
             Supplier<E> onNotFound)
                     throws E {
+    	
+    	requires(type, "type");
         requires(onNotFound, "onNotFound");
         return ifPresentElseThrow(getIfAny(type), onNotFound);
     }
 
+    /**
+     * Gets a singleton instance of {@code type} if there is any,
+     * otherwise throws a NoSuchElementException.
+     * @param type non-null
+     * @return
+     */
+    public static <T> T getElseFail(Class<? super T> type) {
+        return ifPresentElseThrow(getIfAny(type), ()-> 
+        new NoSuchElementException(String.format("Could not resolve an instance of type '%s'", type.getName())));
+    }
+    
+    
     // -- REMOVAL
     
     public static void remove(Class<?> type) {
         // let writes to the map be atomic
-        synchronized (singletonMap) {
-            singletonMap.remove(toKey(type));
+        synchronized ($LOCK) {
+            singletonMap.remove(type);
         }
         tryClose(type);
     }
@@ -205,7 +238,7 @@ public final class _Context {
     public static void clear() {
 
         // let writes to the map be atomic
-        synchronized (singletonMap) {
+        synchronized ($LOCK) {
 
             closeAnyClosables(_Lists.newArrayList(singletonMap.values()));
 
@@ -217,6 +250,59 @@ public final class _Context {
         stream(objects)
         .forEach(_Context::tryClose);
     }
+    
+    // -- THREAD LOCAL SUPPORT
+
+    /**
+     * Puts {@code payload} onto the current thread's map.
+     * @param type - the key into the thread-local store
+     * @param payload
+     */
+	public static <T> void threadLocalPut(Class<? super T> type, T payload) {
+		_Context_ThreadLocal.put(type, payload);
+	}
+
+//TODO [2033] cleanup comments	
+//	/**
+//	 * Puts {@code payload} onto the current thread's map. The provided {@code onCleanup} is called whenever
+//	 * {@code threadLocalCleanup()} is called.  
+//	 * @param type
+//	 * @param payload
+//	 * @param onCleanup
+//	 */
+//	public static <T> void threadLocalPut(Class<? super T> type, T payload, Runnable onCleanup) {
+//		_Context_ThreadLocal.put(type, payload, onCleanup);
+//	}
+
+    
+    /**
+     * Looks up current thread's values for any instances that match the given type, as previously stored 
+     * with {@link _Context#threadLocalPut(Class, Object)}.
+     * @param type - the key into the thread-local store
+     * @return
+     */
+	public static <T> Bin<T> threadLocalGet(Class<? super T> type) {
+		return _Context_ThreadLocal.get(type);
+	}
+	
+    /**
+     * Looks up current thread's values for any instances that match the given type, as previously stored 
+     * with {@link _Context#threadLocalPut(Class, Object)}.
+     * @param type - the key into the thread-local store
+     * @param requiredType - the required type of the elements in the returned bin
+     * @return
+     */
+	public static <T> Bin<T> threadLocalSelect(Class<? super T> type, Class<? super T> requiredType) {
+		return _Context_ThreadLocal.select(type, requiredType);
+	}
+
+	/**
+	 * Removes any of current thread's values as stored with {@link _Context#threadLocalPut(Class, Object)}.
+	 */
+	public static <T> void threadLocalCleanup() {
+		_Context_ThreadLocal.cleanupThread();
+	}
+    
 
     // -- DEFAULT CLASSLOADER
 
@@ -240,8 +326,8 @@ public final class _Context {
         final boolean alreadyRegistered = _Context.getIfAny(ClassLoader.class)!=null;
         if(!alreadyRegistered || override) {
             // let writes to the map be atomic
-            synchronized (singletonMap) {
-                singletonMap.put(toKey(ClassLoader.class), requires(classLoader, "classLoader"));
+            synchronized ($LOCK) {
+                singletonMap.put(ClassLoader.class, requires(classLoader, "classLoader"));
             }
         }
     }
@@ -289,10 +375,6 @@ public final class _Context {
     }
 
     // -- HELPER
-
-    private static String toKey(Class<?> type) {
-        return type.getName();
-    }
     
     private static void tryClose(Object singleton) {
         if(singleton==null) {
@@ -308,4 +390,8 @@ public final class _Context {
     }
 
 
+
+	
+
+
 }
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java
new file mode 100644
index 0000000..66379f6
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java
@@ -0,0 +1,113 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.commons.internal.context;
+
+import static org.apache.isis.commons.internal.base._With.requires;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.core.commons.collections.Bin;
+
+import lombok.val;
+
+/**
+ * <h1>- internal use only -</h1>
+ * <p>
+ * Package private mixin for _Context. 
+ * Provides a context for storing and retrieving thread local object references.
+ * </p>
+ * <p>
+ * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
+ * These may be changed or removed without notice!
+ * </p>
+ * @since 2.0.0-M3
+ */
+final class _Context_ThreadLocal {
+
+	//TODO [2033] cleanup comments ...
+	
+    // -- MIXINS
+    
+	static <T> void put(Class<? super T> type, T variant) {
+		requires(type, "type");
+    	requires(variant, "variant");
+    	THREAD_LOCAL_MAP.get()
+    	.compute(type, (k, v) -> v == null 
+    		? Bin.<T>ofSingleton(variant)
+    				: Bin.<T>concat(_Casts.uncheckedCast(v), variant));
+    }
+	
+//    static <T> void put(Class<? super T> type, Object payload, Runnable onCleanup) {
+//    	requires(type, "type");
+//    	requires(payload, "payload");
+//    	requires(onCleanup, "onCleanup");
+//    	THREAD_LOCAL_MAP.get().put(type, Payload.of(payload, onCleanup));
+//    }
+	
+	static <T> Bin<T> select(Class<? super T> type, Class<? super T> instanceOf) {
+		val bin = _Context_ThreadLocal.<T>get(type);
+		return bin.filter(t -> isInstanceOf(t, instanceOf));
+	}
+	
+	private static boolean isInstanceOf(Object obj, Class<?> type) {
+		return type.isAssignableFrom(obj.getClass());
+	}
+    
+    static <T> Bin<T> get(Class<? super T> type) {
+    	val bin = THREAD_LOCAL_MAP.get().get(type);
+    	if(bin!=null) {
+    		return _Casts.uncheckedCast(bin);
+    	}
+    	return Bin.empty();
+    }
+    
+    static void cleanupThread() {
+//    	THREAD_LOCAL_MAP.get().forEach((key, payload)->payload.cleanUp());
+    	THREAD_LOCAL_MAP.remove();
+    }
+    
+    // -- HELPER
+    
+    private _Context_ThreadLocal(){}
+
+	/**
+	 * Inheritable... allows to have concurrent computations utilizing the ForkJoinPool.
+	 */
+    private final static ThreadLocal<Map<Class<?>, Bin<?>>> THREAD_LOCAL_MAP = 
+    		InheritableThreadLocal.withInitial(HashMap::new);
+
+
+//  @Value(staticConstructor="of")
+//  private final static class Payload<T> {
+//		final Instance<T> instance;
+//  	final Runnable onCleanup;
+//  	void cleanUp() {
+//			if(onCleanup!=null) {
+//				onCleanup.run();
+//			}
+//		}
+//  }
+
+
+    
+    
+}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal_Singleton.java b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal_Singleton.java
new file mode 100644
index 0000000..ebfa716
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal_Singleton.java
@@ -0,0 +1,99 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.commons.internal.context;
+
+import static org.apache.isis.commons.internal.base._With.requires;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.isis.commons.internal.base._Casts;
+
+import lombok.Value;
+import lombok.val;
+
+/**
+ * <h1>- internal use only -</h1>
+ * <p>
+ * Package private mixin for _Context. 
+ * Provides a context for storing and retrieving thread local object references.
+ * </p>
+ * <p>
+ * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
+ * These may be changed or removed without notice!
+ * </p>
+ * @since 2.0.0-M3
+ */
+@Deprecated //TODO [2033] superseded by _Context_ThreadLocal 
+final class _Context_ThreadLocal_Singleton {
+
+    // -- MIXINS
+    
+	static <T> void put(Class<? super T> type, T payload) {
+		requires(type, "type");
+    	requires(payload, "payload");
+    	THREAD_LOCAL_MAP.get().put(type, Payload.of(payload, null));
+    }
+	
+    static <T> void put(Class<? super T> type, Object payload, Runnable onCleanup) {
+    	requires(type, "type");
+    	requires(payload, "payload");
+    	requires(onCleanup, "onCleanup");
+    	THREAD_LOCAL_MAP.get().put(type, Payload.of(payload, onCleanup));
+    }
+    
+    static <T> T get(Class<? super T> type) {
+    	val payload = THREAD_LOCAL_MAP.get().get(type);
+    	if(payload!=null) {
+    		return _Casts.uncheckedCast(payload.pojo);
+    	}
+    	return null;
+    }
+    
+    static void cleanupThread() {
+    	THREAD_LOCAL_MAP.get().forEach((key, payload)->payload.cleanUp());
+    	THREAD_LOCAL_MAP.remove();
+    }
+    
+    // -- HELPER
+    
+    private _Context_ThreadLocal_Singleton(){}
+    
+    @Value(staticConstructor="of")
+    private final static class Payload {
+		final Object pojo;
+    	final Runnable onCleanup;
+    	void cleanUp() {
+			if(onCleanup!=null) {
+				onCleanup.run();
+			}
+		}
+    }
+
+	/**
+	 * Inheritable... allows to have concurrent computations utilizing the ForkJoinPool.
+	 */
+    private final static ThreadLocal<Map<Class<?>, Payload>> THREAD_LOCAL_MAP = 
+    		InheritableThreadLocal.withInitial(HashMap::new);
+
+
+    
+    
+}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/debug/_Probe.java b/core/commons/src/main/java/org/apache/isis/commons/internal/debug/_Probe.java
index cd56a2e..000ead5 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/debug/_Probe.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/debug/_Probe.java
@@ -24,6 +24,8 @@ import java.util.concurrent.atomic.LongAdder;
 
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 
+import lombok.val;
+
 /**
  * <h1>- internal use only -</h1>
  * <p>
@@ -56,6 +58,7 @@ public class _Probe {
     private String label = "Probe";
     private String indentLiteral = "  ";
     private String emphasisFormat = "__PROBE__ %s";
+    private boolean silenced = false;
     
     private final LongAdder counter = new LongAdder();
     
@@ -108,6 +111,11 @@ public class _Probe {
         return this;
     }
     
+    public _Probe silence() {
+    	this.silenced = true;
+		return this;
+	}
+    
     // -- INDENTING
     
     public int currentIndent = 0;
@@ -117,7 +125,9 @@ public class _Probe {
     public void println(int indent, CharSequence chars) {
         if(counter.longValue()<maxCalls) {
             counter.increment();
-            print_line(indent, chars);
+            if(!silenced) {
+            	print_line(indent, chars);
+            }
             return;
         }
         
@@ -150,6 +160,17 @@ public class _Probe {
     public void println(String format, Object...args) {
         println(currentIndent, format, args);
     }
+    
+    public void warnNotImplementedYet(String format, Object... args) {
+    	val warnMsg = String.format(format, args);
+    	val restore_out = out;
+    	out=System.err;
+    	println("WARN NotImplementedYet %s", warnMsg);
+    	errOut("-------------------------------------");
+    	_Exceptions.dumpStackTrace(System.err, 1, 12);
+    	errOut("-------------------------------------");
+    	out=restore_out;
+	}
 
     // -- CONVENIENT DEBUG TOOLS (STATIC)
 
@@ -172,5 +193,9 @@ public class _Probe {
         out.println(String.format(emphasisFormat, message));
     }
 
+	
+
+	
+
 
 }
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java b/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
index 214936a..c191db3 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
@@ -25,6 +25,7 @@ import static org.apache.isis.commons.internal.base._With.requires;
 import java.io.PrintStream;
 import java.util.Collections;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -73,7 +74,16 @@ public final class _Exceptions {
         requires(format, "format");
         return new IllegalArgumentException(String.format(format, _case));
     }
+    
+    public static final NoSuchElementException noSuchElement(String msg) {
+        return new NoSuchElementException(msg);
+    }
 
+    public static final NoSuchElementException noSuchElement(String format, Object ...args) {
+        requires(format, "format");
+        return noSuchElement(String.format(format, args));
+    }
+    
     public static final IllegalStateException unexpectedCodeReach() {
         return new IllegalStateException("internal error: code was reached, that is expected unreachable");
     }
@@ -82,6 +92,18 @@ public final class _Exceptions {
         return new IllegalStateException("internal error: code was reached, that is not implemented yet");
     }
     
+    public static RuntimeException unrecoverable(Exception cause) {
+        return new RuntimeException("unrecoverable error: with cause ...", cause);
+    }
+    
+    public static RuntimeException unrecoverable(String msg) {
+        return new RuntimeException(String.format("unrecoverable error: '%s'", msg));
+    }
+    
+    public static RuntimeException unrecoverable(String msg, Exception cause) {
+        return new RuntimeException(String.format("unrecoverable error: '%s' with cause ...", msg), cause);
+    }
+    
     /**
     * Used to hide from the compiler the fact, that this call always throws.
     *
@@ -314,4 +336,7 @@ public final class _Exceptions {
     }
 
 
+    
+
+
 }
diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin.java b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin.java
new file mode 100644
index 0000000..2d565a0
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin.java
@@ -0,0 +1,185 @@
+package org.apache.isis.core.commons.collections;
+
+import static org.apache.isis.commons.internal.base._With.requires;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+import javax.enterprise.inject.Instance;
+
+import org.apache.isis.commons.internal.base._NullSafe;
+
+import lombok.val;
+
+/**
+ * 
+ * Immutable 'multi-set'. 
+ * 
+ * @param <T>
+ * @since 2.0.0-M3
+ */
+public interface Bin<T> extends Iterable<T> {
+
+	Cardinality getCardinality();
+	int size();
+	
+	Stream<T> stream();
+	
+	Optional<T> getFirst();
+	Optional<T> getSingleton();
+	
+	// -- FACTORIES
+	
+	@SuppressWarnings("unchecked") // this is how the JDK does it for eg. empty lists
+	public static <T> Bin<T> empty() {
+		return (Bin<T>) Bin_Empty.INSTANCE;
+	}
+	
+	public static <T> Bin<T> ofNullable(@Nullable T element) {
+		if(element==null) {
+			return empty();
+		}
+		return Bin_Singleton.of(element);
+	}
+	
+	public static <T> Bin<T> ofSingleton(T element) {
+		requires(element, "element");
+		return Bin_Singleton.of(element);
+	}
+	
+	public static <T> Bin<T> ofCollection(@Nullable Collection<T> collection){
+		
+		if(_NullSafe.size(collection)==0) {
+			return empty();
+		}
+		
+		val maxSize = collection.size();
+		
+		val nonNullElements = collection.stream()
+				.filter(_NullSafe::isPresent)
+				.collect(Collectors.toCollection(()->new ArrayList<>(maxSize)));
+		
+		nonNullElements.trimToSize(); // in case we have a 'sparse' collection as input to this method
+		
+		val size = nonNullElements.size();
+		
+		if(size==0) {
+			return empty();
+		}
+		
+		if(size==1) {
+			return ofSingleton(((List<T>)nonNullElements).get(0));
+		}
+		
+		return Bin_Multiple.of(nonNullElements);
+	}
+	
+	public static <T> Bin<T> ofInstance(@Nullable Instance<T> instance) {
+		if(instance==null || instance.isUnsatisfied()) {
+			return empty();
+		}
+		if(instance.isResolvable()) { 
+			return Bin_Singleton.of(instance.get());
+		}
+		val nonNullElements = instance.stream()
+				.collect(Collectors.toCollection(()->new ArrayList<>()));
+		
+		return Bin_Multiple.of(nonNullElements);
+		
+	}
+	
+	
+	// -- OPERATORS
+	
+	public default Bin<T> filter(@Nullable Predicate<? super T> predicate) {
+		if(predicate==null || isEmpty()) {
+			return this;
+		}
+		
+		// optimization for the singleton case
+		if(isCardinalityOne()) {
+			val singleton = getSingleton().get();
+			return predicate.test(singleton)
+					? this
+							: empty();
+		}
+		
+		val filteredElements = 
+		stream()
+		.filter(predicate)
+		.collect(Collectors.toCollection(ArrayList::new));
+		
+		// optimization for the case when the filter accepted all
+		if(filteredElements.size()==size()) {
+			return this;
+		}
+		
+		return ofCollection(filteredElements);
+	}
+	
+	public static <T> Bin<T> concat(@Nullable Bin<T> bin, @Nullable T variant) {
+		if(bin==null || bin.isEmpty()) {
+			return ofNullable(variant);
+		}
+		if(variant==null) {
+			return bin;
+		}
+		// at this point: bin is not empty and variant is not null
+		val newSize = bin.size() + 1;
+    	val union = bin.stream().collect(Collectors.toCollection(()->new ArrayList<>(newSize)));
+    	union.add(variant);
+    	return Bin_Multiple.of(union);
+    }
+	
+    default <R> Bin<R> map(Function<? super T, R> mapper) {
+    	
+        if(isEmpty()) {
+            return empty();
+        }
+        
+        requires(mapper, "mapper");
+        
+        val mappedElements = 
+        		stream()
+        		.map(mapper)
+        		.filter(_NullSafe::isPresent)
+        		.collect(Collectors.toCollection(ArrayList::new));
+        
+        return ofCollection(mappedElements);
+    }
+	
+	// -- TRAVERSAL
+	
+    default void forEach(Consumer<? super T> action) {
+        requires(action, "action");
+        stream().forEach(action);
+    }
+	
+	// -- SHORTCUTS FOR PREDICATES
+	
+	default boolean isEmpty() {
+		return getCardinality().isZero();
+	}
+	
+	default boolean isNotEmpty() {
+		return !getCardinality().isZero();
+	}
+	
+	default boolean isCardinalityOne() {
+		return getCardinality().isOne();
+	}
+	
+	default boolean isCardinalityMultiple() {
+		return getCardinality().isMultiple();
+	}
+	
+	
+}
diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Empty.java b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Empty.java
new file mode 100644
index 0000000..c6b9783
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Empty.java
@@ -0,0 +1,45 @@
+package org.apache.isis.core.commons.collections;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import lombok.Value;
+
+@Value(staticConstructor="of")
+final class Bin_Empty<T> implements Bin<T> {
+	
+	static final Bin_Empty<?> INSTANCE = new Bin_Empty<>(); 
+	
+	@Override
+	public Cardinality getCardinality() {
+		return Cardinality.ZERO;
+	}
+
+	@Override
+	public Stream<T> stream() {
+		return Stream.empty();
+	}
+
+	@Override
+	public Optional<T> getSingleton() {
+		return Optional.empty();
+	}
+
+	@Override
+	public Optional<T> getFirst() {
+		return Optional.empty();
+	}
+
+	@Override
+	public int size() {
+		return 0;
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		return Collections.<T>emptyList().iterator();
+	}
+
+}
diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Multiple.java b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Multiple.java
new file mode 100644
index 0000000..7d6fc5f
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Multiple.java
@@ -0,0 +1,45 @@
+package org.apache.isis.core.commons.collections;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName="of")
+final class Bin_Multiple<T> implements Bin<T> {
+	
+	private final List<T> elements;
+	
+	@Getter(lazy=true, onMethod=@__({@Override})) 
+	private final Optional<T> first = Optional.of(elements.get(0));
+
+	@Override
+	public Cardinality getCardinality() {
+		return Cardinality.MULTIPLE;
+	}
+
+	@Override
+	public Stream<T> stream() {
+		return elements.stream();
+	}
+
+	@Override
+	public Optional<T> getSingleton() {
+		return Optional.empty();
+	}
+
+	@Override
+	public int size() {
+		return elements.size();
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		return Collections.unmodifiableList(elements).iterator();
+	}
+
+}
diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Singleton.java b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Singleton.java
new file mode 100644
index 0000000..11ef069
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Bin_Singleton.java
@@ -0,0 +1,44 @@
+package org.apache.isis.core.commons.collections;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor(staticName="of")
+final class Bin_Singleton<T> implements Bin<T> {
+	
+	private final T element;
+	
+	@Getter(lazy=true, onMethod=@__({@Override})) 
+	private final Optional<T> singleton = Optional.of(element);
+
+	@Override
+	public Cardinality getCardinality() {
+		return Cardinality.ONE;
+	}
+
+	@Override
+	public Stream<T> stream() {
+		return Stream.of(element);
+	}
+
+	@Override
+	public Optional<T> getFirst() {
+		return getSingleton();
+	}
+
+	@Override
+	public int size() {
+		return 1;
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		return Collections.singletonList(element).iterator();
+	}
+
+}
diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/collections/Cardinality.java b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Cardinality.java
new file mode 100644
index 0000000..c4ff2a2
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/collections/Cardinality.java
@@ -0,0 +1,25 @@
+package org.apache.isis.core.commons.collections;
+
+/**
+ * @since 2.0.0-M3
+ */
+public enum Cardinality {
+	
+	ZERO,
+	ONE,
+	MULTIPLE
+	;
+	
+	public boolean isZero() {
+		return this == ZERO;
+	}
+	
+	public boolean isOne() {
+		return this == ONE;
+	}
+	
+	public boolean isMultiple() {
+		return this == MULTIPLE;
+	}
+	
+}
diff --git a/core/commons/src/test/java/org/apache/isis/commons/internal/context/ContextTest.java b/core/commons/src/test/java/org/apache/isis/commons/internal/context/ContextTest.java
index a72af53..4183ebb 100644
--- a/core/commons/src/test/java/org/apache/isis/commons/internal/context/ContextTest.java
+++ b/core/commons/src/test/java/org/apache/isis/commons/internal/context/ContextTest.java
@@ -185,12 +185,12 @@ class ContextTest {
         final AClass singleton = new AClass();
 
         assertThrows(IllegalStateException.class, 
-                ()->{_Context.getOrThrow(type, IllegalStateException::new);},
+                ()->{_Context.getElseThrow(type, IllegalStateException::new);},
                 "expected to throw: no singleton on context that matches the given type");
         
         _Context.putSingleton(type, singleton);
         
-        final Object actual = _Context.getOrThrow(type, IllegalStateException::new);
+        final Object actual = _Context.getElseThrow(type, IllegalStateException::new);
         assertTrue(singleton==actual, "singleton on context is expected to be the same as the 'local' singleton");
     }
 
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
index 7454c9d..b4d2818 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
@@ -58,7 +58,7 @@ public interface IsisContext {
      */
     // Implementation Note: Populated only by {@link IsisSessionFactoryBuilder}.
     public static IsisSessionFactory getSessionFactory() {
-        return _Context.getOrThrow(
+        return _Context.getElseThrow(
                 IsisSessionFactory.class,
                 ()->new IllegalStateException(
                         "internal error: should have been populated by IsisSessionFactoryBuilder") );