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.Function;
26
27 import java.beans.BeanInfo;
28 import java.beans.IntrospectionException;
29 import java.beans.Introspector;
30 import java.beans.PropertyDescriptor;
31 import java.lang.reflect.Method;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.TreeMap;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 public class BinderModelBuilder<S, T> {
107
108
109
110
111 protected boolean canTypeMismatch;
112
113
114
115
116 protected Binder.BinderModel<S, T> model;
117
118
119
120
121 protected Map<String, PropertyDescriptor> sourceDescriptors;
122
123
124
125
126 protected Map<String, PropertyDescriptor> targetDescriptors;
127
128
129
130
131
132
133
134
135 public static <S> BinderModelBuilder<S, S> newEmptyBuilder(Class<S> type) {
136 return new BinderModelBuilder<S, S>(type, type);
137 }
138
139
140
141
142
143
144
145
146
147
148 public static <S, T> BinderModelBuilder<S, T> newEmptyBuilder(Class<S> sourceType,
149 Class<T> targetType) {
150 return new BinderModelBuilder<S, T>(sourceType, targetType);
151 }
152
153
154
155
156
157
158
159
160
161
162 public static <S> BinderModelBuilder<S, S> newDefaultBuilder(Class<S> sourceType) {
163 return newDefaultBuilder(sourceType, sourceType);
164
165 }
166
167
168
169
170
171
172
173
174
175
176
177 public static <S, T> BinderModelBuilder<S, T> newDefaultBuilder(Class<S> sourceType,
178 Class<T> targetType) {
179 return newDefaultBuilder(sourceType, targetType, true);
180 }
181
182
183
184
185
186
187
188
189
190
191
192
193
194 public static <S, T> BinderModelBuilder<S, T> newDefaultBuilder(Class<S> sourceType,
195 Class<T> targetType,
196 boolean checkType) {
197 BinderModelBuilder<S, T> builder =
198 newEmptyBuilder(sourceType, targetType);
199 Map<String, PropertyDescriptor> source = builder.sourceDescriptors;
200 Map<String, PropertyDescriptor> target = builder.targetDescriptors;
201 List<String> properties = new ArrayList<String>();
202 for (String propertyName : source.keySet()) {
203 if (!target.containsKey(propertyName)) {
204
205
206 continue;
207 }
208 PropertyDescriptor sourceDescriptor = source.get(propertyName);
209 Method readMethod = sourceDescriptor.getReadMethod();
210 if (readMethod == null) {
211
212
213 continue;
214 }
215 PropertyDescriptor targetDescriptor = target.get(propertyName);
216 Method writeMethod = targetDescriptor.getWriteMethod();
217 if (writeMethod == null) {
218
219
220 continue;
221 }
222
223 if (checkType) {
224
225
226
227 Class<?> writerType = writeMethod.getParameterTypes()[0];
228 Class<?> readerType = readMethod.getReturnType();
229 if (!writerType.equals(readerType)) {
230
231
232 continue;
233 }
234 }
235
236 properties.add(propertyName);
237 }
238
239
240 builder.addSimpleProperties(
241 properties.toArray(new String[properties.size()]));
242 return builder;
243 }
244
245
246
247
248
249
250
251 public BinderModelBuilder<S, T> canTypeMismatch(boolean canTypeMismatch) {
252 this.canTypeMismatch = canTypeMismatch;
253 return this;
254 }
255
256 public <K, V> BinderModelBuilder<S, T> addFunction(Class<K> type, Function<K, V> function) {
257 model.functions.put(type, function);
258 return this;
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272 public Binder<S, T> toBinder() {
273 Binder<S, T> binder = toBinder(Binder.class);
274 return binder;
275 }
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290 public <B extends Binder<S, T>> B toBinder(Class<B> binderType) {
291 B binder = BinderFactory.newBinder(model, binderType);
292 return binder;
293 }
294
295
296
297
298
299 public void setInstanceFactory(InstanceFactory<T> instanceFactory) {
300 model.setInstanceFactory(instanceFactory);
301 }
302
303
304
305
306
307
308
309
310
311
312
313 public BinderModelBuilder<S, T> addSimpleProperties(String... properties)
314 throws IllegalStateException, NullPointerException {
315 for (String property : properties) {
316 if (property == null) {
317 throw new NullPointerException(
318 "parameter 'properties' can not contains a null value");
319 }
320 addProperty0(property, property);
321 }
322 return this;
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336
337 public BinderModelBuilder<S, T> addProperty(String sourceProperty,
338 String targetProperty)
339 throws IllegalStateException, NullPointerException {
340 if (sourceProperty == null) {
341 throw new NullPointerException(
342 "parameter 'sourceProperty' can not be null");
343 }
344 if (targetProperty == null) {
345 throw new NullPointerException(
346 "parameter 'targetProperty' can not be null");
347 }
348 addProperty0(sourceProperty, targetProperty);
349 return this;
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 public BinderModelBuilder<S, T> addProperties(String... sourceAndTargetProperties)
374 throws IllegalStateException, IllegalArgumentException,
375 NullPointerException {
376 if (sourceAndTargetProperties.length % 2 != 0) {
377 throw new IllegalArgumentException(
378 "must have couple(s) of sourceProperty,targetProperty) " +
379 "but had " + Arrays.toString(sourceAndTargetProperties));
380 }
381 for (int i = 0, max = sourceAndTargetProperties.length / 2;
382 i < max; i++) {
383 String sourceProperty = sourceAndTargetProperties[2 * i];
384 String targetProperty = sourceAndTargetProperties[2 * i + 1];
385 if (sourceProperty == null) {
386 throw new NullPointerException(
387 "parameter 'sourceAndTargetProperties' can not " +
388 "contains a null value");
389 }
390 if (targetProperty == null) {
391 throw new NullPointerException(
392 "parameter 'sourceAndTargetProperties' can not " +
393 "contains a null value");
394 }
395 addProperty0(sourceProperty, targetProperty);
396 }
397 return this;
398 }
399
400 public BinderModelBuilder<S, T> addBinder(String propertyName, Binder<?, ?> binder) {
401
402 if (model.containsCollectionProperty(propertyName)) {
403
404 throw new IllegalStateException("Can't add a property binder, there is already a collection strategy defined!");
405 }
406
407
408 if (!model.containsSourceProperty(propertyName)) {
409 throw new IllegalArgumentException(
410 "source property '" + propertyName + "' " +
411 " is NOT registred.");
412 }
413
414
415 PropertyDescriptor descriptor = sourceDescriptors.get(propertyName);
416 Class<?> type = descriptor.getPropertyType();
417
418 if (!Collection.class.isAssignableFrom(type) &&
419 !binder.model.getSourceType().isAssignableFrom(type)) {
420 throw new IllegalStateException(
421 "source property '" + propertyName +
422 "' has not the same type [" + type +
423 "] of the binder [" + binder.model.getSourceType() + "].");
424 }
425
426
427 model.addBinder(propertyName, binder);
428
429 return this;
430 }
431
432 public BinderModelBuilder<S, T> addCollectionStrategy(Binder.CollectionStrategy strategy,
433 String... propertyNames) {
434
435 if (strategy.equals(Binder.CollectionStrategy.bind)) {
436 throw new IllegalStateException("Can't add bind stragegy here, must use the method addCollectionBinder");
437 }
438
439 for (String propertyName : propertyNames) {
440
441 if (model.containsBinderProperty(propertyName)) {
442
443 throw new IllegalStateException("Can't add a simple collection strategy, there is already a binder defined, please use now the addCollectionBinder method to do this!");
444 }
445
446 addCollectionStrategy0(propertyName, strategy, null);
447
448 }
449 return this;
450 }
451
452 public BinderModelBuilder<S, T> addCollectionBinder(Binder binder,
453 String... propertyNames) {
454
455 for (String propertyName : propertyNames) {
456
457 addCollectionStrategy0(propertyName, Binder.CollectionStrategy.bind, binder);
458
459 }
460
461 return this;
462 }
463
464
465
466
467
468
469
470
471
472
473 public BinderModelBuilder<T, S> buildInverseModelBuilder() {
474
475 BinderModelBuilder<T, S> builder = new BinderModelBuilder<T, S>(model.getTargetType(), model.getSourceType())
476 .canTypeMismatch(canTypeMismatch);
477
478 for (Map.Entry<String, String> entry : model.getPropertiesMapping().entrySet()) {
479 String sourcePropertyName = entry.getKey();
480 String targetPropertyName = entry.getValue();
481 builder.addProperty(targetPropertyName, sourcePropertyName);
482 }
483 return builder;
484
485 }
486
487 protected BinderModelBuilder<S, T> addCollectionStrategy0(String propertyName,
488 Binder.CollectionStrategy strategy,
489 Binder binder) {
490
491
492 if (!model.containsSourceProperty(propertyName)) {
493 throw new IllegalArgumentException(
494 "source property '" + propertyName + "' " +
495 " is NOT registred.");
496 }
497
498
499 PropertyDescriptor descriptor = sourceDescriptors.get(propertyName);
500 Class<?> type = descriptor.getPropertyType();
501 if (!Collection.class.isAssignableFrom(type)) {
502 throw new IllegalStateException(
503 "source property '" + propertyName +
504 "' is not a collection type [" + type + "]");
505 }
506
507
508 model.addCollectionStrategy(propertyName, strategy);
509
510 if (binder != null) {
511
512
513 model.addBinder(propertyName, binder);
514
515 }
516 return this;
517 }
518
519
520
521
522
523
524
525 protected BinderModelBuilder(Class<S> sourceType, Class<T> targetType) {
526 if (sourceType == null) {
527 throw new NullPointerException("sourceType can not be null");
528 }
529 if (targetType == null) {
530 throw new NullPointerException("targetType can not be null");
531 }
532
533 if (model != null) {
534 throw new IllegalStateException(
535 "there is already a binderModel in construction, release " +
536 "it with the method createBinder before using this method."
537 );
538 }
539
540
541 model = new Binder.BinderModel<S, T>(sourceType, targetType);
542
543
544 sourceDescriptors = new TreeMap<String, PropertyDescriptor>();
545 loadDescriptors(model.getSourceType(), sourceDescriptors);
546
547
548 targetDescriptors = new TreeMap<String, PropertyDescriptor>();
549 loadDescriptors(model.getTargetType(), targetDescriptors);
550
551 }
552
553 protected void addProperty0(String sourceProperty,
554 String targetProperty) {
555
556
557 PropertyDescriptor sourceDescriptor =
558 sourceDescriptors.get(sourceProperty);
559 if (sourceDescriptor == null) {
560 throw new IllegalArgumentException("no property '" +
561 sourceProperty + "' " + "found on type " +
562 model.getSourceType());
563 }
564
565 Method readMethod = sourceDescriptor.getReadMethod();
566 if (readMethod == null) {
567 throw new IllegalArgumentException("property '" + sourceProperty +
568 "' " + "is not readable on type " + model.getSourceType());
569 }
570
571
572 PropertyDescriptor targetDescriptor =
573 targetDescriptors.get(targetProperty);
574 if (targetDescriptor == null) {
575 throw new IllegalArgumentException("no property '" +
576 targetProperty + "' " + "found on type " +
577 model.getTargetType());
578 }
579
580 Method writeMethod = targetDescriptor.getWriteMethod();
581 if (writeMethod == null) {
582 throw new IllegalArgumentException("property '" + targetProperty +
583 "' " + "is not writable on type " + model.getTargetType());
584 }
585
586
587 Class<?> sourceType = sourceDescriptor.getPropertyType();
588 Class<?> targetType = targetDescriptor.getPropertyType();
589
590 if (!sourceType.equals(targetType) && !canTypeMismatch) {
591 throw new IllegalArgumentException("source property '" +
592 sourceProperty + "' and target property '" +
593 targetProperty + "' are not compatible ( sourceType : " +
594 sourceType + " vs targetType :" + targetType + ')');
595 }
596
597
598 if (model.containsSourceProperty(sourceProperty)) {
599
600
601 model.removeBinding(sourceProperty);
602
603 }
604
605
606
607
608 if (model.containsTargetProperty(targetProperty)) {
609 throw new IllegalArgumentException("destination property '" +
610 targetProperty + "' " + " was already registred.");
611 }
612
613 model.addBinding(sourceDescriptor, targetDescriptor);
614 }
615
616 protected Binder.BinderModel<S, T> getModel() {
617 return model;
618 }
619
620 protected void clear() {
621 sourceDescriptors = null;
622 targetDescriptors = null;
623 model = null;
624 }
625
626 protected static void loadDescriptors(
627 Class<?> type,
628 Map<String, PropertyDescriptor> descriptors) {
629 try {
630
631 BeanInfo beanInfo = Introspector.getBeanInfo(type);
632 for (PropertyDescriptor descriptor :
633 beanInfo.getPropertyDescriptors()) {
634 if (!descriptors.containsKey(descriptor.getName())) {
635 descriptors.put(descriptor.getName(), descriptor);
636 }
637 }
638 } catch (IntrospectionException e) {
639 throw new RuntimeException("Could not obtain bean properties " +
640 "descriptors for source type " + type, e);
641 }
642 Class<?>[] interfaces = type.getInterfaces();
643 for (Class<?> i : interfaces) {
644 loadDescriptors(i, descriptors);
645 }
646 Class<?> superClass = type.getSuperclass();
647 if (superClass != null && !Object.class.equals(superClass)) {
648 loadDescriptors(superClass, descriptors);
649 }
650 }
651 }