Skip to content

Commit 2a106cb

Browse files
committed
feat: add Wrapper.travel method, and use it in methods Wrapper.inspect/getAttachment 🛥️
1 parent 38d4b55 commit 2a106cb

File tree

1 file changed

+37
-14
lines changed

1 file changed

+37
-14
lines changed

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

+37-14
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;
@@ -68,18 +70,13 @@ static <W> boolean isInstanceOf(final W wrapper, final Class<?> clazz) {
6870
* otherwise return {@code true}
6971
* @throws NullPointerException if any arguments is null or any wrapper {{@link #unwrap()}} returns null
7072
*/
71-
@SuppressWarnings("unchecked")
7273
static <W> boolean inspect(final W wrapper, final Predicate<? super W> predicate) {
7374
requireNonNull(wrapper, "wrapper is null");
7475
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-
}
76+
return travel(wrapper, w -> {
77+
if (predicate.test(w)) return Optional.of(true);
78+
else return Optional.empty();
79+
}).orElse(false);
8380
}
8481

8582
/**
@@ -107,15 +104,41 @@ static <W> boolean inspect(final W wrapper, final Predicate<? super W> predicate
107104
static <W, K, V> V getAttachment(final W wrapper, final K key) {
108105
requireNonNull(wrapper, "wrapper is null");
109106
requireNonNull(key, "key is null");
110-
111-
Object w = wrapper;
112-
while (true) {
107+
return travel(wrapper, w -> {
113108
if (w instanceof Attachable) {
114109
V value = ((Attachable<K, V>) w).getAttachment(key);
115-
if (value != null) return value;
110+
return Optional.ofNullable(value);
111+
} else {
112+
return Optional.empty();
116113
}
114+
}).orElse(null);
115+
}
116+
117+
/**
118+
* Traverses the wrapper chain, and apply the given {@code process} function to each wrapper,
119+
* and returns the first non-empty({@link Optional#empty()}) result of the process function,
120+
* otherwise returns {@link Optional#empty()}.
121+
* <p>
122+
* The wrapper chain consists of wrapper itself, followed by the wrappers
123+
* obtained by repeatedly calling {@link #unwrap()}.
124+
*
125+
* @param wrapper wrapper instance
126+
* @param process process function
127+
* @param <W> the type of instances that be wrapped
128+
* @param <T> the return data type of process function
129+
* @return the first non-empty({@link Optional#empty()}) result of the process function,
130+
* otherwise returns {@link Optional#empty()}.
131+
*/
132+
@SuppressWarnings("unchecked")
133+
static <W, T> Optional<T> travel(final W wrapper, final Function<W, Optional<T>> process) {
134+
requireNonNull(wrapper, "wrapper is null");
135+
requireNonNull(process, "process is null");
136+
Object w = wrapper;
137+
while (true) {
138+
Optional<T> result = process.apply((W) w);
139+
if (result.isPresent()) return result;
117140

118-
if (!(w instanceof Wrapper)) return null;
141+
if (!(w instanceof Wrapper)) return Optional.empty();
119142
w = unwrapNonNull(w);
120143
}
121144
}

0 commit comments

Comments
 (0)