View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.csv;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.IOException;
29  import java.io.ObjectInputStream;
30  import java.io.ObjectOutputStream;
31  import java.io.StringReader;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.Map;
35  import java.util.TreeMap;
36  import java.util.concurrent.ConcurrentHashMap;
37  import java.util.concurrent.atomic.AtomicInteger;
38  
39  import org.apache.commons.lang3.StringUtils;
40  import org.junit.jupiter.api.BeforeEach;
41  import org.junit.jupiter.api.Test;
42  
43  public class CSVRecordTest {
44  
45      private enum EnumFixture {
46          UNKNOWN_COLUMN
47      }
48  
49      public enum EnumHeader {
50          FIRST("first"),
51          SECOND("second"),
52          THIRD("third");
53  
54          private final String number;
55  
56          EnumHeader(final String number) {
57              this.number = number;
58          }
59  
60          @Override
61          public String toString() {
62              return number;
63          }
64      }
65  
66      private Map<String, Integer> headerMap;
67      private CSVRecord record, recordWithHeader;
68      private String[] values;
69  
70      @BeforeEach
71      public void setUp() throws Exception {
72          values = new String[] { "A", "B", "C" };
73          final String rowData = StringUtils.join(values, ',');
74          try (final CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(rowData))) {
75              record = parser.iterator().next();
76          }
77          final String[] headers = { "first", "second", "third" };
78          try (final CSVParser parser = CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) {
79              recordWithHeader = parser.iterator().next();
80              headerMap = parser.getHeaderMap();
81          }
82      }
83  
84      @Test
85      public void testCSVRecordNULLValues() throws IOException {
86          final CSVParser parser = CSVParser.parse("A,B\r\nONE,TWO", CSVFormat.DEFAULT.withHeader());
87          final CSVRecord csvRecord = new CSVRecord(parser, null, null, 0L, 0L);
88          assertEquals(0, csvRecord.size());
89          assertThrows(IllegalArgumentException.class, () -> csvRecord.get("B"));
90      }
91  
92      @Test
93      public void testGetInt() {
94          assertEquals(values[0], record.get(0));
95          assertEquals(values[1], record.get(1));
96          assertEquals(values[2], record.get(2));
97      }
98  
99      @Test
100     public void testGetNullEnum() {
101         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get((Enum<?>) null));
102     }
103 
104     @Test
105     public void testGetString() {
106         assertEquals(values[0], recordWithHeader.get("first"));
107         assertEquals(values[1], recordWithHeader.get("second"));
108         assertEquals(values[2], recordWithHeader.get("third"));
109     }
110 
111     @Test
112     public void testGetStringInconsistentRecord() {
113         headerMap.put("fourth", Integer.valueOf(4));
114         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get("fourth"));
115     }
116 
117     @Test
118     public void testGetStringNoHeader() {
119         assertThrows(IllegalStateException.class, () -> record.get("first"));
120     }
121 
122     @Test
123     public void testGetUnmappedEnum() {
124         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get(EnumFixture.UNKNOWN_COLUMN));
125     }
126 
127     @Test
128     public void testGetUnmappedName() {
129         assertThrows(IllegalArgumentException.class, () -> assertNull(recordWithHeader.get("fourth")));
130     }
131 
132     @Test
133     public void testGetUnmappedNegativeInt() {
134         assertThrows(ArrayIndexOutOfBoundsException.class, () -> recordWithHeader.get(Integer.MIN_VALUE));
135     }
136 
137     @Test
138     public void testGetUnmappedPositiveInt() {
139         assertThrows(ArrayIndexOutOfBoundsException.class, () -> recordWithHeader.get(Integer.MAX_VALUE));
140     }
141 
142     @Test
143     public void testGetWithEnum() {
144         assertEquals(recordWithHeader.get("first"), recordWithHeader.get(EnumHeader.FIRST));
145         assertEquals(recordWithHeader.get("second"), recordWithHeader.get(EnumHeader.SECOND));
146         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get(EnumFixture.UNKNOWN_COLUMN));
147     }
148 
149     @Test
150     public void testIsConsistent() {
151         assertTrue(record.isConsistent());
152         assertTrue(recordWithHeader.isConsistent());
153         final Map<String, Integer> map = recordWithHeader.getParser().getHeaderMap();
154         map.put("fourth", Integer.valueOf(4));
155         // We are working on a copy of the map, so the record should still be OK.
156         assertTrue(recordWithHeader.isConsistent());
157     }
158 
159     @Test
160     public void testIsInconsistent() throws IOException {
161         final String[] headers = { "first", "second", "third" };
162         final String rowData = StringUtils.join(values, ',');
163         try (final CSVParser parser = CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) {
164             final Map<String, Integer> map = parser.getHeaderMapRaw();
165             final CSVRecord record1 = parser.iterator().next();
166             map.put("fourth", Integer.valueOf(4));
167             assertFalse(record1.isConsistent());
168         }
169     }
170 
171     @Test
172     public void testIsMapped() {
173         assertFalse(record.isMapped("first"));
174         assertTrue(recordWithHeader.isMapped("first"));
175         assertFalse(recordWithHeader.isMapped("fourth"));
176     }
177 
178     @Test
179     public void testIsSetInt() {
180         assertFalse(record.isSet(-1));
181         assertTrue(record.isSet(0));
182         assertTrue(record.isSet(2));
183         assertFalse(record.isSet(3));
184         assertTrue(recordWithHeader.isSet(1));
185         assertFalse(recordWithHeader.isSet(1000));
186     }
187 
188     @Test
189     public void testIsSetString() {
190         assertFalse(record.isSet("first"));
191         assertTrue(recordWithHeader.isSet("first"));
192         assertFalse(recordWithHeader.isSet("fourth"));
193     }
194 
195     @Test
196     public void testIterator() {
197         int i = 0;
198         for (final String value : record) {
199             assertEquals(values[i], value);
200             i++;
201         }
202     }
203 
204     @Test
205     public void testPutInMap() {
206         final Map<String, String> map = new ConcurrentHashMap<>();
207         this.recordWithHeader.putIn(map);
208         this.validateMap(map, false);
209         // Test that we can compile with assignment to the same map as the param.
210         final TreeMap<String, String> map2 = recordWithHeader.putIn(new TreeMap<String, String>());
211         this.validateMap(map2, false);
212     }
213 
214     @Test
215     public void testRemoveAndAddColumns() throws IOException {
216         // do:
217         try (final CSVPrinter printer = new CSVPrinter(new StringBuilder(), CSVFormat.DEFAULT)) {
218             final Map<String, String> map = recordWithHeader.toMap();
219             map.remove("OldColumn");
220             map.put("ZColumn", "NewValue");
221             // check:
222             final ArrayList<String> list = new ArrayList<>(map.values());
223             Collections.sort(list);
224             printer.printRecord(list);
225             assertEquals("A,B,C,NewValue" + CSVFormat.DEFAULT.getRecordSeparator(), printer.getOut().toString());
226         }
227     }
228 
229     @Test
230     public void testSerialization() throws IOException, ClassNotFoundException {
231         final CSVRecord shortRec;
232         try (final CSVParser parser = CSVParser.parse("A,B\n#my comment\nOne,Two", CSVFormat.DEFAULT.withHeader().withCommentMarker('#'))) {
233             shortRec = parser.iterator().next();
234         }
235         final ByteArrayOutputStream out = new ByteArrayOutputStream();
236         try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
237             oos.writeObject(shortRec);
238         }
239         final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
240         try (ObjectInputStream ois = new ObjectInputStream(in)) {
241             final Object object = ois.readObject();
242             assertTrue(object instanceof CSVRecord);
243             final CSVRecord rec = (CSVRecord) object;
244             assertEquals(1L, rec.getRecordNumber());
245             assertEquals("One", rec.get(0));
246             assertEquals("Two", rec.get(1));
247             assertEquals(2, rec.size());
248             assertEquals(shortRec.getCharacterPosition(), rec.getCharacterPosition());
249             assertEquals("my comment", rec.getComment());
250             // The parser is not serialized
251             assertNull(rec.getParser());
252             // Check all header map functionality is absent
253             assertTrue(rec.isConsistent());
254             assertFalse(rec.isMapped("A"));
255             assertFalse(rec.isSet("A"));
256             assertEquals(0, rec.toMap().size());
257             // This will throw
258             try {
259                 rec.get("A");
260                 org.junit.jupiter.api.Assertions.fail("Access by name is not expected after deserialisation");
261             } catch (final IllegalStateException expected) {
262                 // OK
263             }
264         }
265     }
266 
267     @Test
268     public void testStream() {
269         final AtomicInteger i = new AtomicInteger();
270         record.stream().forEach(value -> {
271             assertEquals(values[i.get()], value);
272             i.incrementAndGet();
273         });
274     }
275 
276     @Test
277     public void testToList() {
278         int i = 0;
279         for (final String value : record.toList()) {
280             assertEquals(values[i], value);
281             i++;
282         }
283     }
284 
285     @Test
286     public void testToMap() {
287         final Map<String, String> map = this.recordWithHeader.toMap();
288         this.validateMap(map, true);
289     }
290 
291     @Test
292     public void testToMapWithNoHeader() throws Exception {
293         try (final CSVParser parser = CSVParser.parse("a,b", CSVFormat.newFormat(','))) {
294             final CSVRecord shortRec = parser.iterator().next();
295             final Map<String, String> map = shortRec.toMap();
296             assertNotNull(map, "Map is not null.");
297             assertTrue(map.isEmpty(), "Map is empty.");
298         }
299     }
300 
301     @Test
302     public void testToMapWithShortRecord() throws Exception {
303         try (final CSVParser parser = CSVParser.parse("a,b", CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
304             final CSVRecord shortRec = parser.iterator().next();
305             shortRec.toMap();
306         }
307     }
308 
309     @Test
310     public void testToString() {
311         assertNotNull(recordWithHeader.toString());
312         assertTrue(recordWithHeader.toString().contains("comment="));
313         assertTrue(recordWithHeader.toString().contains("recordNumber="));
314         assertTrue(recordWithHeader.toString().contains("values="));
315     }
316 
317     private void validateMap(final Map<String, String> map, final boolean allowsNulls) {
318         assertTrue(map.containsKey("first"));
319         assertTrue(map.containsKey("second"));
320         assertTrue(map.containsKey("third"));
321         assertFalse(map.containsKey("fourth"));
322         if (allowsNulls) {
323             assertFalse(map.containsKey(null));
324         }
325         assertEquals("A", map.get("first"));
326         assertEquals("B", map.get("second"));
327         assertEquals("C", map.get("third"));
328         assertNull(map.get("fourth"));
329     }
330 }