1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau.junit.bct;
18
19 import static org.apache.juneau.commons.utils.CollectionUtils.*;
20 import static org.apache.juneau.commons.utils.Utils.*;
21 import static org.apache.juneau.junit.bct.BctAssertions.*;
22 import static org.junit.jupiter.api.Assertions.*;
23
24 import java.util.*;
25 import java.util.concurrent.*;
26 import java.util.function.*;
27
28 import org.apache.juneau.*;
29 import org.junit.jupiter.api.*;
30
31
32
33
34
35
36
37 class Swapper_Test extends TestBase {
38
39
40
41
42
43 @Nested
44 class A_functionalInterfaceCompliance extends TestBase {
45
46 @SuppressWarnings("cast")
47 @Test
48 void a01_functionalInterfaceContract() {
49
50 Swapper<String> swapper = (converter, str) -> "SWAPPED:" + str;
51
52 assertNotNull(swapper);
53 assertTrue(swapper instanceof BiFunction);
54 assertTrue(swapper instanceof Swapper);
55 }
56
57 @Test
58 void a02_lambdaExpressionCompatibility() {
59
60 Swapper<Integer> lambda = (converter, num) -> num * 2;
61
62 var converter = BasicBeanConverter.DEFAULT;
63 var result = lambda.apply(converter, 21);
64
65 assertEquals(42, result);
66 }
67
68 @Test
69 void a03_methodReferenceCompatibility() {
70
71 Swapper<String> methodRef = SwapperMethods::addPrefix;
72
73 var converter = BasicBeanConverter.DEFAULT;
74 var result = methodRef.apply(converter, "test");
75
76 assertEquals("PREFIX:test", result);
77 }
78
79 @Test
80 void a04_biFunctionInheritance() {
81
82 Swapper<String> swapper = (converter, str) -> str.toUpperCase();
83
84
85 var converter = BasicBeanConverter.DEFAULT;
86 var result = swapper.apply(converter, "test");
87 assertEquals("TEST", result);
88 }
89 }
90
91
92
93
94
95 @Nested
96 class B_lambdaComposition extends TestBase {
97
98 @Test
99 void b01_andThenComposition() {
100 Swapper<String> base = (converter, str) -> str.toLowerCase();
101 Function<Object, String> postProcessor = obj -> "[" + obj.toString() + "]";
102
103 var composed = base.andThen(postProcessor);
104
105 var converter = BasicBeanConverter.DEFAULT;
106 var result = composed.apply(converter, "TEST");
107
108 assertEquals("[test]", result);
109 }
110
111 @Test
112 void b02_chainedSwapping() {
113
114 Swapper<String> upperCase = (converter, str) -> str.toUpperCase();
115 Swapper<String> prefixed = (converter, str) -> "PROCESSED:" + str;
116
117 var converter = BasicBeanConverter.DEFAULT;
118
119 var upperResult = upperCase.apply(converter, "test");
120 assertEquals("TEST", upperResult);
121
122 var prefixedResult = prefixed.apply(converter, "value");
123 assertEquals("PROCESSED:value", prefixedResult);
124 }
125 }
126
127
128
129
130
131 @Nested
132 class C_edgeCases extends TestBase {
133
134 @Test
135 void c01_nullInputHandling() {
136 Swapper<String> nullSafe = (converter, str) -> {
137 if (str == null) return "NULL_INPUT";
138 return str;
139 };
140
141 var converter = BasicBeanConverter.DEFAULT;
142 var result = nullSafe.apply(converter, null);
143
144 assertEquals("NULL_INPUT", result);
145 }
146
147 @Test
148 void c02_nullOutputHandling() {
149 Swapper<String> nullReturning = (converter, str) -> {
150 if ("return_null".equals(str)) return null;
151 return str;
152 };
153
154 var converter = BasicBeanConverter.DEFAULT;
155
156 assertNull(nullReturning.apply(converter, "return_null"));
157 assertEquals("keep", nullReturning.apply(converter, "keep"));
158 }
159
160 @Test
161 void c03_exceptionHandling() {
162 Swapper<String> throwing = (converter, str) -> {
163 if ("ERROR".equals(str)) {
164 throw new RuntimeException("Intentional test exception");
165 }
166 return str;
167 };
168
169 var converter = BasicBeanConverter.DEFAULT;
170
171
172 var normalResult = throwing.apply(converter, "normal");
173 assertEquals("normal", normalResult);
174
175
176 assertThrows(RuntimeException.class, () -> throwing.apply(converter, "ERROR"));
177 }
178
179 @Test
180 void c04_typeTransformation() {
181 Swapper<Number> numberSwapper = (converter, num) -> {
182 if (num instanceof Integer) return num.doubleValue();
183 if (num instanceof Double) return num.intValue();
184 return num;
185 };
186
187 var converter = BasicBeanConverter.DEFAULT;
188
189 assertEquals(42.0, numberSwapper.apply(converter, 42));
190 assertEquals(3, numberSwapper.apply(converter, 3.14));
191 assertEquals(123L, numberSwapper.apply(converter, 123L));
192 }
193 }
194
195
196
197
198
199 @Nested
200 class D_typeSpecific extends TestBase {
201
202 @Test
203 void d01_optionalSwapping() {
204 Swapper<Optional<String>> optionalSwapper = (converter, opt) -> opt.orElse("EMPTY");
205
206 var converter = BasicBeanConverter.DEFAULT;
207
208 assertEquals("value", optionalSwapper.apply(converter, opt("value")));
209 assertEquals("EMPTY", optionalSwapper.apply(converter, opte()));
210 }
211
212 @Test
213 void d02_collectionSwapping() {
214 Swapper<Collection<?>> collectionSwapper = (converter, coll) -> {
215 if (coll.isEmpty()) return "EMPTY_COLLECTION";
216 return "COLLECTION[" + coll.size() + "]:" + coll.iterator().next();
217 };
218
219 var converter = BasicBeanConverter.DEFAULT;
220
221 assertEquals("EMPTY_COLLECTION", collectionSwapper.apply(converter, l()));
222 assertEquals("COLLECTION[3]:a", collectionSwapper.apply(converter, l("a", "b", "c")));
223 }
224
225 @Test
226 void d03_supplierSwapping() {
227 Swapper<Supplier<?>> supplierSwapper = (converter, supplier) -> {
228 try {
229 return supplier.get();
230 } catch (Exception e) {
231 return "SUPPLIER_ERROR:" + e.getMessage();
232 }
233 };
234
235 var converter = BasicBeanConverter.DEFAULT;
236
237 assertEquals("success", supplierSwapper.apply(converter, () -> "success"));
238 assertEquals("SUPPLIER_ERROR:test error",
239 supplierSwapper.apply(converter, () -> { throw new RuntimeException("test error"); }));
240 }
241
242 @Test
243 void d04_futureSwapping() {
244 Swapper<CompletableFuture<?>> futureSwapper = (converter, future) -> {
245 try {
246 if (future.isDone()) {
247 return future.get();
248 }
249 return "<pending>";
250 } catch (Exception e) {
251 return "<error:" + e.getMessage() + ">";
252 }
253 };
254
255 var converter = BasicBeanConverter.DEFAULT;
256
257
258 var completedFuture = CompletableFuture.completedFuture("done");
259 assertEquals("done", futureSwapper.apply(converter, completedFuture));
260
261
262 var pendingFuture = new CompletableFuture<String>();
263 assertEquals("<pending>", futureSwapper.apply(converter, pendingFuture));
264
265
266 var failedFuture = new CompletableFuture<String>();
267 failedFuture.completeExceptionally(new RuntimeException("failed"));
268 var result = (String) futureSwapper.apply(converter, failedFuture);
269 assertTrue(result.startsWith("<error:"));
270 assertTrue(result.contains("failed"));
271 }
272
273 @Test
274 void d05_customObjectSwapping() {
275 Swapper<TestWrapper> wrapperSwapper = (converter, wrapper) -> wrapper.getValue();
276
277 var converter = BasicBeanConverter.DEFAULT;
278 var wrapper = new TestWrapper("inner_value");
279 var result = wrapperSwapper.apply(converter, wrapper);
280
281 assertEquals("inner_value", result);
282 }
283 }
284
285
286
287
288
289 @Nested
290 class E_integration extends TestBase {
291
292 @Test
293 void e01_converterIntegration() {
294
295 var customConverter = BasicBeanConverter.builder()
296 .defaultSettings()
297 .addSwapper(TestWrapper.class, (converter, wrapper) -> wrapper.getValue())
298 .build();
299
300 var wrapper = new TestWrapper("swapped_content");
301 var result = customConverter.stringify(wrapper);
302
303 assertEquals("swapped_content", result);
304 }
305
306 @Test
307 void e02_multipleSwapperRegistration() {
308 var customConverter = BasicBeanConverter.builder()
309 .defaultSettings()
310 .addSwapper(TestWrapper.class, (converter, wrapper) -> "WRAPPER:" + wrapper.getValue())
311 .addSwapper(TestContainer.class, (converter, container) -> "CONTAINER:" + container.content)
312 .build();
313
314
315 var wrapper = new TestWrapper("test");
316 assertEquals("WRAPPER:test", customConverter.stringify(wrapper));
317
318
319 var container = new TestContainer("content");
320 assertEquals("CONTAINER:content", customConverter.stringify(container));
321 }
322
323 @Test
324 void e03_converterPassthrough() {
325
326 Swapper<TestContainer> containerSwapper = (converter, container) -> {
327
328 return "CONTAINER[" + converter.stringify(container.content) + "]";
329 };
330
331 var converter = BasicBeanConverter.DEFAULT;
332 var container = new TestContainer("inner_content");
333 var result = containerSwapper.apply(converter, container);
334
335 assertEquals("CONTAINER[inner_content]", result);
336 }
337
338 @Test
339 void e04_nestedConverterCalls() {
340
341 Swapper<TestComplexObject> complexSwapper = (converter, obj) -> {
342 var itemsStr = converter.stringify(obj.items);
343 return "COMPLEX{items=" + itemsStr + ", count=" + obj.items.size() + "}";
344 };
345
346 var converter = BasicBeanConverter.DEFAULT;
347 var complex = new TestComplexObject(l("x", "y", "z"));
348 var result = complexSwapper.apply(converter, complex);
349
350 assertTrue(result.toString().contains("COMPLEX{items="));
351 assertTrue(result.toString().contains("count=3"));
352 }
353
354 @Test
355 void e05_swapperChaining() {
356
357 var converter = BasicBeanConverter.builder()
358 .defaultSettings()
359 .addSwapper(TestWrapper.class, (conv, wrapper) -> new TestContainer(wrapper.getValue()))
360 .addSwapper(TestContainer.class, (conv, container) -> "CHAINED:" + container.content)
361 .build();
362
363 var wrapper = new TestWrapper("hello");
364 var result = converter.stringify(wrapper);
365
366
367 assertEquals("CHAINED:hello", result);
368 }
369
370 @Test
371 void e06_swapperWithListification() {
372
373 var converter = BasicBeanConverter.builder()
374 .defaultSettings()
375 .addSwapper(TestContainer.class, (conv, container) -> l(container.content, "extra"))
376 .build();
377
378 var container = new TestContainer("test");
379 var list = converter.listify(container);
380
381 assertSize(2, list);
382 assertEquals("test", list.get(0));
383 assertEquals("extra", list.get(1));
384 }
385 }
386
387
388
389
390
391 @Nested
392 class F_performance extends TestBase {
393
394 @Test
395 void f01_simpleSwapperPerformance() {
396 Swapper<String> simpleSwapper = (converter, str) -> str.toUpperCase();
397 var converter = BasicBeanConverter.DEFAULT;
398
399
400 var start = System.nanoTime();
401 for (var i = 0; i < 1000; i++) {
402 simpleSwapper.apply(converter, "test" + i);
403 }
404 var end = System.nanoTime();
405
406 var durationMs = (end - start) / 1_000_000;
407 assertTrue(durationMs < 100, "Simple swapper should execute quickly, took: " + durationMs + "ms");
408 }
409
410 @Test
411 void f02_complexSwapperPerformance() {
412 Swapper<List<String>> complexSwapper = (converter, list) -> {
413 return list.stream()
414 .map(String::toUpperCase)
415 .sorted()
416 .toList();
417 };
418
419 var converter = BasicBeanConverter.DEFAULT;
420 var testList = l("z", "a", "m", "c", "x");
421
422 var start = System.nanoTime();
423 for (var i = 0; i < 100; i++) {
424 complexSwapper.apply(converter, testList);
425 }
426 var end = System.nanoTime();
427
428 var durationMs = (end - start) / 1_000_000;
429 assertTrue(durationMs < 1000, "Complex swapper should complete reasonably quickly, took: " + durationMs + "ms");
430 }
431 }
432
433
434
435
436
437 static class SwapperMethods {
438 static String addPrefix(BeanConverter converter, String str) {
439 return "PREFIX:" + str;
440 }
441 }
442
443 static class TestWrapper {
444 final String value;
445
446 TestWrapper(String value) {
447 this.value = value;
448 }
449
450 String getValue() { return value; }
451 }
452
453 static class TestContainer {
454 final String content;
455
456 TestContainer(String content) {
457 this.content = content;
458 }
459 }
460
461 static class TestComplexObject {
462 final List<String> items;
463
464 TestComplexObject(List<String> items) {
465 this.items = items;
466 }
467 }
468 }