This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-csv.git
The following commit(s) were added to refs/heads/master by this push:
new ef51c1e [CSV-239] Cannot get headers in column order from CSVRecord.
ef51c1e is described below
commit ef51c1ecad91aad5527b8e1568e2a17748039be8
Author: Gary Gregory <[email protected]>
AuthorDate: Sun May 19 19:49:23 2019 -0400
[CSV-239] Cannot get headers in column order from CSVRecord.
- Redo so that the record tracks its source parser where call sites can
find metadata. This avoids adding slots to the record class itself.
---
.../java/org/apache/commons/csv/CSVParser.java | 43 +++++++++++---
.../java/org/apache/commons/csv/CSVRecord.java | 67 +++++++++-------------
.../java/org/apache/commons/csv/CSVParserTest.java | 34 +++++++----
.../org/apache/commons/csv/CSVPrinterTest.java | 2 +-
.../java/org/apache/commons/csv/CSVRecordTest.java | 52 +++++++++--------
.../org/apache/commons/csv/PerformanceTest.java | 4 ++
6 files changed, 121 insertions(+), 81 deletions(-)
diff --git a/src/main/java/org/apache/commons/csv/CSVParser.java
b/src/main/java/org/apache/commons/csv/CSVParser.java
index 6eb97f8..7fd8292 100644
--- a/src/main/java/org/apache/commons/csv/CSVParser.java
+++ b/src/main/java/org/apache/commons/csv/CSVParser.java
@@ -340,7 +340,7 @@ public final class CSVParser implements
Iterable<CSVRecord>, Closeable {
/** A mapping of column names to column indices */
private final Map<String, Integer> headerMap;
- /** Preserve the column order to avoid re-computing it. */
+ /** The column order to avoid re-computing it. */
private final List<String> headerNames;
private final Lexer lexer;
@@ -444,6 +444,12 @@ public final class CSVParser implements
Iterable<CSVRecord>, Closeable {
}
}
+ private Map<String, Integer> createEmptyHeaderMap() {
+ return this.format.getIgnoreHeaderCase() ?
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER) :
+ new TreeMap<>();
+ }
+
/**
* Creates the name to index mapping if the format defines a header.
*
@@ -454,10 +460,7 @@ public final class CSVParser implements
Iterable<CSVRecord>, Closeable {
Map<String, Integer> hdrMap = null;
final String[] formatHeader = this.format.getHeader();
if (formatHeader != null) {
- hdrMap = this.format.getIgnoreHeaderCase() ?
- new TreeMap<>(String.CASE_INSENSITIVE_ORDER) :
- new TreeMap<>();
-
+ hdrMap = createEmptyHeaderMap();
String[] headerRecord = null;
if (formatHeader.length == 0) {
// read the header from the first line of the file
@@ -523,7 +526,31 @@ public final class CSVParser implements
Iterable<CSVRecord>, Closeable {
* @return a copy of the header map.
*/
public Map<String, Integer> getHeaderMap() {
- return this.headerMap == null ? null : new
LinkedHashMap<>(this.headerMap);
+ if (this.headerMap == null) {
+ return null;
+ }
+ final Map<String, Integer> map = createEmptyHeaderMap();
+ map.putAll(this.headerMap);
+ return map;
+ }
+
+ /**
+ * Returns the header map.
+ *
+ * @return the header map.
+ */
+ Map<String, Integer> getHeaderMapRaw() {
+ return this.headerMap;
+ }
+
+ /**
+ * Returns a copy of the header names that iterates in column order.
+ *
+ * @return a copy of the header names that iterates in column order.
+ * @since 1.7
+ */
+ public List<String> getHeaderNames() {
+ return new ArrayList<>(headerNames);
}
/**
@@ -633,8 +660,8 @@ public final class CSVParser implements
Iterable<CSVRecord>, Closeable {
if (!this.recordList.isEmpty()) {
this.recordNumber++;
final String comment = sb == null ? null : sb.toString();
- result = new CSVRecord(this.recordList.toArray(new
String[this.recordList.size()]), this.headerMap,
- this.headerNames, comment, this.recordNumber,
startCharPosition);
+ result = new CSVRecord(this, this.recordList.toArray(new
String[this.recordList.size()]),
+ comment, this.recordNumber, startCharPosition);
}
return result;
}
diff --git a/src/main/java/org/apache/commons/csv/CSVRecord.java
b/src/main/java/org/apache/commons/csv/CSVRecord.java
index 3984405..a88e3cb 100644
--- a/src/main/java/org/apache/commons/csv/CSVRecord.java
+++ b/src/main/java/org/apache/commons/csv/CSVRecord.java
@@ -18,7 +18,6 @@
package org.apache.commons.csv;
import java.io.Serializable;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -40,24 +39,20 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
/** The accumulated comments (if any) */
private final String comment;
- /** The column name to index mapping. */
- private final Map<String, Integer> headerMap;
-
- /** The column order to avoid re-computing it. */
- private final List<String> headerNames;
-
/** The record number. */
private final long recordNumber;
/** The values of the record */
private final String[] values;
- CSVRecord(final String[] values, final Map<String, Integer> headerMap,
List<String> headerNames, final String comment,
- final long recordNumber, final long characterPosition) {
+ /** The parser that originates this record. */
+ private final CSVParser parser;
+
+ CSVRecord(final CSVParser parser, final String[] values, final String
comment, final long recordNumber,
+ final long characterPosition) {
this.recordNumber = recordNumber;
this.values = values != null ? values : EMPTY_STRING_ARRAY;
- this.headerMap = headerMap;
- this.headerNames = headerNames;
+ this.parser = parser;
this.comment = comment;
this.characterPosition = characterPosition;
}
@@ -84,6 +79,10 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
return values[i];
}
+ private Map<String, Integer> getHeaderMapRaw() {
+ return parser.getHeaderMapRaw();
+ }
+
/**
* Returns a value by name.
*
@@ -98,7 +97,12 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
* @see CSVFormat#withNullString(String)
*/
public String get(final String name) {
- final Integer index = getIndex(name);
+ final Map<String, Integer> headerMap = getHeaderMapRaw();
+ if (headerMap == null) {
+ throw new IllegalStateException(
+ "No header mapping was specified, the record values can't be
accessed by name");
+ }
+ final Integer index = headerMap.get(name);
if (index == null) {
throw new IllegalArgumentException(String.format("Mapping for %s
not found, expected one of %s", name,
headerMap.keySet()));
@@ -135,29 +139,13 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
}
/**
- * Returns a copy of the header names that iterates in column order.
- *
- * @return a copy of the header names that iterates in column order.
+ * Returns the parser.
+ *
+ * @return the parser.
* @since 1.7
*/
- public List<String> getHeaderNames() {
- return new ArrayList<>(headerNames);
- }
-
- Integer getIndex(final String name) {
- if (headerMap == null) {
- throw new IllegalStateException(
- "No header mapping was specified, the record values can't be
accessed by name");
- }
- return headerMap.get(name);
- }
-
- String getName(final int index) {
- if (headerMap == null) {
- throw new IllegalStateException(
- "No header mapping was specified, the record values can't be
accessed by name");
- }
- return headerNames.get(index);
+ public CSVParser getCSVParser() {
+ return parser;
}
/**
@@ -199,6 +187,7 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
* @return true of this record is valid, false if not
*/
public boolean isConsistent() {
+ final Map<String, Integer> headerMap = getHeaderMapRaw();
return headerMap == null || headerMap.size() == values.length;
}
@@ -210,6 +199,7 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
* @return whether a given column is mapped.
*/
public boolean isMapped(final String name) {
+ final Map<String, Integer> headerMap = getHeaderMapRaw();
return headerMap != null && headerMap.containsKey(name);
}
@@ -221,7 +211,7 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
* @return whether a given columns is mapped and has a value
*/
public boolean isSet(final String name) {
- return isMapped(name) && headerMap.get(name).intValue() <
values.length;
+ return isMapped(name) && getHeaderMapRaw().get(name).intValue() <
values.length;
}
/**
@@ -242,10 +232,10 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
* @return the given map.
*/
<M extends Map<String, String>> M putIn(final M map) {
- if (headerMap == null) {
+ if (getHeaderMapRaw() == null) {
return map;
}
- for (final Entry<String, Integer> entry : headerMap.entrySet()) {
+ for (final Entry<String, Integer> entry :
getHeaderMapRaw().entrySet()) {
final int col = entry.getValue().intValue();
if (col < values.length) {
map.put(entry.getKey(), values[col]);
@@ -291,9 +281,8 @@ public final class CSVRecord implements Serializable,
Iterable<String> {
*/
@Override
public String toString() {
- return "CSVRecord [comment=" + comment + ", mapping=" + headerMap +
- ", recordNumber=" + recordNumber + ", values=" +
- Arrays.toString(values) + "]";
+ return "CSVRecord [comment='" + comment + "', recordNumber=" +
recordNumber + ", values="
+ + Arrays.toString(values) + "]";
}
String[] values() {
diff --git a/src/test/java/org/apache/commons/csv/CSVParserTest.java
b/src/test/java/org/apache/commons/csv/CSVParserTest.java
index a5539f6..530df93 100644
--- a/src/test/java/org/apache/commons/csv/CSVParserTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVParserTest.java
@@ -491,6 +491,20 @@ public class CSVParserTest {
}
@Test
+ public void testGetHeaderNames() throws IOException {
+ try (final CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z",
+ CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
+ final Map<String, Integer> nameIndexMap = parser.getHeaderMap();
+ final List<String> headerNames = parser.getHeaderNames();
+ Assert.assertEquals(nameIndexMap.size(), headerNames.size());
+ for (int i = 0; i < headerNames.size(); i++) {
+ final String name = headerNames.get(i);
+ Assert.assertEquals(i, nameIndexMap.get(name).intValue());
+ }
+ }
+ }
+
+ @Test
public void testGetLine() throws IOException {
try (final CSVParser parser = CSVParser.parse(CSV_INPUT,
CSVFormat.DEFAULT.withIgnoreSurroundingSpaces())) {
for (final String[] re : RESULT) {
@@ -678,9 +692,9 @@ public class CSVParserTest {
@Test
public void testIgnoreCaseHeaderMapping() throws Exception {
- final Reader in = new StringReader("1,2,3");
+ final Reader reader = new StringReader("1,2,3");
final Iterator<CSVRecord> records =
CSVFormat.DEFAULT.withHeader("One", "TWO", "three").withIgnoreHeaderCase()
- .parse(in).iterator();
+ .parse(reader).iterator();
final CSVRecord record = records.next();
assertEquals("1", record.get("one"));
assertEquals("2", record.get("two"));
@@ -742,10 +756,10 @@ public class CSVParserTest {
// Iterator hasNext() shouldn't break sequence
CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows));
int recordNumber = 0;
- Iterator<CSVRecord> iter = parser.iterator();
+ final Iterator<CSVRecord> iter = parser.iterator();
recordNumber = 0;
while (iter.hasNext()) {
- CSVRecord record = iter.next();
+ final CSVRecord record = iter.next();
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
if (recordNumber >= 2) {
@@ -754,7 +768,7 @@ public class CSVParserTest {
}
iter.hasNext();
while (iter.hasNext()) {
- CSVRecord record = iter.next();
+ final CSVRecord record = iter.next();
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
}
@@ -762,14 +776,14 @@ public class CSVParserTest {
// Consecutive enhanced for loops shouldn't break sequence
parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows));
recordNumber = 0;
- for (CSVRecord record : parser) {
+ for (final CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
if (recordNumber >= 2) {
break;
}
}
- for (CSVRecord record : parser) {
+ for (final CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
}
@@ -777,7 +791,7 @@ public class CSVParserTest {
// Consecutive enhanced for loops with hasNext() peeking shouldn't
break sequence
parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows));
recordNumber = 0;
- for (CSVRecord record : parser) {
+ for (final CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
if (recordNumber >= 2) {
@@ -785,7 +799,7 @@ public class CSVParserTest {
}
}
parser.iterator().hasNext();
- for (CSVRecord record : parser) {
+ for (final CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
}
@@ -799,7 +813,7 @@ public class CSVParserTest {
assertEquals(4, records.size());
}
}
-
+
@Test
public void testMappedButNotSetAsOutlook2007ContactExport() throws
Exception {
final Reader in = new StringReader("a,b,c\n1,2\nx,y,z");
diff --git a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
index 6a0e3af..7fe6c5c 100644
--- a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
@@ -82,7 +82,7 @@ public class CSVPrinterTest {
}
private final String recordSeparator =
CSVFormat.DEFAULT.getRecordSeparator();
- private String longText2;
+ private String longText2;
private void doOneRandom(final CSVFormat format) throws Exception {
final Random r = new Random();
diff --git a/src/test/java/org/apache/commons/csv/CSVRecordTest.java
b/src/test/java/org/apache/commons/csv/CSVRecordTest.java
index dab4d55..ea4b797 100644
--- a/src/test/java/org/apache/commons/csv/CSVRecordTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVRecordTest.java
@@ -23,21 +23,23 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class CSVRecordTest {
- private enum EnumFixture { UNKNOWN_COLUMN }
+ private enum EnumFixture {
+ UNKNOWN_COLUMN
+ }
private String[] values;
private CSVRecord record, recordWithHeader;
@@ -46,12 +48,15 @@ public class CSVRecordTest {
@Before
public void setUp() throws Exception {
values = new String[] { "A", "B", "C" };
- record = new CSVRecord(values, null, null, null, 0, -1);
- headerMap = new HashMap<>();
- headerMap.put("first", Integer.valueOf(0));
- headerMap.put("second", Integer.valueOf(1));
- headerMap.put("third", Integer.valueOf(2));
- recordWithHeader = new CSVRecord(values, headerMap,
CSVParser.createHeaderNames(headerMap), null, 0, -1);
+ final String rowData = StringUtils.join(values, ',');
+ try (final CSVParser parser = CSVFormat.DEFAULT.parse(new
StringReader(rowData))) {
+ record = parser.iterator().next();
+ }
+ final String[] headers = { "first", "second", "third" };
+ try (final CSVParser parser =
CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) {
+ recordWithHeader = parser.iterator().next();
+ headerMap = parser.getHeaderMap();
+ }
}
@Test
@@ -103,9 +108,22 @@ public class CSVRecordTest {
public void testIsConsistent() {
assertTrue(record.isConsistent());
assertTrue(recordWithHeader.isConsistent());
+ final Map<String, Integer> map =
recordWithHeader.getCSVParser().getHeaderMap();
+ map.put("fourth", Integer.valueOf(4));
+ // We are working on a copy of the map, so the record should still be
OK.
+ assertTrue(recordWithHeader.isConsistent());
+ }
- headerMap.put("fourth", Integer.valueOf(4));
- assertFalse(recordWithHeader.isConsistent());
+ @Test
+ public void testIsInconsistent() throws IOException {
+ final String[] headers = { "first", "second", "third" };
+ final String rowData = StringUtils.join(values, ',');
+ try (final CSVParser parser =
CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) {
+ final Map<String, Integer> map = parser.getHeaderMapRaw();
+ final CSVRecord record1 = parser.iterator().next();
+ map.put("fourth", Integer.valueOf(4));
+ assertFalse(record1.isConsistent());
+ }
}
@Test
@@ -163,18 +181,6 @@ public class CSVRecordTest {
}
@Test
- public void testGetHeaderNames() {
- final Map<String, String> nameValueMap = this.recordWithHeader.toMap();
- final List<String> headerNames =
this.recordWithHeader.getHeaderNames();
- Assert.assertEquals(nameValueMap.size(), headerNames.size());
- for (int i = 0; i < headerNames.size(); i++) {
- String name = headerNames.get(i);
- Assert.assertEquals(i,
this.recordWithHeader.getIndex(name).intValue());
- Assert.assertEquals(name, this.recordWithHeader.getName(i));
- }
- }
-
- @Test
public void testToMapWithShortRecord() throws Exception {
try (final CSVParser parser = CSVParser.parse("a,b",
CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
final CSVRecord shortRec = parser.iterator().next();
diff --git a/src/test/java/org/apache/commons/csv/PerformanceTest.java
b/src/test/java/org/apache/commons/csv/PerformanceTest.java
index 9ab7071..1727b79 100644
--- a/src/test/java/org/apache/commons/csv/PerformanceTest.java
+++ b/src/test/java/org/apache/commons/csv/PerformanceTest.java
@@ -246,6 +246,7 @@ public class PerformanceTest {
private static void testParseCommonsCSV() throws Exception {
testParser("CSV", new CSVParserFactory() {
+ @Override
public CSVParser createParser() throws IOException {
return new CSVParser(createReader(), format);
}
@@ -254,6 +255,7 @@ public class PerformanceTest {
private static void testParsePath() throws Exception {
testParser("CSV-PATH", new CSVParserFactory() {
+ @Override
public CSVParser createParser() throws IOException {
return
CSVParser.parse(Files.newInputStream(Paths.get(BIG_FILE.toURI())),
StandardCharsets.ISO_8859_1, format);
}
@@ -262,6 +264,7 @@ public class PerformanceTest {
private static void testParsePathDoubleBuffering() throws Exception {
testParser("CSV-PATH-DB", new CSVParserFactory() {
+ @Override
public CSVParser createParser() throws IOException {
return
CSVParser.parse(Files.newBufferedReader(Paths.get(BIG_FILE.toURI()),
StandardCharsets.ISO_8859_1), format);
}
@@ -270,6 +273,7 @@ public class PerformanceTest {
private static void testParseURL() throws Exception {
testParser("CSV-URL", new CSVParserFactory() {
+ @Override
public CSVParser createParser() throws IOException {
//NOTE: URL will always return a BufferedInputStream.
return CSVParser.parse(BIG_FILE.toURI().toURL(),
StandardCharsets.ISO_8859_1, format);