1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.opencsv.bean;
17
18 import com.opencsv.ICSVParser;
19 import com.opencsv.exceptions.*;
20 import org.apache.commons.collections4.ListValuedMap;
21 import org.apache.commons.collections4.MapIterator;
22 import org.apache.commons.collections4.MultiValuedMap;
23 import org.apache.commons.collections4.SetUtils;
24 import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
25 import org.apache.commons.lang3.ArrayUtils;
26 import org.apache.commons.lang3.ObjectUtils;
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.commons.lang3.reflect.FieldUtils;
29
30 import java.lang.annotation.Annotation;
31 import java.lang.reflect.Field;
32 import java.lang.reflect.InvocationTargetException;
33 import java.util.*;
34 import java.util.function.Function;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 public abstract class AbstractMappingStrategy<I, K extends Comparable<K>, C extends ComplexFieldMapEntry<I, K, T>, T> implements MappingStrategy<T> {
53
54
55
56
57
58
59
60 private static final Set<Class> FORBIDDEN_CLASSES_FOR_RECURSION = new HashSet<>(Arrays.asList(Byte.TYPE, Short.TYPE,
61 Integer.TYPE, Float.TYPE, Double.TYPE, Boolean.TYPE, Long.TYPE, Character.TYPE));
62
63
64 protected Class<? extends T> type;
65
66
67
68
69
70 protected final HeaderIndex headerIndex = new HeaderIndex();
71
72
73
74
75
76
77
78 protected RecursiveType recursiveTypeTree;
79
80
81 private MultiValuedMap<Class<?>, Field> ignoredFields = new ArrayListValuedHashMap<>();
82
83
84 protected Locale errorLocale = Locale.getDefault();
85
86
87 protected String profile = StringUtils.EMPTY;
88
89
90
91
92
93
94
95
96
97
98 protected abstract K chooseMultivaluedFieldIndexFromHeaderIndex(int index);
99
100
101
102
103
104
105 protected abstract FieldMap<I, K, ? extends C, T> getFieldMap();
106
107
108
109
110
111
112
113
114
115
116
117 protected Set<Class<? extends Annotation>> getBindingAnnotations() {return Collections.emptySet();}
118
119
120
121
122
123
124
125
126
127
128
129
130 protected void loadAnnotatedFieldMap(ListValuedMap<Class<?>, Field> fields) {}
131
132
133
134
135
136
137
138
139
140
141
142
143 protected abstract void loadUnadornedFieldMap(ListValuedMap<Class<?>, Field> fields);
144
145
146
147
148
149
150
151
152
153 protected abstract void initializeFieldMap();
154
155
156
157
158
159
160
161
162
163
164 protected abstract BeanField<T, K> findField(int col);
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181 protected abstract void verifyLineLength(int numberOfFields) throws CsvRequiredFieldEmptyException;
182
183
184
185
186
187
188
189
190
191 protected Map<Class<?>, Object> createBean()
192 throws CsvBeanIntrospectionException, IllegalStateException {
193 if(type == null) {
194 throw new IllegalStateException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("type.unset"));
195 }
196
197
198 Map<Class<?>, Object> instanceMap = new HashMap<>();
199 try {
200 T rootBean = type.newInstance();
201 instanceMap.put(type, rootBean);
202 createSubordinateBeans(recursiveTypeTree, instanceMap, rootBean);
203 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
204 CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
205 ResourceBundle.getBundle(
206 ICSVParser.DEFAULT_BUNDLE_NAME,
207 errorLocale)
208 .getString("bean.instantiation.impossible"));
209 csve.initCause(e);
210 throw csve;
211 }
212
213 return instanceMap;
214 }
215
216 private static void createSubordinateBeans(RecursiveType typeTree, Map<Class<?>, Object> instanceMap, Object containingObject)
217 throws InstantiationException, IllegalAccessException, InvocationTargetException {
218 for(Map.Entry<FieldAccess<Object>, RecursiveType> entry : typeTree.getRecursiveMembers().entrySet()) {
219 Object childObject = entry.getKey().getField(containingObject);
220 if(childObject == null) {
221 childObject = entry.getValue().type.newInstance();
222 entry.getKey().setField(containingObject, childObject);
223 }
224 instanceMap.put(entry.getValue().getType(), childObject);
225 createSubordinateBeans(entry.getValue(), instanceMap, childObject);
226 }
227 }
228
229
230
231
232
233
234
235
236
237
238
239
240
241 protected Map<Class<?>, Object> indexBean(T bean)
242 throws IllegalAccessException, InvocationTargetException {
243 Map<Class<?>, Object> instanceMap = new HashMap<>();
244 instanceMap.put(type, bean);
245 indexSubordinateBeans(recursiveTypeTree, instanceMap, bean);
246 return instanceMap;
247 }
248
249 private static void indexSubordinateBeans(RecursiveType typeTree, Map<Class<?>, Object> instanceMap, Object containingObject)
250 throws IllegalAccessException, InvocationTargetException {
251 for(Map.Entry<FieldAccess<Object>, RecursiveType> entry : typeTree.getRecursiveMembers().entrySet()) {
252 Object childObject;
253 if(containingObject == null) {
254 childObject = null;
255 }
256 else {
257 childObject = entry.getKey().getField(containingObject);
258 }
259 instanceMap.put(entry.getValue().getType(), childObject);
260 indexSubordinateBeans(entry.getValue(), instanceMap, childObject);
261 }
262 }
263
264
265
266
267
268
269
270
271
272 public abstract String findHeader(int col);
273
274
275
276
277
278
279
280
281
282
283 @Override
284 public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
285 if(type == null) {
286 throw new IllegalStateException(ResourceBundle
287 .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
288 .getString("type.before.header"));
289 }
290
291
292 if(headerIndex.isEmpty()) {
293 String[] header = getFieldMap().generateHeader(bean);
294 headerIndex.initializeHeaderIndex(header);
295 return header;
296 }
297
298
299 return headerIndex.getHeaderIndex();
300 }
301
302
303
304
305
306
307
308
309 protected String getColumnName(int col) {
310
311 return headerIndex.getByPosition(col);
312 }
313
314
315
316
317
318
319 public Class<? extends T> getType() {
320 return type;
321 }
322
323 @SuppressWarnings("unchecked")
324 @Override
325 public T populateNewBean(String[] line)
326 throws CsvBeanIntrospectionException, CsvFieldAssignmentException,
327 CsvChainedException {
328 verifyLineLength(line.length);
329 Map<Class<?>, Object> beanTree = createBean();
330
331 CsvChainedException chainedException = null;
332 for (int col = 0; col < line.length; col++) {
333 try {
334 setFieldValue(beanTree, line[col], col);
335 } catch (CsvFieldAssignmentException e) {
336 if(chainedException != null) {
337 chainedException.add(e);
338 }
339 else {
340 chainedException = new CsvChainedException(e);
341 }
342 }
343 }
344 if(chainedException != null) {
345 if (chainedException.hasOnlyOneException()) {
346 throw chainedException.getFirstException();
347 }
348 throw chainedException;
349 }
350 return (T)beanTree.get(type);
351 }
352
353
354
355
356
357
358
359
360 @Override
361 public void setType(Class<? extends T> type) throws CsvBadConverterException {
362 this.type = type;
363 loadFieldMap();
364 }
365
366
367
368
369
370
371 @Override
372 public void setProfile(String profile) {
373 this.profile = StringUtils.defaultString(profile);
374 }
375
376 @Override
377 public void ignoreFields(MultiValuedMap<Class<?>, Field> fields) throws IllegalArgumentException {
378
379
380 if(fields == null) {
381 ignoredFields = new ArrayListValuedHashMap<>();
382 }
383 else {
384 ignoredFields = fields;
385 MapIterator<Class<?>, Field> it = ignoredFields.mapIterator();
386 it.forEachRemaining(t -> {
387 final Field f = it.getValue();
388 if (t == null || f == null
389 || !f.getDeclaringClass().isAssignableFrom(t)) {
390 throw new IllegalArgumentException(ResourceBundle.getBundle(
391 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
392 .getString("ignore.field.inconsistent"));
393 }
394 });
395 }
396
397
398 if(this.type != null) {
399 loadFieldMap();
400 }
401 }
402
403
404
405
406
407
408
409
410
411
412 protected List<Field> filterIgnoredFields(final Class<?> type, Field[] fields) {
413 final List<Field> filteredFields = new LinkedList<>();
414 for(Field f : fields) {
415 CsvIgnore ignoreAnnotation = f.getAnnotation(CsvIgnore.class);
416 Set<String> ignoredProfiles = ignoreAnnotation == null ?
417 SetUtils.<String>emptySet() :
418 new HashSet<String>(Arrays.asList(ignoreAnnotation.profiles()));
419 if(!ignoredFields.containsMapping(type, f) &&
420 !ignoredProfiles.contains(profile) &&
421 !ignoredProfiles.contains(StringUtils.EMPTY)) {
422 filteredFields.add(f);
423 }
424 }
425 return filteredFields;
426 }
427
428
429
430
431
432
433
434 protected void loadFieldMap() throws CsvBadConverterException {
435
436
437 initializeFieldMap();
438
439
440 recursiveTypeTree = loadRecursiveClasses(this.type, new HashSet<>());
441
442
443 Map<Boolean, ListValuedMap<Class<?>, Field>> partitionedFields = partitionFields();
444 if(!partitionedFields.get(Boolean.TRUE).isEmpty()) {
445 loadAnnotatedFieldMap(partitionedFields.get(Boolean.TRUE));
446 }
447 else {
448 loadUnadornedFieldMap(partitionedFields.get(Boolean.FALSE));
449 }
450 }
451
452
453
454
455
456
457
458 protected boolean isForbiddenClassForRecursion(Class<?> type) {
459 return FORBIDDEN_CLASSES_FOR_RECURSION.contains(type);
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478 protected RecursiveType loadRecursiveClasses(Class<?> newType, Set<Class<?>> encounteredTypes) {
479
480
481 if (isForbiddenClassForRecursion(newType)) {
482 throw new CsvRecursionException(
483 ResourceBundle.getBundle(
484 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
485 .getString("recursion.on.primitive"), newType);
486 }
487
488
489 if(encounteredTypes.contains(newType)) {
490 throw new CsvRecursionException(String.format(ResourceBundle
491 .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
492 .getString("recursive.type.encountered.twice"), newType.toString()), newType);
493 }
494 encounteredTypes.add(newType);
495
496
497 RecursiveType localRecursiveTypeTree = new RecursiveType(newType);
498 for(Field f : filterIgnoredFields(newType, FieldUtils.getFieldsWithAnnotation(newType, CsvRecurse.class))) {
499
500
501 Set<Class<? extends Annotation>> bindingAnnotations = getBindingAnnotations();
502 if(bindingAnnotations.stream().anyMatch(f::isAnnotationPresent)) {
503 throw new CsvRecursionException(
504 ResourceBundle.getBundle(
505 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
506 .getString("recursion.binding.mutually.exclusive"),
507 f.getType());
508 }
509
510
511 localRecursiveTypeTree.addRecursiveMember(
512 new FieldAccess<>(f),
513 loadRecursiveClasses(f.getType(), encounteredTypes));
514 }
515
516 return localRecursiveTypeTree;
517 }
518
519
520
521
522
523
524
525
526
527
528 private void assembleCompleteFieldList(RecursiveType root, final ListValuedMap<Class<?>, Field> encounteredFields) {
529 encounteredFields.putAll(root.type, filterIgnoredFields(root.type, FieldUtils.getAllFields(root.type)));
530 root.getRecursiveMembers().values().forEach(f -> assembleCompleteFieldList(f, encounteredFields));
531 }
532
533
534
535
536
537
538
539
540
541
542
543 protected Map<Boolean, ListValuedMap<Class<?>, Field>> partitionFields() {
544
545 ListValuedMap<Class<?>, Field> allFields = new ArrayListValuedHashMap<>();
546 assembleCompleteFieldList(recursiveTypeTree, allFields);
547
548
549 final Set<Class<? extends Annotation>> bindingAnnotations = getBindingAnnotations();
550
551
552
553 Map<Boolean, ListValuedMap<Class<?>, Field>> returnValue = new TreeMap<>();
554 returnValue.put(Boolean.TRUE, new ArrayListValuedHashMap<>());
555 returnValue.put(Boolean.FALSE, new ArrayListValuedHashMap<>());
556 allFields.entries().stream()
557 .filter(entry -> !entry.getValue().isSynthetic())
558 .forEach(entry -> {
559 if(bindingAnnotations.stream()
560 .anyMatch(a -> entry.getValue().isAnnotationPresent(a))) {
561 returnValue.get(Boolean.TRUE).put(entry.getKey(), entry.getValue());
562 }
563 else {
564 returnValue.get(Boolean.FALSE).put(entry.getKey(), entry.getValue());
565 }
566 });
567 return returnValue;
568 }
569
570
571
572
573
574
575
576
577 protected BeanField<T, K> instantiateCustomConverter(Class<? extends AbstractBeanField<T, K>> converter)
578 throws CsvBadConverterException {
579 try {
580 BeanField<T, K> c = converter.newInstance();
581 c.setErrorLocale(errorLocale);
582 return c;
583 } catch (IllegalAccessException | InstantiationException oldEx) {
584 CsvBadConverterException newEx =
585 new CsvBadConverterException(converter,
586 String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("custom.converter.invalid"), converter.getCanonicalName()));
587 newEx.initCause(oldEx);
588 throw newEx;
589 }
590 }
591
592 @Override
593 public void setErrorLocale(Locale errorLocale) {
594 this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
595
596
597
598
599 if(getFieldMap() != null) {
600 getFieldMap().setErrorLocale(this.errorLocale);
601 getFieldMap().values().forEach(f -> f.setErrorLocale(this.errorLocale));
602 }
603 }
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625 protected void setFieldValue(Map<Class<?>, Object> beanTree, String value, int column)
626 throws CsvDataTypeMismatchException, CsvRequiredFieldEmptyException,
627 CsvConstraintViolationException, CsvValidationException {
628 BeanField<T, K> beanField = findField(column);
629 if (beanField != null) {
630 Object subordinateBean = beanTree.get(beanField.getType());
631 beanField.setFieldValue(subordinateBean, value, findHeader(column));
632 }
633 }
634
635 @Override
636 public String[] transmuteBean(T bean) throws CsvFieldAssignmentException, CsvChainedException {
637 int numColumns = headerIndex.findMaxIndex()+1;
638 BeanField<T, K> firstBeanField, subsequentBeanField;
639 K firstIndex, subsequentIndex;
640 List<String> contents = new ArrayList<>(Math.max(numColumns, 0));
641
642
643 Map<Class<?>, Object> instanceMap;
644 try {
645 instanceMap = indexBean(bean);
646 }
647 catch(IllegalAccessException | InvocationTargetException e) {
648
649
650
651 CsvBeanIntrospectionException csve = new CsvBeanIntrospectionException(
652 ResourceBundle.getBundle(
653 ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
654 .getString("error.introspecting.beans"));
655 csve.initCause(e);
656 throw csve;
657 }
658
659 CsvChainedException chainedException = null;
660 for(int i = 0; i < numColumns;) {
661
662
663 firstBeanField = findField(i);
664 firstIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
665 String[] fields = ArrayUtils.EMPTY_STRING_ARRAY;
666 if(firstBeanField != null) {
667 try {
668 fields = firstBeanField.write(instanceMap.get(firstBeanField.getType()), firstIndex);
669 }
670 catch(CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
671 if(chainedException != null) {
672 chainedException.add(e);
673 }
674 else {
675 chainedException = new CsvChainedException(e);
676 }
677 }
678 }
679
680 if(fields.length == 0) {
681
682
683 contents.add(StringUtils.EMPTY);
684 i++;
685 }
686 else {
687
688
689 contents.add(StringUtils.defaultString(fields[0]));
690
691
692
693
694 int j = 1;
695 int displacedIndex = i+j;
696 subsequentBeanField = findField(displacedIndex);
697 subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(displacedIndex);
698 while(j < fields.length
699 && displacedIndex < numColumns
700 && Objects.equals(firstBeanField, subsequentBeanField)
701 && Objects.equals(firstIndex, subsequentIndex)) {
702
703 contents.add(StringUtils.defaultString(fields[j]));
704
705
706 displacedIndex = i + (++j);
707 subsequentBeanField = findField(displacedIndex);
708 subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(displacedIndex);
709 }
710
711 i = displacedIndex;
712
713
714
715 if(i < numColumns) {
716 subsequentBeanField = findField(i);
717 subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
718 while(Objects.equals(firstBeanField, subsequentBeanField)
719 && Objects.equals(firstIndex, subsequentIndex)
720 && i < numColumns) {
721 contents.add(StringUtils.EMPTY);
722 subsequentBeanField = findField(++i);
723 subsequentIndex = chooseMultivaluedFieldIndexFromHeaderIndex(i);
724 }
725 }
726 }
727 }
728
729
730 if(chainedException != null) {
731 if (chainedException.hasOnlyOneException()) {
732 throw chainedException.getFirstException();
733 }
734 throw chainedException;
735 }
736
737 return contents.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
738 }
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756 protected CsvConverter determineConverter(Field field,
757 Class<?> elementType, String locale, String writeLocale,
758 Class<? extends AbstractCsvConverter> customConverter)
759 throws CsvBadConverterException {
760 CsvConverter converter;
761
762
763 if (customConverter != null && !customConverter.equals(AbstractCsvConverter.class)) {
764 try {
765 converter = customConverter.newInstance();
766 } catch (IllegalAccessException | InstantiationException oldEx) {
767 CsvBadConverterException newEx =
768 new CsvBadConverterException(customConverter,
769 String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("custom.converter.invalid"), customConverter.getCanonicalName()));
770 newEx.initCause(oldEx);
771 throw newEx;
772 }
773 converter.setType(elementType);
774 converter.setLocale(locale);
775 converter.setWriteLocale(writeLocale);
776 converter.setErrorLocale(errorLocale);
777 }
778
779
780 else if (field.isAnnotationPresent(CsvDate.class) || field.isAnnotationPresent(CsvDates.class)) {
781 CsvDate annotation = selectAnnotationForProfile(
782 field.getAnnotationsByType(CsvDate.class),
783 CsvDate::profiles);
784 if(annotation == null) {
785 throw new CsvBadConverterException(CsvDate.class, String.format(
786 ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME).getString("profile.not.found.date"),
787 profile));
788 }
789 String readFormat = annotation.value();
790 String writeFormat = annotation.writeFormatEqualsReadFormat()
791 ? readFormat : annotation.writeFormat();
792 String readChrono = annotation.chronology();
793 String writeChrono = annotation.writeChronologyEqualsReadChronology()
794 ? readChrono : annotation.writeChronology();
795 converter = new ConverterDate(elementType, locale, writeLocale,
796 errorLocale, readFormat, writeFormat, readChrono, writeChrono);
797 }
798
799
800 else if(field.isAnnotationPresent(CsvNumber.class) || field.isAnnotationPresent(CsvNumbers.class)) {
801 CsvNumber annotation = selectAnnotationForProfile(
802 field.getAnnotationsByType(CsvNumber.class),
803 CsvNumber::profiles);
804 if(annotation == null) {
805 throw new CsvBadConverterException(CsvNumber.class, String.format(
806 ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME).getString("profile.not.found.number"),
807 profile));
808 }
809 String readFormat = annotation.value();
810 String writeFormat = annotation.writeFormatEqualsReadFormat()
811 ? readFormat : annotation.writeFormat();
812 converter = new ConverterNumber(elementType, locale, writeLocale,
813 errorLocale, readFormat, writeFormat, annotation.roundingMode());
814 }
815
816
817 else if (elementType.equals(java.util.Currency.class)){
818 converter = new ConverterCurrency(errorLocale);
819 }
820
821
822 else if (elementType.isEnum()) {
823 converter = new ConverterEnum(elementType, locale, writeLocale, errorLocale);
824 }
825
826
827 else if (elementType.equals(UUID.class)) {
828 converter = new ConverterUUID(errorLocale);
829 }
830
831 else {
832 converter = new ConverterPrimitiveTypes(elementType, locale, writeLocale, errorLocale);
833 }
834
835 return converter;
836 }
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853 protected <A extends Annotation> A selectAnnotationForProfile(A[] annotations, Function<A, String[]> getProfiles) {
854 A defaultAnnotation = null;
855 String[] profilesForAnnotation;
856 for(A annotation : annotations) {
857 profilesForAnnotation = getProfiles.apply(annotation);
858 for(String p : profilesForAnnotation) {
859 if(profile.equals(p)) {
860 return annotation;
861 }
862 if(StringUtils.EMPTY.equals(p)) {
863 defaultAnnotation = annotation;
864 }
865 }
866 }
867 return defaultAnnotation;
868 }
869
870
871
872
873
874 protected static class RecursiveType {
875 private final Class<?> type;
876 private final Map<FieldAccess<Object>, RecursiveType> recursiveMembers = new HashMap<>();
877
878
879
880
881
882
883 protected RecursiveType(Class<?> type) {
884 this.type = type;
885 }
886
887
888
889
890 public Class<?> getType() {
891 return type;
892 }
893
894
895
896
897
898
899
900 public void addRecursiveMember(FieldAccess<Object> member, RecursiveType memberType) {
901 recursiveMembers.put(member, memberType);
902 }
903
904
905
906
907 public Map<FieldAccess<Object>, RecursiveType> getRecursiveMembers() {
908 return recursiveMembers;
909 }
910 }
911 }