View Javadoc
1   /*
2    * Copyright 2016 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.ICSVWriter;
19  import com.opencsv.TestUtils;
20  import com.opencsv.bean.mocks.*;
21  import com.opencsv.exceptions.CsvDataTypeMismatchException;
22  import com.opencsv.exceptions.CsvException;
23  import com.opencsv.exceptions.CsvFieldAssignmentException;
24  import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
25  import org.apache.commons.lang3.tuple.ImmutablePair;
26  import org.junit.jupiter.api.AfterEach;
27  import org.junit.jupiter.api.BeforeAll;
28  import org.junit.jupiter.api.BeforeEach;
29  import org.junit.jupiter.api.Test;
30  
31  import java.io.IOException;
32  import java.io.StringWriter;
33  import java.text.ParseException;
34  import java.text.RuleBasedCollator;
35  import java.util.*;
36  import java.util.regex.Pattern;
37  
38  import static org.junit.jupiter.api.Assertions.*;
39  
40  /**
41   * Tests {@link StatefulBeanToCsv}.
42   * @author Andrew Rucker Jones
43   */
44  public class StatefulBeanToCsvTest {
45      
46      private static Locale systemLocale;
47  
48      // If alternatives in these regular expressions are always related to different
49      // handling of the same locale in different Java versions. There was a break from
50      // Java 8 to Java 9, and another from Java 12 to Java 13.
51      private static final String EXTRA_STRING_FOR_WRITING = "extrastringforwritinghowcreative";
52      private static final String GOOD_DATA_1 = "test string;value: true;false;1;2;3;4;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;123101.1;1.000,2;2000.3;3.000,4;5000;6.000;2147476647;8.000;9000;10.000;11000;12.000;13000;14.000;15000;16.000;a;b;123101.101;123.102,102;101;102;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;1.01;TEST1;EUR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;";
53      private static final String GOOD_DATA_2 = "test string;;false;1;2;3;4;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;123101.1;1.000,2;2000.3;3.000,4;5000;6.000;2147476647;8.000;9000;10.000;11000;12.000;13000;14.000;15000;16.000;a;b;123101.101;123.102,102;101;102;19780115T063209;19780115T163209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;2.02;Test2;CHF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;";
54      private static final String GOOD_DATA_OPTIONALS_NULL = "test string;value: true;false;1;2;3;4;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;;1.000,2;2000.3;3.000,4;5000;6.000;2147476647;8.000;9000;10.000;11000;12.000;13000;14.000;15000;16.000;a;b;123101.101;123.102,102;101;102;19780115T063209;19780115T063209;;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;1.01;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;";
55      private static final String GOOD_DATA_CUSTOM_1 = "inside custom converter;wahr;falsch;127;127;127;;1.7976931348623157E308;1.7976931348623157E308;1.7976931348623157E308;1.7976931348623157E308;3.4028235E38;3.4028235E38;3.4028235E38;3.4028235E38;2147483647;2147483647;2147483647;2147483647;9223372036854775807;9223372036854775807;9223372036854775807;9223372036854775807;32767;32767;32767;32767;\uFFFF;\uFFFF;10;10;10;10;;;;;;;;;;;;;falsch;wahr;really long test string, yeah!;1.a.long,long.string1;2147483645.z.Inserted in setter methodlong,long.string2;3.c.long,long.derived.string3;inside custom converter";
56      private static final String HEADER_NAME_FULL = "BIGDECIMAL1;BIGDECIMAL2;BIGINTEGER1;BIGINTEGER2;BOOL1;BOOLPRIMITIVE;BYTE1;BYTE2;BYTE3;BYTE4;CHAR1;CHAR2;CURRENCY1;DATE1;DATE10;DATE11;DATE12;DATE13;DATE14;DATE15;DATE16;DATE2;DATE3;DATE4;DATE5;DATE6;DATE7;DATE8;DATE9;DOUBLE1;DOUBLE2;DOUBLE3;DOUBLE4;ENUM1;FLOAT1;FLOAT2;FLOAT3;FLOAT4;FLOAT5;INTEGER1;INTEGER2;INTEGER3;INTEGER4;ITNOGOODCOLUMNITVERYBAD;LONG1;LONG2;LONG3;LONG4;SHORT1;SHORT2;SHORT3;SHORT4;STRING1";
57      private static final String GOOD_DATA_NAME_1 = "123101.101;123.102,102;101;102;value: true;false;1;2;3;4;a;b;EUR;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;TEST1;123101.1;1.000,2;2000.3;3.000,4;1.01;5000;6.000;2147476647;8.000;;9000;10.000;11000;12.000;13000;14.000;15000;16.000;test string";
58      private static final String HEADER_NAME_FULL_CUSTOM = "BIGDECIMAL1;BIGDECIMAL2;BIGINTEGER1;BIGINTEGER2;BOOL1;BOOL2;BOOL3;BOOLPRIMITIVE;BYTE1;BYTE2;BYTE3;CHAR1;CHAR2;COMPLEX1;COMPLEX2;COMPLEX3;DOUBLE1;DOUBLE2;DOUBLE3;DOUBLE4;FLOAT1;FLOAT2;FLOAT3;FLOAT4;INTEGER1;INTEGER2;INTEGER3;INTEGER4;LONG1;LONG2;LONG3;LONG4;REQUIREDWITHCUSTOM;SHORT1;SHORT2;SHORT3;SHORT4;STRING1;STRING2";
59      private static final String GOOD_DATA_NAME_CUSTOM_1 = "10;10;10;10;wahr;falsch;wahr;falsch;127;127;127;\uFFFF;\uFFFF;1.a.long,long.string1;2147483645.z.Inserted in setter methodlong,long.string2;3.c.long,long.derived.string3;1.7976931348623157E308;1.7976931348623157E308;1.7976931348623157E308;1.7976931348623157E308;3.4028235E38;3.4028235E38;3.4028235E38;3.4028235E38;2147483647;2147483647;2147483647;2147483647;9223372036854775807;9223372036854775807;9223372036854775807;9223372036854775807;inside custom converter;32767;32767;32767;32767;inside custom converter;really long test string, yeah!";
60      private static final String GOOD_DATA_NAME_CUSTOM_2 = "10;10;10;10;wahr;falsch;wahr;falsch;127;127;127;\uFFFF;\uFFFF;4.d.long,long.string4;2147483642.z.Inserted in setter methodlong,long.derived.string5;6.f.long,long.string6;1.7976931348623157E308;1.7976931348623157E308;1.7976931348623157E308;1.7976931348623157E308;3.4028235E38;3.4028235E38;3.4028235E38;3.4028235E38;2147483647;2147483647;2147483647;2147483647;9223372036854775807;9223372036854775807;9223372036854775807;9223372036854775807;inside custom converter;32767;32767;32767;32767;inside custom converter;really";
61      private static final String HEADER_NAME_FULL_DERIVED = "BIGDECIMAL1;BIGDECIMAL2;BIGINTEGER1;BIGINTEGER2;BOOL1;BOOLPRIMITIVE;BYTE1;BYTE2;BYTE3;BYTE4;CHAR1;CHAR2;CURRENCY1;DATE1;DATE10;DATE11;DATE12;DATE13;DATE14;DATE15;DATE16;DATE2;DATE3;DATE4;DATE5;DATE6;DATE7;DATE8;DATE9;DOUBLE1;DOUBLE2;DOUBLE3;DOUBLE4;ENUM1;FLOAT1;FLOAT2;FLOAT3;FLOAT4;FLOAT5;INT IN SUBCLASS;INTEGER1;INTEGER2;INTEGER3;INTEGER4;ITNOGOODCOLUMNITVERYBAD;LONG1;LONG2;LONG3;LONG4;SHORT1;SHORT2;SHORT3;SHORT4;STRING1";
62      private static final String GOOD_DATA_NAME_DERIVED_1 = "123101.101;123.102,102;101;102;value: true;false;1;2;3;4;a;b;EUR;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;TEST1;123101.1;123.202,203;123303.305;123.404,406;1.01;7;5000;6.000;2147476647;8.000;;9000;10.000;11000;12.000;13000;14.000;15000;16.000;test string";
63      private static final String GOOD_DATA_NAME_DERIVED_SUB_1 = "123101.101;123.102,102;101;102;value: true;false;1;2;3;4;a;b;EUR;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;TEST1;123101.1;123.202,203;123303.305;123.404,406;1.01;5000;6.000;2147476647;8.000;;9000;10.000;11000;12.000;13000;14.000;15000;16.000;test string";
64      private static final String REVERSE_GOOD_DATA_1 = ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EUR;TEST1;1.01;19780115T063209;19780115T063209;13. Dezember 2018;01/15/1978;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;102;101;123.102,102;123101.101;b;a;16.000;15000;14.000;13000;12.000;11000;10.000;9000;8.000;2147476647;6.000;5000;3.000,4;2000.3;1.000,2;123101.1;123[\u00A0\u202F]404,404;123303.303;123.202,202;123,101.101;4;3;2;1;false;value: true;test string";
65      private static final String COLLATED_HEADER_NAME_FULL = "SHORT1;SHORT2;SHORT3;SHORT4;STRING1;BIGDECIMAL1;BIGDECIMAL2;BIGINTEGER1;BIGINTEGER2;BOOLPRIMITIVE;BOOL1;BYTE1;BYTE2;BYTE3;BYTE4;CHAR1;CHAR2;CURRENCY1;DATE1;DATE10;DATE11;DATE12;DATE13;DATE14;DATE15;DATE16;DATE2;DATE3;DATE4;DATE5;DATE6;DATE7;DATE8;DATE9;DOUBLE1;DOUBLE2;DOUBLE3;DOUBLE4;ENUM1;FLOAT1;FLOAT2;FLOAT3;FLOAT4;FLOAT5;INTEGER1;INTEGER2;INTEGER3;INTEGER4;ITNOGOODCOLUMNITVERYBAD;LONG1;LONG2;LONG3;LONG4";
66      private static final String COLLATED_GOOD_DATA_NAME_1 = "13000;14.000;15000;16.000;test string;123101.101;123.102,102;101;102;false;value: true;1;2;3;4;a;b;EUR;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13. Dezember 2018;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;123,101.101;123.202,202;123303.303;123[\u00A0\u202F]404,404;TEST1;123101.1;1.000,2;2000.3;3.000,4;1.01;5000;6.000;2147476647;8.000;;9000;10.000;11000;12.000";
67  
68      @BeforeAll
69      public static void storeSystemLocale() {
70          systemLocale = Locale.getDefault();
71      }
72  
73      @BeforeEach
74      public void setSystemLocaleToValueNotGerman() {
75          Locale.setDefault(Locale.US);
76      }
77  
78      @AfterEach
79      public void setSystemLocaleBackToDefault() {
80          Locale.setDefault(systemLocale);
81      }
82  
83      /**
84       * Test of writing a single bean.
85       * This also incidentally covers the following conditions because of the
86       * datatypes and annotations in the bean used in testing:<ul>
87       * <li>Writing every primitive data type</li>
88       * <li>Writing every wrapped primitive data type</li>
89       * <li>Writing String, BigDecimal and BigInteger</li>
90       * <li>Writing all locale-sensitive data without locales</li>
91       * <li>Writing all locale-sensitive data with locales</li>
92       * <li>Writing a date type without an explicit format string</li>
93       * <li>Writing a date type with an explicit format string</li>
94       * <li>Writing with mixed @CsvBindByName and @CsvBindByPosition annotation
95       * types (expected behavior: The column position mapping strategy is
96       * automatically selected)</li>
97       * <li>Writing with a format string using a column mapping</li></ul>
98       * @throws IOException Never
99       * @throws CsvException Never
100      */
101     @Test
102     public void writeSingleBeanNoQuotes() throws IOException, CsvException {
103         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
104         StringWriter writer = new StringWriter();
105         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
106                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
107                 .withSeparator(';')
108                 .build();
109         btcsv.write(beans.left);
110         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n", writer.toString()));
111     }
112 
113     /**
114      * Tests writing one bean with optional quotes.
115      * <p>Also incidentally tests:<ul>
116      * <li>Writing with a different locale, @CsvBindByPosition, primitive</li>
117      * </ul></p>
118      *
119      * @throws IOException Never
120      * @throws CsvException Never
121      */
122     @Test
123     public void writeSingleOptionallyQuotedBean() throws IOException, CsvException {
124         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
125         StringWriter writer = new StringWriter();
126         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
127                 .withSeparator(';')
128                 .build();
129         beans.left.setStringClass("Quoted \"air quotes\" string");
130         btcsv.write(beans.left);
131         String output = writer.toString();
132         assertTrue(Pattern.matches(
133                 "\"Quoted \"\"air quotes\"\" string\";\"value: true\";\"false\";\"1\";\"2\";\"3\";\"4\";\"123,101\\.101\";\"123\\.202,202\";\"123303\\.303\";\"123[\u00A0\u202F]404,404\";\"123101\\.1\";\"1\\.000,2\";\"2000\\.3\";\"3\\.000,4\";\"5000\";\"6\\.000\";\"2147476647\";\"8\\.000\";\"9000\";\"10\\.000\";\"11000\";\"12\\.000\";\"13000\";\"14\\.000\";\"15000\";\"16\\.000\";\"a\";\"b\";\"123101\\.101\";\"123\\.102,102\";\"101\";\"102\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"19780115T063209\";\"01/15/1978\";\"13\\. Dezember 2018\";\"19780115T063209\";\"19780115T063209\";\"1\\.01\";\"TEST1\";\"EUR\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\";\"\"\n",
134                 output));
135     }
136 
137     @Test
138     public void writeSingleQuotedBean() throws IOException, CsvException {
139         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
140         StringWriter writer = new StringWriter();
141         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
142                 .withApplyQuotesToAll(false)
143                 .withSeparator(';')
144                 .build();
145         beans.left.setStringClass("Quoted \"air quotes\" string");
146         btcsv.write(beans.left);
147         assertTrue(Pattern.matches(
148                 "\"Quoted \"\"air quotes\"\" string\";value: true;false;1;2;3;4;123,101\\.101;123\\.202,202;123303\\.303;123[\u00A0\u202F]404,404;123101\\.1;1\\.000,2;2000\\.3;3\\.000,4;5000;6\\.000;2147476647;8\\.000;9000;10\\.000;11000;12\\.000;13000;14\\.000;15000;16\\.000;a;b;123101\\.101;123\\.102,102;101;102;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;19780115T063209;01/15/1978;13\\. Dezember 2018;19780115T063209;19780115T063209;1\\.01;TEST1;EUR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n",
149                 writer.toString()));
150     }
151 
152     /**
153      * Test of writing multiple beans at once when order counts.
154      * <p>Also incidentally tests:
155      * <ul><li>Writing empty values with a format string</li></ul></p>
156      *
157      * @throws IOException Never
158      * @throws CsvException Never
159      */
160     @Test
161     public void writeMultipleBeansOrdered() throws IOException, CsvException {
162         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
163         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
164         beanList.add(beans.left); beanList.add(beans.right);
165         StringWriter writer = new StringWriter();
166         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
167                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
168                 .withSeparator(';')
169                 .build();
170         btcsv.write(beanList);
171         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n" + GOOD_DATA_2 + "\n", writer.toString()));
172     }
173 
174     /**
175      * Test of writing multiple beans with iterator.
176      *
177      * @throws IOException  Never
178      * @throws CsvException Never
179      */
180     @Test
181     public void writeMultipleBeansOrderedWithIterator() throws IOException, CsvException {
182         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
183         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
184         beanList.add(beans.left);
185         beanList.add(beans.right);
186         StringWriter writer = new StringWriter();
187         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
188                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
189                 .withSeparator(';')
190                 .build();
191         btcsv.write(beanList.iterator());
192         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n" + GOOD_DATA_2 + "\n", writer.toString()));
193     }
194 
195     /**
196      * Test of writing multiple beans from a stream.
197      *
198      * @throws IOException  Never
199      * @throws CsvException Never
200      */
201     @Test
202     public void writeMultipleBeansOrderedFromStream() throws IOException, CsvException {
203         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
204         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
205         beanList.add(beans.left);
206         beanList.add(beans.right);
207         StringWriter writer = new StringWriter();
208         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
209                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
210                 .withSeparator(';')
211                 .build();
212         btcsv.write(beanList.stream());
213         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n" + GOOD_DATA_2 + "\n", writer.toString()));
214     }
215 
216     /**
217      * Test of writing multiple beans at once when order doesn't matter.
218      *
219      * @throws IOException Never
220      * @throws CsvException Never
221      */
222     @Test
223     public void writeMultipleBeansUnordered() throws IOException, CsvException {
224         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
225         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
226         beanList.add(beans.left); beanList.add(beans.right);
227         StringWriter writer = new StringWriter();
228         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
229                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
230                 .withSeparator(';')
231                 .withOrderedResults(false)
232                 .build();
233         btcsv.write(beanList);
234         String r = writer.toString();
235         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n" + GOOD_DATA_2 + "\n", r) || Pattern.matches(GOOD_DATA_2 + "\n" + GOOD_DATA_1 + "\n", r));
236     }
237 
238     /**
239      * Test of writing multiple beans at once when order doesn't matter using the iterator.
240      *
241      * @throws IOException  Never
242      * @throws CsvException Never
243      */
244     @Test
245     public void writeMultipleBeansUnorderedWithIterator() throws IOException, CsvException {
246         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
247         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
248         beanList.add(beans.left);
249         beanList.add(beans.right);
250         StringWriter writer = new StringWriter();
251         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
252                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
253                 .withSeparator(';')
254                 .withOrderedResults(false)
255                 .build();
256         btcsv.write(beanList.iterator());
257         String r = writer.toString();
258         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n" + GOOD_DATA_2 + "\n", r) || Pattern.matches(GOOD_DATA_2 + "\n" + GOOD_DATA_1 + "\n", r));
259     }
260 
261         
262     /**
263      * Test of writing a mixture of single beans and multiple beans.
264      * @throws IOException Never
265      * @throws CsvException Never
266      */
267     @Test
268     public void writeMixedSingleMultipleBeans() throws IOException, CsvException {
269         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
270         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
271         beanList.add(beans.left); beanList.add(beans.right);
272         StringWriter writer = new StringWriter();
273         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
274                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
275                 .withSeparator(';')
276                 .withLineEnd("arj\n")
277                 .build();
278         btcsv.write(beanList);
279         btcsv.write(beans.left);
280         assertTrue(Pattern.matches(GOOD_DATA_1 + "arj\n" + GOOD_DATA_2 + "arj\n" + GOOD_DATA_1 + "arj\n", writer.toString()));
281     }
282 
283     /**
284      * Test of writing a mixture of single beans and multiple beans using the iterator.
285      *
286      * @throws IOException  Never
287      * @throws CsvException Never
288      */
289     @Test
290     public void writeMixedSingleMultipleBeansWithIterator() throws IOException, CsvException {
291         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
292         List<AnnotatedMockBeanFull> beanList = new ArrayList<>();
293         beanList.add(beans.left);
294         beanList.add(beans.right);
295         StringWriter writer = new StringWriter();
296         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
297                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
298                 .withSeparator(';')
299                 .withLineEnd("arj\n")
300                 .build();
301         btcsv.write(beanList.iterator());
302         btcsv.write(beans.left);
303         assertTrue(Pattern.matches(GOOD_DATA_1 + "arj\n" + GOOD_DATA_2 + "arj\n" + GOOD_DATA_1 + "arj\n", writer.toString()));
304     }
305         
306     /**
307      * Test of writing optional fields whose values are null.
308      * We test:<ul>
309      * <li>A wrapped primitive, and</li>
310      * <li>A date</li></ul>
311      * @throws IOException Never
312      * @throws CsvException Never
313      */
314     @Test
315     public void writeOptionalFieldsWithNull() throws IOException, CsvException {
316         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
317         beans.left.setFloatWrappedDefaultLocale(null);
318         beans.left.setCalDefaultLocale(null);
319         beans.left.setTestEnum(null);
320         beans.left.setTestCurrency(null);
321         StringWriter writer = new StringWriter();
322         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
323                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
324                 .withSeparator(';')
325                 .withEscapechar('|') // Just for code coverage. Doesn't do anything else.
326                 .build();
327         btcsv.write(beans.left);
328         assertTrue(Pattern.matches(GOOD_DATA_OPTIONALS_NULL + "\n", writer.toString()));
329     }
330         
331     /**
332      * Test of writing an optional field with a column position not adjacent
333      * to the other column positions.
334      * @throws IOException Never
335      * @throws CsvException Never
336      */
337     @Test
338     public void writeOptionalNonContiguousField() throws IOException, CsvException {
339         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
340         beans.left.setColumnDoesntExist(EXTRA_STRING_FOR_WRITING);
341         StringWriter writer = new StringWriter();
342         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
343                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
344                 .withSeparator(';')
345                 .build();
346         btcsv.write(beans.left);
347         assertTrue(Pattern.matches(GOOD_DATA_1 + EXTRA_STRING_FOR_WRITING + "\n", writer.toString()));
348     }
349     
350     /**
351      * Test of writing using a specified mapping strategy.
352      * <p>Also incidentally tests:
353      * <ul><li>Writing with a format string using a header name mapping strategy</li>
354      * <li>Writing with a different locale, @CsvBindByName, primitive</li></ul></p>
355      *
356      * @throws IOException Never
357      * @throws CsvException Never
358      */
359     @Test
360     public void writeSpecifiedStrategy() throws IOException, CsvException {
361         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
362         StringWriter writer = new StringWriter();
363         HeaderColumnNameMappingStrategy<AnnotatedMockBeanFull> strat = new HeaderColumnNameMappingStrategy<>();
364         strat.setType(AnnotatedMockBeanFull.class);
365         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
366                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
367                 .withSeparator(';')
368                 .withMappingStrategy(strat)
369                 .build();
370         btcsv.write(beans.left);
371         assertTrue(Pattern.matches(HEADER_NAME_FULL + "\n" + GOOD_DATA_NAME_1 + "\n", writer.toString()));
372     }
373         
374     /**
375      * Test of writing with @CsvBindByPosition attached to unknown type.
376      * Expected behavior: Data are written with toString().
377      * @throws CsvException Never
378      */
379     @Test
380     public void writeBindByPositionUnknownType() throws CsvException {
381         BindUnknownType byNameUnsupported = new BindUnknownType();
382         StringWriter writer = new StringWriter();
383         StatefulBeanToCsv<Object> btcsv = new StatefulBeanToCsvBuilder<Object>(writer)
384                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
385                 .build();
386         btcsv.write(byNameUnsupported);
387         assertEquals(BindUnknownType.TOSTRING + "\n", writer.toString());
388     }
389         
390     /**
391      * Test of writing with @CsvBindByName attached to unknown type.
392      * Expected behavior: Data are written with toString().
393      * @throws CsvException Never
394      */
395     @Test
396     public void writeBindByNameUnknownType() throws CsvException {
397         BindUnknownType byNameUnsupported = new BindUnknownType();
398         StringWriter writer = new StringWriter();
399         HeaderColumnNameMappingStrategy<BindUnknownType> strat = new HeaderColumnNameMappingStrategy<>();
400         strat.setType(BindUnknownType.class);
401         StatefulBeanToCsv<BindUnknownType> btcsv = new StatefulBeanToCsvBuilder<BindUnknownType>(writer)
402                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
403                 .withMappingStrategy(strat)
404                 .build();
405         btcsv.write(byNameUnsupported);
406         assertEquals("TEST\n" + BindUnknownType.TOSTRING + "\n", writer.toString());
407     }
408         
409     /**
410      * Test writing with no annotations.
411      * @throws CsvException Never
412      */
413     @Test
414     public void writeWithoutAnnotations() throws CsvException {
415         StringWriter writer = new StringWriter();
416         ComplexClassForCustomAnnotation cc = new ComplexClassForCustomAnnotation();
417         cc.c = 'A'; cc.i = 1; cc.s = "String";
418         StatefulBeanToCsv<ComplexClassForCustomAnnotation> btcsv = new StatefulBeanToCsvBuilder<ComplexClassForCustomAnnotation>(writer)
419                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
420                 .withSeparator(';')
421                 .build();
422         btcsv.write(cc);
423         assertEquals("C;I;S\nA;1;String\n", writer.toString());
424     }
425         
426     /**
427      * Writing a subclass with annotations in the subclass and the superclass.
428      * @throws IOException Never
429      * @throws CsvException Never
430      */
431     @Test
432     public void writeDerivedSubclass() throws IOException, CsvException {
433         ImmutablePair<AnnotatedMockBeanFullDerived, AnnotatedMockBeanFullDerived> derivedList = TestUtils.createTwoGoodDerivedBeans();
434         StringWriter writer = new StringWriter();
435         HeaderColumnNameMappingStrategy<AnnotatedMockBeanFullDerived> strat = new HeaderColumnNameMappingStrategy<>();
436         strat.setType(AnnotatedMockBeanFullDerived.class);
437         StatefulBeanToCsv<AnnotatedMockBeanFullDerived> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFullDerived>(writer)
438                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
439                 .withSeparator(';')
440                 .withMappingStrategy(strat)
441                 .build();
442         btcsv.write(derivedList.left);
443         assertTrue(Pattern.matches(HEADER_NAME_FULL_DERIVED + "\n" + GOOD_DATA_NAME_DERIVED_1 + "\n", writer.toString()));
444     }
445         
446     /**
447      * Specifying a superclass, but writing a subclass.
448      * Expected behavior: Data from superclass are written.
449      * @throws IOException Never
450      * @throws CsvException Never
451      */
452     @Test
453     public void writeDerivedSuperclass() throws IOException, CsvException {
454         ImmutablePair<AnnotatedMockBeanFullDerived, AnnotatedMockBeanFullDerived> derivedList = TestUtils.createTwoGoodDerivedBeans();
455         StringWriter writer = new StringWriter();
456         HeaderColumnNameMappingStrategy<AnnotatedMockBeanFull> strat = new HeaderColumnNameMappingStrategy<>();
457         strat.setType(AnnotatedMockBeanFull.class);
458         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
459                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
460                 .withSeparator(';')
461                 .withMappingStrategy(strat)
462                 .build();
463         btcsv.write(derivedList.left);
464         assertTrue(Pattern.matches(HEADER_NAME_FULL + "\n" + GOOD_DATA_NAME_DERIVED_SUB_1 + "\n", writer.toString()));
465     }
466     
467     /**
468      * Tests of writing when getter is missing.
469      * Also tests incidentally:<ul>
470      * <li>Writing bad data without exceptions captured</li></ul>
471      * @throws CsvException Never
472      */
473     @Test
474     public void writeGetterMissing() throws CsvException {
475         GetterMissing getterMissing = new GetterMissing();
476         StringWriter writer = new StringWriter();
477         StatefulBeanToCsv<GetterMissing> sbtcsv = new StatefulBeanToCsvBuilder<GetterMissing>(writer)
478                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
479                 .build();
480         sbtcsv.write(getterMissing);
481         assertEquals("TEST\n123\n", writer.toString());
482     }
483         
484     /**
485      * Tests writing when getter is private.
486      * @throws CsvException Never
487      */
488     @Test
489     public void writeGetterPrivate() throws CsvException {
490         GetterPrivate getterPrivate = new GetterPrivate();
491         StringWriter writer = new StringWriter();
492         StatefulBeanToCsv<GetterPrivate> sbtcsv = new StatefulBeanToCsvBuilder<GetterPrivate>(writer)
493                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
494                 .build();
495         sbtcsv.write(getterPrivate);
496         assertEquals("TEST\n123\n", writer.toString());
497     }
498         
499     /**
500      * Writing a required wrapped primitive field that is null.
501      * Also tests incidentally:<ul>
502      * <li>Writing bad data with exceptions captured</li></ul>
503      * @throws IOException Never
504      * @throws CsvException Never
505      * @throws NoSuchFieldException Never
506      */
507     @Test
508     public void writeNullRequiredWrappedPrimitive() throws IOException, CsvException, NoSuchFieldException {
509         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
510         StringWriter writer = new StringWriter();
511         StatefulBeanToCsv<AnnotatedMockBeanFull> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
512                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
513                 .withThrowExceptions(false)
514                 .build();
515         beans.left.setByteWrappedSetLocale(null); // required
516         sbtcsv.write(beans.left);
517         List<CsvException> csves = sbtcsv.getCapturedExceptions();
518         assertNotNull(csves);
519         assertEquals(1, csves.size());
520         CsvException csve = csves.get(0);
521         assertTrue(csve instanceof CsvRequiredFieldEmptyException);
522         CsvRequiredFieldEmptyException rfe = (CsvRequiredFieldEmptyException) csve;
523         assertEquals(1L, rfe.getLineNumber());
524         assertEquals(AnnotatedMockBeanFull.class, rfe.getBeanClass());
525         assertEquals(beans.left.getClass().getDeclaredField("byteWrappedSetLocale"),
526                 rfe.getDestinationField());
527     }
528         
529     /**
530      * Writing a required field with a custom converter that is null.
531      * @throws IOException Never
532      * @throws CsvException Never
533      * @throws NoSuchFieldException Never
534      */
535     @Test
536     public void writeNullRequiredCustom() throws IOException, CsvException, NoSuchFieldException {
537         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
538         StringWriter writer = new StringWriter();
539         StatefulBeanToCsv<AnnotatedMockBeanCustom> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
540                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
541                 .withThrowExceptions(false)
542                 .build();
543         beans.left.setRequiredWithCustom(null); // required
544         sbtcsv.write(beans.left);
545         List<CsvException> csves = sbtcsv.getCapturedExceptions();
546         assertNotNull(csves);
547         assertEquals(1, csves.size());
548         CsvException csve = csves.get(0);
549         assertTrue(csve instanceof CsvRequiredFieldEmptyException);
550         CsvRequiredFieldEmptyException rfe = (CsvRequiredFieldEmptyException) csve;
551         assertEquals(1L, rfe.getLineNumber());
552         assertEquals(AnnotatedMockBeanCustom.class, rfe.getBeanClass());
553         assertEquals(beans.left.getClass().getDeclaredField("requiredWithCustom"),
554                 rfe.getDestinationField());
555     }
556 
557     /**
558      * Writing a bad bean at the beginning of a long list to trigger shutting
559      * down the ExecutorService.
560      * @throws IOException Never
561      * @throws CsvException Never
562      * @throws NoSuchFieldException Never
563      */
564     @Test
565     public void writeManyFirstBeanIsBad() throws IOException, CsvException, NoSuchFieldException {
566         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
567         StringWriter writer = new StringWriter();
568         StatefulBeanToCsv<AnnotatedMockBeanCustom> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
569                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
570                 .withThrowExceptions(true)
571                 .build();
572         beans.left.setRequiredWithCustom(null); // required
573         List<AnnotatedMockBeanCustom> beanList = new ArrayList<>(1000);
574         beanList.add(beans.left);
575         for(int i = 0; i < 999; i++) {beanList.add(beans.right);}
576         try {
577             sbtcsv.write(beanList);
578         } catch(CsvRequiredFieldEmptyException rfe) {
579             assertEquals(1L, rfe.getLineNumber());
580             assertEquals(AnnotatedMockBeanCustom.class, rfe.getBeanClass());
581             assertEquals(beans.left.getClass().getDeclaredField("requiredWithCustom"),
582                     rfe.getDestinationField());
583         }
584     }
585 
586     /**
587      * Writing a bad bean using the iterator write method at the
588      * beginning of a long list to trigger shutting down the ExecutorService.
589      *
590      * @throws IOException          Never
591      * @throws CsvException         Never
592      * @throws NoSuchFieldException Never
593      */
594     @Test
595     public void writeManyWithIteratorFirstBeanIsBad() throws IOException, CsvException, NoSuchFieldException {
596         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
597         StringWriter writer = new StringWriter();
598         StatefulBeanToCsv<AnnotatedMockBeanCustom> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
599                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
600                 .withThrowExceptions(true)
601                 .build();
602         beans.left.setRequiredWithCustom(null); // required
603         List<AnnotatedMockBeanCustom> beanList = new ArrayList<>(1000);
604         beanList.add(beans.left);
605         for (int i = 0; i < 999; i++) {
606             beanList.add(beans.right);
607         }
608         try {
609             sbtcsv.write(beanList.iterator());
610         } catch (CsvRequiredFieldEmptyException rfe) {
611             assertEquals(1L, rfe.getLineNumber());
612             assertEquals(AnnotatedMockBeanCustom.class, rfe.getBeanClass());
613             assertEquals(beans.left.getClass().getDeclaredField("requiredWithCustom"),
614                     rfe.getDestinationField());
615         }
616     }
617         
618     /**
619      * Writing a bad bean when exceptions are not thrown and the results are
620      * unordered.
621      * @throws IOException Never
622      * @throws CsvException Never
623      * @throws NoSuchFieldException Never
624      */
625     @Test
626     public void writeBadBeanUnorderedCaptureExceptions() throws IOException, CsvException, NoSuchFieldException {
627         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
628         StringWriter writer = new StringWriter();
629         StatefulBeanToCsv<AnnotatedMockBeanCustom> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
630                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
631                 .withThrowExceptions(false)
632                 .withOrderedResults(false)
633                 .build();
634         beans.left.setRequiredWithCustom(null); // required
635         List<AnnotatedMockBeanCustom> beanList = new ArrayList<>(10);
636         beanList.add(beans.left);
637         for(int i = 0; i < 9; i++) {beanList.add(beans.right);}
638         sbtcsv.write(beanList);
639         List<CsvException> exceptionList = sbtcsv.getCapturedExceptions();
640         assertNotNull(exceptionList);
641         assertEquals(1, exceptionList.size());
642         CsvException csve = exceptionList.get(0);
643         assertTrue(csve instanceof CsvRequiredFieldEmptyException);
644         CsvRequiredFieldEmptyException rfe = (CsvRequiredFieldEmptyException) csve;
645         assertEquals(1L, rfe.getLineNumber());
646         assertEquals(AnnotatedMockBeanCustom.class, rfe.getBeanClass());
647         assertEquals(beans.left.getClass().getDeclaredField("requiredWithCustom"),
648                 rfe.getDestinationField());
649     }
650 
651     /**
652      * Writing a bad bean using the iterator when exceptions are not thrown and
653      * the results are unordered.
654      *
655      * @throws IOException          Never
656      * @throws CsvException         Never
657      * @throws NoSuchFieldException Never
658      */
659     @Test
660     public void writeWithIteratorBadBeanUnorderedCaptureExceptions() throws IOException, CsvException, NoSuchFieldException {
661         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
662         StringWriter writer = new StringWriter();
663         StatefulBeanToCsv<AnnotatedMockBeanCustom> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
664                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
665                 .withThrowExceptions(false)
666                 .withOrderedResults(false)
667                 .build();
668         beans.left.setRequiredWithCustom(null); // required
669         List<AnnotatedMockBeanCustom> beanList = new ArrayList<>(10);
670         beanList.add(beans.left);
671         for (int i = 0; i < 9; i++) {
672             beanList.add(beans.right);
673         }
674         sbtcsv.write(beanList.iterator());
675         List<CsvException> exceptionList = sbtcsv.getCapturedExceptions();
676         assertNotNull(exceptionList);
677         assertEquals(1, exceptionList.size());
678         CsvException csve = exceptionList.get(0);
679         assertTrue(csve instanceof CsvRequiredFieldEmptyException);
680         CsvRequiredFieldEmptyException rfe = (CsvRequiredFieldEmptyException) csve;
681         assertEquals(1L, rfe.getLineNumber());
682         assertEquals(AnnotatedMockBeanCustom.class, rfe.getBeanClass());
683         assertEquals(beans.left.getClass().getDeclaredField("requiredWithCustom"),
684                 rfe.getDestinationField());
685     }
686 
687         
688     /**
689      * Writing a required date field that is null.
690      * @throws IOException Never
691      * @throws CsvException Never
692      * @throws NoSuchFieldException Never
693      */
694     @Test
695     public void writeNullRequiredDate() throws IOException, CsvException, NoSuchFieldException {
696         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
697         beans.right.setDateDefaultLocale(null); // required
698         StringWriter writer = new StringWriter();
699         StatefulBeanToCsv<AnnotatedMockBeanFull> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
700                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
701                 .withThrowExceptions(false)
702                 .build();
703         sbtcsv.write(beans.left);
704         sbtcsv.write(beans.right);
705         List<CsvException> csves = sbtcsv.getCapturedExceptions();
706         assertNotNull(csves);
707         assertEquals(1, csves.size());
708         CsvException csve = csves.get(0);
709         assertTrue(csve instanceof CsvRequiredFieldEmptyException);
710         CsvRequiredFieldEmptyException rfe = (CsvRequiredFieldEmptyException) csve;
711         assertEquals(2L, rfe.getLineNumber());
712         assertEquals(AnnotatedMockBeanFull.class, rfe.getBeanClass());
713         assertEquals(beans.right.getClass().getDeclaredField("dateDefaultLocale"),
714                 rfe.getDestinationField());
715     }
716         
717     /**
718      * Reading captured exceptions twice in a row.
719      * @throws IOException Never
720      * @throws CsvException Never
721      */
722     @Test
723     public void readCapturedExceptionsIsDestructive() throws IOException, CsvException {
724         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
725         beans.left.setByteWrappedSetLocale(null); // required
726         beans.right.setDateDefaultLocale(null); // required
727         StringWriter writer = new StringWriter();
728         StatefulBeanToCsv<AnnotatedMockBeanFull> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
729                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
730                 .withThrowExceptions(false)
731                 .build();
732         sbtcsv.write(beans.left);
733         sbtcsv.write(beans.right);
734         sbtcsv.getCapturedExceptions(); // First call
735         List<CsvException> csves = sbtcsv.getCapturedExceptions(); // Second call
736         assertTrue(csves.isEmpty());
737     }
738         
739     /**
740      * Tests writing multiple times with exceptions from each write.
741      * @throws IOException Never
742      * @throws CsvException Never
743      */
744     @Test
745     public void multipleWritesCapturedExceptions() throws IOException, CsvException {
746         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
747         beans.left.setByteWrappedSetLocale(null); // required
748         beans.right.setDateDefaultLocale(null); // required
749         StringWriter writer = new StringWriter();
750         StatefulBeanToCsv<AnnotatedMockBeanFull> sbtcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
751                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
752                 .withThrowExceptions(false)
753                 .build();
754         sbtcsv.write(beans.left);
755         sbtcsv.write(beans.right);
756         List<CsvException> csves = sbtcsv.getCapturedExceptions();
757         assertEquals(2, csves.size());
758     }
759         
760     /**
761      * Tests binding a custom converter to the wrong data type.
762      * Also incidentally tests that the error locale works.
763      * @throws CsvException Never
764      */
765     @Test
766     public void bindCustomConverterToWrongDataType() throws CsvException {
767         BindCustomToWrongDataType wrongTypeBean = new BindCustomToWrongDataType();
768         wrongTypeBean.setWrongType(GOOD_DATA_1);
769         StringWriter writer = new StringWriter();
770         StatefulBeanToCsv<BindCustomToWrongDataType> sbtcsv = new StatefulBeanToCsvBuilder<BindCustomToWrongDataType>(writer)
771                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
772                 .withThrowExceptions(false)
773                 .build();
774         sbtcsv.write(wrongTypeBean);
775         List<CsvException> csves = sbtcsv.getCapturedExceptions();
776         assertNotNull(csves);
777         assertEquals(1, csves.size());
778         CsvException csve = csves.get(0);
779         assertTrue(csve instanceof CsvDataTypeMismatchException);
780         CsvDataTypeMismatchException dtm = (CsvDataTypeMismatchException) csve;
781         assertEquals(1L, dtm.getLineNumber());
782         assertTrue(dtm.getSourceObject() instanceof BindCustomToWrongDataType);
783         assertEquals(String.class, dtm.getDestinationClass());
784         String englishErrorMessage = dtm.getLocalizedMessage();
785         
786         // Now with another locale
787         writer = new StringWriter();
788         sbtcsv = new StatefulBeanToCsvBuilder<BindCustomToWrongDataType>(writer)
789                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
790                 .withThrowExceptions(false)
791                 .withErrorLocale(Locale.GERMAN)
792                 .build();
793         sbtcsv.write(wrongTypeBean);
794         csves = sbtcsv.getCapturedExceptions();
795         assertNotNull(csves);
796         assertEquals(1, csves.size());
797         csve = csves.get(0);
798         assertTrue(csve instanceof CsvDataTypeMismatchException);
799         dtm = (CsvDataTypeMismatchException) csve;
800         assertEquals(1L, dtm.getLineNumber());
801         assertTrue(dtm.getSourceObject() instanceof BindCustomToWrongDataType);
802         assertEquals(String.class, dtm.getDestinationClass());
803         assertNotSame(englishErrorMessage, dtm.getLocalizedMessage());
804     }
805         
806     /**
807      * Test of good data with custom converters and a column position mapping
808      * strategy.
809      * Incidentally covers the following behavior by virtue of the beans
810      * written:<ul>
811      * <li>Writing with ConvertGermanToBoolean</li>
812      * <li>Writing with ConvertSplitOnWhitespace</li>
813      * </ul>
814      * @throws IOException Never
815      * @throws CsvException Never
816      */
817     @Test
818     public void writeCustomByPosition() throws IOException, CsvException {
819         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
820         StringWriter writer = new StringWriter();
821         StatefulBeanToCsv<AnnotatedMockBeanCustom> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
822                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
823                 .withSeparator(';')
824                 .build();
825         btcsv.write(beans.left);
826         assertEquals(GOOD_DATA_CUSTOM_1 + "\n", writer.toString());
827     }
828         
829     /**
830      * Test of good data with custom converters and a header name mapping
831      * strategy.
832      * Incidentally test writing a mixture of single and multiple beans with
833      * custom converters.
834      * @throws IOException Never
835      * @throws CsvException Never
836      */
837     @Test
838     public void writeCustomByName() throws IOException, CsvException {
839         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
840         StringWriter writer = new StringWriter();
841         HeaderColumnNameMappingStrategy<AnnotatedMockBeanCustom> strat = new HeaderColumnNameMappingStrategy<>();
842         strat.setType(AnnotatedMockBeanCustom.class);
843         StatefulBeanToCsv<AnnotatedMockBeanCustom> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
844                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
845                 .withSeparator(';')
846                 .withMappingStrategy(strat)
847                 .build();
848         btcsv.write(beans.right);
849         btcsv.write(Arrays.asList(beans.left, beans.right));
850         assertEquals(
851                 HEADER_NAME_FULL_CUSTOM + "\n" + GOOD_DATA_NAME_CUSTOM_2 + "\n" + GOOD_DATA_NAME_CUSTOM_1 + "\n" + GOOD_DATA_NAME_CUSTOM_2 + "\n",
852                 writer.toString());
853     }
854 
855     /**
856      * Test of good data with custom converters, a header name mapping strategy,
857      * and an iterator.
858      *
859      * @throws IOException  Never
860      * @throws CsvException Never
861      */
862     @Test
863     public void writeCustomByNameWithIterator() throws IOException, CsvException {
864         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
865         StringWriter writer = new StringWriter();
866         HeaderColumnNameMappingStrategy<AnnotatedMockBeanCustom> strat = new HeaderColumnNameMappingStrategy<>();
867         strat.setType(AnnotatedMockBeanCustom.class);
868         StatefulBeanToCsv<AnnotatedMockBeanCustom> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
869                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
870                 .withSeparator(';')
871                 .withMappingStrategy(strat)
872                 .build();
873         btcsv.write(beans.right);
874         btcsv.write(Arrays.asList(beans.left, beans.right).iterator());
875         assertEquals(
876                 HEADER_NAME_FULL_CUSTOM + "\n" + GOOD_DATA_NAME_CUSTOM_2 + "\n" + GOOD_DATA_NAME_CUSTOM_1 + "\n" + GOOD_DATA_NAME_CUSTOM_2 + "\n",
877                 writer.toString());
878     }
879 
880     
881     /**
882      * Tests writing an empty field annotated with the custom converter
883      * {@link com.opencsv.bean.customconverter.ConvertGermanToBoolean} with
884      * required set to true.
885      * @throws IOException Never
886      * @throws CsvException Never
887      */
888     @Test
889     public void writeEmptyFieldWithConvertGermanToBooleanRequired() throws IOException, CsvException {
890         ImmutablePair<AnnotatedMockBeanCustom, AnnotatedMockBeanCustom> beans = TestUtils.createTwoGoodCustomBeans();
891         StringWriter writer = new StringWriter();
892         StatefulBeanToCsv<AnnotatedMockBeanCustom> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanCustom>(writer)
893                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
894                 .withSeparator(';')
895                 .build();
896         beans.left.setBoolWrapped(null);
897         try {
898             btcsv.write(beans.left);
899             fail("Exception should have been thrown!");
900         }
901         catch(CsvRequiredFieldEmptyException e) {
902             assertEquals(1, e.getLineNumber());
903             assertEquals(AnnotatedMockBeanCustom.class, e.getBeanClass());
904             assertEquals("boolWrapped", e.getDestinationField().getName());
905         }
906     }
907 
908     @Test
909     public void writeDifferentOrderPositionTypeFirst() throws IOException, CsvException {
910         ColumnPositionMappingStrategy<AnnotatedMockBeanFull> strat = new ColumnPositionMappingStrategy<>();
911         strat.setType(AnnotatedMockBeanFull.class);
912         strat.setColumnMapping();
913         strat.setColumnOrderOnWrite(Comparator.reverseOrder());
914         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
915         StringWriter writer = new StringWriter();
916         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
917                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
918                 .withSeparator(';')
919                 .withMappingStrategy(strat)
920                 .build();
921         btcsv.write(beans.left);
922         assertTrue(Pattern.matches(REVERSE_GOOD_DATA_1 + "\n", writer.toString()));
923     }
924 
925     @Test
926     public void writeDifferentOrderPositionTypeLast() throws IOException, CsvException {
927         ColumnPositionMappingStrategy<AnnotatedMockBeanFull> strat = new ColumnPositionMappingStrategy<>();
928         strat.setColumnOrderOnWrite(Comparator.reverseOrder());
929         strat.setType(AnnotatedMockBeanFull.class);
930         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
931         StringWriter writer = new StringWriter();
932         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
933                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
934                 .withSeparator(';')
935                 .withMappingStrategy(strat)
936                 .build();
937         btcsv.write(beans.left);
938         assertTrue(Pattern.matches(REVERSE_GOOD_DATA_1 + "\n", writer.toString()));
939     }
940 
941     @Test
942     public void writeNullOrderPosition() throws IOException, CsvException {
943         ColumnPositionMappingStrategy<AnnotatedMockBeanFull> strat = new ColumnPositionMappingStrategy<>();
944         strat.setColumnOrderOnWrite(null);
945         strat.setType(AnnotatedMockBeanFull.class);
946         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
947         StringWriter writer = new StringWriter();
948         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
949                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
950                 .withSeparator(';')
951                 .withMappingStrategy(strat)
952                 .build();
953         btcsv.write(beans.left);
954         assertTrue(Pattern.matches(GOOD_DATA_1 + "\n", writer.toString()));
955     }
956 
957     @Test
958     public void writeDifferentOrderNameTypeFirst() throws IOException, CsvException {
959         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
960         StringWriter writer = new StringWriter();
961         HeaderColumnNameMappingStrategy<AnnotatedMockBeanFull> strat = new HeaderColumnNameMappingStrategy<>();
962         strat.setType(AnnotatedMockBeanFull.class);
963         strat.setColumnOrderOnWrite(new SFirstCollator());
964         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
965                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
966                 .withSeparator(';')
967                 .withMappingStrategy(strat)
968                 .build();
969         btcsv.write(beans.left);
970         assertTrue(Pattern.matches(COLLATED_HEADER_NAME_FULL + "\n" + COLLATED_GOOD_DATA_NAME_1 + "\n", writer.toString()));
971     }
972 
973     @Test
974     public void writeDifferentOrderNameTypeLast() throws IOException, CsvException {
975         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
976         StringWriter writer = new StringWriter();
977         HeaderColumnNameMappingStrategy<AnnotatedMockBeanFull> strat = new HeaderColumnNameMappingStrategy<>();
978         strat.setColumnOrderOnWrite(new SFirstCollator());
979         strat.setType(AnnotatedMockBeanFull.class);
980         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
981                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
982                 .withSeparator(';')
983                 .withMappingStrategy(strat)
984                 .build();
985         btcsv.write(beans.left);
986         assertTrue(Pattern.matches(COLLATED_HEADER_NAME_FULL + "\n" + COLLATED_GOOD_DATA_NAME_1 + "\n", writer.toString()));
987     }
988 
989     @Test
990     public void writeNullOrderName() throws IOException, CsvException {
991         ImmutablePair<AnnotatedMockBeanFull, AnnotatedMockBeanFull> beans = TestUtils.createTwoGoodBeans();
992         StringWriter writer = new StringWriter();
993         HeaderColumnNameMappingStrategy<AnnotatedMockBeanFull> strat = new HeaderColumnNameMappingStrategy<>();
994         strat.setColumnOrderOnWrite(null);
995         strat.setType(AnnotatedMockBeanFull.class);
996         StatefulBeanToCsv<AnnotatedMockBeanFull> btcsv = new StatefulBeanToCsvBuilder<AnnotatedMockBeanFull>(writer)
997                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
998                 .withSeparator(';')
999                 .withMappingStrategy(strat)
1000                 .build();
1001         btcsv.write(beans.left);
1002         assertTrue(Pattern.matches(HEADER_NAME_FULL + "\n" + GOOD_DATA_NAME_1 + "\n", writer.toString()));
1003     }
1004 
1005     @Test
1006     public void writeMultipleExceptionsPerBean() throws CsvFieldAssignmentException {
1007         StringWriter writer = new StringWriter();
1008         HeaderColumnNameMappingStrategy<WriteLocale> strat = new HeaderColumnNameMappingStrategy<>();
1009         strat.setColumnOrderOnWrite(null);
1010         strat.setType(WriteLocale.class);
1011         StatefulBeanToCsv<WriteLocale> btcsv = new StatefulBeanToCsvBuilder<WriteLocale>(writer)
1012                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
1013                 .withSeparator(';')
1014                 .withMappingStrategy(strat)
1015                 .withThrowExceptions(false)
1016                 .build();
1017 
1018         // Broken beans. Each has multiple required fields.
1019         List<WriteLocale> beans = Arrays.asList(new WriteLocale(), new WriteLocale(), new WriteLocale());
1020         btcsv.write(beans);
1021         final int errorsPerLine = 4;
1022         final int totalLines = 3;
1023         List<CsvException> thrownExceptions = btcsv.getCapturedExceptions();
1024         assertNotNull(thrownExceptions);
1025         assertEquals(errorsPerLine*totalLines, thrownExceptions.size());
1026         for(int line = 0; line < totalLines ; line++) {
1027             for(int mistake = 0; mistake < errorsPerLine; mistake++) {
1028                 CsvException e = thrownExceptions.get(line*errorsPerLine+mistake);
1029                 assertTrue(e instanceof CsvRequiredFieldEmptyException);
1030                 assertEquals(line+1, e.getLineNumber());
1031             }
1032         }
1033     }
1034 
1035     @Test
1036     public void writeMultipleExceptionsOneBean() throws CsvFieldAssignmentException {
1037         StringWriter writer = new StringWriter();
1038         HeaderColumnNameMappingStrategy<WriteLocale> strat = new HeaderColumnNameMappingStrategy<>();
1039         strat.setColumnOrderOnWrite(null);
1040         strat.setType(WriteLocale.class);
1041         StatefulBeanToCsv<WriteLocale> btcsv = new StatefulBeanToCsvBuilder<WriteLocale>(writer)
1042                 .withQuotechar(ICSVWriter.NO_QUOTE_CHARACTER)
1043                 .withSeparator(';')
1044                 .withMappingStrategy(strat)
1045                 .withThrowExceptions(false)
1046                 .build();
1047 
1048         // Broken beans. Each has multiple required fields.
1049         btcsv.write(new WriteLocale());
1050         List<CsvException> thrownExceptions = btcsv.getCapturedExceptions();
1051         assertNotNull(thrownExceptions);
1052         final int errorsPerLine = 4;
1053         assertEquals(errorsPerLine, thrownExceptions.size());
1054         for(int mistake = 0; mistake < errorsPerLine; mistake++) {
1055             CsvException e = thrownExceptions.get(mistake);
1056             assertTrue(e instanceof CsvRequiredFieldEmptyException);
1057             assertEquals(1, e.getLineNumber());
1058         }
1059     }
1060 
1061     private static class SFirstCollator implements Comparator<String> {
1062         private final Comparator<Object> c;
1063 
1064         public SFirstCollator() {
1065             RuleBasedCollator rbc = null;
1066             try {
1067                 rbc = new RuleBasedCollator("< s, S < a, A < b, B < c, C < d, D < e, E < f, F < g, G < h, H < i, I < j, J < k, K < l, L < m, M < n, N < o, O < p, P < q, Q < r, R < t, T < u, U < v, V < w, W < x, X < y, Y < z, Z ");
1068             }
1069             catch(ParseException e) { /* Do nothing. */}
1070             c = rbc;
1071         }
1072 
1073         @Override
1074         public int compare(String o1, String o2) {
1075             return c.compare(o1, o2);
1076         }
1077     }
1078 }