View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.junit.bct;
18  
19  import static org.apache.juneau.commons.utils.CollectionUtils.*;
20  import static org.apache.juneau.junit.bct.BctAssertions.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.io.*;
24  import java.nio.file.*;
25  import java.time.format.*;
26  import java.util.*;
27  
28  import org.apache.juneau.*;
29  import org.junit.jupiter.api.*;
30  import org.junit.jupiter.params.*;
31  import org.junit.jupiter.params.provider.*;
32  
33  /**
34   * Unit tests for {@link Stringifiers}.
35   */
36  class Stringifiers_Test extends TestBase {
37  
38  	@Nested
39  	class A_mapEntryStringifier extends TestBase {
40  
41  		@Test
42  		void a01_stringifySimpleEntry() {
43  			var converter = BasicBeanConverter.builder().defaultSettings().build();
44  			var stringifier = Stringifiers.mapEntryStringifier();
45  			var entry = Map.entry("key", "value");
46  			var result = stringifier.apply(converter, entry);
47  
48  			assertEquals("key=value", result);
49  		}
50  
51  		@Test
52  		void a02_stringifyWithNullKey() {
53  			var converter = BasicBeanConverter.builder().defaultSettings().build();
54  			var stringifier = Stringifiers.mapEntryStringifier();
55  			var entry = new AbstractMap.SimpleEntry<String, String>(null, "value");
56  			var result = stringifier.apply(converter, entry);
57  
58  			assertEquals("<null>=value", result);
59  		}
60  
61  		@Test
62  		void a03_stringifyWithNullValue() {
63  			var converter = BasicBeanConverter.builder().defaultSettings().build();
64  			var stringifier = Stringifiers.mapEntryStringifier();
65  			var entry = new AbstractMap.SimpleEntry<>("key", null);
66  			var result = stringifier.apply(converter, entry);
67  
68  			assertEquals("key=<null>", result);
69  		}
70  
71  		@Test
72  		void a04_stringifyWithCustomSeparator() {
73  			var converter = BasicBeanConverter.builder()
74  				.defaultSettings()
75  				.addSetting("mapEntrySeparator", ": ")
76  				.build();
77  			var stringifier = Stringifiers.mapEntryStringifier();
78  			var entry = Map.entry("name", "John");
79  			var result = stringifier.apply(converter, entry);
80  
81  			assertEquals("name: John", result);
82  		}
83  
84  		@Test
85  		void a05_stringifyWithComplexValues() {
86  		var converter = BasicBeanConverter.builder().defaultSettings().build();
87  		var stringifier = Stringifiers.mapEntryStringifier();
88  		var entry = Map.entry("list", l("a", "b"));
89  		var result = stringifier.apply(converter, entry);
90  
91  			assertEquals("list=[a,b]", result);
92  		}
93  	}
94  
95  	@Nested
96  	class B_calendarStringifier extends TestBase {
97  
98  		@Test
99  		void b01_stringifyGregorianCalendar() {
100 			var converter = BasicBeanConverter.builder().defaultSettings().build();
101 			var stringifier = Stringifiers.calendarStringifier();
102 			var calendar = new GregorianCalendar(2023, Calendar.JANUARY, 15, 10, 30, 0);
103 			calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
104 			var result = stringifier.apply(converter, calendar);
105 
106 			assertMatchesGlob("2023-01-15T*", result);
107 		}
108 
109 		@Test
110 		void b02_stringifyWithCustomFormat() {
111 			var converter = BasicBeanConverter.builder()
112 				.defaultSettings()
113 				.addSetting("calendarFormat", DateTimeFormatter.ISO_LOCAL_DATE)
114 				.build();
115 			var stringifier = Stringifiers.calendarStringifier();
116 			var calendar = new GregorianCalendar(2023, Calendar.DECEMBER, 25);
117 			var result = stringifier.apply(converter, calendar);
118 
119 			assertMatchesGlob("2023-12-25", result);
120 		}
121 	}
122 
123 	@Nested
124 	class C_dateStringifier extends TestBase {
125 
126 		@Test
127 		void c01_stringifyDate() {
128 			var stringifier = Stringifiers.dateStringifier();
129 			var date = new Date(1673780400000L); // 2023-01-15T11:00:00Z
130 			var result = stringifier.apply(null, date);
131 
132 			assertEquals("2023-01-15T11:00:00Z", result);
133 		}
134 
135 		@Test
136 		void c02_stringifyDateWithConverter() {
137 			var converter = BasicBeanConverter.builder().defaultSettings().build();
138 			var stringifier = Stringifiers.dateStringifier();
139 			var date = new Date(0); // Epoch
140 			var result = stringifier.apply(converter, date);
141 
142 			assertEquals("1970-01-01T00:00:00Z", result);
143 		}
144 	}
145 
146 	@Nested
147 	class D_inputStreamStringifier extends TestBase {
148 
149 		@Test
150 		void d01_stringifyInputStream() {
151 			var stringifier = Stringifiers.inputStreamStringifier();
152 			var input = new ByteArrayInputStream(bytes(0x48, 0x65, 0x6C, 0x6C, 0x6F)); // "Hello"
153 			var result = stringifier.apply(null, input);
154 
155 			assertEquals("48656C6C6F", result);
156 		}
157 
158 		@Test
159 		void d02_stringifyEmptyInputStream() {
160 			var stringifier = Stringifiers.inputStreamStringifier();
161 			var input = new ByteArrayInputStream(new byte[0]);
162 			var result = stringifier.apply(null, input);
163 
164 			assertEquals("", result);
165 		}
166 
167 		@Test
168 		void d03_stringifyInputStreamWithZeros() {
169 			var stringifier = Stringifiers.inputStreamStringifier();
170 			var input = new ByteArrayInputStream(bytes(0x00, (byte)0xFF, 0x7F));
171 			var result = stringifier.apply(null, input);
172 
173 			assertEquals("00FF7F", result);
174 		}
175 	}
176 
177 	@Nested
178 	class E_byteArrayStringifier extends TestBase {
179 
180 		@Test
181 		void e01_stringifyByteArray() {
182 			var stringifier = Stringifiers.byteArrayStringifier();
183 			var input = bytes(0x48, 0x65, 0x6C, 0x6C, 0x6F); // "Hello"
184 			var result = stringifier.apply(null, input);
185 
186 			assertEquals("48656C6C6F", result);
187 		}
188 
189 		@Test
190 		void e02_stringifyEmptyByteArray() {
191 			var stringifier = Stringifiers.byteArrayStringifier();
192 			var input = new byte[0];
193 			var result = stringifier.apply(null, input);
194 
195 			assertEquals("", result);
196 		}
197 
198 		@Test
199 		void e03_stringifyByteArrayWithAllValues() {
200 			var stringifier = Stringifiers.byteArrayStringifier();
201 			var input = bytes(0x00, (byte)0xFF, 0x7F, (byte)0x80);
202 			var result = stringifier.apply(null, input);
203 
204 			assertEquals("00FF7F80", result);
205 		}
206 	}
207 
208 	@Nested
209 	class F_charArrayStringifier extends TestBase {
210 
211 		@Test
212 		void f01_stringifyCharArray() {
213 			var stringifier = Stringifiers.charArrayStringifier();
214 			var input = chars('H', 'e', 'l', 'l', 'o');
215 			var result = stringifier.apply(null, input);
216 
217 			assertEquals("Hello", result);
218 		}
219 
220 		@Test
221 		void f02_stringifyEmptyCharArray() {
222 			var stringifier = Stringifiers.charArrayStringifier();
223 			var input = new char[0];
224 			var result = stringifier.apply(null, input);
225 
226 			assertEquals("", result);
227 		}
228 
229 		@Test
230 		void f03_stringifyCharArrayWithHexChars() {
231 			var stringifier = Stringifiers.charArrayStringifier();
232 			var input = chars('0', '0', '0', '0');
233 			var result = stringifier.apply(null, input);
234 
235 			assertEquals("0000", result);
236 		}
237 
238 		@Test
239 		void f04_stringifyCharArrayWithSpecialChars() {
240 			var stringifier = Stringifiers.charArrayStringifier();
241 			var input = chars('A', 'B', 'C', 'D', 'E', 'F');
242 			var result = stringifier.apply(null, input);
243 
244 			assertEquals("ABCDEF", result);
245 		}
246 
247 		@Test
248 		void f05_stringifyCharArrayWithMixedChars() {
249 			var stringifier = Stringifiers.charArrayStringifier();
250 			var input = chars('a', '1', 'B', '2', 'c', '3');
251 			var result = stringifier.apply(null, input);
252 
253 			assertEquals("a1B2c3", result);
254 		}
255 	}
256 
257 	@Nested
258 	class G_readerStringifier extends TestBase {
259 
260 		@ParameterizedTest
261 		@ValueSource(strings = {
262 			"Hello World",
263 			"",
264 			"Line 1\nLine 2\nLine 3"
265 		})
266 		void g01_stringifyReader(String input) {
267 			var stringifier = Stringifiers.readerStringifier();
268 			var reader = new StringReader(input);
269 			var result = stringifier.apply(null, reader);
270 
271 			assertEquals(input, result);
272 		}
273 	}
274 
275 	@Nested
276 	class G_fileStringifier extends TestBase {
277 
278 		@Test
279 		void g01_stringifyTempFile() throws Exception {
280 			var stringifier = Stringifiers.fileStringifier();
281 			var tempFile = Files.createTempFile("test", ".txt");
282 			Files.writeString(tempFile, "Test content");
283 
284 			try {
285 				var result = stringifier.apply(null, tempFile.toFile());
286 				assertEquals("Test content", result);
287 			} finally {
288 				Files.deleteIfExists(tempFile);
289 			}
290 		}
291 
292 		@Test
293 		void g02_stringifyEmptyFile() throws Exception {
294 			var stringifier = Stringifiers.fileStringifier();
295 			var tempFile = Files.createTempFile("empty", ".txt");
296 
297 			try {
298 				var result = stringifier.apply(null, tempFile.toFile());
299 				assertEquals("", result);
300 			} finally {
301 				Files.deleteIfExists(tempFile);
302 			}
303 		}
304 	}
305 
306 	@Nested
307 	class H_enumStringifier extends TestBase {
308 
309 		enum TestEnum { VALUE1, VALUE2, LONG_VALUE_NAME }
310 
311 		@Test
312 		void h01_stringifyEnum() {
313 			var stringifier = Stringifiers.enumStringifier();
314 			var result = stringifier.apply(null, TestEnum.VALUE1);
315 
316 			assertEquals("VALUE1", result);
317 		}
318 
319 		@Test
320 		void h02_stringifyEnumWithLongName() {
321 			var stringifier = Stringifiers.enumStringifier();
322 			var result = stringifier.apply(null, TestEnum.LONG_VALUE_NAME);
323 
324 			assertEquals("LONG_VALUE_NAME", result);
325 		}
326 
327 		@Test
328 		void h03_stringifyStandardEnum() {
329 			var stringifier = Stringifiers.enumStringifier();
330 			var result = stringifier.apply(null, Thread.State.RUNNABLE);
331 
332 			assertEquals("RUNNABLE", result);
333 		}
334 	}
335 
336 	@Nested
337 	class I_classStringifier extends TestBase {
338 
339 		@Test
340 		void i01_stringifyClassSimple() {
341 			var converter = BasicBeanConverter.builder()
342 				.defaultSettings()
343 				.addSetting("classNameFormat", "simple")
344 				.build();
345 			var stringifier = Stringifiers.classStringifier();
346 			var result = stringifier.apply(converter, String.class);
347 
348 			assertEquals("String", result);
349 		}
350 
351 		@Test
352 		void i02_stringifyClassCanonical() {
353 			var converter = BasicBeanConverter.builder()
354 				.defaultSettings()
355 				.addSetting("classNameFormat", "canonical")
356 				.build();
357 			var stringifier = Stringifiers.classStringifier();
358 			var result = stringifier.apply(converter, ArrayList.class);
359 
360 			assertEquals("java.util.ArrayList", result);
361 		}
362 
363 		@Test
364 		void i03_stringifyClassFull() {
365 			var converter = BasicBeanConverter.builder()
366 				.defaultSettings()
367 				.addSetting("classNameFormat", "full")
368 				.build();
369 			var stringifier = Stringifiers.classStringifier();
370 			var result = stringifier.apply(converter, HashMap.class);
371 
372 			assertEquals("java.util.HashMap", result);
373 		}
374 
375 		@Test
376 		void i04_stringifyPrimitiveClass() {
377 			var converter = BasicBeanConverter.builder().defaultSettings().build();
378 			var stringifier = Stringifiers.classStringifier();
379 			var result = stringifier.apply(converter, int.class);
380 
381 			assertEquals("int", result);
382 		}
383 	}
384 
385 	@Nested
386 	class J_constructorStringifier extends TestBase {
387 
388 		@Test
389 		void j01_stringifyNoArgConstructor() throws Exception {
390 			var converter = BasicBeanConverter.builder().defaultSettings().build();
391 			var stringifier = Stringifiers.constructorStringifier();
392 			var constructor = ArrayList.class.getConstructor();
393 			var result = stringifier.apply(converter, constructor);
394 
395 			assertEquals("ArrayList()", result);
396 		}
397 
398 		@Test
399 		void j02_stringifyParameterizedConstructor() throws Exception {
400 			var converter = BasicBeanConverter.builder().defaultSettings().build();
401 			var stringifier = Stringifiers.constructorStringifier();
402 			var constructor = String.class.getConstructor(char[].class);
403 			var result = stringifier.apply(converter, constructor);
404 
405 			assertEquals("String(char[])", result);
406 		}
407 
408 		@Test
409 		void j03_stringifyMultiParameterConstructor() throws Exception {
410 			var converter = BasicBeanConverter.builder().defaultSettings().build();
411 			var stringifier = Stringifiers.constructorStringifier();
412 			var constructor = String.class.getConstructor(byte[].class, int.class, int.class);
413 			var result = stringifier.apply(converter, constructor);
414 
415 			assertEquals("String(byte[],int,int)", result);
416 		}
417 	}
418 
419 	@Nested
420 	class K_methodStringifier extends TestBase {
421 
422 		@Test
423 		void k01_stringifyNoArgMethod() throws Exception {
424 			var converter = BasicBeanConverter.builder().defaultSettings().build();
425 			var stringifier = Stringifiers.methodStringifier();
426 			var method = Object.class.getMethod("toString");
427 			var result = stringifier.apply(converter, method);
428 
429 			assertEquals("toString()", result);
430 		}
431 
432 		@Test
433 		void k02_stringifyParameterizedMethod() throws Exception {
434 			var converter = BasicBeanConverter.builder().defaultSettings().build();
435 			var stringifier = Stringifiers.methodStringifier();
436 			var method = String.class.getMethod("substring", int.class);
437 			var result = stringifier.apply(converter, method);
438 
439 			assertEquals("substring(int)", result);
440 		}
441 
442 		@Test
443 		void k03_stringifyMultiParameterMethod() throws Exception {
444 			var converter = BasicBeanConverter.builder().defaultSettings().build();
445 			var stringifier = Stringifiers.methodStringifier();
446 			var method = String.class.getMethod("substring", int.class, int.class);
447 			var result = stringifier.apply(converter, method);
448 
449 			assertEquals("substring(int,int)", result);
450 		}
451 	}
452 
453 	@Nested
454 	class L_listStringifier extends TestBase {
455 
456 		@Test
457 		void l01_stringifyList() {
458 		var converter = BasicBeanConverter.builder().defaultSettings().build();
459 		var stringifier = Stringifiers.listStringifier();
460 		var input = l("a", "b", "c");
461 		var result = stringifier.apply(converter, input);
462 
463 			assertEquals("[a,b,c]", result);
464 		}
465 
466 		@Test
467 		void l02_stringifyEmptyList() {
468 		var converter = BasicBeanConverter.builder().defaultSettings().build();
469 		var stringifier = Stringifiers.listStringifier();
470 		var input = l();
471 		var result = stringifier.apply(converter, input);
472 
473 			assertEquals("[]", result);
474 		}
475 
476 		@Test
477 		void l03_stringifyWithCustomDelimiters() {
478 			var converter = BasicBeanConverter.builder()
479 				.defaultSettings()
480 				.addSetting("fieldSeparator", "; ")
481 				.addSetting("collectionPrefix", "(")
482 				.addSetting("collectionSuffix", ")")
483 			.build();
484 		var stringifier = Stringifiers.listStringifier();
485 		var input = l("x", "y", "z");
486 		var result = stringifier.apply(converter, input);
487 
488 			assertEquals("(x; y; z)", result);
489 		}
490 
491 		@Test
492 		void l04_stringifyWithNullElements() {
493 			var converter = BasicBeanConverter.builder().defaultSettings().build();
494 			var stringifier = Stringifiers.listStringifier();
495 			var input = l("a", null, "c");
496 			var result = stringifier.apply(converter, input);
497 
498 			assertEquals("[a,<null>,c]", result);
499 		}
500 	}
501 
502 	@Nested
503 	class M_mapStringifier extends TestBase {
504 
505 		@Test
506 		void m01_stringifyMap() {
507 			var converter = BasicBeanConverter.builder().defaultSettings().build();
508 			var stringifier = Stringifiers.mapStringifier();
509 			var input = m("key1", "value1", "key2", "value2");
510 			var result = stringifier.apply(converter, input);
511 	
512 			// Order may vary, so check both possibilities
513 			assertTrue(result.equals("{key1=value1,key2=value2}") ||
514 				result.equals("{key2=value2,key1=value1}"));
515 		}
516 
517 		@Test
518 		void m02_stringifyEmptyMap() {
519 			var converter = BasicBeanConverter.builder().defaultSettings().build();
520 			var stringifier = Stringifiers.mapStringifier();
521 			var input = m();
522 			var result = stringifier.apply(converter, input);
523 	
524 			assertEquals("{}", result);
525 		}
526 
527 		@Test
528 		void m03_stringifyWithCustomDelimiters() {
529 			var converter = BasicBeanConverter.builder()
530 				.defaultSettings()
531 				.addSetting("fieldSeparator", "; ")
532 				.addSetting("mapPrefix", "<")
533 				.addSetting("mapSuffix", ">")
534 				.build();
535 			var stringifier = Stringifiers.mapStringifier();
536 			var input = m("a", "1");
537 			var result = stringifier.apply(converter, input);
538 
539 			assertEquals("<a=1>", result);
540 		}
541 
542 		@Test
543 		void m04_stringifyWithNullValues() {
544 			var converter = BasicBeanConverter.builder().defaultSettings().build();
545 			var stringifier = Stringifiers.mapStringifier();
546 			var input = new HashMap<String, String>();
547 			input.put("key1", "value1");
548 			input.put("key2", null);
549 			var result = stringifier.apply(converter, input);
550 
551 			assertTrue(result.contains("key1=value1"));
552 			assertTrue(result.contains("key2=<null>"));
553 		}
554 	}
555 
556 	@Nested
557 	class N_integration extends TestBase {
558 
559 		@Test
560 		void n01_useInBasicBeanConverter() {
561 		// Test various stringifiable objects
562 		assertBean(Map.entry("k", "v"), "<self>", "k=v");
563 		assertBean(l("a", "b"), "<self>", "[a,b]");
564 		assertBean(m("x", "y"), "<self>", "{x=y}");
565 		assertBean(TestEnum.VALUE1, "<self>", "VALUE1");
566 		assertBean(String.class, "<self>", "String");
567 	}
568 
569 	@Test
570 	void n02_customStringifierRegistration() {
571 		// Test that custom registration works
572 		assertBean(l("custom"), "<self>", "[custom]");
573 	}
574 
575 	@Test
576 	void n03_stringifierChaining() {
577 		// Map with list values
578 		var complexMap = m("items", l("a", "b"));
579 		assertMatchesGlob("*items=[a,b]*", BasicBeanConverter.DEFAULT.stringify(complexMap));
580 	}
581 
582 		private enum TestEnum { VALUE1, VALUE2 }
583 	}
584 }