Skip to content

Commit

Permalink
refactor(Inspector): rename the methods of Inspector 🔠
Browse files Browse the repository at this point in the history
  • Loading branch information
oldratlee committed Mar 30, 2024
1 parent 0b45008 commit ac7a1ab
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 62 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ The purpose of **Inspectable Wrappers** is to provide a standard for wrapper cha
enhance the wrapper instances with the attachment storage ability
- [`WrapperAdapter`](src/main/java/io/foldright/inspectablewrappers/WrapperAdapter.java) interface is used to
adapt an existed wrapper instance to type `Wrapper` without modifying it
- The [`Inspector`](src/main/java/io/foldright/inspectablewrappers/Inspector.java) class is used to inspect the **wrapper
- The [`Inspector`](src/main/java/io/foldright/inspectablewrappers/Inspector.java) class is used to inspect the *
*wrapper
chain**

## 🌰 Usage Demo
Expand Down Expand Up @@ -125,10 +126,10 @@ public class Demo {
////////////////////////////////////////

System.out.println("Is executor lazy? " +
Inspector.isInstanceOf(executor, LazyExecutorWrapper.class));
containsInstanceOnWrapperChain(executor, LazyExecutorWrapper.class));
// print true

String busy = Inspector.getAttachment(executor, "busy");
String busy = getAttachmentFromWrapperChain(executor, "busy");
System.out.println("Is executor busy? " + busy);
// print "very, very busy!"

