diff --git a/src/main/java/io/foldright/inspectablewrappers/WrapperAdapter.java b/src/main/java/io/foldright/inspectablewrappers/WrapperAdapter.java
index 674efd6..1b9e6f0 100644
--- a/src/main/java/io/foldright/inspectablewrappers/WrapperAdapter.java
+++ b/src/main/java/io/foldright/inspectablewrappers/WrapperAdapter.java
@@ -14,11 +14,16 @@
*
*
+ *
+ * Provided {@link io.foldright.inspectablewrappers.utils.WrapperAdapterUtils WrapperAdapterUtils}
+ * to creates {@link WrapperAdapter} instances of the given biz interface type
+ * by the adaptee and underlying instance without writing boilerplate code of creating a new adapter class.
*
* @param the type of instances that be wrapped
* @author Jerry Lee (oldratlee at gmail dot com)
* @author Zava Xu (zava dot kid at gmail dot com)
* @see Wrapper
+ * @see io.foldright.inspectablewrappers.utils.WrapperAdapterUtils
*/
public interface WrapperAdapter extends Wrapper {
/**
diff --git a/src/main/java/io/foldright/inspectablewrappers/utils/WrapperAdapterUtils.java b/src/main/java/io/foldright/inspectablewrappers/utils/WrapperAdapterUtils.java
new file mode 100644
index 0000000..ad9b270
--- /dev/null
+++ b/src/main/java/io/foldright/inspectablewrappers/utils/WrapperAdapterUtils.java
@@ -0,0 +1,120 @@
+package io.foldright.inspectablewrappers.utils;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import io.foldright.inspectablewrappers.Attachable;
+import io.foldright.inspectablewrappers.Wrapper;
+import io.foldright.inspectablewrappers.WrapperAdapter;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+
+import static java.util.Objects.requireNonNull;
+
+
+/**
+ * Utility class for creating {@link WrapperAdapter} instances
+ * without writing boilerplate code of creating a new adapter class.
+ *
+ * @author Jerry Lee (oldratlee at gmail dot com)
+ */
+public final class WrapperAdapterUtils {
+ /**
+ * Creates a {@link WrapperAdapter} instance of the given biz interface type
+ * by the adapted/existed wrapper instance and the underlying instance that be wrapped.
+ *
+ * @param the type of instances that be wrapped
+ * @param bizInterface the class of instances that be wrapped
+ * @param underlying the underlying instance that be wrapped, more info see {@link Wrapper#unwrap()}
+ * @param adaptee the adapted/existed wrapper instance, more info see {@link WrapperAdapter#adaptee()}
+ * @return the new {@link WrapperAdapter} instance
+ * @see Wrapper#unwrap()
+ * @see WrapperAdapter#adaptee()
+ */
+ @NonNull
+ public static T createWrapperAdapter(Class bizInterface, T underlying, T adaptee) {
+ return createWrapperAdapter0(
+ requireNonNull(bizInterface, "bizInterface is null"),
+ requireNonNull(underlying, "underlying is null"),
+ requireNonNull(adaptee, "adaptee is null"),
+ null);
+ }
+
+ /**
+ * Creates a {@link WrapperAdapter} instance of the given biz interface type with attachable interface
+ * by the adapted/existed wrapper instance, the underlying instance that be wrapped and an attachable instance.
+ *
+ * @param the type of instances that be wrapped
+ * @param bizInterface the class of instances that be wrapped
+ * @param underlying the underlying instance that be wrapped, more info see {@link Wrapper#unwrap()}
+ * @param adaptee the adapted/existed wrapper instance, more info see {@link WrapperAdapter#adaptee()}
+ * @param attachable the attachable instance, more info see {@link Attachable}
+ * @return the new {@link WrapperAdapter} instance
+ * @see Wrapper#unwrap()
+ * @see WrapperAdapter#adaptee()
+ * @see Attachable#getAttachment(Object)
+ * @see Attachable#setAttachment(Object, Object)
+ */
+ @NonNull
+ public static T createWrapperAdapter(Class bizInterface, T underlying, T adaptee, Attachable, ?> attachable) {
+ return createWrapperAdapter0(
+ requireNonNull(bizInterface, "bizInterface is null"),
+ requireNonNull(underlying, "underlying is null"),
+ requireNonNull(adaptee, "adaptee is null"),
+ requireNonNull(attachable, "attachable is null"));
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static T createWrapperAdapter0(Class bizInterface, T underlying, T adaptee, @Nullable Attachable, ?> attachable) {
+ final InvocationHandler handler = (proxy, method, args) -> {
+ if (isMethodUnwrap(method)) return underlying;
+ if (isMethodAdaptee(method)) return adaptee;
+
+ if (attachable != null && isMethodGetAttachment(method))
+ return ((Attachable) attachable).getAttachment(args[0]);
+ if (attachable != null && isMethodSetAttachment(method)) {
+ ((Attachable) attachable).setAttachment(args[0], args[1]);
+ return null;
+ }
+
+ return method.invoke(adaptee, args);
+ };
+
+ return (T) Proxy.newProxyInstance(
+ adaptee.getClass().getClassLoader(),
+ attachable == null
+ ? new Class[]{bizInterface, WrapperAdapter.class}
+ : new Class[]{bizInterface, WrapperAdapter.class, Attachable.class},
+ handler);
+ }
+
+ private static boolean isMethodAdaptee(Method method) {
+ return checkMethod(method, "adaptee");
+ }
+
+ private static boolean isMethodUnwrap(Method method) {
+ return checkMethod(method, "unwrap");
+ }
+
+ private static boolean isMethodGetAttachment(Method method) {
+ return checkMethod(method, "getAttachment", Object.class);
+ }
+
+ private static boolean isMethodSetAttachment(Method method) {
+ return checkMethod(method, "setAttachment", Object.class, Object.class);
+ }
+
+ private static boolean checkMethod(Method method, String methodName, Class>... parameterTypes) {
+ return method.getName().equals(methodName)
+ && method.getParameterCount() == parameterTypes.length
+ && Arrays.equals(method.getParameterTypes(), parameterTypes);
+ }
+
+ /**
+ * NO need to create instance at all
+ */
+ private WrapperAdapterUtils() {
+ }
+}
diff --git a/src/test/java/io/foldright/inspectablewrappers/WrapperAdapterTest.kt b/src/test/java/io/foldright/inspectablewrappers/WrapperAdapterTest.kt
index 9cd2c9b..5c18027 100644
--- a/src/test/java/io/foldright/inspectablewrappers/WrapperAdapterTest.kt
+++ b/src/test/java/io/foldright/inspectablewrappers/WrapperAdapterTest.kt
@@ -3,6 +3,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.foldright.inspectablewrappers.utils.WrapperAdapterUtils.createWrapperAdapter
import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
@@ -36,6 +37,58 @@ class WrapperAdapterTest : FunSpec({
getAttachmentFromWrapperChain(executorChain, "not existed").shouldBeNull()
}
+ @Suppress("UNCHECKED_CAST")
+ test("WrapperAdapter - createWrapperAdapter with Attachable") {
+ val chain: Executor = Executor { runnable -> runnable.run() }
+ .let {
+ val existed = ExistedExecutorWrapper(it)
+ val adapter = createWrapperAdapter(
+ Executor::class.java,
+ it,
+ existed,
+ AttachableDelegate(),
+ )
+ val attachable = adapter as Attachable
+ attachable.setAttachment(ADAPTED_MSG_KEY, ADAPTED_MSG_VALUE)
+ attachable.getAttachment(ADAPTED_MSG_KEY) shouldBe ADAPTED_MSG_VALUE
+ adapter
+ }
+ .let(::ChattyExecutorWrapper)
+
+ containsInstanceOnWrapperChain(chain, ExistedExecutorWrapper::class.java).shouldBeTrue()
+ containsInstanceOnWrapperChain(chain, ChattyExecutorWrapper::class.java).shouldBeTrue()
+ containsInstanceOnWrapperChain(chain, ExecutorService::class.java).shouldBeFalse()
+
+ val value: String? = getAttachmentFromWrapperChain(chain, ADAPTED_MSG_KEY)
+ value shouldBe ADAPTED_MSG_VALUE
+
+ getAttachmentFromWrapperChain(chain, "not existed").shouldBeNull()
+
+ // testing the proxy invocation
+ chain.execute { println("I'm working.") }
+ }
+
+ test("WrapperAdapter - createWrapperAdapter without Attachable") {
+ val chain: Executor = Executor { runnable -> runnable.run() }
+ .let {
+ createWrapperAdapter(
+ Executor::class.java,
+ it,
+ ExistedExecutorWrapper(it),
+ )
+ }
+ .let(::ChattyExecutorWrapper)
+
+ containsInstanceOnWrapperChain(chain, ExistedExecutorWrapper::class.java).shouldBeTrue()
+ containsInstanceOnWrapperChain(chain, ChattyExecutorWrapper::class.java).shouldBeTrue()
+ containsInstanceOnWrapperChain(chain, ExecutorService::class.java).shouldBeFalse()
+
+ getAttachmentFromWrapperChain(chain, "not existed").shouldBeNull()
+
+ // testing the proxy invocation
+ chain.execute { println("I'm working.") }
+ }
+
test("ClassCastException") {
shouldThrow {
val value = getAttachmentFromWrapperChain(executorChain, ADAPTED_MSG_KEY)