Skip to content

Commit

Permalink
feat: check WrapperAdapter#adaptee contract when `createWrapperAdap…
Browse files Browse the repository at this point in the history
…ter` ‍⚖️
  • Loading branch information
oldratlee committed Apr 3, 2024
1 parent 4c30534 commit ed84a49
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public static <T> T createWrapperAdapter(
@SuppressWarnings({"unchecked", "rawtypes"})
private static <T> T createWrapperAdapter0(
Class<T> bizInterface, T underlying, T adaptee, @Nullable Attachable<?, ?> attachable) {
checkTypeRequirements(bizInterface, underlying, adaptee);

final InvocationHandler handler = (proxy, method, args) -> {
if (UNWRAP.sameSignatureAs(method)) return underlying;
if (ADAPTEE.sameSignatureAs(method)) return adaptee;
Expand Down Expand Up @@ -101,6 +103,32 @@ private static <T> T createWrapperAdapter0(
handler);
}

private static <T> void checkTypeRequirements(Class<T> bizInterface, T underlying, T adaptee) {
if (!bizInterface.isInterface()) {
throw new IllegalArgumentException("bizInterface(" + bizInterface.getName() + ") is not an interface");
}
if (bizInterface == Wrapper.class
|| bizInterface == WrapperAdapter.class
|| bizInterface == Attachable.class) {
throw new IllegalArgumentException(bizInterface.getName() +
" is auto implemented by proxy, not a valid biz interface");
}

if (!bizInterface.isAssignableFrom(underlying.getClass())) {
throw new IllegalArgumentException("underlying(" + underlying.getClass().getName() +
") is not a " + bizInterface.getName());
}
if (!bizInterface.isAssignableFrom(adaptee.getClass())) {
throw new IllegalArgumentException("adaptee(" + adaptee.getClass().getName() +
") is not a " + bizInterface.getName());
}

if (adaptee instanceof Wrapper) {
throw new IllegalArgumentException("adaptee(" + adaptee.getClass().getName() +
") is type Wrapper, adapting a Wrapper to a Wrapper is unnecessary!");
}
}

/**
* NO need to create instance at all
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,19 @@ 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
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService


private const val ADAPTED_MSG_KEY = "adapted-existed-executor-wrapper-msg"
private const val ADAPTED_MSG_VALUE =
const val ADAPTED_MSG_KEY = "adapted-existed-executor-wrapper-msg"
const val ADAPTED_MSG_VALUE =
"I'm a adapter of an existed executor which have nothing to do with ~inspectable~wrappers~."

class WrapperAdapterTest : FunSpec({
Expand All @@ -37,62 +35,6 @@ 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,
it,
existed,
AttachableDelegate<String, String>(),
)
adapter.toString() shouldStartWith "[WrapperAdapterProxy created by WrapperAdapterUtils] "

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,
it,
ExistedExecutorWrapper(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()

// testing the proxy invocation
chain.execute { println("I'm working.") }
}

test("ClassCastException") {
shouldThrow<ClassCastException> {
val value = getAttachmentFromWrapperChain<Executor, String, Int?>(executorChain, ADAPTED_MSG_KEY)
Expand Down Expand Up @@ -132,7 +74,6 @@ class WrapperAdapterTest : FunSpec({
}
})


/**
* Adaption an existed wrapper([ExistedExecutorWrapper]) without modifying it.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package io.foldright.inspectablewrappers.utils

import io.foldright.inspectablewrappers.*
import io.foldright.inspectablewrappers.utils.WrapperAdapterUtils.createWrapperAdapter
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ThreadPoolExecutor

class WrapperAdapterUtilsTest : FunSpec({
@Suppress("UNCHECKED_CAST")
test("createWrapperAdapter with Attachable") {
val chain: Executor = Executor { runnable -> runnable.run() }
.let {
val existed = ExistedExecutorWrapper(it)
val adapter = createWrapperAdapter(
Executor::class.java,
it,
existed,
AttachableDelegate<String, String>(),
)
adapter.toString() shouldStartWith "[WrapperAdapterProxy created by WrapperAdapterUtils] "

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)

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

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

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

// testing the proxy invocation
chain.execute { println("I'm working.") }
}

test("createWrapperAdapter without Attachable") {
val chain: Executor = Executor { runnable -> runnable.run() }
.let {
createWrapperAdapter(
Executor::class.java,
it,
ExistedExecutorWrapper(it),
)
}
.let(::ChattyExecutorWrapper)

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

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

// testing the proxy invocation
chain.execute { println("I'm working.") }
}

test("adaptee contract") {
val executor = Executor { it.run() }
val wrongAdaptee = WrongWrapperAdapter(executor)
shouldThrow<IllegalArgumentException> {
createWrapperAdapter(Executor::class.java, executor, wrongAdaptee)
}.message shouldBe "adaptee(io.foldright.inspectablewrappers.utils.WrongWrapperAdapter) is type Wrapper," +
" adapting a Wrapper to a Wrapper is unnecessary!"
}

@Suppress("UNCHECKED_CAST")
test("checkTypeRequirements - bizInterface") {
shouldThrow<IllegalArgumentException> {
createWrapperAdapter(
ThreadPoolExecutor::class.java,
Executors.newCachedThreadPool() as ThreadPoolExecutor,
Executors.newCachedThreadPool() as ThreadPoolExecutor
)
}.message shouldBe "bizInterface(java.util.concurrent.ThreadPoolExecutor) is not an interface"

shouldThrow<IllegalArgumentException> {
createWrapperAdapter(Wrapper::class.java as Class<Any>, "", "")
}.message shouldBe "io.foldright.inspectablewrappers.Wrapper is auto implemented by proxy, not a valid biz interface"
shouldThrow<IllegalArgumentException> {
createWrapperAdapter(WrapperAdapter::class.java as Class<Any>, "", "")
}.message shouldBe "io.foldright.inspectablewrappers.WrapperAdapter is auto implemented by proxy, not a valid biz interface"
shouldThrow<IllegalArgumentException> {
createWrapperAdapter(Attachable::class.java as Class<Any>, "", "")
}.message shouldBe "io.foldright.inspectablewrappers.Attachable is auto implemented by proxy, not a valid biz interface"
}

@Suppress("UNCHECKED_CAST")
test("checkTypeRequirements - underlying/adaptee") {
shouldThrow<IllegalArgumentException> {
createWrapperAdapter(
Executor::class.java as Class<Any>,
"", ""
)
}.message shouldBe "underlying(java.lang.String) is not a java.util.concurrent.Executor"

shouldThrow<IllegalArgumentException> {
createWrapperAdapter(
Executor::class.java as Class<Any>,
Executor { it.run() }, ""
)
}.message shouldBe "adaptee(java.lang.String) is not a java.util.concurrent.Executor"
}
})

class WrongWrapperAdapter(private val executor: Executor) : WrapperAdapter<Executor>, Executor by executor {
override fun unwrap(): Executor = executor
override fun adaptee(): Executor = executor
}

0 comments on commit ed84a49

Please sign in to comment.