Expand Down Expand Up @@ -205,9 +206,9 @@ public class IntegrationDemo {
////////////////////////////////////////

System.out.println("Is executor ExistedExecutorWrapper? " +
Inspector.isInstanceOf(executor, ExistedExecutorWrapper.class));
containsInstanceOnWrapperChain(executor, ExistedExecutorWrapper.class));
// print true
String adaptAttachment = Inspector.getAttachment(executor, "adapted-existed-executor-wrapper-msg");
String adaptAttachment = getAttachmentFromWrapperChain(executor, "adapted-existed-executor-wrapper-msg");
System.out.println("Adapted existed executor wrapper msg: " + adaptAttachment);
// print "I'm an adapter of an existed executor which have nothing to do with ~inspectable~wrappers~."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
* This {@code Attachable} interface is used to be implemented by wrapper classes,
* provide the attachment storage ability.
* <p>
* Retrieves the attachment from wrapper chain the static method {@link Inspector#getAttachment(Object, Object)}.
* Retrieves the attachment from wrapper chain
* by method {@link Inspector#getAttachmentFromWrapperChain(Object, Object)}.
* <p>
* Provided {@link io.foldright.inspectablewrappers.utils.AttachableDelegate AttachableDelegate}
* as a simple delegate implementation.
Expand All @@ -17,7 +18,7 @@
* @param <V> the value type to be stored
* @author Jerry Lee (oldratlee at gmail dot com)
* @author Yang Fang (snoop dot fy at gmail dot com)
* @see Inspector#getAttachment(Object, Object)
* @see Inspector#getAttachmentFromWrapperChain(Object, Object)
* @see io.foldright.inspectablewrappers.utils.AttachableDelegate
*/
public interface Attachable<K, V> {
Expand All @@ -37,7 +38,7 @@ public interface Attachable<K, V> {
* @return return the attachment value, or {@code null} if contains no attachment for the key
* @throws NullPointerException if key argument is null
* @throws ClassCastException if the return value is not type {@code <V>}
* @see Inspector#getAttachment(Object, Object)
* @see Inspector#getAttachmentFromWrapperChain(Object, Object)
*/
@Nullable
V getAttachment(@NonNull K key);
Expand Down
59 changes: 40 additions & 19 deletions src/main/java/io/foldright/inspectablewrappers/Inspector.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,29 @@
* <h2>Common simple usages</h2>
* <ul>
* <li>Reports whether any instance on the wrapper chain matches the given type
* by static method {@link #isInstanceOf(Object, Class)}
* by static method {@link #containsInstanceOnWrapperChain(Object, Class)}
* <li>Retrieves the attachment of instance on the wrapper chain
* by static method {@link #getAttachment(Object, Object)}
* by static method {@link #getAttachmentFromWrapperChain(Object, Object)}
* </ul>
*
* <h2>Advanced usages</h2>
* <ul>
* <li>Reports whether any instance on the wrapper chain satisfy the given {@link Predicate}
* by static method {@link #inspect(Object, Predicate)}
* by static method {@link #testWrapperChain(Object, Predicate)}
* <li>Traverses the wrapper chain and applies the given {@link Function} to each instance on the wrapper chain
* by static method {@link #travel(Object, Function)}
* by static method {@link #travelWrapperChain(Object, Function)}
* </ul>
* <p>
* You can implement your own inspection logic using above advanced methods.
*
* <h2>Note about usage and methods naming</h2>
* <p>
* All method names contain the word "wrapper chain",
* so the usage code is easily recognizable as related to {@code inspectable wrappers}.
* <p>
* Because the method names are long and informative,
* it's recommended to {@code static import} these methods.
*
* @author Jerry Lee (oldratlee at gmail dot com)
* @author Zava Xu (zava dot kid at gmail dot com)
* @see Wrapper
Expand All @@ -47,19 +55,26 @@ public final class Inspector {
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap()}.
*
* @param wrapper wrapper instance/wrapper chain
* @param clazz target type
* @param <W> the type of instances that be wrapped
* @param wrapper wrapper instance/wrapper chain
* @param instanceType target type
* @param <W> the type of instances that be wrapped
* @return return {@code false} if no wrapper on the wrapper chain matches the given type,
* otherwise return {@code true}
* @throws NullPointerException if any arguments is null or any wrapper {@link Wrapper#unwrap()} returns null
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is type {@link Wrapper}
* @see Wrapper#unwrap()
* @see WrapperAdapter#adaptee()
*/
public static <W> boolean isInstanceOf(final W wrapper, final Class<?> clazz) {
public static <W> boolean containsInstanceOnWrapperChain(final W wrapper, final Class<?> instanceType) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(clazz, "clazz is null");
return inspect(wrapper, w -> clazz.isAssignableFrom(w.getClass()));
requireNonNull(instanceType, "instanceType is null");
return testWrapperChain(wrapper, w -> isInstanceOf(w, instanceType));
}

private static boolean isInstanceOf(final Object o, final Class<?> clazz) {
return clazz.isAssignableFrom(o.getClass());
}

/**
Expand All @@ -78,17 +93,19 @@ public static <W> boolean isInstanceOf(final W wrapper, final Class<?> clazz) {
* @param <V> the type of attachment value
* @return the attachment value of wrapper for given key on the wrapper chain,
* or null if the attachment is absent
* @throws NullPointerException if any arguments is null or any wrapper {@link Wrapper#unwrap()} returns null
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws ClassCastException if the return value is not type {@code <V>}
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is type {@link Wrapper}
* @see Attachable#getAttachment(Object)
*/
@Nullable
@SuppressWarnings("unchecked")
public static <W, K, V> V getAttachment(final W wrapper, final K key) {
public static <W, K, V> V getAttachmentFromWrapperChain(final W wrapper, final K key) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(key, "key is null");
return travel(wrapper, w -> {
return travelWrapperChain(wrapper, w -> {
if (w instanceof Attachable) {
V value = ((Attachable<K, V>) w).getAttachment(key);
return Optional.ofNullable(value);
Expand All @@ -109,13 +126,15 @@ public static <W, K, V> V getAttachment(final W wrapper, final K key) {
* @param <W> the type of instances that be wrapped
* @return return {@code false} if no wrapper on the wrapper chain satisfy the given {@code predicate},
* otherwise return {@code true}
* @throws NullPointerException if any arguments is null or any wrapper {@link Wrapper#unwrap()} returns null
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is type {@link Wrapper}
*/
public static <W> boolean inspect(final W wrapper, final Predicate<? super W> predicate) {
public static <W> boolean testWrapperChain(final W wrapper, final Predicate<? super W> predicate) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(predicate, "predicate is null");
return travel(wrapper, w -> {
return travelWrapperChain(wrapper, w -> {
if (predicate.test(w)) return Optional.of(true);
else return Optional.empty();
}).orElse(false);
Expand All @@ -135,12 +154,14 @@ public static <W> boolean inspect(final W wrapper, final Predicate<? super W> pr
* @param <T> the return data type of process function
* @return the first non-empty({@link Optional#empty()}) result of the process function,
* otherwise returns {@link Optional#empty()}
* @throws NullPointerException if any arguments is null or any wrapper {@link Wrapper#unwrap()} returns null
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is type {@link Wrapper}
*/
@NonNull
@SuppressWarnings("unchecked")
public static <W, T> Optional<T> travel(final W wrapper, final Function<? super W, Optional<T>> process) {
public static <W, T> Optional<T> travelWrapperChain(final W wrapper, final Function<? super W, Optional<T>> process) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(process, "process is null");

Expand Down
7 changes: 5 additions & 2 deletions src/test/java/io/foldright/demo/Demo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import java.util.concurrent.Executor;

import static io.foldright.inspectablewrappers.Inspector.containsInstanceOnWrapperChain;
import static io.foldright.inspectablewrappers.Inspector.getAttachmentFromWrapperChain;


public class Demo {
public static void main(String[] args) {
Expand All @@ -14,10 +17,10 @@ public static void main(String[] args) {
////////////////////////////////////////

System.out.println("Is executor lazy? " +
Inspector.isInstanceOf(executor, LazyExecutorWrapper.class));
containsInstanceOnWrapperChain(executor, LazyExecutorWrapper.class));
// print true

String busy = Inspector.getAttachment(executor, "busy");
String busy = getAttachmentFromWrapperChain(executor, "busy");
System.out.println("Is executor busy? " + busy);
// print "very, very busy!"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public Executor getExecutor() {

@Override
public void execute(Runnable command) {
System.out.println("I'm a adapter of an existed executor which have nothing to do with ~inspectable~wrappers~.");
System.out.println("I'm existed executor, have nothing to do with ~inspectable~wrappers~.");
executor.execute(command);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault;
import io.foldright.demo.ChattyExecutorWrapper;
import io.foldright.inspectablewrappers.Attachable;
import io.foldright.inspectablewrappers.Inspector;
import io.foldright.inspectablewrappers.WrapperAdapter;
import io.foldright.inspectablewrappers.utils.AttachableDelegate;

import javax.annotation.ParametersAreNonnullByDefault;
import java.util.concurrent.Executor;

import static io.foldright.inspectablewrappers.Inspector.containsInstanceOnWrapperChain;
import static io.foldright.inspectablewrappers.Inspector.getAttachmentFromWrapperChain;


@ParametersAreNonnullByDefault
@ReturnValuesAreNonnullByDefault
Expand All @@ -23,9 +25,9 @@ public static void main(String[] args) {
////////////////////////////////////////

System.out.println("Is executor ExistedExecutorWrapper? " +
Inspector.isInstanceOf(executor, ExistedExecutorWrapper.class));
containsInstanceOnWrapperChain(executor, ExistedExecutorWrapper.class));
// print true
String adaptAttachment = Inspector.getAttachment(executor, "adapted-existed-executor-wrapper-msg");
String adaptAttachment = getAttachmentFromWrapperChain(executor, "adapted-existed-executor-wrapper-msg");
System.out.println("Adapted existed executor wrapper msg: " + adaptAttachment);
// print "I'm an adapter of an existed executor which have nothing to do with ~inspectable~wrappers~."

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.foldright.inspectablewrappers

import io.foldright.inspectablewrappers.Inspector.containsInstanceOnWrapperChain
import io.foldright.inspectablewrappers.Inspector.getAttachmentFromWrapperChain
import io.foldright.inspectablewrappers.utils.AttachableDelegate
import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow
Expand All @@ -25,36 +27,36 @@ class WrapperAdapterTest : FunSpec({
}.let(::ChattyExecutorWrapper)

test("WrapperAdapter") {
Inspector.isInstanceOf(executorChain, ExistedExecutorWrapper::class.java).shouldBeTrue()
Inspector.isInstanceOf(executorChain, ExistedExecutorWrapperAdapter::class.java).shouldBeTrue()
Inspector.isInstanceOf(executorChain, ChattyExecutorWrapper::class.java).shouldBeTrue()
Inspector.isInstanceOf(executorChain, ExecutorService::class.java).shouldBeFalse()
containsInstanceOnWrapperChain(executorChain, ExistedExecutorWrapper::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(executorChain, ExistedExecutorWrapperAdapter::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(executorChain, ChattyExecutorWrapper::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(executorChain, ExecutorService::class.java).shouldBeFalse()

val value: String? = Inspector.getAttachment(executorChain, ADAPTED_MSG_KEY)
val value: String? = getAttachmentFromWrapperChain(executorChain, ADAPTED_MSG_KEY)
value shouldBe ADAPTED_MSG_VALUE

Inspector.getAttachment<Executor, String, String?>(executorChain, "not existed").shouldBeNull()
getAttachmentFromWrapperChain<Executor, String, String?>(executorChain, "not existed").shouldBeNull()
}

test("ClassCastException") {
shouldThrow<ClassCastException> {
val value = Inspector.getAttachment<Executor, String, Int?>(executorChain, ADAPTED_MSG_KEY)
val value = getAttachmentFromWrapperChain<Executor, String, Int?>(executorChain, ADAPTED_MSG_KEY)
fail(value.toString())
}
}

@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
test("argument null") {
shouldThrow<NullPointerException> {
Inspector.getAttachment<Executor, String, String?>(null, ADAPTED_MSG_KEY)
getAttachmentFromWrapperChain<Executor, String, String?>(null, ADAPTED_MSG_KEY)
}.message shouldBe "wrapper is null"

shouldThrow<NullPointerException> {
Inspector.getAttachment<Executor, String, String?>(executorChain, null)
getAttachmentFromWrapperChain<Executor, String, String?>(executorChain, null)
}.message shouldBe "key is null"
}

test("travel IllegalStateException - the adaptee of WrapperAdapter is a wrapper instance") {
test("travelWrapperChain IllegalStateException - the adaptee of WrapperAdapter is type wrapper") {
val chain: Executor = ChattyExecutorWrapper { runnable -> runnable.run() }
.let(::ChattyExecutorWrapperAdapter)

Expand All @@ -63,15 +65,15 @@ class WrapperAdapterTest : FunSpec({
" is type Wrapper, adapting a Wrapper to a Wrapper is unnecessary!"

shouldThrow<IllegalStateException> {
Inspector.isInstanceOf(chain, ExecutorService::class.java)
containsInstanceOnWrapperChain(chain, ExecutorService::class.java)
}.message shouldBe errMsg
// first instance is ok, not trigger the check logic yet...
Inspector.isInstanceOf(chain, Executor::class.java).shouldBeTrue()
Inspector.isInstanceOf(chain, ChattyExecutorWrapperAdapter::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(chain, Executor::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(chain, ChattyExecutorWrapperAdapter::class.java).shouldBeTrue()


shouldThrow<IllegalStateException> {
Inspector.getAttachment(chain, "k1")
getAttachmentFromWrapperChain(chain, "k1")
}.message shouldBe errMsg
}
})
Expand Down
Loading

0 comments on commit ac7a1ab

Please sign in to comment.