View Javadoc
1   /*
2    * Copyright 2018 Andrew Rucker Jones.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.opencsv.bean;
17  
18  import com.opencsv.bean.customconverter.BadCollectionConverter;
19  import com.opencsv.bean.mocks.join.*;
20  import com.opencsv.enums.CSVReaderNullFieldIndicator;
21  import com.opencsv.exceptions.CsvBadConverterException;
22  import com.opencsv.exceptions.CsvBeanIntrospectionException;
23  import com.opencsv.exceptions.CsvException;
24  import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
25  import org.apache.commons.collections4.MultiValuedMap;
26  import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
27  import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
28  import org.apache.commons.lang3.StringUtils;
29  import org.junit.jupiter.api.AfterEach;
30  import org.junit.jupiter.api.BeforeAll;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  
34  import java.io.FileReader;
35  import java.io.IOException;
36  import java.io.StringReader;
37  import java.io.StringWriter;
38  import java.util.*;
39  import java.util.regex.Pattern;
40  import java.util.regex.PatternSyntaxException;
41  
42  import static org.junit.jupiter.api.Assertions.*;
43  
44  /**
45   *
46   * @author Andrew Rucker Jones
47   */
48  public class JoinTest {
49      
50      private static Locale systemLocale;
51  
52      @BeforeAll
53      public static void storeSystemLocale() {
54          systemLocale = Locale.getDefault();
55      }
56  
57      @BeforeEach
58      public void setSystemLocaleToValueNotGerman() {
59          Locale.setDefault(Locale.US);
60      }
61  
62      @AfterEach
63      public void setSystemLocaleBackToDefault() {
64          Locale.setDefault(systemLocale);
65      }
66  
67      /**
68       * Tests reading a wrapped primitive into a MultiValuedMap.
69       * <p>Also tests:</p>
70       * <ul><li>Reading with a header name mapping strategy</li>
71       * <li>Reading with a regular expression that matches more than one
72       * identically named headers</li>
73       * <li>Map does not already exist</li>
74       * <li>Capture by name without a matching regular expression</li></ul>
75       * 
76       * @throws IOException Never
77       */
78      @Test
79      public void testReadPrimitive() throws IOException {
80          List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(
81                  new FileReader("src/test/resources/testinputjoinbynamegood.csv"))
82                  .withType(GoodJoinByNameAnnotations.class).build().parse();
83          assertNotNull(beans);
84          assertEquals(3, beans.size());
85          
86          MultiValuedMap<String, Integer> map = beans.get(0).getMap1();
87          assertNotNull(map);
88          assertEquals(1, map.keySet().size());
89          Collection<Integer> values = map.get("index");
90          assertEquals(3, values.size());
91          assertTrue(values.containsAll(Arrays.asList(1, 2, 3)));
92          
93          map = beans.get(1).getMap1();
94          assertNotNull(map);
95          assertEquals(1, map.keySet().size());
96          values = map.get("index");
97          assertEquals(3, values.size());
98          assertTrue(values.containsAll(Arrays.asList(2, 3, 4)));
99          
100         map = beans.get(2).getMap1();
101         assertNotNull(map);
102         assertEquals(1, map.keySet().size());
103         values = map.get("index");
104         assertEquals(3, values.size());
105         assertTrue(values.containsAll(Arrays.asList(3, 4, 5)));
106     }
107     
108     /**
109      * Tests reading in formatted dates.
110      * <p>Also tests:</p>
111      * <ul><li>Reading a date with a conversion locale and header mapping</li>
112      * <li>Reading with a regular expression that matches more than one not
113      * identically named headers</li>
114      * <li>Test with comma-separated range values</li></ul>
115      * 
116      * @throws IOException Never
117      */
118     @Test
119     public void testReadDate() throws IOException {
120         List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(
121                 new FileReader("src/test/resources/testinputjoinbynamegood.csv"))
122                 .withType(GoodJoinByNameAnnotations.class).build().parse();
123         assertNotNull(beans);
124         assertEquals(3, beans.size());
125         
126         MultiValuedMap<String, Date> map = beans.get(0).getMap2();
127         assertNotNull(map);
128         assertEquals(3, map.keySet().size());
129         Collection<Date> values = map.get("date1");
130         assertEquals(1, values.size());
131         assertTrue(values.contains(new GregorianCalendar(1978, Calendar.DECEMBER, 15).getTime()));
132         values = map.get("date2");
133         assertEquals(1, values.size());
134         assertTrue(values.contains(new GregorianCalendar(1974, Calendar.FEBRUARY, 27).getTime()));
135         values = map.get("date3");
136         assertEquals(1, values.size());
137         assertTrue(values.contains(new GregorianCalendar(2013, Calendar.APRIL, 13).getTime()));
138         
139         map = beans.get(1).getMap2();
140         assertNotNull(map);
141         assertEquals(3, map.keySet().size());
142         values = map.get("date1");
143         assertEquals(1, values.size());
144         assertTrue(values.contains(new GregorianCalendar(1978, Calendar.DECEMBER, 16).getTime()));
145         values = map.get("date2");
146         assertEquals(1, values.size());
147         assertTrue(values.contains(new GregorianCalendar(1974, Calendar.FEBRUARY, 28).getTime()));
148         values = map.get("date3");
149         assertEquals(1, values.size());
150         assertTrue(values.contains(new GregorianCalendar(2013, Calendar.APRIL, 14).getTime()));
151         
152         map = beans.get(2).getMap2();
153         assertNotNull(map);
154         assertEquals(3, map.keySet().size());
155         values = map.get("date1");
156         assertEquals(1, values.size());
157         assertTrue(values.contains(new GregorianCalendar(1978, Calendar.DECEMBER, 17).getTime()));
158         values = map.get("date2");
159         assertEquals(1, values.size());
160         assertTrue(values.contains(new GregorianCalendar(1974, Calendar.MARCH, 1).getTime()));
161         values = map.get("date3");
162         assertEquals(1, values.size());
163         assertTrue(values.contains(new GregorianCalendar(2013, Calendar.APRIL, 15).getTime()));
164     }
165     
166     // Test with CsvBindByName taking the same name as CsvBindAndJoinByName
167     /**
168      * Tests that an overlap in naming between {@link CsvBindByName} and
169      * {@link CsvBindAndJoinByName} is resolved to the benefit of the former.
170      * <p>Also incidentally tests:</p>
171      * <ul><li>Use of a class derived from an implementation of
172      * {@link org.apache.commons.collections4.MultiValuedMap}</li></ul>
173      * 
174      * @throws IOException Never
175      */
176     @Test
177     public void testNamingOverlap() throws IOException {
178         List<NamingOverlap> beans = new CsvToBeanBuilder<NamingOverlap>(new FileReader("src/test/resources/testinputjoinnamingoverlap.csv"))
179                 .withType(NamingOverlap.class).build().parse();
180         assertNotNull(beans);
181         assertEquals(1, beans.size());
182         NamingOverlap bean = beans.get(0);
183         assertEquals("nameString", bean.getName());
184         DerivedStringMultiValuedMap map = bean.getOtherValues();
185         assertEquals("string1", map.get("header1").get(0));
186         assertEquals("string2", map.get("header2").get(0));
187     }
188     
189     @Test
190     public void testIllegalRegularExpression() {
191         try {
192         new CsvToBeanBuilder<JoinIllegalRegex>(new StringReader(StringUtils.EMPTY))
193                 .withType(JoinIllegalRegex.class)
194                 .build()
195                 .parse();
196             fail("Exception should have been thrown");
197         }
198         catch(CsvBadConverterException csve) {
199             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
200             assertFalse(StringUtils.isBlank(csve.getLocalizedMessage()));
201             Throwable e = csve.getCause();
202             assertNotNull(e);
203             assertTrue(e instanceof PatternSyntaxException);
204         }
205     }
206     
207     /**
208      * Test with regular expression that doesn't match any header names.
209      * 
210      * @throws IOException Never
211      */
212     @Test
213     public void testNonMatchingRegularExpression() throws IOException {
214         List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(
215                 new FileReader("src/test/resources/testinputjoinbynamegood.csv"))
216                 .withType(GoodJoinByNameAnnotations.class).build().parse();
217         assertNotNull(beans);
218         assertEquals(3, beans.size());
219         MultiValuedMap<String, String> map = beans.get(0).getMap3();
220         assertNull(map);
221     }
222     
223     @Test
224     public void testFieldNotMultiValuedMap() {
225         try {
226             new CsvToBeanBuilder<FieldNotMultiValuedMap>(new StringReader(StringUtils.EMPTY))
227                     .withType(FieldNotMultiValuedMap.class).build().parse();
228             fail("Exception should have been thrown.");
229         }
230         catch(CsvBadConverterException csve) {
231             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
232             assertFalse(StringUtils.isBlank(csve.getLocalizedMessage()));
233         }
234     }
235     
236     /**
237      * Tests that the range specification "-" works as a double open range.
238      * <p>Also incidentally tests:</p>
239      * <ul><li>Use of an interface derived from
240      * {@link org.apache.commons.collections4.MultiValuedMap}</li>
241      * <li>Overlap of position designations between {@link CsvBindByPosition}
242      * and {@link CsvBindAndJoinByPosition}</li></ul>
243      * 
244      * @throws IOException Never
245      */
246     @Test
247     public void testDoubleOpenRange() throws IOException {
248         List<DoubleOpenRange> beans = new CsvToBeanBuilder<DoubleOpenRange>(
249                 new FileReader("src/test/resources/testinputopenrange.csv"))
250                 .withType(DoubleOpenRange.class).build().parse();
251         assertNotNull(beans);
252         assertEquals(1, beans.size());
253         DoubleOpenRange bean = beans.get(0);
254         assertEquals(10, bean.getPositionOne());
255         MultiValuedMap<Integer, Integer> map = bean.getOtherPositions();
256         assertNotNull(map);
257         assertEquals(5, map.keySet().size());
258         assertEquals(20, map.get(2).toArray(new Integer[1])[0].intValue());
259         assertEquals(30, map.get(3).toArray(new Integer[1])[0].intValue());
260         assertEquals(40, map.get(4).toArray(new Integer[1])[0].intValue());
261         assertEquals(50, map.get(5).toArray(new Integer[1])[0].intValue());
262         assertEquals(1, map.get(0).toArray(new Integer[1])[0].intValue());
263     }
264     
265     @Test
266     public void testOpenRangeNoLowerBound() throws IOException {
267         List<OpenRangeNoLowerBound> beans = new CsvToBeanBuilder<OpenRangeNoLowerBound>(new FileReader("src/test/resources/testinputjoinbypositiongood.csv"))
268                 .withType(OpenRangeNoLowerBound.class).build().parse();
269         assertNotNull(beans);
270         assertEquals(3, beans.size());
271         
272         OpenRangeNoLowerBound bean = beans.get(0);
273         assertNotNull(bean);
274         assertNotNull(bean.getMap());
275         MultiValuedMap<Integer, String> map = bean.getMap();
276         assertEquals(4, map.keySet().size());
277         assertEquals("10", map.get(0).toArray(new String[1])[0]);
278         assertEquals("15. Dezember 1978", map.get(1).toArray(new String[1])[0]);
279         assertEquals("|20|", map.get(2).toArray(new String[1])[0]);
280         assertEquals("|30|", map.get(3).toArray(new String[1])[0]);
281         
282         bean = beans.get(1);
283         assertNotNull(bean);
284         assertNotNull(bean.getMap());
285         map = bean.getMap();
286         assertEquals(4, map.keySet().size());
287         assertEquals("11", map.get(0).toArray(new String[1])[0]);
288         assertEquals("16. Dezember 1978", map.get(1).toArray(new String[1])[0]);
289         assertEquals("|21|", map.get(2).toArray(new String[1])[0]);
290         assertEquals("|31|", map.get(3).toArray(new String[1])[0]);
291         
292         bean = beans.get(2);
293         assertNotNull(bean);
294         assertNotNull(bean.getMap());
295         map = bean.getMap();
296         assertEquals(4, map.keySet().size());
297         assertEquals("12", map.get(0).toArray(new String[1])[0]);
298         assertEquals("17. Dezember 1978", map.get(1).toArray(new String[1])[0]);
299         assertEquals("|22|", map.get(2).toArray(new String[1])[0]);
300         assertEquals("|32|", map.get(3).toArray(new String[1])[0]);
301     }
302     
303     @Test
304     public void testNonNumberRangeExpression() {
305         try {
306             new CsvToBeanBuilder<NonNumberRange>(new StringReader(StringUtils.EMPTY))
307                     .withType(NonNumberRange.class)
308                     .build().parse();
309             fail("Exception should have been thrown.");
310         }
311         catch(CsvBadConverterException csve) {
312             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
313             assertNotNull(csve.getLocalizedMessage());
314             assertTrue(csve.getCause() instanceof NumberFormatException);
315         }
316     }
317     
318     @Test
319     public void testEmptyRangeExpression() {
320         try {
321             new CsvToBeanBuilder<EmptyRange>(new StringReader(StringUtils.EMPTY))
322                     .withType(EmptyRange.class).build().parse();
323             fail("Exception should have been thrown.");
324         }
325         catch(CsvBadConverterException csve) {
326             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
327             assertFalse(StringUtils.isBlank(csve.getLocalizedMessage()));
328             assertNull(csve.getCause());
329         }
330     }
331     
332     /**
333      * Tests a position definition with exactly one column index.
334      * <p>Also incidentally tests:</p>
335      * <ul><li>Reading with a column position mapping strategy</li>
336      * <li>Map already exists</li>
337      * <li>Capture by position without a matching regular expression</li></ul>
338      * 
339      * @throws IOException Never
340      */
341     @Test
342     public void testRangeWithOnePosition() throws IOException {
343         List<GoodJoinByPositionAnnotations> beans = new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(
344                 new FileReader("src/test/resources/testinputjoinbypositiongood.csv"))
345                 .withType(GoodJoinByPositionAnnotations.class).build().parse();
346         assertNotNull(beans);
347         assertEquals(3, beans.size());
348         
349         MultiValuedMap<Integer, Integer> map = beans.get(0).getMap1();
350         assertNotNull(map);
351         assertEquals(2, map.keySet().size());
352         assertEquals(10, map.get(0).toArray(new Integer[1])[0].intValue());
353         
354         // Map already exists and is initialized in the constructor with this value
355         assertEquals(Integer.MIN_VALUE, map.get(Integer.MAX_VALUE).toArray(new Integer[1])[0].intValue());
356         
357         map = beans.get(1).getMap1();
358         assertNotNull(map);
359         assertEquals(2, map.keySet().size());
360         assertEquals(11, map.get(0).toArray(new Integer[1])[0].intValue());
361         
362         // Map already exists and is initialized in the constructor with this value
363         assertEquals(Integer.MIN_VALUE, map.get(Integer.MAX_VALUE).toArray(new Integer[1])[0].intValue());
364         
365         map = beans.get(2).getMap1();
366         assertNotNull(map);
367         assertEquals(2, map.keySet().size());
368         assertEquals(12, map.get(0).toArray(new Integer[1])[0].intValue());
369         
370         // Map already exists and is initialized in the constructor with this value
371         assertEquals(Integer.MIN_VALUE, map.get(Integer.MAX_VALUE).toArray(new Integer[1])[0].intValue());
372     }
373     
374     /**
375      * Tests that closed ranges work as expected.
376      * <p>Also incidentally tests:</p>
377      * <ul><li>Adjacent ranges in one range expression</li>
378      * <li>Overlapping ranges in one range expression</li>
379      * <li>Multiple individual positions</li>
380      * <li>Spaces in range expressions</li>
381      * <li>Gaps in range expressions</li>
382      * <li>Implementation of
383      * {@link org.apache.commons.collections4.MultiValuedMap} as bean member</li>
384      * <li>Use of {@link org.apache.commons.collections4.multimap.ArrayListValuedHashMap}</li>
385      * <li>First setting the bean type, then the error locale</li>
386      * <li>Setting an error locale with complex many-to-one mappings</li></ul>
387      * 
388      * @throws IOException Never
389      */
390     @Test
391     public void testClosedRange() throws IOException {
392         MappingStrategy<GoodJoinByPositionAnnotations> mappingStrategy = new ColumnPositionMappingStrategy<>();
393         mappingStrategy.setType(GoodJoinByPositionAnnotations.class);
394         CsvToBean<GoodJoinByPositionAnnotations> csvToBean = new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(
395                 new FileReader("src/test/resources/testinputjoinbypositiongood.csv"))
396                 .withType(GoodJoinByPositionAnnotations.class)
397                 .withMappingStrategy(mappingStrategy)
398                 .build();
399         csvToBean.setErrorLocale(Locale.GERMAN);
400         List<GoodJoinByPositionAnnotations> beans = csvToBean.parse();
401         assertNotNull(beans);
402         assertEquals(3, beans.size());
403         
404         MultiValuedMap<Integer, String> map = beans.get(0).getMap4();
405         assertNotNull(map);
406         assertTrue(map instanceof ArrayListValuedHashMap);
407         assertEquals(10, map.keySet().size());
408         Collection<String> values = map.get(4);
409         assertEquals(1, values.size());
410         assertTrue(values.contains("string4"));
411         values = map.get(5);
412         assertEquals(1, values.size());
413         assertTrue(values.contains("string5"));
414         values = map.get(6);
415         assertEquals(1, values.size());
416         assertTrue(values.contains("string6"));
417         values = map.get(7);
418         assertEquals(1, values.size());
419         assertTrue(values.contains("string7"));
420         values = map.get(8);
421         assertEquals(1, values.size());
422         assertTrue(values.contains("string8"));
423         values = map.get(9);
424         assertEquals(1, values.size());
425         assertTrue(values.contains("string9"));
426         values = map.get(10);
427         assertEquals(1, values.size());
428         assertTrue(values.contains("string10"));
429         values = map.get(12);
430         assertEquals(1, values.size());
431         assertTrue(values.contains("string12"));
432         values = map.get(13);
433         assertEquals(1, values.size());
434         assertTrue(values.contains("string13"));
435         values = map.get(15);
436         assertEquals(1, values.size());
437         assertTrue(values.contains("string15"));
438         
439         map = beans.get(1).getMap4();
440         assertNotNull(map);
441         assertTrue(map instanceof ArrayListValuedHashMap);
442         assertEquals(10, map.keySet().size());
443         values = map.get(4);
444         assertEquals(1, values.size());
445         assertTrue(values.contains("string42"));
446         values = map.get(5);
447         assertEquals(1, values.size());
448         assertTrue(values.contains("string52"));
449         values = map.get(6);
450         assertEquals(1, values.size());
451         assertTrue(values.contains("string62"));
452         values = map.get(7);
453         assertEquals(1, values.size());
454         assertTrue(values.contains("string72"));
455         values = map.get(8);
456         assertEquals(1, values.size());
457         assertTrue(values.contains("string82"));
458         values = map.get(9);
459         assertEquals(1, values.size());
460         assertTrue(values.contains("string92"));
461         values = map.get(10);
462         assertEquals(1, values.size());
463         assertTrue(values.contains("string102"));
464         values = map.get(12);
465         assertEquals(1, values.size());
466         assertTrue(values.contains("string122"));
467         values = map.get(13);
468         assertEquals(1, values.size());
469         assertTrue(values.contains("string132"));
470         values = map.get(15);
471         assertEquals(1, values.size());
472         assertTrue(values.contains("string152"));
473         
474         map = beans.get(2).getMap4();
475         assertNotNull(map);
476         assertTrue(map instanceof ArrayListValuedHashMap);
477         assertEquals(10, map.keySet().size());
478         values = map.get(4);
479         assertEquals(1, values.size());
480         assertTrue(values.contains("string43"));
481         values = map.get(5);
482         assertEquals(1, values.size());
483         assertTrue(values.contains("string53"));
484         values = map.get(6);
485         assertEquals(1, values.size());
486         assertTrue(values.contains("string63"));
487         values = map.get(7);
488         assertEquals(1, values.size());
489         assertTrue(values.contains("string73"));
490         values = map.get(8);
491         assertEquals(1, values.size());
492         assertTrue(values.contains("string83"));
493         values = map.get(9);
494         assertEquals(1, values.size());
495         assertTrue(values.contains("string93"));
496         values = map.get(10);
497         assertEquals(1, values.size());
498         assertTrue(values.contains("string103"));
499         values = map.get(12);
500         assertEquals(1, values.size());
501         assertTrue(values.contains("string123"));
502         values = map.get(13);
503         assertEquals(1, values.size());
504         assertTrue(values.contains("string133"));
505         values = map.get(15);
506         assertEquals(1, values.size());
507         assertTrue(values.contains("string153"));
508     }
509     
510     /**
511      * Tests that an open range without an upper boundry works.
512      * <p>Also tests:</p>
513      * <ul><li>No accessor method for member variable</li>
514      * <li>Locale conversion with date and position mapping</li></ul>
515      * 
516      * @throws IOException Never
517      */
518     @Test
519     public void testOpenRangeWithoutUpperBoundry() throws IOException {
520         List<GoodJoinByPositionAnnotations> beans = new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(
521                 new FileReader("src/test/resources/testinputjoinbypositiongood.csv"))
522                 .withType(GoodJoinByPositionAnnotations.class).build().parse();
523         assertNotNull(beans);
524         assertEquals(3, beans.size());
525         
526         MultiValuedMap<Integer, Date> map = beans.get(0).showMeTheSecondMap();
527         assertNotNull(map);
528         assertEquals(3, map.keySet().size());
529         Collection<Date> values = map.get(1);
530         assertEquals(1, values.size());
531         assertTrue(values.contains(new GregorianCalendar(1978, Calendar.DECEMBER, 15).getTime()));
532         values = map.get(16);
533         assertEquals(1, values.size());
534         assertTrue(values.contains(new GregorianCalendar(1974, Calendar.FEBRUARY, 27).getTime()));
535         values = map.get(17);
536         assertEquals(1, values.size());
537         assertTrue(values.contains(new GregorianCalendar(2013, Calendar.APRIL, 13).getTime()));
538         
539         map = beans.get(1).showMeTheSecondMap();
540         assertNotNull(map);
541         assertEquals(3, map.keySet().size());
542         values = map.get(1);
543         assertEquals(1, values.size());
544         assertTrue(values.contains(new GregorianCalendar(1978, Calendar.DECEMBER, 16).getTime()));
545         values = map.get(16);
546         assertEquals(1, values.size());
547         assertTrue(values.contains(new GregorianCalendar(1974, Calendar.FEBRUARY, 28).getTime()));
548         values = map.get(17);
549         assertEquals(1, values.size());
550         assertTrue(values.contains(new GregorianCalendar(2013, Calendar.APRIL, 14).getTime()));
551         
552         map = beans.get(2).showMeTheSecondMap();
553         assertNotNull(map);
554         assertEquals(3, map.keySet().size());
555         values = map.get(1);
556         assertEquals(1, values.size());
557         assertTrue(values.contains(new GregorianCalendar(1978, Calendar.DECEMBER, 17).getTime()));
558         values = map.get(16);
559         assertEquals(1, values.size());
560         assertTrue(values.contains(new GregorianCalendar(1974, Calendar.MARCH, 1).getTime()));
561         values = map.get(17);
562         assertEquals(1, values.size());
563         assertTrue(values.contains(new GregorianCalendar(2013, Calendar.APRIL, 15).getTime()));
564     }
565     
566     /**
567      * Tests that a range expression of the form "maximum-minimum" functions
568      * properly.
569      * <p>Also incidentally tests:</p>
570      * <ul><li>Bean member variable lacks an assignment method</li>
571      * <li>Explicit map type</li>
572      * <li>Capture by position with a matching regular expression</li></ul>
573      * 
574      * @throws IOException Never
575      */
576     @Test
577     public void testRangeBackward() throws IOException {
578         List<GoodJoinByPositionAnnotations> beans = new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(
579                 new FileReader("src/test/resources/testinputjoinbypositiongood.csv"))
580                 .withType(GoodJoinByPositionAnnotations.class).build().parse();
581         assertNotNull(beans);
582         assertEquals(3, beans.size());
583         
584         MultiValuedMap<Integer, Integer> map = beans.get(0).getMap3();
585         assertNotNull(map);
586         assertTrue(map instanceof HashSetValuedHashMap);
587         assertEquals(2, map.keySet().size());
588         Collection<Integer> values = map.get(2);
589         assertEquals(1, values.size());
590         assertTrue(values.contains(20));
591         values = map.get(3);
592         assertEquals(1, values.size());
593         assertTrue(values.contains(30));
594         
595         map = beans.get(1).getMap3();
596         assertNotNull(map);
597         assertTrue(map instanceof HashSetValuedHashMap);
598         assertEquals(2, map.keySet().size());
599         values = map.get(2);
600         assertEquals(1, values.size());
601         assertTrue(values.contains(21));
602         values = map.get(3);
603         assertEquals(1, values.size());
604         assertTrue(values.contains(31));
605         
606         map = beans.get(2).getMap3();
607         assertNotNull(map);
608         assertTrue(map instanceof HashSetValuedHashMap);
609         assertEquals(2, map.keySet().size());
610         values = map.get(2);
611         assertEquals(1, values.size());
612         assertTrue(values.contains(22));
613         values = map.get(3);
614         assertEquals(1, values.size());
615         assertTrue(values.contains(32));
616     }
617 
618     /**
619      * Tests conversion of a primitive with a specified locale using header
620      * name-based mapping.
621      * <p>Also incidentally tests:
622      * <ul><li>Capture by name with a matching regular expression</li></ul></p>
623      * @throws IOException Never
624      */
625     @Test
626     public void testReadConversionLocalePrimitiveHeaderMapping() throws IOException {
627         List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(new FileReader("src/test/resources/testinputjoinbynamegood.csv"))
628                 .withType(GoodJoinByNameAnnotations.class).build().parse();
629         assertNotNull(beans);
630         assertEquals(3, beans.size());
631         
632         MultiValuedMap<String, Integer> map = beans.get(0).getMap4();
633         assertNotNull(map);
634         assertEquals(1, map.keySet().size());
635         assertEquals(10000, map.get("conversion").toArray(new Integer[1])[0].intValue());
636         
637         map = beans.get(1).getMap4();
638         assertNotNull(map);
639         assertEquals(1, map.keySet().size());
640         assertEquals(11000, map.get("conversion").toArray(new Integer[1])[0].intValue());
641         
642         map = beans.get(2).getMap4();
643         assertNotNull(map);
644         assertEquals(1, map.keySet().size());
645         assertEquals(12000, map.get("conversion").toArray(new Integer[1])[0].intValue());
646     }
647 
648     @Test
649     public void testReadConversionLocalePrimitivePositionMapping() throws IOException {
650         List<GoodJoinByPositionAnnotations> beans = new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(new FileReader("src/test/resources/testinputjoinbypositiongood.csv"))
651                 .withType(GoodJoinByPositionAnnotations.class).build().parse();
652         assertNotNull(beans);
653         assertEquals(3, beans.size());
654         
655         MultiValuedMap<Integer, Integer> map = beans.get(0).getMap5();
656         assertNotNull(map);
657         assertEquals(1, map.keySet().size());
658         assertEquals(20000, map.get(11).toArray(new Integer[1])[0].intValue());
659         
660         map = beans.get(1).getMap5();
661         assertNotNull(map);
662         assertEquals(1, map.keySet().size());
663         assertEquals(21000, map.get(11).toArray(new Integer[1])[0].intValue());
664         
665         map = beans.get(2).getMap5();
666         assertNotNull(map);
667         assertEquals(1, map.keySet().size());
668         assertEquals(22000, map.get(11).toArray(new Integer[1])[0].intValue());
669     }
670 
671     /**
672      * Tests what happens when a required field specified as a single header
673      * name is missing.
674      * In case it's not clear, this test uses a header name mapping strategy.
675      * 
676      * @throws IOException Never
677      */
678     @Test
679     public void testReadEmptyIndividualRequiredFieldHeaderNameMapping() throws IOException {
680         try {
681             new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(new FileReader("src/test/resources/testinputjoinbypositionrequiredindividualmissing.csv"))
682                     .withType(GoodJoinByPositionAnnotations.class).build().parse();
683             fail("Exception should have been thrown.");
684         }
685         catch(RuntimeException e) {
686             assertNotNull(e.getCause());
687             assertTrue(e.getCause() instanceof CsvRequiredFieldEmptyException);
688             CsvRequiredFieldEmptyException csve = (CsvRequiredFieldEmptyException) e.getCause();
689             assertEquals(GoodJoinByPositionAnnotations.class, csve.getBeanClass());
690             assertNotNull(csve.getDestinationFields());
691             assertEquals(1, csve.getDestinationFields().size());
692             assertEquals("map1", csve.getDestinationFields().get(0).getName());
693             assertEquals(1, csve.getLineNumber());
694             assertNotNull(csve.getLine());
695         }
696     }
697     
698     /**
699      * Tests what happens if a field marked as required and fed out of multiple
700      * headers is missing a value in one matching column.
701      * In case it's not clear, this test uses a header name mapping strategy.
702      * 
703      * @throws IOException Never
704      */
705     @Test
706     public void testReadEmptyRegexSingleRequiredFieldHeaderNameMappingValueOnly() throws IOException {
707         try {
708             new CsvToBeanBuilder<GoodJoinByNameAnnotations>(new FileReader("src/test/resources/testinputjoinbynameonerequiredmissing.csv"))
709                     .withType(GoodJoinByNameAnnotations.class).build().parse();
710         }
711         catch(RuntimeException e) {
712             assertNotNull(e.getCause());
713             assertTrue(e.getCause() instanceof CsvRequiredFieldEmptyException);
714             CsvRequiredFieldEmptyException csve = (CsvRequiredFieldEmptyException) e.getCause();
715             assertEquals(GoodJoinByNameAnnotations.class, csve.getBeanClass());
716             assertEquals("map2", csve.getDestinationField().getName());
717             assertEquals(2, csve.getLineNumber());
718             assertNotNull(csve.getLine());
719         }
720     }
721     
722     /**
723      * Tests what happens if a field marked as required and fed out of multiple
724      * headers is missing all headers that would match.
725      * In case it's not clear, this test uses a header name mapping strategy.
726      * 
727      * @throws IOException Never
728      */
729     @Test
730     public void testReadEmptyRegexAllRequiredFieldHeaderNameMapping() throws IOException {
731         try {
732             new CsvToBeanBuilder<GoodJoinByNameAnnotations>(new FileReader("src/test/resources/testinputjoinbynamerequiredheadermissing.csv"))
733                     .withType(GoodJoinByNameAnnotations.class).build().parse();
734         }
735         catch(RuntimeException e) {
736             assertNotNull(e.getCause());
737             assertTrue(e.getCause() instanceof CsvRequiredFieldEmptyException);
738             CsvRequiredFieldEmptyException csve = (CsvRequiredFieldEmptyException) e.getCause();
739             assertEquals(GoodJoinByNameAnnotations.class, csve.getBeanClass());
740             assertEquals("map2", csve.getDestinationField().getName());
741             assertEquals(-1, csve.getLineNumber());
742             assertNotNull(csve.getLine());
743         }
744     }
745     
746     /**
747      * Tests what happens if at least one position for a field marked as
748      * required with {@link CsvBindAndJoinByPosition} contains no value.
749      * In case it's not clear, this test uses a column position mapping
750      * strategy.
751      * 
752      * @throws IOException Never
753      */
754     @Test
755     public void testReadEmptyIndividualRequiredFieldColumnPositionMapping() throws IOException {
756         try {
757             new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(new FileReader("src/test/resources/testinputjoinbypositionrequiredmissing.csv"))
758                     .withType(GoodJoinByPositionAnnotations.class).build().parse();
759         }
760         catch(RuntimeException e) {
761             assertNotNull(e.getCause());
762             assertTrue(e.getCause() instanceof CsvRequiredFieldEmptyException);
763             CsvRequiredFieldEmptyException csve = (CsvRequiredFieldEmptyException) e.getCause();
764             assertEquals(GoodJoinByPositionAnnotations.class, csve.getBeanClass());
765             assertEquals("map2", csve.getDestinationField().getName());
766             assertEquals(1, csve.getLineNumber());
767             assertNotNull(csve.getLine());
768         }
769     }
770     
771     @Test
772     public void testReadEmptyOptionalFieldValueOnly() throws IOException {
773         List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(new FileReader("src/test/resources/testinputjoinbynameoptionalvaluemissing.csv"))
774                 .withType(GoodJoinByNameAnnotations.class)
775                 .build().parse();
776         assertNotNull(beans);
777         assertEquals(1, beans.size());
778         GoodJoinByNameAnnotations bean = beans.get(0);
779         assertNotNull(bean.getMap4());
780         assertEquals(1, bean.getMap4().size());
781         assertNull(bean.getMap4().get("converted").toArray(new Integer[1])[0]);
782     }
783 
784     @Test
785     public void testReadNullOptionalFieldValueOnly() throws IOException {
786         List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(new FileReader("src/test/resources/testinputjoinbynameoptionalvaluemissing.csv"))
787                 .withType(GoodJoinByNameAnnotations.class)
788                 .withFieldAsNull(CSVReaderNullFieldIndicator.BOTH)
789                 .build().parse();
790         assertNotNull(beans);
791         assertEquals(1, beans.size());
792         GoodJoinByNameAnnotations bean = beans.get(0);
793         assertNotNull(bean.getMap4());
794         assertEquals(1, bean.getMap4().size());
795         assertNull(bean.getMap4().get("converted").toArray(new Integer[1])[0]);
796     }
797 
798     @Test
799     public void testReadEmptyOptionalFieldHeader() throws IOException {
800         List<GoodJoinByNameAnnotations> beans = new CsvToBeanBuilder<GoodJoinByNameAnnotations>(new FileReader("src/test/resources/testinputjoinbynameoptionalheadermissing.csv"))
801                 .withType(GoodJoinByNameAnnotations.class)
802                 .build().parse();
803         assertNotNull(beans);
804         assertEquals(1, beans.size());
805         GoodJoinByNameAnnotations bean = beans.get(0);
806         assertNull(bean.getMap4());
807     }
808     
809     @Test
810     public void testReadEmptyOptionalFieldPosition() throws IOException {
811         List<GoodJoinByPositionAnnotations> beans = new CsvToBeanBuilder<GoodJoinByPositionAnnotations>(new FileReader("src/test/resources/testinputjoinbypositionoptionalmissing.csv"))
812                 .withType(GoodJoinByPositionAnnotations.class)
813                 .build().parse();
814         assertNotNull(beans);
815         assertEquals(1, beans.size());
816         GoodJoinByPositionAnnotations bean = beans.get(0);
817         MultiValuedMap<Integer, Integer> map = bean.getMap3();
818         assertNotNull(map);
819         assertEquals(2, map.keySet().size());
820         assertNull(map.get(2).toArray(new Integer[1])[0]);
821         assertNull(map.get(3).toArray(new Integer[1])[0]);
822     }
823     
824     /**
825      * Tests what happens when a required field is missing in a column position
826      * mapping strategy on writing.
827      * 
828      * @throws CsvException Never
829      */
830     @Test
831     public void testWriteEmptyRequiredFieldColumnPositionMapping() throws CsvException {
832         GoodJoinByPositionAnnotations bean = new GoodJoinByPositionAnnotations();
833         StringWriter w = new StringWriter();
834         StatefulBeanToCsv<GoodJoinByPositionAnnotations> b2csv = new StatefulBeanToCsvBuilder<GoodJoinByPositionAnnotations>(w).build();
835         try {
836             b2csv.write(bean);
837             fail("Exception should have been thrown.");
838         }
839         catch(CsvRequiredFieldEmptyException e) {
840             assertEquals(GoodJoinByPositionAnnotations.class, e.getBeanClass());
841             assertNotNull(e.getDestinationFields());
842             assertEquals(2, e.getDestinationFields().size());
843             assertEquals("map1", e.getDestinationFields().get(0).getName());
844             assertEquals("map2", e.getDestinationFields().get(1).getName());
845             assertEquals(-1, e.getLineNumber());
846         }
847     }
848     
849     /**
850      * Tests that multi-valued fields of (wrapped) primitives are written
851      * correctly.
852      * <p>Also incidentally tests</p>
853      * <ul><li>Writing a date type</li>
854      * <li>Conversion of a wrapped primitive with a locale</li>
855      * <li>Conversion of a date type with a locale</li>
856      * <li>Writing with a header mapping</li>
857      * <li>Too few values for a multivalued field</li>
858      * <li>Too many values for a multivalued field</li>
859      * <li>No value for an optional field (null)</li>
860      * <li>No value for an optional field (empty map)</li>
861      * <li>No value for an optional field (existing entries with {@code null}
862      * as their value)</li>
863      * <li>Writing multiple multivalued beans</li>
864      * <li>Subsequent bean has different headers than first bean</li>
865      * <li>Writing with a format string using header name mapping</li>
866      * <li>Writing with a format string an empty inputs</li></ul>
867      * 
868      * @throws CsvException Never
869      */
870     @Test
871     public void testWritePrimitive() throws CsvException {
872         List<GoodJoinByNameAnnotations> beanList = new ArrayList<>();
873         
874         GoodJoinByNameAnnotations bean = new GoodJoinByNameAnnotations();
875         MultiValuedMap<String, Integer> map1 = new ArrayListValuedHashMap<>();
876         map1.put("index", 1);
877         map1.put("index", 2);
878         map1.put("index", 3);
879         bean.setMap1(map1);
880         MultiValuedMap<String, Date> map2 = new ArrayListValuedHashMap<>();
881         map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 15).getTime());
882         map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 7).getTime());
883         bean.setMap2(map2);
884         MultiValuedMap<String, String> map3 = new ArrayListValuedHashMap<>();
885         map3.put("test", "string1");
886         bean.setMap3(map3);
887         MultiValuedMap<String, Integer> map4 = new ArrayListValuedHashMap<>();
888         map4.put("conversion", 10000);
889         map4.put("conversion", 20000);
890         bean.setMap4(map4);
891         beanList.add(bean);
892         
893         bean = new GoodJoinByNameAnnotations();
894         map1 = new ArrayListValuedHashMap<>();
895         map1.put("index", 4);
896         map1.put("index", 5);
897         // Third value is missing; "required" applies to the bean field, not every column
898         bean.setMap1(map1);
899         map2 = new ArrayListValuedHashMap<>();
900         map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 16).getTime());
901         map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 8).getTime());
902         bean.setMap2(map2);
903         map3 = new ArrayListValuedHashMap<>();
904         map3.put("test", "string2");
905         bean.setMap3(map3);
906         map4 = new ArrayListValuedHashMap<>();
907         map4.put("conversion", 10001);
908         map4.put("conversion", 20002);
909         bean.setMap4(map4);
910         beanList.add(bean);
911         
912         bean = new GoodJoinByNameAnnotations();
913         map1 = new ArrayListValuedHashMap<>();
914         map1.put("index", 6);
915         map1.put("index", 7);
916         map1.put("index", 8);
917         map1.put("index", 9); // Fourth value
918         map1.put("unknown header", -1); // Different headers from first bean will be ignored
919         bean.setMap1(map1);
920         map2 = new ArrayListValuedHashMap<>();
921         map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 17).getTime());
922         map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 9).getTime());
923         bean.setMap2(map2);
924         map3 = new ArrayListValuedHashMap<>();
925         map3.put("test", "string3");
926         bean.setMap3(map3);
927         bean.setMap4(null); // map4 missing, but optional
928         beanList.add(bean);
929         
930         bean = new GoodJoinByNameAnnotations();
931         map1 = new ArrayListValuedHashMap<>();
932         map1.put("index", 10);
933         map1.put("index", 11);
934         map1.put("index", 12);
935         bean.setMap1(map1);
936         map2 = new ArrayListValuedHashMap<>();
937         map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 18).getTime());
938         map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 10).getTime());
939         bean.setMap2(map2);
940         map3 = new ArrayListValuedHashMap<>();
941         map3.put("test", "string4");
942         bean.setMap3(map3);
943         map4 = new ArrayListValuedHashMap<>(); // map4 is full of nulls
944         map4.put("conversion", null);
945         map4.put("conversion", null);
946         bean.setMap4(map4);
947         beanList.add(bean);
948         
949         bean = new GoodJoinByNameAnnotations();
950         map1 = new ArrayListValuedHashMap<>();
951         map1.put("index", 13);
952         map1.put("index", 14);
953         map1.put("index", 15);
954         bean.setMap1(map1);
955         map2 = new ArrayListValuedHashMap<>();
956         map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 19).getTime());
957         map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 11).getTime());
958         bean.setMap2(map2);
959         map3 = new ArrayListValuedHashMap<>();
960         map3.put("test", "string5");
961         bean.setMap3(map3);
962         bean.setMap4(new ArrayListValuedHashMap<>());
963         beanList.add(bean);
964         
965         StringWriter w = new StringWriter();
966         StatefulBeanToCsv<GoodJoinByNameAnnotations> btc = new StatefulBeanToCsvBuilder<GoodJoinByNameAnnotations>(w).build();
967         btc.write(beanList);
968         assertEquals(
969                 "\"conversion\",\"conversion\",\"date1\",\"date2\",\"index\",\"index\",\"index\"\n"
970                 + "\"x10.000\",\"x20.000\",\"15. Januar 1978\",\"07. Februar 2018\",\"1\",\"2\",\"3\"\n"
971                 + "\"x10.001\",\"x20.002\",\"16. Januar 1978\",\"08. Februar 2018\",\"4\",\"5\",\"\"\n"
972                 + "\"\",\"\",\"17. Januar 1978\",\"09. Februar 2018\",\"6\",\"7\",\"8\"\n"
973                 + "\"\",\"\",\"18. Januar 1978\",\"10. Februar 2018\",\"10\",\"11\",\"12\"\n"
974                 + "\"\",\"\",\"19. Januar 1978\",\"11. Februar 2018\",\"13\",\"14\",\"15\"\n",
975                 w.toString());
976     }
977     
978     /**
979      * Tests that writing with a column position-based mapping strategy works.
980      * <p>Also incidentally tests:</p>
981      * <ul><li>Writing more than one bean works with the column position
982      * strategy.</li>
983      * <li>More values for a position than one</li>
984      * <li>Writing empty optional positions</li>
985      * <li>Using a column position in a bean field that would not be read by
986      * that bean field</li>
987      * <li>Writing using a format string using column position mapping</li></ul>
988      * 
989      * @throws CsvException Never
990      */
991     @Test
992     public void testWriteColumnMapping() throws CsvException {
993         List<GoodJoinByPositionAnnotationsForWriting> beanList = new ArrayList<>();
994         
995         GoodJoinByPositionAnnotationsForWriting bean = new GoodJoinByPositionAnnotationsForWriting();
996         MultiValuedMap<Integer, Integer> map1 = new HashSetValuedHashMap<>();
997         map1.put(0, 10);
998         map1.put(0, 11); // Two values for one position can never work
999         bean.setMap1(map1);
1000         MultiValuedMap<Integer, Date> map2 = new HashSetValuedHashMap<>();
1001         map2.put(1, new GregorianCalendar(1978, Calendar.JANUARY, 15).getTime());
1002         map2.put(16, new GregorianCalendar(2018, Calendar.MARCH, 6).getTime());
1003         bean.setMap2(map2);
1004         ArrayListValuedHashMap<Integer, String> map4 = new ArrayListValuedHashMap<>();
1005         map4.put(4, "string4");
1006         map4.put(5, "string5");
1007         map4.put(6, "string6");
1008         map4.put(7, "string7");
1009         map4.put(8, "string8");
1010         map4.put(9, "string9");
1011         map4.put(10, "string10");
1012         map4.put(11, "string11"); // Matched by another field
1013         map4.put(12, "string12");
1014         map4.put(13, "string13");
1015         map4.put(14, "string14"); // Matched by no field
1016         map4.put(15, "string15");
1017         bean.setMap4(map4);
1018         MultiValuedMap<Integer, Integer> map5 = new HashSetValuedHashMap<>();
1019         map5.put(11, 1111);
1020         bean.setMap5(map5);
1021         beanList.add(bean);
1022         
1023         bean = new GoodJoinByPositionAnnotationsForWriting();
1024         map1 = new HashSetValuedHashMap<>();
1025         map1.put(0, 12);
1026         bean.setMap1(map1);
1027         map2 = new HashSetValuedHashMap<>();
1028         map2.put(1, new GregorianCalendar(1978, Calendar.JANUARY, 16).getTime());
1029         map2.put(16, new GregorianCalendar(2018, Calendar.MARCH, 7).getTime());
1030         bean.setMap2(map2);
1031         map4 = new ArrayListValuedHashMap<>();
1032         map4.put(4, "string42");
1033         map4.put(5, "string52");
1034         map4.put(6, "string62");
1035         map4.put(7, "string72");
1036         map4.put(8, "string82");
1037         map4.put(9, "string92");
1038         map4.put(10, "string102");
1039         map4.put(12, "string122");
1040         map4.put(13, "string132");
1041         map4.put(15, "string152");
1042         bean.setMap4(map4);
1043         map5 = new HashSetValuedHashMap<>();
1044         map5.put(11, 1112);
1045         bean.setMap5(map5);
1046         beanList.add(bean);
1047         
1048         StringWriter w = new StringWriter();
1049         StatefulBeanToCsv<GoodJoinByPositionAnnotationsForWriting> btc = new StatefulBeanToCsvBuilder<GoodJoinByPositionAnnotationsForWriting>(w).build();
1050         btc.write(beanList);
1051         assertTrue(Pattern.matches(
1052                 "\"\\?10\\?\",\"15\\. Jan\\.? 1978\",\"\",\"\",\"string4\",\"string5\",\"string6\",\"string7\",\"string8\",\"string9\",\"string10\",\"1\\.111\",\"string12\",\"string13\",\"\",\"string15\",\"06\\. Mä?rz? 2018\"\n"
1053                 + "\"\\?12\\?\",\"16\\. Jan\\.? 1978\",\"\",\"\",\"string42\",\"string52\",\"string62\",\"string72\",\"string82\",\"string92\",\"string102\",\"1\\.112\",\"string122\",\"string132\",\"\",\"string152\",\"07\\. Mä?rz? 2018\"\n",
1054                 w.toString()));
1055     }
1056     
1057     @Test
1058     public void testWriteEmptyRequiredFieldInFirstBean() throws CsvException {
1059         for(MultiValuedMap<String, Integer> map1 : Arrays.asList(null, new ArrayListValuedHashMap<String, Integer>())) {
1060             GoodJoinByNameAnnotations bean = new GoodJoinByNameAnnotations();
1061             bean.setMap1(map1); // Required
1062             MultiValuedMap<String, Date> map2 = new ArrayListValuedHashMap<>();
1063             map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 15).getTime());
1064             map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 7).getTime());
1065             bean.setMap2(map2);
1066             MultiValuedMap<String, String> map3 = new ArrayListValuedHashMap<>();
1067             map3.put("test", "string1");
1068             bean.setMap3(map3);
1069             MultiValuedMap<String, Integer> map4 = new ArrayListValuedHashMap<>();
1070             map4.put("conversion", 10000);
1071             map4.put("conversion", 20000);
1072             bean.setMap4(map4);
1073 
1074             StringWriter w = new StringWriter();
1075             StatefulBeanToCsv<GoodJoinByNameAnnotations> btc = new StatefulBeanToCsvBuilder<GoodJoinByNameAnnotations>(w).build();
1076             try {
1077                 btc.write(bean);
1078                 fail("Exception should have been thrown.");
1079             }
1080             catch(CsvRequiredFieldEmptyException e) {
1081                 assertEquals(-1, e.getLineNumber());
1082                 assertEquals(GoodJoinByNameAnnotations.class, e.getBeanClass());
1083                 assertEquals("map1", e.getDestinationField().getName());
1084             }
1085         }
1086     }
1087     
1088     @Test
1089     public void testWriteEmptyRequiredFieldInSecondBean() throws CsvException {
1090         for(MultiValuedMap<String, Integer> map1version2 : Arrays.asList(null, new ArrayListValuedHashMap<String, Integer>())) {
1091             List<GoodJoinByNameAnnotations> beanList = new ArrayList<>();
1092         
1093             GoodJoinByNameAnnotations bean = new GoodJoinByNameAnnotations();
1094             MultiValuedMap<String, Integer> map1 = new ArrayListValuedHashMap<>();
1095             map1.put("index", 1);
1096             map1.put("index", 2);
1097             map1.put("index", 3);
1098             bean.setMap1(map1);
1099             MultiValuedMap<String, Date> map2 = new ArrayListValuedHashMap<>();
1100             map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 15).getTime());
1101             map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 7).getTime());
1102             bean.setMap2(map2);
1103             MultiValuedMap<String, Integer> map4 = new ArrayListValuedHashMap<>();
1104             map4.put("conversion", 10000);
1105             map4.put("conversion", 20000);
1106             bean.setMap4(map4);
1107             beanList.add(bean);
1108 
1109             bean = new GoodJoinByNameAnnotations();
1110             bean.setMap1(map1version2);
1111             map2 = new ArrayListValuedHashMap<>();
1112             map2.put("date1", new GregorianCalendar(1978, Calendar.JANUARY, 16).getTime());
1113             map2.put("date2", new GregorianCalendar(2018, Calendar.FEBRUARY, 8).getTime());
1114             bean.setMap2(map2);
1115             map4 = new ArrayListValuedHashMap<>();
1116             map4.put("conversion", 10001);
1117             map4.put("conversion", 20002);
1118             bean.setMap4(map4);
1119             beanList.add(bean);
1120 
1121             StringWriter w = new StringWriter();
1122             StatefulBeanToCsv<GoodJoinByNameAnnotations> btc = new StatefulBeanToCsvBuilder<GoodJoinByNameAnnotations>(w).build();
1123             try {
1124                 btc.write(beanList);
1125                 fail("Exception should have been thrown.");
1126             }
1127             catch(CsvRequiredFieldEmptyException e) {
1128                 assertEquals(2, e.getLineNumber());
1129                 assertEquals(GoodJoinByNameAnnotations.class, e.getBeanClass());
1130                 assertEquals("map1", e.getDestinationField().getName());
1131             }
1132         }
1133     }
1134     
1135     @Test
1136     public void testWriteFieldWithoutGetter() throws CsvException {
1137         GoodJoinByPositionAnnotations bean = new GoodJoinByPositionAnnotations();
1138         ArrayListValuedHashMap<Integer, Integer> map1 = new ArrayListValuedHashMap<>();
1139         map1.put(0, Integer.MIN_VALUE);
1140         bean.setMap1(map1);
1141         ArrayListValuedHashMap<Integer, Date> map2 = new ArrayListValuedHashMap<>();
1142         map2.put(1, new GregorianCalendar(1974, Calendar.FEBRUARY, 27).getTime());
1143         map2.put(16, new GregorianCalendar(1978, Calendar.JANUARY, 15).getTime());
1144         map2.put(17, new GregorianCalendar(2003, Calendar.APRIL, 13).getTime());
1145         bean.setMap2(map2);
1146         
1147         StringWriter w = new StringWriter();
1148         StatefulBeanToCsv<GoodJoinByPositionAnnotations> b2csv = new StatefulBeanToCsvBuilder<GoodJoinByPositionAnnotations>(w).build();
1149         b2csv.write(bean);
1150 
1151         assertTrue(Pattern.matches("\"-2147483648\",\"27\\. Feb\\.? 1974\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"15\\. Jan\\.? 1978\",\"13\\. Apr\\.? 2003\"\n", w.toString()));
1152     }
1153     
1154     @Test
1155     public void testSetterThrowsException() {
1156         try {
1157             new CsvToBeanBuilder<SetterThrowsException>(new StringReader("map\nstring"))
1158                     .withType(SetterThrowsException.class).build().parse();
1159             fail("Exception should have been thrown");
1160         }
1161         catch(RuntimeException e) {
1162             assertNotNull(e.getCause());
1163             assertTrue(e.getCause() instanceof CsvBeanIntrospectionException);
1164             CsvBeanIntrospectionException csve = (CsvBeanIntrospectionException)e.getCause();
1165             assertEquals("map", csve.getField().getName());
1166         }
1167     }
1168     
1169     @Test
1170     public void testUnknownMultiValuedMap() {
1171         try {
1172             new CsvToBeanBuilder<UnknownMultiValuedMapField>(new StringReader(StringUtils.EMPTY))
1173                     .withType(UnknownMultiValuedMapField.class)
1174                     .build();
1175             fail("Exception should have been thrown");
1176         }
1177         catch(CsvBadConverterException e) {
1178             assertEquals(BeanFieldJoin.class, e.getConverterClass());
1179         }
1180     }
1181     
1182     @Test
1183     public void testUnassignableMultiValuedMap() {
1184         try {
1185             new CsvToBeanBuilder<MismatchedMultiValuedMap>(new StringReader(StringUtils.EMPTY))
1186                     .withType(MismatchedMultiValuedMap.class)
1187                     .build();
1188             fail("Exception should have been thrown.");
1189         }
1190         catch(CsvBadConverterException e) {
1191             assertEquals(BeanFieldJoin.class, e.getConverterClass());
1192         }
1193     }
1194     
1195     @Test
1196     public void testBeanInstantiationImpossibleIllegalAccess() {
1197         try {
1198             new CsvToBeanBuilder<InstantiationImpossibleIllegalAccess>(new StringReader("map\n1"))
1199                     .withType(InstantiationImpossibleIllegalAccess.class)
1200                     .build().parse();
1201             fail("Exception should have been thrown.");
1202         }
1203         catch(RuntimeException e) {
1204             assertNotNull(e.getCause());
1205             assertTrue(e.getCause() instanceof CsvBadConverterException);
1206             CsvBadConverterException csve = (CsvBadConverterException)e.getCause();
1207             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
1208         }
1209     }
1210     
1211     @Test
1212     public void testNoNullaryConstructor() {
1213         try {
1214             new CsvToBeanBuilder<NoNullaryConstructor>(new StringReader("map\n1"))
1215                     .withType(NoNullaryConstructor.class)
1216                     .build().parse();
1217             fail("Exception should have been thrown.");
1218         }
1219         catch(RuntimeException e) {
1220             assertNotNull(e.getCause());
1221             assertTrue(e.getCause() instanceof CsvBadConverterException);
1222             CsvBadConverterException csve = (CsvBadConverterException)e.getCause();
1223             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
1224         }
1225     }
1226     
1227     @Test
1228     public void testNoNullaryConstructorNoSetter() {
1229         try {
1230             new CsvToBeanBuilder<NoNullaryConstructorNoSetter>(new StringReader("map\n1"))
1231                     .withType(NoNullaryConstructorNoSetter.class)
1232                     .build().parse();
1233             fail("Exception should have been thrown.");
1234         }
1235         catch(RuntimeException e) {
1236             assertNotNull(e.getCause());
1237             assertTrue(e.getCause() instanceof CsvBadConverterException);
1238             CsvBadConverterException csve = (CsvBadConverterException)e.getCause();
1239             assertEquals(BeanFieldJoin.class, csve.getConverterClass());
1240         }
1241     }
1242 
1243     @Test
1244     public void testCustomConverterByNameRead() throws IOException {
1245         ResourceBundle res = ResourceBundle.getBundle("collectionconverter", Locale.GERMAN);
1246         List<IdAndErrorJoinByName> beanList = new CsvToBeanBuilder<IdAndErrorJoinByName>(new FileReader("src/test/resources/testinputjoincustombyname.csv"))
1247                 .withType(IdAndErrorJoinByName.class)
1248                 .build().parse();
1249         assertEquals(2, beanList.size());
1250 
1251         // Bean one
1252         IdAndErrorJoinByName bean = beanList.get(0);
1253         assertEquals(1, bean.getId());
1254         MultiValuedMap<String, ErrorCode> map = bean.getEc();
1255         assertEquals(1, map.keySet().size());
1256         Collection<ErrorCode> errorCodes = map.values();
1257         assertEquals(3, errorCodes.size());
1258         ErrorCode[] errorArray = new ErrorCode[3];
1259         errorArray = errorCodes.toArray(errorArray);
1260         ErrorCode ec = errorArray[0];
1261         assertEquals(10, ec.errorCode);
1262         assertEquals(res.getString("default.error"), ec.errorMessage);
1263         ec = errorArray[1];
1264         assertEquals(11, ec.errorCode);
1265         assertEquals("doesnt.exist", ec.errorMessage);
1266         ec = errorArray[2];
1267         assertEquals(12, ec.errorCode);
1268         assertEquals(res.getString("default.error"), ec.errorMessage);
1269 
1270         // Bean two
1271         bean = beanList.get(1);
1272         assertEquals(2, bean.getId());
1273         map = bean.getEc();
1274         assertEquals(1, map.keySet().size());
1275         errorCodes = map.values();
1276         assertEquals(3, errorCodes.size());
1277         errorArray = errorCodes.toArray(errorArray);
1278         ec = errorArray[0];
1279         assertEquals(20, ec.errorCode);
1280         assertEquals("doesnt.exist", ec.errorMessage);
1281         ec = errorArray[1];
1282         assertEquals(21, ec.errorCode);
1283         assertEquals(res.getString("default.error"), ec.errorMessage);
1284         ec = errorArray[2];
1285         assertEquals(22, ec.errorCode);
1286         assertEquals("doesnt.exist", ec.errorMessage);
1287     }
1288 
1289     @Test
1290     public void testCustomConverterByPositionRead() throws IOException {
1291         ResourceBundle res = ResourceBundle.getBundle("collectionconverter");
1292         List<IdAndErrorJoinByPosition> beanList = new CsvToBeanBuilder<IdAndErrorJoinByPosition>(new FileReader("src/test/resources/testinputjoincustombyposition.csv"))
1293                 .withType(IdAndErrorJoinByPosition.class)
1294                 .build().parse();
1295         assertEquals(2, beanList.size());
1296 
1297         // Bean one
1298         IdAndErrorJoinByPosition bean = beanList.get(0);
1299         assertEquals(1, bean.getId());
1300         MultiValuedMap<Integer, ErrorCode> map = bean.getEc();
1301         assertEquals(3, map.keySet().size());
1302         Collection<ErrorCode> errorCodes = map.values();
1303         assertEquals(3, errorCodes.size());
1304         ErrorCode[] errorArray = new ErrorCode[3];
1305         errorArray = errorCodes.toArray(errorArray);
1306         ErrorCode ec = errorArray[0];
1307         assertEquals(10, ec.errorCode);
1308         assertEquals(res.getString("default.error"), ec.errorMessage);
1309         ec = errorArray[1];
1310         assertEquals(11, ec.errorCode);
1311         assertEquals("doesnt.exist", ec.errorMessage);
1312         ec = errorArray[2];
1313         assertEquals(12, ec.errorCode);
1314         assertEquals(res.getString("default.error"), ec.errorMessage);
1315 
1316         // Bean two
1317         bean = beanList.get(1);
1318         assertEquals(2, bean.getId());
1319         map = bean.getEc();
1320         assertEquals(3, map.keySet().size());
1321         errorCodes = map.values();
1322         assertEquals(3, errorCodes.size());
1323         errorArray = errorCodes.toArray(errorArray);
1324         ec = errorArray[0];
1325         assertEquals(20, ec.errorCode);
1326         assertEquals("doesnt.exist", ec.errorMessage);
1327         ec = errorArray[1];
1328         assertEquals(21, ec.errorCode);
1329         assertEquals(res.getString("default.error"), ec.errorMessage);
1330         ec = errorArray[2];
1331         assertEquals(22, ec.errorCode);
1332         assertEquals("doesnt.exist", ec.errorMessage);
1333     }
1334 
1335     @Test
1336     public void testCustomConverterByNameWrite() throws CsvException, IOException {
1337         List<IdAndErrorJoinByName> beanList = new CsvToBeanBuilder<IdAndErrorJoinByName>(new FileReader("src/test/resources/testinputjoincustombyname.csv"))
1338                 .withType(IdAndErrorJoinByName.class)
1339                 .build().parse();
1340         StringWriter writer = new StringWriter();
1341         new StatefulBeanToCsvBuilder<IdAndErrorJoinByName>(writer).build().write(beanList);
1342         assertEquals("\"ID\",\"ec\",\"ec\",\"ec\"\n\"1\",\"10default.error\",\"11default.error\",\"12default.error\"\n\"2\",\"20default.error\",\"21default.error\",\"22default.error\"\n", writer.toString());
1343     }
1344 
1345     @Test
1346     public void testCustomConverterByPositionWrite() throws CsvException, IOException {
1347         List<IdAndErrorJoinByPosition> beanList = new CsvToBeanBuilder<IdAndErrorJoinByPosition>(new FileReader("src/test/resources/testinputjoincustombyposition.csv"))
1348                 .withType(IdAndErrorJoinByPosition.class)
1349                 .build().parse();
1350         StringWriter writer = new StringWriter();
1351         new StatefulBeanToCsvBuilder<IdAndErrorJoinByPosition>(writer).build().write(beanList);
1352         assertEquals("\"1\",\"10default.error\",\"11default.error\",\"12default.error\"\n\"2\",\"20default.error\",\"21default.error\",\"22default.error\"\n", writer.toString());
1353     }
1354 
1355     @Test
1356     public void testBadCustomConverter() throws IOException {
1357         try {
1358             // Input doesn't matter. The test doesn't get that far.
1359             new CsvToBeanBuilder<BadJoinConverter>(new FileReader("src/test/resources/testinputjoincustombyname.csv"))
1360                     .withType(BadJoinConverter.class)
1361                     .build().parse();
1362         }
1363         catch(CsvBadConverterException csve) {
1364             assertEquals(BadCollectionConverter.class, csve.getConverterClass());
1365         }
1366     }
1367 
1368     @Test
1369     public void testCaptureByNameInvalidRegex() {
1370         try {
1371             MappingStrategy<InvalidCapture> strat = new HeaderColumnNameMappingStrategy<>();
1372             strat.setType(InvalidCapture.class);
1373             fail("Exception should have been thrown.");
1374         }
1375         catch(CsvBadConverterException csve) {
1376             assertEquals(BeanFieldSingleValue.class, csve.getConverterClass());
1377             assertNotNull(csve.getCause());
1378         }
1379     }
1380 
1381     @Test
1382     public void testCaptureByPositionInvalidRegex() {
1383         try {
1384             MappingStrategy<InvalidCapture> strat = new ColumnPositionMappingStrategy<>();
1385             strat.setType(InvalidCapture.class);
1386             fail("Exception should have been thrown.");
1387         }
1388         catch(CsvBadConverterException csve) {
1389             assertEquals(BeanFieldSingleValue.class, csve.getConverterClass());
1390             assertNotNull(csve.getCause());
1391         }
1392     }
1393 
1394     @Test
1395     public void testCaptureByNameRegexWithoutCaptureGroup() {
1396         try {
1397             MappingStrategy<NoCaptureGroup> strat = new HeaderColumnNameMappingStrategy<>();
1398             strat.setType(NoCaptureGroup.class);
1399             fail("Exception should have been thrown.");
1400         }
1401         catch(CsvBadConverterException csve) {
1402             assertEquals(BeanFieldSingleValue.class, csve.getConverterClass());
1403             assertNull(csve.getCause());
1404         }
1405     }
1406 
1407     @Test
1408     public void testCaptureByPositionRegexWithoutCaptureGroup() {
1409         try {
1410             MappingStrategy<NoCaptureGroup> strat = new ColumnPositionMappingStrategy<>();
1411             strat.setType(NoCaptureGroup.class);
1412             fail("Exception should have been thrown.");
1413         }
1414         catch(CsvBadConverterException csve) {
1415             assertEquals(BeanFieldSingleValue.class, csve.getConverterClass());
1416             assertNull(csve.getCause());
1417         }
1418     }
1419 
1420     @Test
1421     public void testFormatByNameWriteInvalidFormatString() {
1422         try {
1423             MappingStrategy<InvalidFormatString> strat = new HeaderColumnNameMappingStrategy<>();
1424             strat.setType(InvalidFormatString.class);
1425             fail("Exception should have been thrown.");
1426         }
1427         catch(CsvBadConverterException csve) {
1428             assertEquals(BeanFieldSingleValue.class, csve.getConverterClass());
1429             assertNotNull(csve.getCause());
1430         }
1431     }
1432 
1433     @Test
1434     public void testFormatByPositionWriteInvalidFormatString() {
1435         try {
1436             MappingStrategy<InvalidFormatString> strat = new ColumnPositionMappingStrategy<>();
1437             strat.setType(InvalidFormatString.class);
1438             fail("Exception should have been thrown.");
1439         }
1440         catch(CsvBadConverterException csve) {
1441             assertEquals(BeanFieldSingleValue.class, csve.getConverterClass());
1442             assertNotNull(csve.getCause());
1443         }
1444     }
1445 }