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") );