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.junit.bct.BctAssertions.*;
20 import static org.junit.jupiter.api.Assertions.*;
21
22 import java.util.*;
23 import java.util.concurrent.*;
24
25 import org.apache.juneau.*;
26 import org.junit.jupiter.api.*;
27
28
29
30
31 @DisplayName("PropertyExtractors")
32 class PropertyExtractors_Test extends TestBase {
33
34 private BasicBeanConverter converter;
35
36 @BeforeEach
37 void setUp() {
38 converter = BasicBeanConverter.builder().defaultSettings().build();
39 }
40
41
42
43
44
45 @Nested
46 @DisplayName("ObjectPropertyExtractor")
47 class A_objectPropertyExtractorTest extends TestBase {
48
49 private PropertyExtractors.ObjectPropertyExtractor extractor = new PropertyExtractors.ObjectPropertyExtractor();
50
51 @Test
52 @DisplayName("canExtract() - always returns true")
53 void a01_canExtract_alwaysReturnsTrue() {
54 assertTrue(extractor.canExtract(converter, new TestBean(), "name"));
55 assertTrue(extractor.canExtract(converter, "string", "length"));
56 assertTrue(extractor.canExtract(converter, 123, "value"));
57 assertTrue(extractor.canExtract(converter, null, "anything"));
58 }
59
60 @Test
61 @DisplayName("extract() - getter methods")
62 void a02_extract_getterMethods() {
63 var bean = new TestBean("John", 30, true);
64
65 assertEquals("John", extractor.extract(converter, bean, "name"));
66 assertEquals(30, extractor.extract(converter, bean, "age"));
67 assertEquals(true, extractor.extract(converter, bean, "active"));
68 }
69
70 @Test
71 @DisplayName("extract() - boolean is* methods")
72 void a03_extract_booleanIsMethods() {
73 var bean = new TestBean("John", 30, true);
74
75 assertEquals(true, extractor.extract(converter, bean, "active"));
76 }
77
78 @Test
79 @DisplayName("extract() - public fields")
80 void a04_extract_publicFields() {
81 var bean = new TestBeanWithFields();
82 bean.publicField = "test value";
83
84 assertEquals("test value", extractor.extract(converter, bean, "publicField"));
85 }
86
87 @Test
88 @DisplayName("extract() - inherited fields")
89 void a05_extract_inheritedFields() {
90 var bean = new ChildBeanWithFields();
91 bean.parentField = "parent value";
92 bean.childField = "child value";
93
94 assertEquals("parent value", extractor.extract(converter, bean, "parentField"));
95 assertEquals("child value", extractor.extract(converter, bean, "childField"));
96 }
97
98 @Test
99 @DisplayName("extract() - method with property name")
100 void a06_extract_methodWithPropertyName() {
101 var bean = new TestBeanWithMethods();
102
103 assertEquals("custom method", extractor.extract(converter, bean, "customMethod"));
104 }
105
106 @Test
107 @DisplayName("extract() - Map-style get(String) method")
108 void a07_extract_mapStyleGetter() {
109 var bean = new TestBeanWithMapGetter();
110
111 assertEquals("mapped value", extractor.extract(converter, bean, "key1"));
112 }
113
114 @Test
115 @DisplayName("extract() - null object returns null")
116 void a08_extract_nullObject() {
117 assertNull(extractor.extract(converter, null, "anything"));
118 }
119
120 @Test
121 @DisplayName("extract() - property not found throws RuntimeException")
122 void a09_extract_propertyNotFound() {
123 var bean = new TestBean("John", 30, true);
124
125 var ex = assertThrows(RuntimeException.class, () ->
126 extractor.extract(converter, bean, "nonExistentProperty"));
127 assertContains("Property 'nonExistentProperty' not found on object of type TestBean", ex.getMessage());
128 }
129
130 @Test
131 @DisplayName("extract() - isX methods with parameters are ignored (line 91)")
132 void a10_extract_isMethodsWithParametersIgnored() {
133 var bean = new TestBeanWithParameterizedMethods();
134
135
136 assertEquals(true, extractor.extract(converter, bean, "active"));
137
138
139 var ex = assertThrows(RuntimeException.class, () ->
140 extractor.extract(converter, bean, "valid"));
141 assertContains("Property 'valid' not found", ex.getMessage());
142 }
143
144 @Test
145 @DisplayName("extract() - getX methods with parameters are ignored (line 96)")
146 void a11_extract_getMethodsWithParametersIgnored() {
147 var bean = new TestBeanWithParameterizedMethods();
148
149
150 assertEquals("test", extractor.extract(converter, bean, "name"));
151
152
153 var ex = assertThrows(RuntimeException.class, () ->
154 extractor.extract(converter, bean, "description"));
155 assertContains("Property 'description' not found", ex.getMessage());
156 }
157
158 @Test
159 @DisplayName("extract() - get() methods with wrong signature are ignored (line 101)")
160 void a12_extract_getMethodsWithWrongSignatureIgnored() {
161 var bean = new TestBeanWithInvalidGetMethods();
162
163
164 assertEquals("mapped_value", extractor.extract(converter, bean, "key"));
165
166
167
168 assertEquals("mapped_value", extractor.extract(converter, bean, "nonExistent"));
169
170
171 var beanWithoutValidGet = new TestBeanWithOnlyInvalidGetMethods();
172 var ex = assertThrows(RuntimeException.class, () ->
173 extractor.extract(converter, beanWithoutValidGet, "nonExistent"));
174 assertContains("Property 'nonExistent' not found", ex.getMessage());
175 }
176 }
177
178
179
180
181
182 @Nested
183 @DisplayName("ListPropertyExtractor")
184 class B_listPropertyExtractorTest extends TestBase {
185
186 private PropertyExtractors.ListPropertyExtractor extractor = new PropertyExtractors.ListPropertyExtractor();
187
188 @Test
189 @DisplayName("canExtract() - returns true for listifiable objects")
190 void b01_canExtract_listifiableObjects() {
191 assertTrue(extractor.canExtract(converter, Arrays.asList("a", "b", "c"), "0"));
192 assertTrue(extractor.canExtract(converter, new String[]{"a", "b", "c"}, "1"));
193 assertTrue(extractor.canExtract(converter, Set.of("a", "b", "c"), "size"));
194 assertFalse(extractor.canExtract(converter, "not listifiable", "0"));
195 }
196
197 @Test
198 @DisplayName("extract() - numeric indices")
199 void b02_extract_numericIndices() {
200 var list = Arrays.asList("first", "second", "third");
201
202 assertEquals("first", extractor.extract(converter, list, "0"));
203 assertEquals("second", extractor.extract(converter, list, "1"));
204 assertEquals("third", extractor.extract(converter, list, "2"));
205 }
206
207 @Test
208 @DisplayName("extract() - negative indices")
209 void b03_extract_negativeIndices() {
210 var list = Arrays.asList("first", "second", "third");
211
212 assertEquals("third", extractor.extract(converter, list, "-1"));
213 assertEquals("second", extractor.extract(converter, list, "-2"));
214 assertEquals("first", extractor.extract(converter, list, "-3"));
215 }
216
217 @Test
218 @DisplayName("extract() - arrays")
219 void b04_extract_arrays() {
220 var array = new String[]{"a", "b", "c"};
221
222 assertEquals("a", extractor.extract(converter, array, "0"));
223 assertEquals("b", extractor.extract(converter, array, "1"));
224 assertEquals("c", extractor.extract(converter, array, "2"));
225 }
226
227 @Test
228 @DisplayName("extract() - length property")
229 void b05_extract_lengthProperty() {
230 var list = Arrays.asList("a", "b", "c");
231 var array = new String[]{"a", "b", "c"};
232
233 assertEquals(3, extractor.extract(converter, list, "length"));
234 assertEquals(3, extractor.extract(converter, array, "length"));
235 }
236
237 @Test
238 @DisplayName("extract() - size property")
239 void b06_extract_sizeProperty() {
240 var list = Arrays.asList("a", "b", "c");
241 var set = Set.of("a", "b", "c");
242
243 assertEquals(3, extractor.extract(converter, list, "size"));
244 assertEquals(3, extractor.extract(converter, set, "size"));
245 }
246
247 @Test
248 @DisplayName("extract() - falls back to ObjectPropertyExtractor")
249 void b07_extract_fallbackToObjectPropertyExtractor() {
250 var list = new ArrayList<>(Arrays.asList("a", "b", "c"));
251
252
253 assertEquals(false, extractor.extract(converter, list, "empty"));
254 }
255 }
256
257
258
259
260
261 @Nested
262 @DisplayName("MapPropertyExtractor")
263 class C_mapPropertyExtractorTest extends TestBase {
264
265 private PropertyExtractors.MapPropertyExtractor extractor = new PropertyExtractors.MapPropertyExtractor();
266
267 @Test
268 @DisplayName("canExtract() - returns true only for Map objects")
269 void c01_canExtract_mapObjects() {
270 assertTrue(extractor.canExtract(converter, Map.of("key", "value"), "key"));
271 assertTrue(extractor.canExtract(converter, new HashMap<>(), "size"));
272 assertFalse(extractor.canExtract(converter, Arrays.asList("a", "b"), "0"));
273 assertFalse(extractor.canExtract(converter, "not a map", "length"));
274 }
275
276 @Test
277 @DisplayName("extract() - direct key access")
278 void c02_extract_directKeyAccess() {
279 var map = Map.of("name", "John", "age", 30, "active", true);
280
281 assertEquals("John", extractor.extract(converter, map, "name"));
282 assertEquals(30, extractor.extract(converter, map, "age"));
283 assertEquals(true, extractor.extract(converter, map, "active"));
284 }
285
286 @Test
287 @DisplayName("extract() - size property")
288 void c03_extract_sizeProperty() {
289 var map = Map.of("a", 1, "b", 2, "c", 3);
290
291 assertEquals(3, extractor.extract(converter, map, "size"));
292 }
293
294 @Test
295 @DisplayName("extract() - empty map")
296 void c04_extract_emptyMap() {
297 var map = new HashMap<String, Object>();
298
299 assertEquals(0, extractor.extract(converter, map, "size"));
300
301
302 var ex = assertThrows(RuntimeException.class, () ->
303 extractor.extract(converter, map, "nonExistentKey"));
304 assertContains("Property 'nonExistentKey' not found on object of type HashMap", ex.getMessage());
305 }
306
307 @Test
308 @DisplayName("extract() - Properties object")
309 void c05_extract_propertiesObject() {
310 var props = new Properties();
311 props.setProperty("config.timeout", "5000");
312 props.setProperty("config.enabled", "true");
313
314 assertEquals("5000", extractor.extract(converter, props, "config.timeout"));
315 assertEquals("true", extractor.extract(converter, props, "config.enabled"));
316 }
317
318 @Test
319 @DisplayName("extract() - ConcurrentHashMap")
320 void c06_extract_concurrentHashMap() {
321 var map = new ConcurrentHashMap<String, Object>();
322 map.put("thread-safe", true);
323 map.put("capacity", 16);
324
325 assertEquals(true, extractor.extract(converter, map, "thread-safe"));
326 assertEquals(16, extractor.extract(converter, map, "capacity"));
327 assertEquals(2, extractor.extract(converter, map, "size"));
328 }
329
330 @Test
331 @DisplayName("extract() - key priority over JavaBean properties")
332 void c07_extract_keyPriorityOverJavaBeanProperties() {
333 var map = new TestMapWithMethods();
334 map.put("customProperty", "map value");
335
336
337 assertEquals("map value", extractor.extract(converter, map, "customProperty"));
338 }
339
340 @Test
341 @DisplayName("extract() - null value setting handling")
342 void c08_extract_nullValueSettingHandling() {
343
344 var map = new HashMap<String, Object>();
345 map.put(null, "null key value");
346 map.put("other", "other value");
347
348
349 assertEquals("null key value", extractor.extract(converter, map, "<null>"));
350
351
352 var customConverter = BasicBeanConverter.builder()
353 .defaultSettings()
354 .addSetting(BasicBeanConverter.SETTING_nullValue, "NULL_KEY")
355 .build();
356
357 var customExtractor = new PropertyExtractors.MapPropertyExtractor();
358 map.put("NULL_KEY", "literal null key string");
359
360
361 assertEquals("null key value", customExtractor.extract(customConverter, map, "NULL_KEY"));
362 }
363
364 @Test
365 @DisplayName("extract() - falls back to ObjectPropertyExtractor")
366 void c09_extract_fallbackToObjectPropertyExtractor() {
367 var map = new TestMapWithMethods();
368
369
370 assertEquals(true, extractor.extract(converter, map, "empty"));
371 }
372 }
373
374
375
376
377
378 public static class TestBean {
379 private String name;
380 private int age;
381 private boolean active;
382
383 public TestBean() {}
384
385 public TestBean(String name, int age, boolean active) {
386 this.name = name;
387 this.age = age;
388 this.active = active;
389 }
390
391 public String getName() { return name; }
392 public int getAge() { return age; }
393 public boolean isActive() { return active; }
394
395 void setName(String name) { this.name = name; }
396 void setAge(int age) { this.age = age; }
397 void setActive(boolean active) { this.active = active; }
398 }
399
400 public static class TestBeanWithFields {
401 public String publicField;
402 @SuppressWarnings("unused")
403 private String privateField = "private";
404 }
405
406 public static class ParentBeanWithFields {
407 public String parentField;
408 }
409
410 public static class ChildBeanWithFields extends ParentBeanWithFields {
411 public String childField;
412 }
413
414 public static class TestBeanWithMethods {
415 public String customMethod() {
416 return "custom method";
417 }
418 }
419
420 public static class TestBeanWithMapGetter {
421 private Map<String, String> data = Map.of("key1", "mapped value", "key2", "another value");
422
423 public String get(String key) {
424 return data.get(key);
425 }
426 }
427
428 public static class TestMapWithMethods extends HashMap<String, Object> {
429 private static final long serialVersionUID = 1L;
430
431 public String getCustomProperty() {
432 return "method value";
433 }
434 }
435
436 public static class TestBeanWithParameterizedMethods {
437
438 public boolean isActive() {
439 return true;
440 }
441
442 public String getName() {
443 return "test";
444 }
445
446
447 public boolean isValid(String criteria) {
448 return criteria != null;
449 }
450
451 public String getDescription(String language) {
452 return "Description in " + language;
453 }
454
455 public String getDescription(String language, String format) {
456 return "Description in " + language + " format " + format;
457 }
458 }
459
460 public static class TestBeanWithInvalidGetMethods {
461
462 public String get(String key) {
463 return "mapped_value";
464 }
465
466
467 public String get() {
468 return "no_parameters";
469 }
470
471 public String get(int index) {
472 return "wrong_parameter_type";
473 }
474
475 public String get(String key1, String key2) {
476 return "too_many_parameters";
477 }
478
479 public Integer get(String key, boolean flag) {
480 return 42;
481 }
482 }
483
484 public static class TestBeanWithOnlyInvalidGetMethods {
485
486 public String get() {
487 return "no_parameters";
488 }
489
490 public String get(int index) {
491 return "wrong_parameter_type";
492 }
493
494 public String get(String key1, String key2) {
495 return "too_many_parameters";
496 }
497
498 public Integer get(String key, boolean flag) {
499 return 42;
500 }
501 }
502 }