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