1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.nuiton.util.beans;
24
25 import com.google.common.base.Defaults;
26 import com.google.common.base.Function;
27 import com.google.common.base.Preconditions;
28 import org.apache.commons.lang3.ObjectUtils;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 import java.beans.PropertyDescriptor;
33 import java.io.Serializable;
34 import java.lang.reflect.Method;
35 import java.lang.reflect.Type;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedHashSet;
43 import java.util.LinkedList;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.TreeMap;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public class Binder<I, O> implements Serializable {
72
73
74 private static final Log log = LogFactory.getLog(Binder.class);
75
76 private static final long serialVersionUID = 1L;
77
78
79
80
81
82
83 public enum CollectionStrategy {
84
85
86 copy {
87 public Object copy(Object readValue) {
88
89
90 return readValue;
91 }
92 },
93
94
95 duplicate {
96 @SuppressWarnings({"unchecked"})
97 @Override
98 public Object copy(Object readValue) {
99 if (readValue instanceof LinkedHashSet<?>) {
100 return new LinkedHashSet((Set<?>) readValue);
101 }
102 if (readValue instanceof Set<?>) {
103 return new HashSet((Set<?>) readValue);
104 }
105
106 if (readValue instanceof Collection<?>) {
107 return new ArrayList((Collection<?>) readValue);
108 }
109 return readValue;
110 }
111 },
112
113
114
115
116
117
118
119
120 bind {
121 @Override
122 public Object copy(Object readValue) {
123 if (readValue instanceof LinkedHashSet<?>) {
124 return new LinkedHashSet();
125 }
126 if (readValue instanceof Set<?>) {
127 return new HashSet();
128 }
129
130 if (readValue instanceof Collection<?>) {
131 return new ArrayList();
132 }
133 return readValue;
134 }
135 };
136
137
138
139
140
141
142
143 public abstract Object copy(Object readValue);
144 }
145
146
147 protected BinderModel<I, O> model;
148
149
150
151
152
153
154 public Class<I> getSourceType() {
155 return getModel().getSourceType();
156 }
157
158
159
160
161
162
163 public Class<O> getTargetType() {
164 return getModel().getTargetType();
165 }
166
167
168
169
170
171
172
173
174
175
176
177
178 public Map<String, Object> obtainProperties(I source,
179 boolean keepPrimitiveDefaultValues,
180 boolean includeNullValues,
181 String... propertyNames) {
182 if (source == null) {
183
184 return Collections.emptyMap();
185 }
186
187 propertyNames = getProperties(propertyNames);
188
189 Map<String, Object> result = new TreeMap<String, Object>();
190 for (String sourceProperty : propertyNames) {
191
192 try {
193 Object read;
194 Method readMethod = model.getSourceReadMethod(sourceProperty);
195 read = readMethod.invoke(source);
196 if (log.isDebugEnabled()) {
197 log.debug("property " + sourceProperty + ", type : " +
198 readMethod.getReturnType() + ", value = " + read);
199 }
200 if (readMethod.getReturnType().isPrimitive()
201 && !keepPrimitiveDefaultValues
202 && Defaults.defaultValue(readMethod.getReturnType()).equals(read)) {
203
204 read = null;
205 }
206 if (read != null) {
207 if (model.containsBinderProperty(sourceProperty)) {
208 if (model.containsCollectionProperty(sourceProperty)) {
209 read = bindCollection(sourceProperty, read);
210 } else {
211 read = bindProperty(sourceProperty, read);
212 }
213 } else if (model.containsCollectionProperty(sourceProperty)) {
214
215
216 read = getCollectionValue(sourceProperty, read);
217 }
218 }
219
220 boolean include = read != null || includeNullValues;
221 if (include) {
222 result.put(sourceProperty, read);
223 }
224 } catch (Exception e) {
225 throw new RuntimeException("Could not obtain property: " + sourceProperty, e);
226 }
227 }
228 return result;
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243 public Map<String, Object> obtainProperties(I source,
244 boolean includeNullValues, String... propertyNames) {
245
246 return obtainProperties(source, false, includeNullValues, propertyNames);
247 }
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public Map<String, Object> obtainProperties(I source,
262 String... propertyNames) {
263 return obtainProperties(source, false, propertyNames);
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 public <OO> OO obtainSourceProperty(I source, String propertyName) {
280
281 Preconditions.checkNotNull(source, "source can not be null");
282 Preconditions.checkNotNull(propertyName, "propertyName can not be null");
283
284 Method readMethod = model.getSourceReadMethod(propertyName);
285
286 Preconditions.checkNotNull(readMethod, "Could not find source getter for property: " + propertyName);
287
288 try {
289 OO result = (OO) readMethod.invoke(source);
290 if (log.isDebugEnabled()) {
291 log.debug("property " + propertyName + ", type : " +
292 readMethod.getReturnType() + ", value = " + result);
293 }
294 return result;
295
296 } catch (Exception e) {
297 throw new RuntimeException("Could not obtain property: " + propertyName, e);
298 }
299 }
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314 public <OO> OO obtainTargetProperty(O target, String propertyName) {
315
316 Preconditions.checkNotNull(target, "target can not be null");
317 Preconditions.checkNotNull(propertyName, "propertyName can not be null");
318
319 Method readMethod = model.getTargetReadMethod(propertyName);
320
321 Preconditions.checkNotNull(readMethod, "Could not find target getter for property: " + propertyName);
322
323 try {
324 OO result = (OO) readMethod.invoke(target);
325 if (log.isDebugEnabled()) {
326 log.debug("property " + propertyName + ", type : " +
327 readMethod.getReturnType() + ", value = " + result);
328 }
329 return result;
330
331 } catch (Exception e) {
332 throw new RuntimeException("Could not obtain property: " + propertyName, e);
333 }
334
335 }
336
337
338
339
340
341
342
343
344 public void injectProperties(Map<String, Object> properties, O target) {
345 injectProperties(properties, target, false);
346 }
347
348
349
350
351
352
353
354
355
356 public void injectProperties(Map<String, Object> properties, O target, boolean includeNullValues) {
357
358 boolean useFunctions = model.isUseFunctions();
359
360 for (Map.Entry<String, Object> entry : properties.entrySet()) {
361 String propertyName = entry.getKey();
362 if (!getModel().containsTargetProperty(propertyName)) {
363
364 throw new IllegalStateException("Could not find property '" + propertyName + "' in binder " + this + ".");
365 }
366
367 Object propertyValue = entry.getValue();
368 if (propertyValue == null && !includeNullValues) {
369
370
371 continue;
372 }
373
374 if (log.isDebugEnabled()) {
375 log.debug("Inject property: " + propertyName + " to " + target);
376 }
377 if (useFunctions && propertyValue != null) {
378 propertyValue = transform(propertyName, propertyValue);
379 }
380 if (propertyValue == null) {
381 Class<?> targetPropertyType = getTargetPropertyType(propertyName);
382 if (targetPropertyType.isPrimitive()) {
383 propertyValue = Defaults.defaultValue(targetPropertyType);
384 }
385 }
386 Method writeMethod = getModel().getTargetWriteMethod(propertyName);
387 try {
388 writeMethod.invoke(target, propertyValue);
389 } catch (Exception e) {
390 throw new RuntimeException(
391 "Could not set property [" +
392 target.getClass().getName() + ":" +
393 propertyName + "]", e);
394 }
395
396 }
397
398 }
399
400 protected Object transform(String propertyName, Object propertyValue) {
401 Function function = model.getFunction(propertyValue.getClass());
402 if (function != null) {
403 if (log.isDebugEnabled()) {
404 log.debug("Transform property: " + propertyName);
405 }
406 propertyValue = function.apply(propertyValue);
407 }
408 return propertyValue;
409 }
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 public void copy(I source, O target, String... propertyNames) {
427 copy(source, target, false, propertyNames);
428 }
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444 public void copyExcluding(I source, O target, String... propertyNames) {
445 copy(source, target, true, propertyNames);
446 }
447
448
449
450
451
452
453
454
455 public Class<?> getSourcePropertyType(String propertyName) {
456 if (!model.containsSourceProperty(propertyName)) {
457 throw new IllegalArgumentException("Binder " + this + " does not contains source property: " + propertyName);
458 }
459 return model.getSourceReadMethod(propertyName).getReturnType();
460 }
461
462
463
464
465
466
467
468
469 public Type getSourcePropertyGenericType(String propertyName) {
470 if (!model.containsSourceProperty(propertyName)) {
471 throw new IllegalArgumentException("Binder " + this + " does not contains source property: " + propertyName);
472 }
473 return model.getSourceReadMethod(propertyName).getGenericReturnType();
474 }
475
476
477
478
479
480
481
482
483 public Class<?> getTargetPropertyType(String propertyName) {
484 if (!model.containsTargetProperty(propertyName)) {
485 throw new IllegalArgumentException("Binder " + this + " does not contains target property: " + propertyName);
486 }
487 return model.getTargetWriteMethod(propertyName).getParameterTypes()[0];
488 }
489
490
491
492
493
494
495
496
497 public Type getTargetPropertyGenericType(String propertyName) {
498 if (!model.containsTargetProperty(propertyName)) {
499 throw new IllegalArgumentException("Binder " + this + " does not contains target property: " + propertyName);
500 }
501 return model.getTargetWriteMethod(propertyName).getGenericParameterTypes()[0];
502 }
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520 protected void copy(I source, O target, boolean excludeProperties,
521 String... propertyNames)
522 throws RuntimeException {
523 if (target == null) {
524 throw new NullPointerException("parameter 'target' can no be null");
525 }
526
527 propertyNames = excludeProperties ?
528 getAllPropertiesExclude(propertyNames) :
529 getProperties(propertyNames);
530
531 boolean useFunctions = model.isUseFunctions();
532
533 for (String sourceProperty : propertyNames) {
534
535 String targetProperty = model.getTargetProperty(sourceProperty);
536
537 try {
538 Object read = null;
539 Method readMethod = model.getSourceReadMethod(sourceProperty);
540 if (source != null) {
541
542 read = readMethod.invoke(source);
543 }
544
545
546 if (read == null) {
547 read = Defaults.defaultValue(readMethod.getReturnType());
548 }
549 if (log.isDebugEnabled()) {
550 log.debug("property " + sourceProperty + ", type : " +
551 readMethod.getReturnType() + ", value = " + read);
552 }
553
554 if (model.containsBinderProperty(sourceProperty)) {
555 if (model.containsCollectionProperty(sourceProperty)) {
556 read = bindCollection(sourceProperty, read);
557 } else {
558 read = bindProperty(sourceProperty, read);
559 }
560 } else if (model.containsCollectionProperty(sourceProperty)) {
561
562
563 read = getCollectionValue(sourceProperty, read);
564 }
565 if (useFunctions && read != null) {
566 read = transform(sourceProperty, read);
567 }
568 model.getTargetWriteMethod(targetProperty).invoke(target, read);
569 } catch (Exception e) {
570 throw new RuntimeException(
571 "Could not bind property [" +
572 source.getClass().getName() + ":" +
573 sourceProperty + "] to [" +
574 target.getClass().getName() + ":" +
575 targetProperty + "]", e);
576 }
577 }
578 }
579
580 protected Object readProperty(String sourceProperty, Object source,
581 Method readMethod) {
582 try {
583 Object read = null;
584 if (source != null) {
585
586 read = readMethod.invoke(source);
587 }
588
589
590 if (read == null) {
591 read = Defaults.defaultValue(readMethod.getReturnType());
592 }
593 if (log.isDebugEnabled()) {
594 log.debug("property " + sourceProperty + ", type : " +
595 readMethod.getReturnType() + ", value = " + read);
596 }
597
598 if (model.containsBinderProperty(sourceProperty)) {
599 if (model.containsCollectionProperty(sourceProperty)) {
600 read = bindCollection(sourceProperty, read);
601 } else {
602 read = bindProperty(sourceProperty, read);
603 }
604 } else if (model.containsCollectionProperty(sourceProperty)) {
605
606
607 read = getCollectionValue(sourceProperty, read);
608 }
609
610 return read;
611 } catch (Exception e) {
612 throw new RuntimeException(
613 "could not read property " + sourceProperty +
614 " on source " + source);
615 }
616 }
617
618 protected List<PropertyDiff> diff(I source,
619 O target,
620 boolean excludeProperties,
621 String... propertyNames) {
622 if (source == null) {
623 throw new NullPointerException("parameter 'source' can no be null");
624 }
625 if (target == null) {
626 throw new NullPointerException("parameter 'target' can no be null");
627 }
628
629 propertyNames = excludeProperties ?
630 getAllPropertiesExclude(propertyNames) :
631 getProperties(propertyNames);
632
633 List<PropertyDiff> result = new LinkedList<PropertyDiff>();
634
635 for (String sourceProperty : propertyNames) {
636
637 Method sourceReadMethod = model.getSourceReadMethod(sourceProperty);
638
639 Object sourceRead = readProperty(sourceProperty, source,
640 sourceReadMethod);
641
642 String targetProperty = model.getTargetProperty(sourceProperty);
643
644 Method targetReadMethod = model.getTargetReadMethod(targetProperty);
645
646 Object targetRead = readProperty(targetProperty, target,
647 targetReadMethod);
648
649 if (ObjectUtils.notEqual(sourceRead, targetRead)) {
650 PropertyDiff propertyDiff = new PropertyDiff(
651 sourceProperty,
652 sourceRead,
653 targetProperty,
654 targetRead,
655 sourceReadMethod.getReturnType()
656 );
657 result.add(propertyDiff);
658 }
659 }
660
661 return result;
662 }
663
664
665
666
667
668
669
670
671
672
673
674
675
676 public List<PropertyDiff> diff(I source, O target) {
677 return diff(source, target, false);
678 }
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694 public List<PropertyDiff> diffExcluding(I source,
695 O target,
696 String... propertyNames) {
697 return diff(source, target, true, propertyNames);
698 }
699
700
701
702
703
704
705 protected BinderModel<I, O> getModel() {
706 return model;
707 }
708
709
710
711
712
713
714 protected void setModel(BinderModel<I, O> model) {
715 this.model = model;
716 }
717
718
719
720
721
722
723
724
725
726
727 protected String[] getProperties(String... propertyNames) {
728
729 if (propertyNames.length == 0) {
730
731 propertyNames = model.getSourceDescriptors();
732 } else {
733
734
735 for (String propertyName : propertyNames) {
736 if (!model.containsSourceProperty(propertyName)) {
737 throw new IllegalArgumentException(
738 "property '" + propertyName +
739 "' is not known by binder");
740 }
741 }
742 }
743 return propertyNames;
744 }
745
746
747
748
749
750
751
752
753 protected String[] getAllPropertiesExclude(String... propertyNameExcludes) {
754 List<String> excludes = Arrays.asList(propertyNameExcludes);
755 List<String> results = new ArrayList<String>();
756 for (String propertyName : model.getSourceDescriptors()) {
757 if (!excludes.contains(propertyName)) {
758 results.add(propertyName);
759 }
760 }
761 return results.toArray(new String[results.size()]);
762 }
763
764 protected Object getCollectionValue(String sourceProperty, Object readValue) {
765 CollectionStrategy strategy =
766 model.getCollectionStrategy(sourceProperty);
767 Object result = strategy.copy(readValue);
768 return result;
769 }
770
771 protected Object bindProperty(String sourceProperty, Object read) throws IllegalAccessException, InstantiationException {
772 Binder<?, ?> binder = model.getBinder(sourceProperty);
773 Object result = bind(binder, read);
774 return result;
775 }
776
777 @SuppressWarnings({"unchecked"})
778 protected Object bindCollection(String sourceProperty, Object read) throws IllegalAccessException, InstantiationException {
779
780 if (read == null) {
781 return null;
782 }
783
784 Binder<?, ?> binder = model.getBinder(sourceProperty);
785
786 Collection result = (Collection) model.getCollectionStrategy(sourceProperty).copy(read);
787
788
789
790
791
792
793
794
795
796
797 Collection<?> collection = (Collection<?>) read;
798 for (Object o : collection) {
799 Object r = bind(binder, o);
800 result.add(r);
801 }
802 return result;
803 }
804
805 @SuppressWarnings({"unchecked"})
806 protected Object bind(Binder binder, Object read) throws IllegalAccessException, InstantiationException {
807 Object result = null;
808 if (read != null) {
809 InstanceFactory<O> instanceFactory = binder.model.getInstanceFactory();
810 if (instanceFactory == null) {
811 result = read.getClass().newInstance();
812 } else {
813 result = instanceFactory.newInstance();
814 }
815 binder.copy(read, result);
816 }
817 return result;
818 }
819
820
821
822
823
824
825
826
827
828
829
830 public static class BinderModel<S, T> implements Serializable {
831
832
833 protected final Class<S> sourceType;
834
835
836 protected final Class<T> targetType;
837
838
839 protected final Map<String, PropertyDescriptor> sourceDescriptors;
840
841
842 protected final Map<String, PropertyDescriptor> targetDescriptors;
843
844
845
846
847
848 protected final Map<String, String> propertiesMapping;
849
850
851 protected Map<String, CollectionStrategy> collectionStrategies;
852
853
854 protected Map<String, Binder<?, ?>> binders;
855
856
857 protected InstanceFactory<T> instanceFactory;
858
859
860
861
862 protected final Map<Class<?>, Function<?, ?>> functions;
863
864 private static final long serialVersionUID = 2L;
865
866 public BinderModel(Class<S> sourceType, Class<T> targetType) {
867 this.sourceType = sourceType;
868 this.targetType = targetType;
869 sourceDescriptors = new TreeMap<String, PropertyDescriptor>();
870 targetDescriptors = new TreeMap<String, PropertyDescriptor>();
871 propertiesMapping = new TreeMap<String, String>();
872 collectionStrategies = new TreeMap<String, CollectionStrategy>();
873 binders = new TreeMap<String, Binder<?, ?>>();
874 functions = new LinkedHashMap<Class<?>, Function<?, ?>>();
875 }
876
877
878
879
880
881
882 public Class<S> getSourceType() {
883 return sourceType;
884 }
885
886
887
888
889
890
891 public Class<T> getTargetType() {
892 return targetType;
893 }
894
895
896
897
898
899
900 public String[] getSourceDescriptors() {
901 Set<String> universe = sourceDescriptors.keySet();
902 return universe.toArray(new String[sourceDescriptors.size()]);
903 }
904
905 public CollectionStrategy getCollectionStrategy(String property) {
906 return collectionStrategies.get(property);
907 }
908
909
910
911
912
913
914 public String[] getTargetDescriptors() {
915 Set<String> universe = targetDescriptors.keySet();
916 return universe.toArray(new String[targetDescriptors.size()]);
917 }
918
919
920
921
922
923
924
925
926 public String getTargetProperty(String sourceProperty) {
927 if (!containsSourceProperty(sourceProperty)) {
928 return null;
929 }
930 String dstProperty = propertiesMapping.get(sourceProperty);
931 return dstProperty;
932 }
933
934
935
936
937
938
939
940
941 public PropertyDescriptor getSourceDescriptor(String sourceProperty) {
942
943 if (!containsSourceProperty(sourceProperty)) {
944 return null;
945 }
946 PropertyDescriptor descriptor = sourceDescriptors.get(sourceProperty);
947 return descriptor;
948 }
949
950
951
952
953
954 public Method getSourceReadMethod(String srcProperty) {
955 PropertyDescriptor descriptor = getSourceDescriptor(srcProperty);
956 Method readMethod = null;
957 if (descriptor != null) {
958 readMethod = descriptor.getReadMethod();
959 }
960 return readMethod;
961 }
962
963
964
965
966
967 public Method getSourceWriteMethod(String sourceProperty) {
968 PropertyDescriptor descriptor = getSourceDescriptor(sourceProperty);
969 Method writeMethod = null;
970 if (descriptor != null) {
971 writeMethod = descriptor.getWriteMethod();
972 }
973 return writeMethod;
974 }
975
976
977
978
979
980
981
982
983 public PropertyDescriptor getTargetDescriptor(String targetProperty) {
984
985 if (!containsTargetProperty(targetProperty)) {
986 return null;
987 }
988 PropertyDescriptor descriptor = targetDescriptors.get(targetProperty);
989 return descriptor;
990 }
991
992
993
994
995
996
997 public Method getTargetReadMethod(String targetProperty) {
998 PropertyDescriptor descriptor = getTargetDescriptor(targetProperty);
999 Method readMethod = null;
1000 if (descriptor != null) {
1001 readMethod = descriptor.getReadMethod();
1002 }
1003 return readMethod;
1004 }
1005
1006
1007
1008
1009
1010
1011 public Method getTargetWriteMethod(String targetProperty) {
1012 PropertyDescriptor descriptor = getTargetDescriptor(targetProperty);
1013 Method writeMethod = null;
1014 if (descriptor != null) {
1015 writeMethod = descriptor.getWriteMethod();
1016 }
1017 return writeMethod;
1018 }
1019
1020 public Class<?> getCollectionType(String sourceProperty) {
1021 Method method = getSourceReadMethod(sourceProperty);
1022 Class<?> type = method.getReturnType();
1023 if (Collection.class.isAssignableFrom(type)) {
1024 return type;
1025 }
1026 return null;
1027 }
1028
1029 public void addCollectionStrategy(String propertyName,
1030 CollectionStrategy strategy) {
1031 collectionStrategies.put(propertyName, strategy);
1032 }
1033
1034 public void addBinder(String propertyName, Binder<?, ?> binder) {
1035 binders.put(propertyName, binder);
1036 }
1037
1038 public boolean containsSourceProperty(String sourceProperty) {
1039 return propertiesMapping.containsKey(sourceProperty);
1040 }
1041
1042 public boolean containsTargetProperty(String targetProperty) {
1043 return propertiesMapping.containsValue(targetProperty);
1044 }
1045
1046 public boolean containsCollectionProperty(String propertyName) {
1047 return collectionStrategies.containsKey(propertyName);
1048 }
1049
1050 public boolean containsBinderProperty(String propertyName) {
1051 return binders.containsKey(propertyName);
1052 }
1053
1054 protected void addBinding(PropertyDescriptor sourceDescriptor,
1055 PropertyDescriptor targetDescriptor) {
1056
1057 String sourceProperty = sourceDescriptor.getName();
1058 String targetProperty = targetDescriptor.getName();
1059 sourceDescriptors.put(sourceProperty, sourceDescriptor);
1060 targetDescriptors.put(targetProperty, targetDescriptor);
1061 propertiesMapping.put(sourceProperty, targetProperty);
1062 }
1063
1064 protected void removeBinding(String source) {
1065 String target = propertiesMapping.get(source);
1066
1067 sourceDescriptors.remove(source);
1068 targetDescriptors.remove(target);
1069 propertiesMapping.remove(source);
1070
1071 if (containsBinderProperty(source)) {
1072 binders.remove(source);
1073 }
1074 if (containsCollectionProperty(source)) {
1075 collectionStrategies.remove(source);
1076 }
1077 }
1078
1079 protected Map<String, String> getPropertiesMapping() {
1080 return propertiesMapping;
1081 }
1082
1083 public Binder<?, ?> getBinder(String sourceProperty) {
1084 return binders.get(sourceProperty);
1085 }
1086
1087 public void setInstanceFactory(InstanceFactory<T> instanceFactory) {
1088 this.instanceFactory = instanceFactory;
1089 }
1090
1091 public InstanceFactory<T> getInstanceFactory() {
1092 return instanceFactory;
1093 }
1094
1095 public Function getFunction(Class<?> aClass) {
1096 return functions.get(aClass);
1097 }
1098
1099 public boolean isUseFunctions() {
1100 return !functions.isEmpty();
1101 }
1102
1103 }
1104 }