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.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.io.StringReader;
33  import java.util.ArrayList;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.TreeMap;
37  import java.util.concurrent.ConcurrentHashMap;
38  import java.util.concurrent.atomic.AtomicInteger;
39  
40  import org.apache.commons.lang3.StringUtils;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  
44  public class CSVRecordTest {
45  
46      private enum EnumFixture {
47          UNKNOWN_COLUMN
48      }
49  
50      /** This enum overrides toString() but it's the names that matter. */
51      public enum EnumHeader {
52          FIRST("first"), SECOND("second"), 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          try (final CSVParser parser = CSVFormat.DEFAULT.builder().setHeader(EnumHeader.class).build().parse(new StringReader(rowData))) {
78              recordWithHeader = parser.iterator().next();
79              headerMap = parser.getHeaderMap();
80          }
81      }
82  
83      @Test
84      public void testCSVRecordNULLValues() throws IOException {
85          final CSVParser parser = CSVParser.parse("A,B\r\nONE,TWO", CSVFormat.DEFAULT.withHeader());
86          final CSVRecord csvRecord = new CSVRecord(parser, null, null, 0L, 0L);
87          assertEquals(0, csvRecord.size());
88          assertThrows(IllegalArgumentException.class, () -> csvRecord.get("B"));
89      }
90  
91      @Test
92      public void testGetInt() {
93          assertEquals(values[0], record.get(0));
94          assertEquals(values[1], record.get(1));
95          assertEquals(values[2], record.get(2));
96      }
97  
98      @Test
99      public void testGetNullEnum() {
100         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get((Enum<?>) null));
101     }
102 
103     @Test
104     public void testGetString() {
105         assertEquals(values[0], recordWithHeader.get(EnumHeader.FIRST.name()));
106         assertEquals(values[1], recordWithHeader.get(EnumHeader.SECOND.name()));
107         assertEquals(values[2], recordWithHeader.get(EnumHeader.THIRD.name()));
108     }
109 
110     @Test
111     public void testGetStringInconsistentRecord() {
112         headerMap.put("fourth", Integer.valueOf(4));
113         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get("fourth"));
114     }
115 
116     @Test
117     public void testGetStringNoHeader() {
118         assertThrows(IllegalStateException.class, () -> record.get("first"));
119     }
120 
121     @Test
122     public void testGetUnmappedEnum() {
123         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get(EnumFixture.UNKNOWN_COLUMN));
124     }
125 
126     @Test
127     public void testGetUnmappedName() {
128         assertThrows(IllegalArgumentException.class, () -> assertNull(recordWithHeader.get("fourth")));
129     }
130 
131     @Test
132     public void testGetUnmappedNegativeInt() {
133         assertThrows(ArrayIndexOutOfBoundsException.class, () -> recordWithHeader.get(Integer.MIN_VALUE));
134     }
135 
136     @Test
137     public void testGetUnmappedPositiveInt() {
138         assertThrows(ArrayIndexOutOfBoundsException.class, () -> recordWithHeader.get(Integer.MAX_VALUE));
139     }
140 
141     @Test
142     public void testGetWithEnum() {
143         assertEquals(recordWithHeader.get("FIRST"), recordWithHeader.get(EnumHeader.FIRST));
144         assertEquals(recordWithHeader.get("SECOND"), recordWithHeader.get(EnumHeader.SECOND));
145         assertThrows(IllegalArgumentException.class, () -> recordWithHeader.get(EnumFixture.UNKNOWN_COLUMN));
146     }
147 
148     @Test
149     public void testIsConsistent() {
150         assertTrue(record.isConsistent());
151         assertTrue(recordWithHeader.isConsistent());
152         final Map<String, Integer> map = recordWithHeader.getParser().getHeaderMap();
153         map.put("fourth", Integer.valueOf(4));
154         // We are working on a copy of the map, so the record should still be OK.
155         assertTrue(recordWithHeader.isConsistent());
156     }
157 
158     @Test
159     public void testIsInconsistent() throws IOException {
160         final String[] headers = { "first", "second", "third" };
161         final String rowData = StringUtils.join(values, ',');
162         try (final CSVParser parser = CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) {
163             final Map<String, Integer> map = parser.getHeaderMapRaw();
164             final CSVRecord record1 = parser.iterator().next();
165             map.put("fourth", Integer.valueOf(4));
166             assertFalse(record1.isConsistent());
167         }
168     }
169 
170     @Test
171     public void testIsMapped() {
172         assertFalse(record.isMapped("first"));
173         assertTrue(recordWithHeader.isMapped(EnumHeader.FIRST.name()));
174         assertFalse(recordWithHeader.isMapped("fourth"));
175     }
176 
177     @Test
178     public void testIsSetInt() {
179         assertFalse(record.isSet(-1));
180         assertTrue(record.isSet(0));
181         assertTrue(record.isSet(2));
182         assertFalse(record.isSet(3));
183         assertTrue(recordWithHeader.isSet(1));
184         assertFalse(recordWithHeader.isSet(1000));
185     }
186 
187     @Test
188     public void testIsSetString() {
189         assertFalse(record.isSet("first"));
190         assertTrue(recordWithHeader.isSet(EnumHeader.FIRST.name()));
191         assertFalse(recordWithHeader.isSet("DOES NOT EXIST"));
192     }
193 
194     @Test
195     public void testIterator() {
196         int i = 0;
197         for (final String value : record) {
198             assertEquals(values[i], value);
199             i++;
200         }
201     }
202 
203     @Test
204     public void testPutInMap() {
205         final Map<String, String> map = new ConcurrentHashMap<>();
206         this.recordWithHeader.putIn(map);
207         this.validateMap(map, false);
208         // Test that we can compile with assignment to the same map as the param.
209         final TreeMap<String, String> map2 = recordWithHeader.putIn(new TreeMap<>());
210         this.validateMap(map2, false);
211     }
212 
213     @Test
214     public void testRemoveAndAddColumns() throws IOException {
215         // do:
216         try (final CSVPrinter printer = new CSVPrinter(new StringBuilder(), CSVFormat.DEFAULT)) {
217             final Map<String, String> map = recordWithHeader.toMap();
218             map.remove("OldColumn");
219             map.put("ZColumn", "NewValue");
220             // check:
221             final ArrayList<String> list = new ArrayList<>(map.values());
222             list.sort(null);
223             printer.printRecord(list);
224             assertEquals("A,B,C,NewValue" + CSVFormat.DEFAULT.getRecordSeparator(), printer.getOut().toString());
225         }
226     }
227 
228     @Test
229     public void testSerialization() throws IOException, ClassNotFoundException {
230         final CSVRecord shortRec;
231         try (final CSVParser parser = CSVParser.parse("A,B\n#my comment\nOne,Two", CSVFormat.DEFAULT.withHeader().withCommentMarker('#'))) {
232             shortRec = parser.iterator().next();
233         }
234         final ByteArrayOutputStream out = new ByteArrayOutputStream();
235         try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
236             oos.writeObject(shortRec);
237         }
238         final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
239         try (ObjectInputStream ois = new ObjectInputStream(in)) {
240             final Object object = ois.readObject();
241             assertTrue(object instanceof CSVRecord);
242             final CSVRecord rec = (CSVRecord) object;
243             assertEquals(1L, rec.getRecordNumber());
244             assertEquals("One", rec.get(0));
245             assertEquals("Two", rec.get(1));
246             assertEquals(2, rec.size());
247             assertEquals(shortRec.getCharacterPosition(), rec.getCharacterPosition());
248             assertEquals("my comment", rec.getComment());
249             // The parser is not serialized
250             assertNull(rec.getParser());
251             // Check all header map functionality is absent
252             assertTrue(rec.isConsistent());
253             assertFalse(rec.isMapped("A"));
254             assertFalse(rec.isSet("A"));
255             assertEquals(0, rec.toMap().size());
256             // This will throw
257             try {
258                 rec.get("A");
259                 org.junit.jupiter.api.Assertions.fail("Access by name is not expected after deserialisation");
260             } catch (final IllegalStateException expected) {
261                 // OK
262             }
263         }
264     }
265 
266     @Test
267     public void testStream() {
268         final AtomicInteger i = new AtomicInteger();
269         record.stream().forEach(value -> {
270             assertEquals(values[i.get()], value);
271             i.incrementAndGet();
272         });
273     }
274 
275     @Test
276     public void testToListAdd() {
277         final String[] expected = values.clone();
278         final List<String> list = record.toList();
279         list.add("Last");
280         assertEquals("Last", list.get(list.size() - 1));
281         assertEquals(list.size(), values.length + 1);
282         assertArrayEquals(expected, values);
283     }
284 
285     @Test
286     public void testToListFor() {
287         int i = 0;
288         for (final String value : record.toList()) {
289             assertEquals(values[i], value);
290             i++;
291         }
292     }
293 
294     @Test
295     public void testToListForEach() {
296         final AtomicInteger i = new AtomicInteger();
297         record.toList().forEach(e -> {
298             assertEquals(values[i.getAndIncrement()], e);
299         });
300     }
301 
302     @Test
303     public void testToListSet() {
304         final String[] expected = values.clone();
305         final List<String> list = record.toList();
306         list.set(list.size() - 1, "Last");
307         assertEquals("Last", list.get(list.size() - 1));
308         assertEquals(list.size(), values.length);
309         assertArrayEquals(expected, values);
310     }
311 
312     @Test
313     public void testToMap() {
314         final Map<String, String> map = this.recordWithHeader.toMap();
315         this.validateMap(map, true);
316     }
317 
318     @Test
319     public void testToMapWithNoHeader() throws Exception {
320         try (final CSVParser parser = CSVParser.parse("a,b", CSVFormat.newFormat(','))) {
321             final CSVRecord shortRec = parser.iterator().next();
322             final Map<String, String> map = shortRec.toMap();
323             assertNotNull(map, "Map is not null.");
324             assertTrue(map.isEmpty(), "Map is empty.");
325         }
326     }
327 
328     @Test
329     public void testToMapWithShortRecord() throws Exception {
330         try (final CSVParser parser = CSVParser.parse("a,b", CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
331             final CSVRecord shortRec = parser.iterator().next();
332             shortRec.toMap();
333         }
334     }
335 
336     @Test
337     public void testToString() {
338         assertNotNull(recordWithHeader.toString());
339         assertTrue(recordWithHeader.toString().contains("comment="));
340         assertTrue(recordWithHeader.toString().contains("recordNumber="));
341         assertTrue(recordWithHeader.toString().contains("values="));
342     }
343 
344     private void validateMap(final Map<String, String> map, final boolean allowsNulls) {
345         assertTrue(map.containsKey(EnumHeader.FIRST.name()));
346         assertTrue(map.containsKey(EnumHeader.SECOND.name()));
347         assertTrue(map.containsKey(EnumHeader.THIRD.name()));
348         assertFalse(map.containsKey("fourth"));
349         if (allowsNulls) {
350             assertFalse(map.containsKey(null));
351         }
352         assertEquals("A", map.get(EnumHeader.FIRST.name()));
353         assertEquals("B", map.get(EnumHeader.SECOND.name()));
354         assertEquals("C", map.get(EnumHeader.THIRD.name()));
355         assertNull(map.get("fourth"));
356     }
357 }