Skip to content

Commit

Permalink
feat: add WrapperAdapterUtils 🤖
Browse files Browse the repository at this point in the history
  • Loading branch information
oldratlee committed Mar 30, 2024
1 parent 1f00156 commit 67c362a
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
* <p>
* <img src="https://github.com/foldright/inspectable-wrappers/assets/1063891/31f9e604-5864-4312-b280-cc732e84df07"
* width="400" alt="Wrapper Chain contains WrapperAdapter">
* <p>
* 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 <T> 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<T> extends Wrapper<T> {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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 java.util.Objects;


/**
* Utility class for creating {@link WrapperAdapter} instances.
*
* @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 bizInterface the class of instances that be wrapped
* @param adaptee the adapted/existed wrapper instance, more info see {@link WrapperAdapter#adaptee()}
* @param underlying the underlying instance that be wrapped, more info see {@link Wrapper#unwrap()}
* @param <T> the type of instances that be wrapped
* @return the {@link WrapperAdapter} instance
* @see Wrapper#unwrap()
* @see WrapperAdapter#adaptee()
*/
@NonNull
public static <T> T createWrapperAdapter(Class<T> bizInterface, T adaptee, T underlying) {
return createWrapperAdapter0(
Objects.requireNonNull(bizInterface, "bizInterface is null"),
Objects.requireNonNull(adaptee, "adaptee is null"),
Objects.requireNonNull(underlying, "underlying 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 bizInterface the class of instances that be wrapped
* @param adaptee the adapted/existed wrapper instance, more info see {@link WrapperAdapter#adaptee()}
* @param underlying the underlying instance that be wrapped, more info see {@link Wrapper#unwrap()}
* @param attachable the attachable instance, more info see {@link Attachable}
* @param <T> the type of instances that be wrapped
* @return the {@link WrapperAdapter} instance with attachable ability
* @see Wrapper#unwrap()
* @see WrapperAdapter#adaptee()
* @see Attachable#getAttachment(Object)
* @see Attachable#setAttachment(Object, Object)
*/
@NonNull
public static <T> T createWrapperAdapter(Class<T> bizInterface, T adaptee, T underlying, Attachable<?, ?> attachable) {
return createWrapperAdapter0(
Objects.requireNonNull(bizInterface, "bizInterface is null"),
Objects.requireNonNull(adaptee, "adaptee is null"),
Objects.requireNonNull(underlying, "underlying is null"),
Objects.requireNonNull(attachable, "attachable is null"));
}

@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> T createWrapperAdapter0(Class<T> bizInterface, T adaptee, T underlying, @Nullable Attachable<?, ?> attachable) {
final InvocationHandler handler = (proxy, method, args) -> {
if (isMethodAdaptee(method)) return adaptee;
if (isMethodUnwrap(method)) return underlying;

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() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,6 +37,55 @@ class WrapperAdapterTest : FunSpec({
getAttachmentFromWrapperChain<Executor, String, String?>(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,
existed,
it,
AttachableDelegate<String, String>()
)
val attachable = adapter as Attachable<String, String>
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<Executor, String, String?>(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,
ExistedExecutorWrapper(it),
it
)
}
.let(::ChattyExecutorWrapper)

containsInstanceOnWrapperChain(chain, ExistedExecutorWrapper::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(chain, ChattyExecutorWrapper::class.java).shouldBeTrue()
containsInstanceOnWrapperChain(chain, ExecutorService::class.java).shouldBeFalse()

getAttachmentFromWrapperChain<Executor, String, String?>(chain, "not existed").shouldBeNull()
}

test("ClassCastException") {
shouldThrow<ClassCastException> {
val value = getAttachmentFromWrapperChain<Executor, String, Int?>(executorChain, ADAPTED_MSG_KEY)
Expand Down

0 comments on commit 67c362a

Please sign in to comment.