1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.beanutils2;
18
19 import java.beans.BeanInfo;
20 import java.beans.IntrospectionException;
21 import java.beans.Introspector;
22 import java.beans.PropertyDescriptor;
23 import java.lang.reflect.Constructor;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.util.AbstractMap;
27 import java.util.AbstractSet;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.function.Function;
36
37
38
39
40
41
42
43 public class BeanMap extends AbstractMap<String, Object> implements Cloneable {
44
45
46
47
48 protected static class Entry extends AbstractMap.SimpleEntry<String, Object> {
49
50 private static final long serialVersionUID = 1L;
51
52
53
54
55 private final BeanMap owner;
56
57
58
59
60
61
62
63
64 protected Entry(final BeanMap owner, final String key, final Object value) {
65 super(key, value);
66 this.owner = owner;
67 }
68
69
70
71
72
73
74
75 @Override
76 public Object setValue(final Object value) {
77 final String key = getKey();
78 final Object oldValue = owner.get(key);
79
80 owner.put(key, value);
81 final Object newValue = owner.get(key);
82 super.setValue(newValue);
83 return oldValue;
84 }
85 }
86
87
88
89
90 public static final Object[] NULL_ARGUMENTS = {};
91
92
93
94
95
96
97 private static final Map<Class<? extends Object>, Function<?, ?>> typeTransformers = Collections.unmodifiableMap(createTypeTransformers());
98
99 private static Map<Class<? extends Object>, Function<?, ?>> createTypeTransformers() {
100 final Map<Class<? extends Object>, Function<?, ?>> defTransformers = new HashMap<>();
101 defTransformers.put(Boolean.TYPE, input -> Boolean.valueOf(input.toString()));
102 defTransformers.put(Character.TYPE, input -> Character.valueOf(input.toString().charAt(0)));
103 defTransformers.put(Byte.TYPE, input -> Byte.valueOf(input.toString()));
104 defTransformers.put(Short.TYPE, input -> Short.valueOf(input.toString()));
105 defTransformers.put(Integer.TYPE, input -> Integer.valueOf(input.toString()));
106 defTransformers.put(Long.TYPE, input -> Long.valueOf(input.toString()));
107 defTransformers.put(Float.TYPE, input -> Float.valueOf(input.toString()));
108 defTransformers.put(Double.TYPE, input -> Double.valueOf(input.toString()));
109 return defTransformers;
110 }
111
112 private transient Object bean;
113
114 private final transient HashMap<String, Method> readMethods = new HashMap<>();
115
116 private final transient HashMap<String, Method> writeMethods = new HashMap<>();
117
118 private final transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
119
120
121
122
123 public BeanMap() {
124 }
125
126
127
128
129
130
131
132
133 public BeanMap(final Object bean) {
134 this.bean = bean;
135 initialize();
136 }
137
138
139
140
141
142
143 @Override
144 public void clear() {
145 if (bean == null) {
146 return;
147 }
148 Class<? extends Object> beanClass = null;
149 try {
150 beanClass = bean.getClass();
151 bean = beanClass.newInstance();
152 } catch (final Exception e) {
153 throw new UnsupportedOperationException("Could not create new instance of class: " + beanClass, e);
154 }
155 }
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 @Override
173 public Object clone() throws CloneNotSupportedException {
174 final BeanMap newMap = (BeanMap) super.clone();
175 if (bean == null) {
176
177
178 return newMap;
179 }
180 Object newBean = null;
181 final Class<? extends Object> beanClass = bean.getClass();
182 try {
183 newBean = beanClass.newInstance();
184 } catch (final Exception e) {
185
186 final CloneNotSupportedException cnse = new CloneNotSupportedException(
187 "Unable to instantiate the underlying bean \"" + beanClass.getName() + "\": " + e);
188 cnse.initCause(e);
189 throw cnse;
190 }
191 try {
192 newMap.setBean(newBean);
193 } catch (final Exception e) {
194 final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to set bean in the cloned bean map: " + e);
195 cnse.initCause(e);
196 throw cnse;
197 }
198 try {
199
200
201
202 readMethods.keySet().forEach(key -> {
203 if (getWriteMethod(key) != null) {
204 newMap.put(key, get(key));
205 }
206 });
207 } catch (final Exception e) {
208 final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to copy bean values to cloned bean map: " + e);
209 cnse.initCause(e);
210 throw cnse;
211 }
212 return newMap;
213 }
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 @Override
230 public boolean containsKey(final Object name) {
231 return getReadMethod(name) != null;
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 protected <R> Object convertType(final Class<R> newType, final Object value)
258 throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
259
260
261 try {
262 final Constructor<R> constructor = newType.getConstructor(value.getClass());
263 return constructor.newInstance(value);
264 } catch (final NoSuchMethodException e) {
265
266 final Function<Object, R> transformer = getTypeTransformer(newType);
267 if (transformer != null) {
268 return transformer.apply(value);
269 }
270 return value;
271 }
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285 protected Object[] createWriteMethodArguments(final Method method, Object value) throws IllegalAccessException, ClassCastException {
286 try {
287 if (value != null) {
288 final Class<? extends Object>[] paramTypes = method.getParameterTypes();
289 if (paramTypes != null && paramTypes.length > 0) {
290 final Class<? extends Object> paramType = paramTypes[0];
291 if (!paramType.isAssignableFrom(value.getClass())) {
292 value = convertType(paramType, value);
293 }
294 }
295 }
296
297 return new Object[] { value };
298 } catch (final InvocationTargetException | InstantiationException e) {
299 throw new IllegalArgumentException(e.getMessage(), e);
300 }
301 }
302
303
304
305
306
307
308 public Iterator<Map.Entry<String, Object>> entryIterator() {
309 final Iterator<String> iter = keyIterator();
310 return new Iterator<Map.Entry<String, Object>>() {
311 @Override
312 public boolean hasNext() {
313 return iter.hasNext();
314 }
315
316 @Override
317 public Map.Entry<String, Object> next() {
318 final String key = iter.next();
319 final Object value = get(key);
320
321
322 return new Entry(BeanMap.this, key, value);
323 }
324
325 @Override
326 public void remove() {
327 throw new UnsupportedOperationException("remove() not supported for BeanMap");
328 }
329 };
330 }
331
332
333
334
335
336
337
338
339
340 @Override
341 public Set<Map.Entry<String, Object>> entrySet() {
342 return Collections.unmodifiableSet(new AbstractSet<Map.Entry<String, Object>>() {
343 @Override
344 public Iterator<Map.Entry<String, Object>> iterator() {
345 return entryIterator();
346 }
347
348 @Override
349 public int size() {
350 return BeanMap.this.readMethods.size();
351 }
352 });
353 }
354
355
356
357
358
359
360
361
362
363 protected void firePropertyChange(final Object key, final Object oldValue, final Object newValue) {
364
365 }
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 @Override
381 public Object get(final Object name) {
382 if (bean != null) {
383 final Method method = getReadMethod(name);
384 if (method != null) {
385 try {
386 return method.invoke(bean, NULL_ARGUMENTS);
387 } catch (final IllegalAccessException | NullPointerException | InvocationTargetException | IllegalArgumentException e) {
388 logWarn(e);
389 }
390 }
391 }
392 return null;
393 }
394
395
396
397
398
399
400 public Object getBean() {
401 return bean;
402 }
403
404
405
406
407
408
409
410
411
412 protected Method getReadMethod(final Object name) {
413 return readMethods.get(name);
414 }
415
416
417
418
419
420
421
422 public Method getReadMethod(final String name) {
423 return readMethods.get(name);
424 }
425
426
427
428
429
430
431
432 public Class<?> getType(final String name) {
433 return types.get(name);
434 }
435
436
437
438
439
440
441
442
443 protected <R> Function<Object, R> getTypeTransformer(final Class<R> type) {
444 return (Function<Object, R>) typeTransformers.get(type);
445 }
446
447
448
449
450
451
452
453
454 protected Method getWriteMethod(final Object name) {
455 return writeMethods.get(name);
456 }
457
458
459
460
461
462
463
464 public Method getWriteMethod(final String name) {
465 return writeMethods.get(name);
466 }
467
468 private void initialize() {
469 if (getBean() == null) {
470 return;
471 }
472
473 final Class<? extends Object> beanClass = getBean().getClass();
474 try {
475
476 final BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
477 final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
478 if (propertyDescriptors != null) {
479 for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
480 if (propertyDescriptor != null) {
481 final String name = propertyDescriptor.getName();
482 final Method readMethod = propertyDescriptor.getReadMethod();
483 final Method writeMethod = propertyDescriptor.getWriteMethod();
484 final Class<? extends Object> aType = propertyDescriptor.getPropertyType();
485
486 if (readMethod != null) {
487 readMethods.put(name, readMethod);
488 }
489 if (writeMethod != null) {
490 writeMethods.put(name, writeMethod);
491 }
492 types.put(name, aType);
493 }
494 }
495 }
496 } catch (final IntrospectionException e) {
497 logWarn(e);
498 }
499 }
500
501
502
503
504
505
506
507
508
509 public Iterator<String> keyIterator() {
510 return readMethods.keySet().iterator();
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524 @SuppressWarnings({ "unchecked", "rawtypes" })
525
526
527 @Override
528 public Set<String> keySet() {
529 return Collections.unmodifiableSet((Set) readMethods.keySet());
530 }
531
532
533
534
535
536
537 protected void logInfo(final Exception ex) {
538
539 System.out.println("INFO: Exception: " + ex);
540 }
541
542
543
544
545
546
547 protected void logWarn(final Exception ex) {
548
549 System.out.println("WARN: Exception: " + ex);
550 ex.printStackTrace();
551 }
552
553
554
555
556
557
558
559
560
561
562
563 @Override
564 public Object put(final String name, final Object value) throws IllegalArgumentException, ClassCastException {
565 if (bean != null) {
566 final Object oldValue = get(name);
567 final Method method = getWriteMethod(name);
568 if (method == null) {
569 throw new IllegalArgumentException("The bean of type: " + bean.getClass().getName() + " has no property called: " + name);
570 }
571 try {
572 final Object[] arguments = createWriteMethodArguments(method, value);
573 method.invoke(bean, arguments);
574
575 final Object newValue = get(name);
576 firePropertyChange(name, oldValue, newValue);
577 } catch (final InvocationTargetException | IllegalAccessException e) {
578 throw new IllegalArgumentException(e.getMessage(), e);
579 }
580 return oldValue;
581 }
582 return null;
583 }
584
585
586
587
588
589
590 public void putAllWriteable(final BeanMap map) {
591 map.readMethods.keySet().forEach(key -> {
592 if (getWriteMethod(key) != null) {
593 put(key, map.get(key));
594 }
595 });
596 }
597
598
599
600
601
602
603 protected void reinitialise() {
604 readMethods.clear();
605 writeMethods.clear();
606 types.clear();
607 initialize();
608 }
609
610
611
612
613
614
615 public void setBean(final Object newBean) {
616 bean = newBean;
617 reinitialise();
618 }
619
620
621
622
623
624
625 @Override
626 public int size() {
627 return readMethods.size();
628 }
629
630
631
632
633
634
635 @Override
636 public String toString() {
637 return "BeanMap<" + bean + ">";
638 }
639
640
641
642
643
644
645 public Iterator<Object> valueIterator() {
646 final Iterator<?> iter = keyIterator();
647 return new Iterator<Object>() {
648 @Override
649 public boolean hasNext() {
650 return iter.hasNext();
651 }
652
653 @Override
654 public Object next() {
655 final Object key = iter.next();
656 return get(key);
657 }
658
659 @Override
660 public void remove() {
661 throw new UnsupportedOperationException("remove() not supported for BeanMap");
662 }
663 };
664 }
665
666
667
668
669
670
671 @Override
672 public Collection<Object> values() {
673 final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
674 valueIterator().forEachRemaining(answer::add);
675 return Collections.unmodifiableList(answer);
676 }
677 }