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.junit.jupiter.api.Assertions.*;
21
22 import java.util.*;
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 Stringifier_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 Stringifier<String> stringifier = (converter, obj) -> "STRINGIFIED:" + obj;
48
49 assertNotNull(stringifier);
50 assertTrue(stringifier instanceof BiFunction);
51 assertTrue(stringifier instanceof Stringifier);
52 }
53
54 @Test
55 void a02_lambdaExpressionCompatibility() {
56
57 Stringifier<Integer> lambda = (converter, num) -> "NUMBER:" + num;
58
59 var converter = BasicBeanConverter.DEFAULT;
60 var result = lambda.apply(converter, 42);
61
62 assertEquals("NUMBER:42", result);
63 }
64
65 @Test
66 void a03_methodReferenceCompatibility() {
67
68 Stringifier<String> methodRef = StringifierMethods::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 Stringifier<String> stringifier = (converter, str) -> str.toUpperCase();
80
81
82 var converter = BasicBeanConverter.DEFAULT;
83 var result = stringifier.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 Stringifier<String> base = (converter, str) -> str.toLowerCase();
98 Function<String, String> postProcessor = s -> "[" + s + "]";
99
100 BiFunction<BeanConverter, String, String> 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_functionalComposition() {
110
111 Stringifier<String> upperCase = (converter, str) -> str.toUpperCase();
112 Stringifier<String> prefixed = (converter, str) -> "PROCESSED:" + str;
113
114 BeanConverter 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 Stringifier<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_emptyStringHandling() {
146 Stringifier<String> emptyHandler = (converter, str) -> {
147 if (str.isEmpty()) return "EMPTY_STRING";
148 return str;
149 };
150
151 var converter = BasicBeanConverter.DEFAULT;
152 var result = emptyHandler.apply(converter, "");
153
154 assertEquals("EMPTY_STRING", result);
155 }
156
157 @Test
158 void c03_exceptionHandling() {
159 Stringifier<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_specialCharacterHandling() {
178 Stringifier<String> specialHandler = (converter, str) -> {
179 return str.replace("\n", "\\n")
180 .replace("\t", "\\t")
181 .replace("\r", "\\r");
182 };
183
184 var converter = BasicBeanConverter.DEFAULT;
185 var result = specialHandler.apply(converter, "line1\nline2\tcolumn");
186
187 assertEquals("line1\\nline2\\tcolumn", result);
188 }
189
190 @Test
191 void c05_unicodeHandling() {
192 Stringifier<String> unicodeHandler = (converter, str) -> "UNICODE:" + str;
193
194 var converter = BasicBeanConverter.DEFAULT;
195 var result = unicodeHandler.apply(converter, "测试 🎉 ñoël");
196
197 assertEquals("UNICODE:测试 🎉 ñoël", result);
198 }
199 }
200
201
202
203
204
205 @Nested
206 class D_typeSpecific extends TestBase {
207
208 @Test
209 void d01_numberStringification() {
210 Stringifier<Number> numberFormatter = (converter, num) -> {
211 if (num instanceof Integer) return "INT:" + num;
212 if (num instanceof Double) return "DOUBLE:" + String.format("%.2f", num);
213 return "NUMBER:" + num;
214 };
215
216 var converter = BasicBeanConverter.DEFAULT;
217
218 assertEquals("INT:42", numberFormatter.apply(converter, 42));
219 assertEquals("DOUBLE:3.14", numberFormatter.apply(converter, 3.14159));
220 assertEquals("NUMBER:123", numberFormatter.apply(converter, 123L));
221 }
222
223 @Test
224 void d02_collectionStringification() {
225 Stringifier<Collection<?>> collectionFormatter = (converter, coll) -> {
226 if (coll.isEmpty()) return "EMPTY_COLLECTION";
227 return "COLLECTION[" + coll.size() + "]:" +
228 coll.stream().map(Object::toString).reduce("", (a, b) -> a + "," + b);
229 };
230
231 var converter = BasicBeanConverter.DEFAULT;
232
233 assertEquals("EMPTY_COLLECTION", collectionFormatter.apply(converter, l()));
234 assertEquals("COLLECTION[3]:,a,b,c", collectionFormatter.apply(converter, l("a", "b", "c")));
235 }
236
237 @Test
238 void d03_booleanStringification() {
239 Stringifier<Boolean> booleanFormatter = (converter, bool) -> bool ? "YES" : "NO";
240
241 var converter = BasicBeanConverter.DEFAULT;
242
243 assertEquals("YES", booleanFormatter.apply(converter, true));
244 assertEquals("NO", booleanFormatter.apply(converter, false));
245 }
246
247 @Test
248 void d04_customObjectStringification() {
249 Stringifier<TestPerson> personFormatter = (converter, person) -> String.format("Person{name='%s', age=%d}", person.name, person.age);
250
251 var converter = BasicBeanConverter.DEFAULT;
252 var person = new TestPerson("Alice", 30);
253 var result = personFormatter.apply(converter, person);
254
255 assertEquals("Person{name='Alice', age=30}", result);
256 }
257 }
258
259
260
261
262
263 @Nested
264 class E_integration extends TestBase {
265
266 @Test
267 void e01_converterIntegration() {
268
269 var customConverter = BasicBeanConverter.builder()
270 .defaultSettings()
271 .addStringifier(TestPerson.class, (converter, person) ->
272 "CUSTOM:" + person.name + ":" + person.age)
273 .build();
274
275 var person = new TestPerson("Bob", 25);
276 var result = customConverter.stringify(person);
277
278 assertEquals("CUSTOM:Bob:25", result);
279 }
280
281 @Test
282 void e02_multipleStringifierRegistration() {
283 var customConverter = BasicBeanConverter.builder()
284 .defaultSettings()
285 .addStringifier(String.class, (converter, str) -> "STR:" + str)
286 .addStringifier(Integer.class, (converter, num) -> "INT:" + num)
287 .build();
288
289
290 assertEquals("STR:test", customConverter.stringify("test"));
291
292
293 assertEquals("INT:42", customConverter.stringify(42));
294 }
295
296 @Test
297 void e03_converterPassthrough() {
298
299 Stringifier<List<?>> listStringifier = (converter, list) -> {
300
301 var sb = new StringBuilder("[");
302 for (var i = 0; i < list.size(); i++) {
303 if (i > 0) sb.append(",");
304 sb.append(converter.stringify(list.get(i)));
305 }
306 sb.append("]");
307 return sb.toString();
308 };
309
310 var converter = BasicBeanConverter.DEFAULT;
311 var testList = l("a", 42, true);
312 var result = listStringifier.apply(converter, testList);
313
314 assertEquals("[a,42,true]", result);
315 }
316
317 @Test
318 void e04_nestedConverterCalls() {
319
320 Stringifier<TestContainer> containerStringifier = (converter, container) -> {
321 String itemsStr = converter.stringify(container.items);
322 return "Container{items=" + itemsStr + ", count=" + container.items.size() + "}";
323 };
324
325 var converter = BasicBeanConverter.DEFAULT;
326 var container = new TestContainer(l("x", "y", "z"));
327 var result = containerStringifier.apply(converter, container);
328
329 assertTrue(result.contains("Container{items="));
330 assertTrue(result.contains("count=3"));
331 }
332 }
333
334
335
336
337
338 @Nested
339 class F_performance extends TestBase {
340
341 @Test
342 void f01_performanceWithLargeStrings() {
343 Stringifier<String> largeStringHandler = (converter, str) -> {
344 var sb = new StringBuilder();
345 for (var i = 0; i < 1000; i++) {
346 sb.append(str).append("_").append(i);
347 if (i < 999) sb.append(",");
348 }
349 return sb.toString();
350 };
351
352 var converter = BasicBeanConverter.DEFAULT;
353
354 var start = System.currentTimeMillis();
355 var result = largeStringHandler.apply(converter, "test");
356 var end = System.currentTimeMillis();
357
358 assertTrue(result.length() > 8000, "Should generate a large string (actual: " + result.length() + ")");
359 assertTrue(end - start < 1000, "Should complete within 1 second");
360 }
361
362 @Test
363 void f02_memoryEfficiency() {
364 Stringifier<Integer> memoryTest = (converter, num) -> {
365
366 var sb = new StringBuilder();
367 for (var i = 0; i < num; i++) {
368 sb.append("item_").append(i);
369 if (i < num - 1) sb.append(",");
370 }
371 return sb.toString();
372 };
373
374 var converter = BasicBeanConverter.DEFAULT;
375 var result = memoryTest.apply(converter, 100);
376
377 assertTrue(result.startsWith("item_0"));
378 assertTrue(result.endsWith("item_99"));
379 assertTrue(result.contains(","));
380 }
381 }
382
383
384
385
386
387 static class StringifierMethods {
388 static String addPrefix(BeanConverter converter, String str) {
389 return "PREFIX:" + str;
390 }
391 }
392
393 static class TestPerson {
394 final String name;
395 final int age;
396
397 TestPerson(String name, int age) {
398 this.name = name;
399 this.age = age;
400 }
401 }
402
403 static class TestContainer {
404 final List<String> items;
405
406 TestContainer(List<String> items) {
407 this.items = items;
408 }
409 }
410 }