[CSV-68] Use the Builder pattern for CSVFormat.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/csv/trunk@1410759 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gary D. Gregory 2012-11-17 18:00:38 +00:00
parent e4889167d2
commit db11c04d29
3 changed files with 296 additions and 43 deletions

View File

@ -29,12 +29,13 @@ import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Serializable; import java.io.Serializable;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Arrays;
/** /**
* The format specification of a CSV file. * The format specification of a CSV file.
* *
* This class is immutable. * This class is immutable.
* *
* @version $Id$ * @version $Id$
*/ */
public class CSVFormat implements Serializable { public class CSVFormat implements Serializable {
@ -125,8 +126,8 @@ public class CSVFormat implements Serializable {
/** /**
* Creates a new CSV format builds. * Creates a new CSV format builds.
* *
* @param delimiter * @param delimiter
* the char used for value separation, must not be a line break character * the char used for value separation, must not be a line break character
* @throws IllegalArgumentException if the delimiter is a line break character * @throws IllegalArgumentException if the delimiter is a line break character
*/ */
@ -137,7 +138,7 @@ public class CSVFormat implements Serializable {
public static CSVFormatBuilder newBuilder(final CSVFormat format) { public static CSVFormatBuilder newBuilder(final CSVFormat format) {
return new CSVFormatBuilder(format); return new CSVFormatBuilder(format);
} }
/** /**
* Standard comma separated format, as for {@link #RFC4180} but allowing blank lines. * Standard comma separated format, as for {@link #RFC4180} but allowing blank lines.
* <ul> * <ul>
@ -158,7 +159,7 @@ public class CSVFormat implements Serializable {
* the char used for value separation, must not be a line break character * the char used for value separation, must not be a line break character
* @param quoteChar * @param quoteChar
* the char used as value encapsulation marker * the char used as value encapsulation marker
* @param quotePolicy * @param quotePolicy
* the quote policy * the quote policy
* @param commentStart * @param commentStart
* the char used for comment identification * the char used for comment identification
@ -174,10 +175,12 @@ public class CSVFormat implements Serializable {
* the header * the header
* @throws IllegalArgumentException if the delimiter is a line break character * @throws IllegalArgumentException if the delimiter is a line break character
*/ */
private CSVFormat(final char delimiter, final Character quoteChar, final Quote quotePolicy, final Character commentStart, final Character escape, final private CSVFormat(final char delimiter, final Character quoteChar, final Quote quotePolicy, final Character commentStart, final Character escape, final
boolean ignoreSurroundingSpaces, final boolean ignoreEmptyLines, final String lineSeparator, boolean ignoreSurroundingSpaces, final boolean ignoreEmptyLines, final String lineSeparator,
final String[] header) { final String[] header)
if (isLineBreak(delimiter)) { {
if (isLineBreak(delimiter))
{
throw new IllegalArgumentException("The delimiter cannot be a line break"); throw new IllegalArgumentException("The delimiter cannot be a line break");
} }
this.delimiter = delimiter; this.delimiter = delimiter;
@ -188,7 +191,7 @@ public class CSVFormat implements Serializable {
this.ignoreSurroundingSpaces = ignoreSurroundingSpaces; this.ignoreSurroundingSpaces = ignoreSurroundingSpaces;
this.ignoreEmptyLines = ignoreEmptyLines; this.ignoreEmptyLines = ignoreEmptyLines;
this.recordSeparator = lineSeparator; this.recordSeparator = lineSeparator;
this.header = header; this.header = header == null ? null : header.clone();
} }
/** /**
@ -373,7 +376,109 @@ public class CSVFormat implements Serializable {
public Quote getQuotePolicy() { public Quote getQuotePolicy() {
return quotePolicy; return quotePolicy;
} }
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + delimiter;
result = prime * result + ((quotePolicy == null) ? 0 : quotePolicy.hashCode());
result = prime * result + ((quoteChar == null) ? 0 : quoteChar.hashCode());
result = prime * result + ((commentStart == null) ? 0 : commentStart.hashCode());
result = prime * result + ((escape == null) ? 0 : escape.hashCode());
result = prime * result + (ignoreSurroundingSpaces ? 1231 : 1237);
result = prime * result + (ignoreEmptyLines ? 1231 : 1237);
result = prime * result + ((recordSeparator == null) ? 0 : recordSeparator.hashCode());
result = prime * result + Arrays.hashCode(header);
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
CSVFormat other = (CSVFormat) obj;
if (delimiter != other.delimiter)
{
return false;
}
if (quotePolicy != other.quotePolicy)
{
return false;
}
if (quoteChar == null)
{
if (other.quoteChar != null)
{
return false;
}
}
else if (!quoteChar.equals(other.quoteChar))
{
return false;
}
if (commentStart == null)
{
if (other.commentStart != null)
{
return false;
}
}
else if (!commentStart.equals(other.commentStart))
{
return false;
}
if (escape == null)
{
if (other.escape != null)
{
return false;
}
}
else if (!escape.equals(other.escape))
{
return false;
}
if (!Arrays.equals(header, other.header))
{
return false;
}
if (ignoreSurroundingSpaces != other.ignoreSurroundingSpaces)
{
return false;
}
if (ignoreEmptyLines != other.ignoreEmptyLines)
{
return false;
}
if (recordSeparator == null)
{
if (other.recordSeparator != null)
{
return false;
}
}
else if (!recordSeparator.equals(other.recordSeparator))
{
return false;
}
return true;
}
public static class CSVFormatBuilder { public static class CSVFormatBuilder {
private char delimiter; private char delimiter;
@ -393,7 +498,7 @@ public class CSVFormat implements Serializable {
* the char used for value separation, must not be a line break character * the char used for value separation, must not be a line break character
* @param quoteChar * @param quoteChar
* the char used as value encapsulation marker * the char used as value encapsulation marker
* @param quotePolicy * @param quotePolicy
* the quote policy * the quote policy
* @param commentStart * @param commentStart
* the char used for comment identification * the char used for comment identification
@ -410,8 +515,8 @@ public class CSVFormat implements Serializable {
* @throws IllegalArgumentException if the delimiter is a line break character * @throws IllegalArgumentException if the delimiter is a line break character
*/ */
// package protected for use by test code // package protected for use by test code
CSVFormatBuilder(final char delimiter, final Character quoteChar, final Quote quotePolicy, final Character commentStart, final Character escape, final CSVFormatBuilder(final char delimiter, final Character quoteChar, final Quote quotePolicy, final Character commentStart, final Character escape, final
boolean ignoreSurroundingSpaces, final boolean ignoreEmptyLines, final String lineSeparator, boolean ignoreSurroundingSpaces, final boolean ignoreEmptyLines, final String lineSeparator,
final String[] header) { final String[] header) {
if (isLineBreak(delimiter)) { if (isLineBreak(delimiter)) {
throw new IllegalArgumentException("The delimiter cannot be a line break"); throw new IllegalArgumentException("The delimiter cannot be a line break");
@ -426,11 +531,11 @@ public class CSVFormat implements Serializable {
this.recordSeparator = lineSeparator; this.recordSeparator = lineSeparator;
this.header = header; this.header = header;
} }
/** /**
* *
* Creates a CSVFormatBuilder, using the values of the given CSVFormat. * Creates a CSVFormatBuilder, using the values of the given CSVFormat.
* *
* @param format * @param format
* The format to use values from * The format to use values from
*/ */
@ -443,8 +548,8 @@ public class CSVFormat implements Serializable {
/** /**
* Creates a basic CSVFormatBuilder. * Creates a basic CSVFormatBuilder.
* *
* @param delimiter * @param delimiter
* the char used for value separation, must not be a line break character * the char used for value separation, must not be a line break character
* @throws IllegalArgumentException if the delimiter is a line break character * @throws IllegalArgumentException if the delimiter is a line break character
*/ */
@ -457,35 +562,35 @@ public class CSVFormat implements Serializable {
return new CSVFormat(delimiter, quoteChar, quotePolicy, commentStart, escape, return new CSVFormat(delimiter, quoteChar, quotePolicy, commentStart, escape,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, header); ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, header);
} }
/** /**
* Verifies the consistency of the parameters and throws an IllegalStateException if necessary. * Verifies the consistency of the parameters and throws an IllegalStateException if necessary.
* *
* @throws IllegalStateException * @throws IllegalStateException
*/ */
private void validate() throws IllegalStateException { private void validate() throws IllegalStateException {
if (quoteChar != null && delimiter == quoteChar.charValue()) { if (quoteChar != null && delimiter == quoteChar.charValue()) {
throw new IllegalStateException("The quoteChar character and the delimiter cannot be the same ('" + quoteChar + "')"); throw new IllegalStateException("The quoteChar character and the delimiter cannot be the same ('" + quoteChar + "')");
} }
if (escape != null && delimiter == escape.charValue()) { if (escape != null && delimiter == escape.charValue()) {
throw new IllegalStateException("The escape character and the delimiter cannot be the same ('" + escape + "')"); throw new IllegalStateException("The escape character and the delimiter cannot be the same ('" + escape + "')");
} }
if (commentStart != null && delimiter == commentStart.charValue()) { if (commentStart != null && delimiter == commentStart.charValue()) {
throw new IllegalStateException("The comment start character and the delimiter cannot be the same ('" + commentStart + throw new IllegalStateException("The comment start character and the delimiter cannot be the same ('" + commentStart +
"')"); "')");
} }
if (quoteChar != null && quoteChar.equals(commentStart)) { if (quoteChar != null && quoteChar.equals(commentStart)) {
throw new IllegalStateException("The comment start character and the quoteChar cannot be the same ('" + commentStart + throw new IllegalStateException("The comment start character and the quoteChar cannot be the same ('" + commentStart +
"')"); "')");
} }
if (escape != null && escape.equals(commentStart)) { if (escape != null && escape.equals(commentStart)) {
throw new IllegalStateException("The comment start and the escape character cannot be the same ('" + commentStart + "')"); throw new IllegalStateException("The comment start and the escape character cannot be the same ('" + commentStart + "')");
} }
if (escape == null && quotePolicy == Quote.NONE) { if (escape == null && quotePolicy == Quote.NONE) {
throw new IllegalStateException("No quotes mode set but no escape character is set"); throw new IllegalStateException("No quotes mode set but no escape character is set");
} }
@ -504,7 +609,7 @@ public class CSVFormat implements Serializable {
if (isLineBreak(delimiter)) { if (isLineBreak(delimiter)) {
throw new IllegalArgumentException("The delimiter cannot be a line break"); throw new IllegalArgumentException("The delimiter cannot be a line break");
} }
this.delimiter = delimiter; this.delimiter = delimiter;
return this; return this;
} }
@ -625,7 +730,7 @@ public class CSVFormat implements Serializable {
this.header = header; this.header = header;
return this; return this;
} }
/** /**
* Sets the trimming behavior of the format. * Sets the trimming behavior of the format.
* *

View File

@ -21,9 +21,9 @@ import static org.apache.commons.csv.CSVFormat.RFC4180;
import static org.apache.commons.csv.Constants.CR; import static org.apache.commons.csv.Constants.CR;
import static org.apache.commons.csv.Constants.CRLF; import static org.apache.commons.csv.Constants.CRLF;
import static org.apache.commons.csv.Constants.LF; import static org.apache.commons.csv.Constants.LF;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.apache.commons.csv.CSVFormat.CSVFormatBuilder; import org.apache.commons.csv.CSVFormat.CSVFormatBuilder;
@ -159,7 +159,7 @@ public class CSVFormatBuilderTest {
@Test @Test
public void testCopiedFormatIsEqualToOriginal() { public void testCopiedFormatIsEqualToOriginal() {
CSVFormat copyOfRCF4180 = CSVFormat.newBuilder(RFC4180).build(); CSVFormat copyOfRCF4180 = CSVFormat.newBuilder(RFC4180).build();
assertEqualFormats(RFC4180, copyOfRCF4180); assertEquals(RFC4180, copyOfRCF4180);
} }
@Test @Test
@ -168,16 +168,14 @@ public class CSVFormatBuilderTest {
assertTrue(newFormat.getDelimiter() != RFC4180.getDelimiter()); assertTrue(newFormat.getDelimiter() != RFC4180.getDelimiter());
} }
// FIXME implement equals on CSVFormat to allow use of Assert.assertEquals() @Test
private static void assertEqualFormats(CSVFormat expected, CSVFormat acutal) { public void testHeaderReferenceCannotEscape() {
assertEquals(expected.getCommentStart(), acutal.getCommentStart()); String[] header = new String[]{"one", "tow", "three"};
assertEquals(expected.getDelimiter(), acutal.getDelimiter()); builder.withHeader(header);
assertEquals(expected.getEscape(), acutal.getEscape());
assertArrayEquals(expected.getHeader(), acutal.getHeader()); CSVFormat firstFormat = builder.build();
assertEquals(expected.getIgnoreEmptyLines(), acutal.getIgnoreEmptyLines()); CSVFormat secondFormat = builder.build();
assertEquals(expected.getIgnoreSurroundingSpaces(), acutal.getIgnoreSurroundingSpaces()); assertNotSame(header, firstFormat.getHeader());
assertEquals(expected.getQuoteChar(), acutal.getQuoteChar()); assertNotSame(firstFormat, secondFormat.getHeader());
assertEquals(expected.getQuotePolicy(), acutal.getQuotePolicy());
assertEquals(expected.getRecordSeparator(), acutal.getRecordSeparator());
} }
} }

View File

@ -18,6 +18,7 @@
package org.apache.commons.csv; package org.apache.commons.csv;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -65,4 +66,153 @@ public class CSVFormatTest {
assertEquals("trim", CSVFormat.DEFAULT.getIgnoreSurroundingSpaces(), format.getIgnoreSurroundingSpaces()); assertEquals("trim", CSVFormat.DEFAULT.getIgnoreSurroundingSpaces(), format.getIgnoreSurroundingSpaces());
assertEquals("empty lines", CSVFormat.DEFAULT.getIgnoreEmptyLines(), format.getIgnoreEmptyLines()); assertEquals("empty lines", CSVFormat.DEFAULT.getIgnoreEmptyLines(), format.getIgnoreEmptyLines());
} }
@Test
public void testEquals() {
CSVFormat right = CSVFormat.DEFAULT;
CSVFormat left = CSVFormat.newBuilder().build();
assertFalse(right.equals(null));
assertFalse(right.equals("A String Instance"));
assertEquals(right, right);
assertEquals(right, left);
assertEquals(left, right);
assertEquals(right.hashCode(), right.hashCode());
assertEquals(right.hashCode(), left.hashCode());
}
@Test
public void testEqualsDelimiter() {
CSVFormat right = CSVFormat.newBuilder('!').build();
CSVFormat left = CSVFormat.newBuilder('?').build();
assertNotEquals(right, left);
}
@Test
public void testEqualsQuoteChar() {
CSVFormat right = CSVFormat.newBuilder('\'').withQuoteChar('"').build();
CSVFormat left = CSVFormat.newBuilder(right).withQuoteChar('!').build();
assertNotEquals(right, left);
}
@Test
public void testEqualsQuotePolicy() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withQuotePolicy(Quote.MINIMAL)
.build();
assertNotEquals(right, left);
}
@Test
public void testEqualsCommentStart() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.withCommentStart('#')
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withCommentStart('!')
.build();
assertNotEquals(right, left);
}
@Test
public void testEqualsEscape() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.withCommentStart('#')
.withEscape('+')
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withEscape('!')
.build();
assertNotEquals(right, left);
}
@Test
public void testEqualsIgnoreSurroundingSpaces() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.withCommentStart('#')
.withEscape('+')
.withIgnoreSurroundingSpaces(true)
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withIgnoreSurroundingSpaces(false)
.build();
assertNotEquals(right, left);
}
@Test
public void testEqualsIgnoreEmptyLines() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.withCommentStart('#')
.withEscape('+')
.withIgnoreSurroundingSpaces(true)
.withIgnoreEmptyLines(true)
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withIgnoreEmptyLines(false)
.build();
assertNotEquals(right, left);
}
@Test
public void testEqualsRecordSeparator() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.withCommentStart('#')
.withEscape('+')
.withIgnoreSurroundingSpaces(true)
.withIgnoreEmptyLines(true)
.withRecordSeparator('*')
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withRecordSeparator('!')
.build();
assertNotEquals(right, left);
}
@Test
public void testEqualsHeader() {
CSVFormat right = CSVFormat.newBuilder('\'')
.withQuoteChar('"')
.withQuotePolicy(Quote.ALL)
.withCommentStart('#')
.withEscape('+')
.withIgnoreSurroundingSpaces(true)
.withIgnoreEmptyLines(true)
.withRecordSeparator('*')
.withHeader("One", "Two", "Three")
.build();
CSVFormat left = CSVFormat.newBuilder(right)
.withHeader("Three", "Two", "One")
.build();
assertNotEquals(right, left);
}
private static void assertNotEquals(Object right, Object left) {
assertFalse(right.equals(left));
assertFalse(left.equals(right));
}
} }