Skip to content

Commit 86eb3d2

Browse files
committed
feat(LazyMaker): 新增 SupplierLazy 工具以及对应的测试单元
1 parent 6cd7dcd commit 86eb3d2

File tree

2 files changed

+376
-0
lines changed

2 files changed

+376
-0
lines changed

common-util/src/main/kotlin/taboolib/common/util/LazyMaker.kt

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package taboolib.common.util
22

33
import com.google.common.cache.Cache
44
import com.google.common.cache.CacheBuilder
5+
import java.util.Optional
6+
import java.util.concurrent.ConcurrentHashMap
57

68
/**
79
* 声明一个线程不安全的延迟加载对象
@@ -27,6 +29,52 @@ fun <T> resettableLazy(vararg groups: String, synchronized: Boolean = false, ini
2729
}
2830
}
2931

32+
/**
33+
* 声明一个需要传入上下文对象才能初始化的延迟加载对象
34+
*
35+
* @param C 上下文类型
36+
* @param T 值类型
37+
* @param typeIsolation 是否进行类型隔离,如果为 true,传入不同类型的 context 时会重新初始化
38+
* @param initializer 初始化函数,接收上下文对象并返回值
39+
*/
40+
fun <C, T> supplierLazy(typeIsolation: Boolean = false, initializer: (C) -> T): SupplierLazy<C, T> {
41+
return if (typeIsolation) {
42+
SupplierLazyWithTypeIsolationImpl(initializer)
43+
} else {
44+
SupplierLazyImpl(initializer)
45+
}
46+
}
47+
48+
/**
49+
* 包装上下文对象
50+
* 在 [SupplierLazy] 中使用时,当需要携带额外数据时使用
51+
*/
52+
data class WrappedContext<C: Any, E: Any>(val context: C, val extra: E)
53+
54+
/**
55+
* 需要传入上下文对象的延迟加载接口
56+
*/
57+
interface SupplierLazy<C, T> {
58+
59+
/**
60+
* 传入上下文对象获取值
61+
*
62+
* @param context 上下文对象
63+
* @return 初始化后的值
64+
*/
65+
operator fun get(context: C): T
66+
67+
/**
68+
* 检查是否已初始化
69+
*/
70+
fun isInitialized(): Boolean
71+
72+
/**
73+
* 重置状态
74+
*/
75+
fun reset()
76+
}
77+
3078
abstract class ResettableLazy<T>(vararg val groups: String) : Lazy<T> {
3179

3280
abstract fun reset()
@@ -106,4 +154,47 @@ private class ResettableSynchronizedLazyImpl<T>(vararg groups: String, initializ
106154
override fun toString() = if (isInitialized()) value.toString() else "Lazy(${groups.joinToString()}) value not initialized yet."
107155
}
108156

157+
private class SupplierLazyImpl<C, T>(private val initializer: (C) -> T) : SupplierLazy<C, T> {
158+
159+
private var localValue: Any? = UninitializedValue
160+
161+
@Suppress("UNCHECKED_CAST")
162+
override fun get(context: C): T {
163+
if (localValue === UninitializedValue) {
164+
localValue = initializer(context)
165+
}
166+
return localValue as T
167+
}
168+
169+
override fun isInitialized() = localValue !== UninitializedValue
170+
171+
override fun reset() {
172+
localValue = UninitializedValue
173+
}
174+
175+
override fun toString() = if (isInitialized()) localValue.toString() else "SupplierLazy value not initialized yet."
176+
}
177+
178+
private class SupplierLazyWithTypeIsolationImpl<C, T>(private val initializer: (C) -> T) : SupplierLazy<C, T> {
179+
180+
private val valueMap = ConcurrentHashMap<Class<*>, Optional<T>>()
181+
182+
@Suppress("UNCHECKED_CAST")
183+
override fun get(context: C): T {
184+
val contextClass = if (context is WrappedContext<*, *>) context.context::class.java else context!!::class.java
185+
val optional = valueMap.computeIfAbsent(contextClass) {
186+
Optional.ofNullable(initializer(context)) as Optional<T>
187+
}
188+
return optional.orElse(null as T)
189+
}
190+
191+
override fun isInitialized() = valueMap.isNotEmpty()
192+
193+
override fun reset() {
194+
valueMap.clear()
195+
}
196+
197+
override fun toString() = if (isInitialized()) "SupplierLazy(typeIsolation) with ${valueMap.size} type(s) initialized" else "SupplierLazy(typeIsolation) value not initialized yet."
198+
}
199+
109200
internal object UninitializedValue
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package taboolib.common.util
2+
3+
import org.junit.jupiter.api.Test
4+
import org.junit.jupiter.api.Assertions.*
5+
6+
class SupplierLazyTest {
7+
8+
@Test
9+
fun `test basic supplier lazy initialization`() {
10+
var initCount = 0
11+
val lazy = supplierLazy<String, String> { context ->
12+
initCount++
13+
"Hello $context"
14+
}
15+
16+
assertFalse(lazy.isInitialized())
17+
18+
val result1 = lazy["World"]
19+
assertEquals("Hello World", result1)
20+
assertEquals(1, initCount)
21+
assertTrue(lazy.isInitialized())
22+
23+
// Should return cached value, not reinitialize
24+
val result2 = lazy["Different"]
25+
assertEquals("Hello World", result2)
26+
assertEquals(1, initCount)
27+
}
28+
29+
@Test
30+
fun `test supplier lazy with null value`() {
31+
val lazy = supplierLazy<String, String?> { null }
32+
33+
assertFalse(lazy.isInitialized())
34+
val result = lazy["test"]
35+
assertNull(result)
36+
assertTrue(lazy.isInitialized())
37+
}
38+
39+
@Test
40+
fun `test supplier lazy reset`() {
41+
var initCount = 0
42+
val lazy = supplierLazy<Int, String> { context ->
43+
initCount++
44+
"Count: $context"
45+
}
46+
47+
lazy[1]
48+
assertEquals(1, initCount)
49+
assertTrue(lazy.isInitialized())
50+
51+
lazy.reset()
52+
assertFalse(lazy.isInitialized())
53+
54+
lazy[2]
55+
assertEquals(2, initCount)
56+
assertTrue(lazy.isInitialized())
57+
}
58+
59+
@Test
60+
fun `test supplier lazy with type isolation disabled`() {
61+
var initCount = 0
62+
val lazy = supplierLazy<Any, String>(typeIsolation = false) { context ->
63+
initCount++
64+
"Value: ${context::class.simpleName}"
65+
}
66+
67+
val result1 = lazy["String"]
68+
assertEquals("Value: String", result1)
69+
assertEquals(1, initCount)
70+
71+
// Different type but should return cached value
72+
val result2 = lazy[123]
73+
assertEquals("Value: String", result2)
74+
assertEquals(1, initCount)
75+
}
76+
77+
@Test
78+
fun `test supplier lazy with type isolation enabled`() {
79+
var initCount = 0
80+
val lazy = supplierLazy<Any, String>(typeIsolation = true) { context ->
81+
initCount++
82+
"Value: ${context::class.simpleName}-$context"
83+
}
84+
85+
assertFalse(lazy.isInitialized())
86+
87+
val result1 = lazy["String"]
88+
assertEquals("Value: String-String", result1)
89+
assertEquals(1, initCount)
90+
assertTrue(lazy.isInitialized())
91+
92+
// Different type should reinitialize
93+
val result2 = lazy[123]
94+
assertEquals("Value: Int-123", result2)
95+
assertEquals(2, initCount)
96+
97+
// Same type as first should return cached value
98+
val result3 = lazy["Different"]
99+
assertEquals("Value: String-String", result3)
100+
assertEquals(2, initCount)
101+
102+
// Same type as second should return cached value
103+
val result4 = lazy[456]
104+
assertEquals("Value: Int-123", result4)
105+
assertEquals(2, initCount)
106+
}
107+
108+
@Test
109+
fun `test supplier lazy with type isolation and null values`() {
110+
var initCount = 0
111+
val lazy = supplierLazy<Any, String?>(typeIsolation = true) { context ->
112+
initCount++
113+
when (context) {
114+
is String -> "String: $context"
115+
is Int -> null
116+
else -> "Other: $context"
117+
}
118+
}
119+
120+
val result1 = lazy["test"]
121+
assertEquals("String: test", result1)
122+
assertEquals(1, initCount)
123+
124+
val result2 = lazy[42]
125+
assertNull(result2)
126+
assertEquals(2, initCount)
127+
128+
val result3 = lazy[99]
129+
assertNull(result3)
130+
assertEquals(2, initCount) // Should use cached null value
131+
}
132+
133+
@Test
134+
fun `test supplier lazy with type isolation reset`() {
135+
var initCount = 0
136+
val lazy = supplierLazy<Any, String>(typeIsolation = true) { context ->
137+
initCount++
138+
"Init: $initCount"
139+
}
140+
141+
lazy["String"]
142+
lazy[123]
143+
assertEquals(2, initCount)
144+
assertTrue(lazy.isInitialized())
145+
146+
lazy.reset()
147+
assertFalse(lazy.isInitialized())
148+
149+
lazy["NewString"]
150+
lazy[456]
151+
assertEquals(4, initCount)
152+
}
153+
154+
@Test
155+
fun `test supplier lazy with custom classes`() {
156+
data class Person(val name: String)
157+
data class Animal(val species: String)
158+
159+
val lazy = supplierLazy<Any, String>(typeIsolation = true) { context ->
160+
when (context) {
161+
is Person -> "Person: ${context.name}"
162+
is Animal -> "Animal: ${context.species}"
163+
else -> "Unknown"
164+
}
165+
}
166+
167+
val result1 = lazy[Person("Alice")]
168+
assertEquals("Person: Alice", result1)
169+
170+
val result2 = lazy[Animal("Dog")]
171+
assertEquals("Animal: Dog", result2)
172+
173+
// Same type should return cached
174+
val result3 = lazy[Person("Bob")]
175+
assertEquals("Person: Alice", result3)
176+
177+
val result4 = lazy[Animal("Cat")]
178+
assertEquals("Animal: Dog", result4)
179+
}
180+
181+
@Test
182+
fun `test supplier lazy toString`() {
183+
val lazy1 = supplierLazy<String, Int> { it.length }
184+
assertTrue(lazy1.toString().contains("not initialized"))
185+
186+
lazy1["test"]
187+
assertFalse(lazy1.toString().contains("not initialized"))
188+
189+
val lazy2 = supplierLazy<String, Int>(typeIsolation = true) { it.length }
190+
assertTrue(lazy2.toString().contains("not initialized"))
191+
192+
lazy2["test"]
193+
assertTrue(lazy2.toString().contains("typeIsolation"))
194+
assertTrue(lazy2.toString().contains("1 type(s)"))
195+
}
196+
197+
@Test
198+
fun `test wrapped context basic isolation`() {
199+
var initCount = 0
200+
val lazy = supplierLazy<WrappedContext<Any, String>, String>(typeIsolation = true) { ctx ->
201+
initCount++
202+
"${ctx.context}:${ctx.extra}"
203+
}
204+
205+
val c1: WrappedContext<Any, String> = WrappedContext("user" as Any, "a")
206+
val c2: WrappedContext<Any, String> = WrappedContext("user" as Any, "b")
207+
val c3: WrappedContext<Any, String> = WrappedContext(123 as Any, "x")
208+
209+
// 同一个 context 类型(String),虽然 extra 不同,但因为按 context.class 做隔离,只初始化一次
210+
val r1 = lazy[c1]
211+
val r2 = lazy[c2]
212+
assertEquals("user:a", r1)
213+
assertEquals("user:a", r2)
214+
assertEquals(1, initCount)
215+
216+
// 不同 context 类型(Int),会使用另一份缓存
217+
val r3 = lazy[c3]
218+
assertEquals("123:x", r3)
219+
assertEquals(2, initCount)
220+
}
221+
222+
@Test
223+
fun `test wrapped context mixed with plain context`() {
224+
var initCount = 0
225+
val lazy = supplierLazy<Any, String>(typeIsolation = true) { ctx ->
226+
initCount++
227+
when (ctx) {
228+
is WrappedContext<*, *> -> "wrapped:${ctx.context}:${ctx.extra}"
229+
else -> "plain:$ctx"
230+
}
231+
}
232+
233+
val plain: Any = "user"
234+
val wrapped1: Any = WrappedContext("user", "a")
235+
val wrapped2: Any = WrappedContext("user", "b")
236+
237+
// 第一次:普通 String 上下文
238+
val rPlain1 = lazy[plain]
239+
assertEquals("plain:user", rPlain1)
240+
assertEquals(1, initCount)
241+
242+
// 第二次:WrappedContext,同样的 context 类型 String,但因为 SupplierLazyWithTypeIsolationImpl
243+
// 对 WrappedContext 使用 context.context::class.java 作为 key,会与 plain 的 String 类型共用缓存
244+
val rWrapped1 = lazy[wrapped1]
245+
assertEquals("plain:user", rWrapped1)
246+
assertEquals(1, initCount)
247+
248+
// 第三次:另一个 WrappedContext,仍然命中同一个 String 类型缓存
249+
val rWrapped2 = lazy[wrapped2]
250+
assertEquals("plain:user", rWrapped2)
251+
assertEquals(1, initCount)
252+
253+
// 验证 Int 类型仍然是独立的缓存 key
254+
val rInt1 = lazy[123 as Any]
255+
assertEquals("plain:123", rInt1)
256+
assertEquals(2, initCount)
257+
}
258+
259+
@Test
260+
fun `test wrapped context reset isolation map`() {
261+
var initCount = 0
262+
val lazy = supplierLazy<WrappedContext<Any, String>, String>(typeIsolation = true) { ctx ->
263+
initCount++
264+
"${ctx.context}:${ctx.extra}:$initCount"
265+
}
266+
267+
val sCtx: WrappedContext<Any, String> = WrappedContext("user" as Any, "a")
268+
val iCtx: WrappedContext<Any, String> = WrappedContext(1 as Any, "x")
269+
270+
val r1 = lazy[sCtx]
271+
val r2 = lazy[iCtx]
272+
assertEquals("user:a:1", r1)
273+
assertEquals("1:x:2", r2)
274+
assertEquals(2, initCount)
275+
276+
lazy.reset()
277+
assertFalse(lazy.isInitialized())
278+
279+
val r3 = lazy[sCtx]
280+
val r4 = lazy[iCtx]
281+
assertEquals("user:a:3", r3)
282+
assertEquals("1:x:4", r4)
283+
assertEquals(4, initCount)
284+
}
285+
}

0 commit comments

Comments
 (0)