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.function.*;
23
24 import org.apache.juneau.*;
25 import org.junit.jupiter.api.*;
26
27
28
29
30
31
32
33 class Stringifier_Test extends TestBase {
34
35
36
37
38
39 @Nested
40 class A_functionalInterfaceCompliance extends TestBase {
41
42 @SuppressWarnings("cast")
43 @Test
44 void a01_functionalInterfaceContract() {
45
46 Stringifier<String> stringifier = (converter, obj) -> "STRINGIFIED:" + obj;
47
48 assertNotNull(stringifier);
49 assertTrue(stringifier instanceof BiFunction);
50 assertTrue(stringifier instanceof Stringifier);
51 }
52
53 @Test
54 void a02_lambdaExpressionCompatibility() {
55
56 Stringifier<Integer> lambda = (converter, num) -> "NUMBER:" + num;
57
58 var converter = BasicBeanConverter.DEFAULT;
59 var result = lambda.apply(converter, 42);
60
61 assertEquals("NUMBER:42", result);
62 }
63
64 @Test
65 void a03_methodReferenceCompatibility() {
66
67 Stringifier<String> methodRef = StringifierMethods::addPrefix;
68
69 var converter = BasicBeanConverter.DEFAULT;
70 var result = methodRef.apply(converter, "test");
71
72 assertEquals("PREFIX:test", result);
73 }
74
75 @Test
76 void a04_biFunctionInheritance() {
77
78 Stringifier<String> stringifier = (converter, str) -> str.toUpperCase();
79
80
81 var converter = BasicBeanConverter.DEFAULT;
82 var result = stringifier.apply(converter, "test");
83 assertEquals("TEST", result);
84 }
85 }
86
87
88
89
90
91 @Nested
92 class B_lambdaComposition extends TestBase {
93
94 @Test
95 void b01_andThenComposition() {
96 Stringifier<String> base = (converter, str) -> str.toLowerCase();
97 Function<String, String> postProcessor = s -> "[" + s + "]";
98
99 BiFunction<BeanConverter, String, String> composed = base.andThen(postProcessor);
100
101 var converter = BasicBeanConverter.DEFAULT;
102 var result = composed.apply(converter, "TEST");
103
104 assertEquals("[test]", result);
105 }
106
107 @Test
108 void b02_functionalComposition() {
109
110 Stringifier<String> upperCase = (converter, str) -> str.toUpperCase();
111 Stringifier<String> prefixed = (converter, str) -> "PROCESSED:" + str;
112
113 BeanConverter converter = BasicBeanConverter.DEFAULT;
114
115 var upperResult = upperCase.apply(converter, "test");
116 assertEquals("TEST", upperResult);
117
118 var prefixedResult = prefixed.apply(converter, "value");
119 assertEquals("PROCESSED:value", prefixedResult);
120 }
121 }
122
123
124
125
126
127 @Nested
128 class C_edgeCases extends TestBase {
129
130 @Test
131 void c01_nullInputHandling() {
132 Stringifier<String> nullSafe = (converter, str) -> {
133 if (str == null) return "NULL_INPUT";
134 return str;
135 };
136
137 var converter = BasicBeanConverter.DEFAULT;
138 var result = nullSafe.apply(converter, null);
139
140 assertEquals("NULL_INPUT", result);
141 }
142
143 @Test
144 void c02_emptyStringHandling() {
145 Stringifier<String> emptyHandler = (converter, str) -> {
146 if (str.isEmpty()) return "EMPTY_STRING";
147 return str;
148 };
149
150 var converter = BasicBeanConverter.DEFAULT;
151 var result = emptyHandler.apply(converter, "");
152
153 assertEquals("EMPTY_STRING", result);
154 }
155
156 @Test
157 void c03_exceptionHandling() {
158 Stringifier<String> throwing = (converter, str) -> {
159 if ("ERROR".equals(str)) {
160 throw new RuntimeException("Intentional test exception");
161 }
162 return str;
163 };
164
165 var converter = BasicBeanConverter.DEFAULT;
166
167
168 var normalResult = throwing.apply(converter, "normal");
169 assertEquals("normal", normalResult);
170
171
172 assertThrows(RuntimeException.class, () -> throwing.apply(converter, "ERROR"));
173 }
174
175 @Test
176 void c04_specialCharacterHandling() {
177 Stringifier<String> specialHandler = (converter, str) -> {
178 return str.replace("\n", "\\n")
179 .replace("\t", "\\t")
180 .replace("\r", "\\r");
181 };
182
183 var converter = BasicBeanConverter.DEFAULT;
184 var result = specialHandler.apply(converter, "line1\nline2\tcolumn");
185
186 assertEquals("line1\\nline2\\tcolumn", result);
187 }
188
189 @Test
190 void c05_unicodeHandling() {
191 Stringifier<String> unicodeHandler = (converter, str) -> "UNICODE:" + str;
192
193 var converter = BasicBeanConverter.DEFAULT;
194 var result = unicodeHandler.apply(converter, "测试 🎉 ñoël");
195
196 assertEquals("UNICODE:测试 🎉 ñoël", result);
197 }
198 }
199
200
201
202
203
204 @Nested
205 class D_typeSpecific extends TestBase {
206
207 @Test
208 void d01_numberStringification() {
209 Stringifier<Number> numberFormatter = (converter, num) -> {
210 if (num instanceof Integer) return "INT:" + num;
211 if (num instanceof Double) return "DOUBLE:" + String.format("%.2f", num);
212 return "NUMBER:" + num;
213 };
214
215 var converter = BasicBeanConverter.DEFAULT;
216
217 assertEquals("INT:42", numberFormatter.apply(converter, 42));
218 assertEquals("DOUBLE:3.14", numberFormatter.apply(converter, 3.14159));
219 assertEquals("NUMBER:123", numberFormatter.apply(converter, 123L));
220 }
221
222 @Test
223 void d02_collectionStringification() {
224 Stringifier<Collection<?>> collectionFormatter = (converter, coll) -> {
225 if (coll.isEmpty()) return "EMPTY_COLLECTION";
226 return "COLLECTION[" + coll.size() + "]:" +
227 coll.stream().map(Object::toString).reduce("", (a, b) -> a + "," + b);
228 };
229
230 var converter = BasicBeanConverter.DEFAULT;
231
232 assertEquals("EMPTY_COLLECTION", collectionFormatter.apply(converter, Arrays.asList()));
233 assertEquals("COLLECTION[3]:,a,b,c", collectionFormatter.apply(converter, Arrays.asList("a", "b", "c")));
234 }
235
236 @Test
237 void d03_booleanStringification() {
238 Stringifier<Boolean> booleanFormatter = (converter, bool) -> bool ? "YES" : "NO";
239
240 var converter = BasicBeanConverter.DEFAULT;
241
242 assertEquals("YES", booleanFormatter.apply(converter, true));
243 assertEquals("NO", booleanFormatter.apply(converter, false));
244 }
245
246 @Test
247 void d04_customObjectStringification() {
248 Stringifier<TestPerson> personFormatter = (converter, person) -> String.format("Person{name='%s', age=%d}", person.name, person.age);
249
250 var converter = BasicBeanConverter.DEFAULT;
251 var person = new TestPerson("Alice", 30);
252 var result = personFormatter.apply(converter, person);
253
254 assertEquals("Person{name='Alice', age=30}", result);
255 }
256 }
257
258
259
260
261
262 @Nested
263 class E_integration extends TestBase {
264
265 @Test
266 void e01_converterIntegration() {
267
268 var customConverter = BasicBeanConverter.builder()
269 .defaultSettings()
270 .addStringifier(TestPerson.class, (converter, person) ->
271 "CUSTOM:" + person.name + ":" + person.age)
272 .build();
273
274 var person = new TestPerson("Bob", 25);
275 var result = customConverter.stringify(person);
276
277 assertEquals("CUSTOM:Bob:25", result);
278 }
279
280 @Test
281 void e02_multipleStringifierRegistration() {
282 var customConverter = BasicBeanConverter.builder()
283 .defaultSettings()
284 .addStringifier(String.class, (converter, str) -> "STR:" + str)
285 .addStringifier(Integer.class, (converter, num) -> "INT:" + num)
286 .build();
287
288
289 assertEquals("STR:test", customConverter.stringify("test"));
290
291
292 assertEquals("INT:42", customConverter.stringify(42));
293 }
294
295 @Test
296 void e03_converterPassthrough() {
297
298 Stringifier<List<?>> listStringifier = (converter, list) -> {
299
300 StringBuilder sb = new StringBuilder("[");
301 for (int i = 0; i < list.size(); i++) {
302 if (i > 0) sb.append(",");
303 sb.append(converter.stringify(list.get(i)));
304 }
305 sb.append("]");
306 return sb.toString();
307 };
308
309 var converter = BasicBeanConverter.DEFAULT;
310 var testList = Arrays.asList("a", 42, true);
311 var result = listStringifier.apply(converter, testList);
312
313 assertEquals("[a,42,true]", result);
314 }
315
316 @Test
317 void e04_nestedConverterCalls() {
318
319 Stringifier<TestContainer> containerStringifier = (converter, container) -> {
320 String itemsStr = converter.stringify(container.items);
321 return "Container{items=" + itemsStr + ", count=" + container.items.size() + "}";
322 };
323
324 var converter = BasicBeanConverter.DEFAULT;
325 var container = new TestContainer(Arrays.asList("x", "y", "z"));
326 var result = containerStringifier.apply(converter, container);
327
328 assertTrue(result.contains("Container{items="));
329 assertTrue(result.contains("count=3"));
330 }
331 }
332
333
334
335
336
337 @Nested
338 class F_performance extends TestBase {
339
340 @Test
341 void f01_performanceWithLargeStrings() {
342 Stringifier<String> largeStringHandler = (converter, str) -> {
343 StringBuilder sb = new StringBuilder();
344 for (int i = 0; i < 1000; i++) {
345 sb.append(str).append("_").append(i);
346 if (i < 999) sb.append(",");
347 }
348 return sb.toString();
349 };
350
351 var converter = BasicBeanConverter.DEFAULT;
352
353 var start = System.currentTimeMillis();
354 var result = largeStringHandler.apply(converter, "test");
355 var end = System.currentTimeMillis();
356
357 assertTrue(result.length() > 8000, "Should generate a large string (actual: " + result.length() + ")");
358 assertTrue(end - start < 1000, "Should complete within 1 second");
359 }
360
361 @Test
362 void f02_memoryEfficiency() {
363 Stringifier<Integer> memoryTest = (converter, num) -> {
364
365 StringBuilder sb = new StringBuilder();
366 for (int i = 0; i < num; i++) {
367 sb.append("item_").append(i);
368 if (i < num - 1) sb.append(",");
369 }
370 return sb.toString();
371 };
372
373 var converter = BasicBeanConverter.DEFAULT;
374 var result = memoryTest.apply(converter, 100);
375
376 assertTrue(result.startsWith("item_0"));
377 assertTrue(result.endsWith("item_99"));
378 assertTrue(result.contains(","));
379 }
380 }
381
382
383
384
385
386 static class StringifierMethods {
387 static String addPrefix(BeanConverter converter, String str) {
388 return "PREFIX:" + str;
389 }
390 }
391
392 static class TestPerson {
393 final String name;
394 final int age;
395
396 TestPerson(String name, int age) {
397 this.name = name;
398 this.age = age;
399 }
400 }
401
402 static class TestContainer {
403 final List<String> items;
404
405 TestContainer(List<String> items) {
406 this.items = items;
407 }
408 }
409 }