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