java-commons/commons/src/main/java/io/gitlab/jfronny/commons/concurrent/ScopedValue.java

123 lines
3.4 KiB
Java

package io.gitlab.jfronny.commons.concurrent;
import java.util.Deque;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
/**
* Naive, inefficient reimplementation of JDK22 ScopedValue based on ThreadLocal.
* See the official Javadoc for more information.
* Use the JDK22 version if possible!
* Does not include ScopedValue.where
*
* @param <T> The type of the value
*/
public class ScopedValue<T> {
public static <T> ScopedValue<T> newInstance() {
return new ScopedValue<>();
}
public static <T, R> R callWhere(ScopedValue<T> key, T value, Callable<? extends R> op) throws Exception {
key.value.get().setValue(value);
try {
return op.call();
} finally {
key.value.get().clear();
}
}
public static <T, R> R getWhere(ScopedValue<T> key, T value, Supplier<? extends R> op) {
key.value.get().setValue(value);
try {
return op.get();
} finally {
key.value.get().clear();
}
}
public static <T> void runWhere(ScopedValue<T> key, T value, Runnable op) {
key.value.get().setValue(value);
try {
op.run();
} finally {
key.value.get().clear();
}
}
private final InheritableThreadLocal<Carrier<T>> value = new InheritableThreadLocal<>() {
@Override
protected Carrier<T> childValue(Carrier<T> parentValue) {
return new Carrier<>(parentValue);
}
@Override
protected Carrier<T> initialValue() {
return new Carrier<>();
}
};
public T get() {
return value.get().getValue();
}
public boolean isBound() {
return value.get().isPresent();
}
public T orElse(T other) {
return value.get().orElse(other);
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
return value.get().orElseThrow(exceptionSupplier);
}
private static class Carrier<T> {
private Deque<T> value;
private Carrier<T> parent;
public Carrier() {
this(null);
}
public Carrier(Carrier<T> parent) {
this.value = new LinkedList<>();
this.parent = parent;
}
public T getValue() {
if (!value.isEmpty()) return value.peek();
if (parent == null) throw new NoSuchElementException("No value present");
return parent.getValue();
}
public boolean isPresent() {
if (!value.isEmpty()) return true;
if (parent == null) return false;
return parent.isPresent();
}
public void setValue(T value) {
this.value.push(value);
}
public void clear() {
value.pop();
}
public T orElse(T other) {
if (!value.isEmpty()) return value.peek();
if (parent == null) return other;
return parent.orElse(other);
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (!value.isEmpty()) return value.peek();
if (parent == null) throw exceptionSupplier.get();
return parent.orElseThrow(exceptionSupplier);
}
}
}