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
23 import org.apache.juneau.*;
24 import org.junit.jupiter.api.*;
25
26
27
28
29
30
31
32 class PropertyExtractor_Test extends TestBase {
33
34
35
36
37
38 @Nested
39 class A_interfaceContract extends TestBase {
40
41 @SuppressWarnings("cast")
42 @Test
43 void a01_interfaceImplementation() {
44
45 PropertyExtractor extractor = new PropertyExtractor() {
46 @Override
47 public boolean canExtract(BeanConverter converter, Object o, String key) {
48 return true;
49 }
50
51 @Override
52 public Object extract(BeanConverter converter, Object o, String key) {
53 return "EXTRACTED:" + key;
54 }
55 };
56
57 assertNotNull(extractor);
58 assertTrue(extractor instanceof PropertyExtractor);
59 }
60
61 @Test
62 void a02_anonymousClassImplementation() {
63
64 PropertyExtractor impl = new PropertyExtractor() {
65 @Override
66 public boolean canExtract(BeanConverter converter, Object o, String key) {
67 return o instanceof String;
68 }
69
70 @Override
71 public Object extract(BeanConverter converter, Object o, String key) {
72 return o.getClass().getSimpleName() + "." + key;
73 }
74 };
75
76 BeanConverter converter = BasicBeanConverter.DEFAULT;
77 String result = (String) impl.extract(converter, "test", "length");
78
79 assertEquals("String.length", result);
80 }
81
82 @Test
83 void a03_concreteClassImplementation() {
84
85 var concrete = new PrefixPropertyExtractor();
86
87 var converter = BasicBeanConverter.DEFAULT;
88 var result = (String) concrete.extract(converter, "test", "prop");
89
90 assertEquals("PREFIX:prop", result);
91 }
92
93 @Test
94 void a04_canExtractMethodContract() {
95 var extractor = new PropertyExtractor() {
96 @Override
97 public boolean canExtract(BeanConverter converter, Object o, String key) {
98 return true;
99 }
100
101 @Override
102 public Object extract(BeanConverter converter, Object o, String key) {
103 return key.toUpperCase();
104 }
105 };
106
107 var converter = BasicBeanConverter.DEFAULT;
108
109
110 assertTrue(extractor.canExtract(converter, "test", "any"));
111 assertTrue(extractor.canExtract(converter, null, "any"));
112 assertTrue(extractor.canExtract(converter, new Object(), "any"));
113 }
114 }
115
116
117
118
119
120 @Nested
121 class B_customImplementations extends TestBase {
122
123 @Test
124 void b01_customCanExtractLogic() {
125 var selective = new PropertyExtractor() {
126 @Override
127 public boolean canExtract(BeanConverter converter, Object o, String key) {
128 return o instanceof String && key.startsWith("str");
129 }
130
131 @Override
132 public Object extract(BeanConverter converter, Object o, String key) {
133 return "STRING_PROP:" + key;
134 }
135 };
136
137 var converter = BasicBeanConverter.DEFAULT;
138
139
140 assertTrue(selective.canExtract(converter, "test", "string"));
141 assertTrue(selective.canExtract(converter, "test", "str"));
142 assertFalse(selective.canExtract(converter, "test", "other"));
143 assertFalse(selective.canExtract(converter, 123, "string"));
144
145 assertEquals("STRING_PROP:string", selective.extract(converter, "test", "string"));
146 }
147
148 @Test
149 void b02_nullHandlingExtractor() {
150 var nullSafe = new PropertyExtractor() {
151 @Override
152 public boolean canExtract(BeanConverter converter, Object o, String key) {
153 return true;
154 }
155
156 @Override
157 public Object extract(BeanConverter converter, Object o, String key) {
158 if (o == null) return "NULL_OBJECT";
159 if (key == null) return "NULL_PROPERTY";
160 return o.toString() + ":" + key;
161 }
162 };
163
164 var converter = BasicBeanConverter.DEFAULT;
165
166 assertEquals("NULL_OBJECT", nullSafe.extract(converter, null, "any"));
167 assertEquals("NULL_PROPERTY", nullSafe.extract(converter, "obj", null));
168 assertEquals("test:prop", nullSafe.extract(converter, "test", "prop"));
169 }
170
171 @Test
172 void b03_typeSpecificExtractor() {
173 var numberExtractor = new PropertyExtractor() {
174 @Override
175 public boolean canExtract(BeanConverter converter, Object o, String key) {
176 return o instanceof Number;
177 }
178
179 @Override
180 public Object extract(BeanConverter converter, Object o, String key) {
181 if (o instanceof Number) {
182 switch (key) {
183 case "doubled": return ((Number) o).doubleValue() * 2;
184 case "string": return o.toString();
185 case "type": return o.getClass().getSimpleName();
186 default: return "UNKNOWN_PROP:" + key;
187 }
188 }
189 return "NOT_A_NUMBER";
190 }
191 };
192
193 var converter = BasicBeanConverter.DEFAULT;
194
195 assertEquals(84.0, numberExtractor.extract(converter, 42, "doubled"));
196 assertEquals("42", numberExtractor.extract(converter, 42, "string"));
197 assertEquals("Integer", numberExtractor.extract(converter, 42, "type"));
198 assertEquals("UNKNOWN_PROP:other", numberExtractor.extract(converter, 42, "other"));
199 assertEquals("NOT_A_NUMBER", numberExtractor.extract(converter, "string", "doubled"));
200 }
201 }
202
203
204
205
206
207 @Nested
208 class C_edgeCases extends TestBase {
209
210 @Test
211 void c01_exceptionHandling() {
212 var throwing = new PropertyExtractor() {
213 @Override
214 public boolean canExtract(BeanConverter converter, Object o, String key) {
215 return true;
216 }
217
218 @Override
219 public Object extract(BeanConverter converter, Object o, String key) {
220 if ("error".equals(key)) {
221 throw new RuntimeException("Intentional test exception");
222 }
223 return "SUCCESS:" + key;
224 }
225 };
226
227 var converter = BasicBeanConverter.DEFAULT;
228
229
230 assertEquals("SUCCESS:normal", throwing.extract(converter, "obj", "normal"));
231
232
233 assertThrows(RuntimeException.class, () -> throwing.extract(converter, "obj", "error"));
234 }
235
236 @Test
237 void c02_recursiveExtraction() {
238 var recursive = new PropertyExtractor() {
239 @Override
240 public boolean canExtract(BeanConverter converter, Object o, String key) {
241 return o instanceof String && "recursive".equals(key);
242 }
243
244 @Override
245 public Object extract(BeanConverter converter, Object o, String key) {
246 if ("recursive".equals(key) && o instanceof String) {
247
248 String str = (String) o;
249 return "RECURSIVE[" + converter.stringify(str.length()) + "]";
250 }
251 return "NON_RECURSIVE:" + key;
252 }
253 };
254
255 var converter = BasicBeanConverter.DEFAULT;
256
257 assertEquals("RECURSIVE[4]", recursive.extract(converter, "test", "recursive"));
258 assertEquals("NON_RECURSIVE:other", recursive.extract(converter, "test", "other"));
259 }
260
261 @Test
262 void c03_complexObjectExtraction() {
263 var complex = new PropertyExtractor() {
264 @Override
265 public boolean canExtract(BeanConverter converter, Object o, String key) {
266 return o instanceof Map;
267 }
268
269 @Override
270 public Object extract(BeanConverter converter, Object o, String key) {
271 if (o instanceof Map) {
272 Map<?, ?> map = (Map<?, ?>) o;
273 switch (key) {
274 case "keys": return new ArrayList<>(map.keySet());
275 case "values": return new ArrayList<>(map.values());
276 case "entries": return map.entrySet().size();
277 default: return map.get(key);
278 }
279 }
280 return "NOT_A_MAP";
281 }
282 };
283
284 var converter = BasicBeanConverter.DEFAULT;
285 var testMap = Map.of("a", "valueA", "b", "valueB");
286
287 var keys = (List<String>) complex.extract(converter, testMap, "keys");
288 assertEquals(2, keys.size());
289 assertTrue(keys.contains("a"));
290 assertTrue(keys.contains("b"));
291
292 assertEquals(2, complex.extract(converter, testMap, "entries"));
293 assertEquals("valueA", complex.extract(converter, testMap, "a"));
294 assertEquals("NOT_A_MAP", complex.extract(converter, "string", "keys"));
295 }
296 }
297
298
299
300
301
302 @Nested
303 class D_integration extends TestBase {
304
305 @Test
306 void d01_integrationWithBasicBeanConverter() {
307
308 var customExtractor = new PropertyExtractor() {
309 @Override
310 public boolean canExtract(BeanConverter converter, Object o, String key) {
311 return "customProp".equals(key);
312 }
313
314 @Override
315 public Object extract(BeanConverter converter, Object o, String key) {
316 return "CUSTOM[" + o.getClass().getSimpleName() + "." + key + "]";
317 }
318 };
319
320 var customConverter = BasicBeanConverter.builder()
321 .defaultSettings()
322 .addPropertyExtractor(customExtractor)
323 .build();
324
325
326 var bean = new TestBean("test", 42);
327 var result = customConverter.getNested(bean, Utils.tokenize("customProp").get(0));
328
329
330 assertEquals("CUSTOM[TestBean.customProp]", result);
331 }
332
333 @Test
334 void d02_multipleExtractorPriority() {
335 var first = new PropertyExtractor() {
336 @Override
337 public boolean canExtract(BeanConverter converter, Object o, String key) {
338 return "first".equals(key);
339 }
340
341 @Override
342 public Object extract(BeanConverter converter, Object o, String key) {
343 return "FIRST_EXTRACTOR";
344 }
345 };
346
347 var second = new PropertyExtractor() {
348 @Override
349 public boolean canExtract(BeanConverter converter, Object o, String key) {
350 return "second".equals(key);
351 }
352
353 @Override
354 public Object extract(BeanConverter converter, Object o, String key) {
355 return "SECOND_EXTRACTOR";
356 }
357 };
358
359 var converter = BasicBeanConverter.builder()
360 .defaultSettings()
361 .addPropertyExtractor(first)
362 .addPropertyExtractor(second)
363 .build();
364
365
366 var bean = new TestBean("test", 42);
367
368 assertEquals("FIRST_EXTRACTOR", converter.getNested(bean, Utils.tokenize("first").get(0)));
369 assertEquals("SECOND_EXTRACTOR", converter.getNested(bean, Utils.tokenize("second").get(0)));
370 }
371
372 @Test
373 void d03_fallbackToDefaultExtractors() {
374 var custom = new PropertyExtractor() {
375 @Override
376 public boolean canExtract(BeanConverter converter, Object o, String key) {
377 return "custom".equals(key);
378 }
379
380 @Override
381 public Object extract(BeanConverter converter, Object o, String key) {
382 return "CUSTOM_VALUE";
383 }
384 };
385
386 var converter = BasicBeanConverter.builder()
387 .defaultSettings()
388 .addPropertyExtractor(custom)
389 .build();
390
391 var bean = new TestBean("test", 42);
392
393
394 assertEquals("CUSTOM_VALUE", converter.getNested(bean, Utils.tokenize("custom").get(0)));
395
396
397 assertEquals("test", converter.getNested(bean, Utils.tokenize("name").get(0)));
398 assertEquals("42", converter.getNested(bean, Utils.tokenize("value").get(0)));
399 }
400 }
401
402
403
404
405
406 static class PrefixPropertyExtractor implements PropertyExtractor {
407 @Override
408 public boolean canExtract(BeanConverter converter, Object o, String key) {
409 return true;
410 }
411
412 @Override
413 public Object extract(BeanConverter converter, Object o, String key) {
414 return "PREFIX:" + key;
415 }
416 }
417
418 static class TestBean {
419 final String name;
420 final int value;
421
422 TestBean(String name, int value) {
423 this.name = name;
424 this.value = value;
425 }
426
427 public String getName() { return name; }
428 public int getValue() { return value; }
429 }
430 }