Skip to content

Commit 9f40e2c

Browse files
committed
Merge branch 'expt/wrapper-travel'
2 parents 38d4b55 + 6de1b20 commit 9f40e2c

File tree

2 files changed

+87
-22
lines changed

2 files changed

+87
-22
lines changed

src/main/java/io/foldright/inspectablewrappers/Wrapper.java

+56-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import edu.umd.cs.findbugs.annotations.Nullable;
55

66
import javax.annotation.ParametersAreNonnullByDefault;
7+
import java.util.Optional;
8+
import java.util.function.Function;
79
import java.util.function.Predicate;
810

911
import static io.foldright.inspectablewrappers.InternalUtils.unwrapNonNull;
@@ -46,13 +48,7 @@ public interface Wrapper<T> {
4648
static <W> boolean isInstanceOf(final W wrapper, final Class<?> clazz) {
4749
requireNonNull(wrapper, "wrapper is null");
4850
requireNonNull(clazz, "clazz is null");
49-
return inspect(wrapper, w -> {
50-
if (w instanceof WrapperAdapter) {
51-
Object adaptee = ((WrapperAdapter<?>) w).adaptee();
52-
if (clazz.isAssignableFrom(adaptee.getClass())) return true;
53-
}
54-
return clazz.isAssignableFrom(w.getClass());
55-
});
51+
return inspect(wrapper, w -> clazz.isAssignableFrom(w.getClass()));
5652
}
5753

5854
/**
@@ -68,18 +64,13 @@ static <W> boolean isInstanceOf(final W wrapper, final Class<?> clazz) {
6864
* otherwise return {@code true}
6965
* @throws NullPointerException if any arguments is null or any wrapper {{@link #unwrap()}} returns null
7066
*/
71-
@SuppressWarnings("unchecked")
7267
static <W> boolean inspect(final W wrapper, final Predicate<? super W> predicate) {
7368
requireNonNull(wrapper, "wrapper is null");
7469
requireNonNull(predicate, "predicate is null");
75-
76-
Object w = wrapper;
77-
while (true) {
78-
if (predicate.test((W) w)) return true;
79-
80-
if (!(w instanceof Wrapper)) return false;
81-
w = unwrapNonNull(w);
82-
}
70+
return travel(wrapper, w -> {
71+
if (predicate.test(w)) return Optional.of(true);
72+
else return Optional.empty();
73+
}).orElse(false);
8374
}
8475

8576
/**
@@ -107,15 +98,60 @@ static <W> boolean inspect(final W wrapper, final Predicate<? super W> predicate
10798
static <W, K, V> V getAttachment(final W wrapper, final K key) {
10899
requireNonNull(wrapper, "wrapper is null");
109100
requireNonNull(key, "key is null");
101+
return travel(wrapper, w -> {
102+
if (w instanceof Attachable) {
103+
V value = ((Attachable<K, V>) w).getAttachment(key);
104+
return Optional.ofNullable(value);
105+
} else {
106+
return Optional.empty();
107+
}
108+
}).orElse(null);
109+
}
110+
111+
/**
112+
* Traverses the wrapper chain, and apply the given {@code process} function to each wrapper,
113+
* and returns the first non-empty({@link Optional#empty()}) result of the process function,
114+
* otherwise returns {@link Optional#empty()}.
115+
* <p>
116+
* The wrapper chain consists of wrapper itself, followed by the wrappers
117+
* obtained by repeatedly calling {@link #unwrap()}.
118+
*
119+
* @param wrapper wrapper instance
120+
* @param process process function
121+
* @param <W> the type of instances that be wrapped
122+
* @param <T> the return data type of process function
123+
* @return the first non-empty({@link Optional#empty()}) result of the process function,
124+
* otherwise returns {@link Optional#empty()}.
125+
* @throws NullPointerException if any arguments is null or any wrapper {{@link #unwrap()}} returns null
126+
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is a wrapper instance,
127+
* the use of WrapperAdapter is unnecessary!
128+
*/
129+
@NonNull
130+
@SuppressWarnings("unchecked")
131+
static <W, T> Optional<T> travel(final W wrapper, final Function<W, Optional<T>> process) {
132+
requireNonNull(wrapper, "wrapper is null");
133+
requireNonNull(process, "process is null");
110134

111135
Object w = wrapper;
112136
while (true) {
113-
if (w instanceof Attachable) {
114-
V value = ((Attachable<K, V>) w).getAttachment(key);
115-
if (value != null) return value;
137+
// process the instance on wrapper chain
138+
Optional<T> result = process.apply((W) w);
139+
if (result.isPresent()) return result;
140+
141+
// also process the adaptee if it's a WrapperAdapter
142+
if (w instanceof WrapperAdapter) {
143+
final Object adaptee = ((WrapperAdapter<?>) w).adaptee();
144+
if (adaptee instanceof Wrapper) {
145+
throw new IllegalStateException("adaptee(" + adaptee.getClass().getName() +
146+
") of WrapperAdapter(" + w.getClass().getName() +
147+
") is a wrapper instance, the use of WrapperAdapter is unnecessary!");
148+
}
149+
150+
Optional<T> r = process.apply((W) adaptee);
151+
if (r.isPresent()) return r;
116152
}
117153

118-
if (!(w instanceof Wrapper)) return null;
154+
if (!(w instanceof Wrapper)) return Optional.empty();
119155
w = unwrapNonNull(w);
120156
}
121157
}

src/test/java/io/foldright/inspectablewrappers/WrapperAdapterTest.kt

+31-2
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,34 @@ class WrapperAdapterTest : FunSpec({
4646
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
4747
test("argument null") {
4848
shouldThrow<NullPointerException> {
49-
Wrapper.getAttachment<Executor, String, String?>(null , ADAPTED_MSG_KEY)
49+
Wrapper.getAttachment<Executor, String, String?>(null, ADAPTED_MSG_KEY)
5050
}.message shouldBe "wrapper is null"
5151

5252
shouldThrow<NullPointerException> {
5353
Wrapper.getAttachment<Executor, String, String?>(executorChain, null)
5454
}.message shouldBe "key is null"
5555
}
5656

57+
test("travel IllegalStateException - the adaptee of WrapperAdapter is a wrapper instance") {
58+
val chain: Executor = ChattyExecutorWrapper { runnable -> runnable.run() }
59+
.let(::ChattyExecutorWrapperAdapter)
60+
61+
val errMsg = "adaptee(io.foldright.inspectablewrappers.ChattyExecutorWrapper)" +
62+
" of WrapperAdapter(io.foldright.inspectablewrappers.ChattyExecutorWrapperAdapter)" +
63+
" is a wrapper instance, the use of WrapperAdapter is unnecessary!"
64+
65+
shouldThrow<IllegalStateException> {
66+
Wrapper.isInstanceOf(chain, ExecutorService::class.java)
67+
}.message shouldBe errMsg
68+
// first instance is ok, not trigger the check logic yet...
69+
Wrapper.isInstanceOf(chain, Executor::class.java).shouldBeTrue()
70+
Wrapper.isInstanceOf(chain, ChattyExecutorWrapperAdapter::class.java).shouldBeTrue()
71+
72+
73+
shouldThrow<IllegalStateException> {
74+
Wrapper.getAttachment(chain, "k1")
75+
}.message shouldBe errMsg
76+
}
5777
})
5878

5979
/**
@@ -62,7 +82,6 @@ class WrapperAdapterTest : FunSpec({
6282
private class ExistedExecutorWrapperAdapter(private val adaptee: ExistedExecutorWrapper) :
6383
Executor by adaptee, WrapperAdapter<Executor>, Attachable<String, String> by AttachableDelegate() {
6484
override fun unwrap(): Executor = adaptee.executor
65-
6685
override fun adaptee(): Executor = adaptee
6786
}
6887

@@ -72,3 +91,13 @@ class ExistedExecutorWrapper(val executor: Executor) : Executor {
7291
executor.execute(command)
7392
}
7493
}
94+
95+
/**
96+
* Wrong use the [WrapperAdapter], the adaptee is [Wrapper].
97+
*/
98+
private class ChattyExecutorWrapperAdapter(private val adaptee: ChattyExecutorWrapper) :
99+
Executor by adaptee, WrapperAdapter<Executor>, Attachable<String, String> by AttachableDelegate() {
100+
override fun unwrap(): Executor = adaptee.unwrap()
101+
override fun adaptee(): Executor = adaptee
102+
}
103+

0 commit comments

Comments
 (0)