You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2016/05/11 21:09:48 UTC
svn commit: r1743430 - in /commons/proper/csv/trunk: pom.xml
src/changes/changes.xml src/main/java/org/apache/commons/csv/CSVFormat.java
src/main/java/org/apache/commons/csv/CSVPrinter.java
Author: ggregory
Date: Wed May 11 21:09:48 2016
New Revision: 1743430
URL: http://svn.apache.org/viewvc?rev=1743430&view=rev
Log:
[CSV-182] Allow some printing operations directly from CSVFormat. Adds APIs to CSVFormat so update version from 1.3.1-SNAPSHOT to 1.4-SNAPHOT a la semver. This commit refactors a small bit of guts code from CSVPrinter to CSVFormat.
Modified:
commons/proper/csv/trunk/pom.xml
commons/proper/csv/trunk/src/changes/changes.xml
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java
Modified: commons/proper/csv/trunk/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/pom.xml?rev=1743430&r1=1743429&r2=1743430&view=diff
==============================================================================
--- commons/proper/csv/trunk/pom.xml (original)
+++ commons/proper/csv/trunk/pom.xml Wed May 11 21:09:48 2016
@@ -24,7 +24,7 @@ limitations under the License.
</parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
- <version>1.3.1-SNAPSHOT</version>
+ <version>1.4-SNAPSHOT</version>
<name>Apache Commons CSV</name>
<url>http://commons.apache.org/proper/commons-csv/</url>
<description>
Modified: commons/proper/csv/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/changes/changes.xml?rev=1743430&r1=1743429&r2=1743430&view=diff
==============================================================================
--- commons/proper/csv/trunk/src/changes/changes.xml (original)
+++ commons/proper/csv/trunk/src/changes/changes.xml Wed May 11 21:09:48 2016
@@ -38,8 +38,9 @@
<title>Release Notes</title>
</properties>
<body>
- <release version="1.3.1" date="2016-MM-DD" description="Feature and bug fix release">
+ <release version="1.4" date="2016-MM-DD" description="Feature and bug fix release">
<action issue="CSV-181" type="update" dev="ggregory" due-to="Gary Gregory">Make CSVPrinter.print(Object) GC-free.</action>
+ <action issue="CSV-182" type="update" dev="ggregory" due-to="Gary Gregory">Allow some printing operations directly from CSVFormat.</action>
</release>
<release version="1.3" date="2016-05-09" description="Feature and bug fix release">
<action issue="CSV-179" type="add" dev="britter">Add shortcut method for using first record as header to CSVFormat</action>
Modified: commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java
URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java?rev=1743430&r1=1743429&r2=1743430&view=diff
==============================================================================
--- commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java (original)
+++ commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java Wed May 11 21:09:48 2016
@@ -19,11 +19,13 @@ package org.apache.commons.csv;
import static org.apache.commons.csv.Constants.BACKSLASH;
import static org.apache.commons.csv.Constants.COMMA;
+import static org.apache.commons.csv.Constants.COMMENT;
import static org.apache.commons.csv.Constants.CR;
import static org.apache.commons.csv.Constants.CRLF;
import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR;
import static org.apache.commons.csv.Constants.LF;
import static org.apache.commons.csv.Constants.PIPE;
+import static org.apache.commons.csv.Constants.SP;
import static org.apache.commons.csv.Constants.TAB;
import java.io.IOException;
@@ -499,7 +501,8 @@ public final class CSVFormat implements
* TODO
* @param trim
* TODO
- * @param trailingDelimiter TODO
+ * @param trailingDelimiter
+ * TODO
* @throws IllegalArgumentException
* if the delimiter is a line break character
*/
@@ -860,6 +863,242 @@ public final class CSVFormat implements
return new CSVPrinter(out, this);
}
+ /**
+ * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed. Useful when
+ * one wants to avoid creating CSVPrinters.
+ *
+ * @param value
+ * value to be output.
+ * @param out
+ * where to print the value
+ * @param newRecord
+ * is this a new record
+ * @throws IOException
+ * If an I/O error occurs
+ * @since 1.4
+ */
+ public void print(final Object value, final Appendable out, final boolean newRecord) throws IOException {
+ // null values are considered empty
+ // Only call CharSequence.toString() if you have to, helps GC-free use cases.
+ CharSequence charSequence;
+ if (value == null) {
+ charSequence = nullString == null ? Constants.EMPTY : nullString;
+ } else {
+ charSequence = value instanceof CharSequence ? (CharSequence) value : value.toString();
+ }
+ charSequence = getTrim() ? trim(charSequence) : charSequence;
+ this.print(value, charSequence, 0, charSequence.length(), out, newRecord);
+ }
+
+ private void print(final Object object, final CharSequence value, final int offset, final int len,
+ final Appendable out, final boolean newRecord) throws IOException {
+ if (!newRecord) {
+ out.append(getDelimiter());
+ }
+ if (object == null) {
+ out.append(value);
+ } else if (isQuoteCharacterSet()) {
+ // the original object is needed so can check for Number
+ printAndQuote(object, value, offset, len, out, newRecord);
+ } else if (isEscapeCharacterSet()) {
+ printAndEscape(value, offset, len, out);
+ } else {
+ out.append(value, offset, offset + len);
+ }
+ }
+
+ /*
+ * Note: must only be called if escaping is enabled, otherwise will generate NPE
+ */
+ private void printAndEscape(final CharSequence value, final int offset, final int len, final Appendable out)
+ throws IOException {
+ int start = offset;
+ int pos = offset;
+ final int end = offset + len;
+
+ final char delim = getDelimiter();
+ final char escape = getEscapeCharacter().charValue();
+
+ while (pos < end) {
+ char c = value.charAt(pos);
+ if (c == CR || c == LF || c == delim || c == escape) {
+ // write out segment up until this char
+ if (pos > start) {
+ out.append(value, start, pos);
+ }
+ if (c == LF) {
+ c = 'n';
+ } else if (c == CR) {
+ c = 'r';
+ }
+
+ out.append(escape);
+ out.append(c);
+
+ start = pos + 1; // start on the current char after this one
+ }
+
+ pos++;
+ }
+
+ // write last segment
+ if (pos > start) {
+ out.append(value, start, pos);
+ }
+ }
+
+ /*
+ * Note: must only be called if quoting is enabled, otherwise will generate NPE
+ */
+ // the original object is needed so can check for Number
+ private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len,
+ final Appendable out, final boolean newRecord) throws IOException {
+ boolean quote = false;
+ int start = offset;
+ int pos = offset;
+ final int end = offset + len;
+
+ final char delimChar = getDelimiter();
+ final char quoteChar = getQuoteCharacter().charValue();
+
+ QuoteMode quoteModePolicy = getQuoteMode();
+ if (quoteModePolicy == null) {
+ quoteModePolicy = QuoteMode.MINIMAL;
+ }
+ switch (quoteModePolicy) {
+ case ALL:
+ quote = true;
+ break;
+ case NON_NUMERIC:
+ quote = !(object instanceof Number);
+ break;
+ case NONE:
+ // Use the existing escaping code
+ printAndEscape(value, offset, len, out);
+ return;
+ case MINIMAL:
+ if (len <= 0) {
+ // always quote an empty token that is the first
+ // on the line, as it may be the only thing on the
+ // line. If it were not quoted in that case,
+ // an empty line has no tokens.
+ if (newRecord) {
+ quote = true;
+ }
+ } else {
+ char c = value.charAt(pos);
+
+ // TODO where did this rule come from?
+ if (newRecord && (c < '0' || c > '9' && c < 'A' || c > 'Z' && c < 'a' || c > 'z')) {
+ quote = true;
+ } else if (c <= COMMENT) {
+ // Some other chars at the start of a value caused the parser to fail, so for now
+ // encapsulate if we start in anything less than '#'. We are being conservative
+ // by including the default comment char too.
+ quote = true;
+ } else {
+ while (pos < end) {
+ c = value.charAt(pos);
+ if (c == LF || c == CR || c == quoteChar || c == delimChar) {
+ quote = true;
+ break;
+ }
+ pos++;
+ }
+
+ if (!quote) {
+ pos = end - 1;
+ c = value.charAt(pos);
+ // Some other chars at the end caused the parser to fail, so for now
+ // encapsulate if we end in anything less than ' '
+ if (c <= SP) {
+ quote = true;
+ }
+ }
+ }
+ }
+
+ if (!quote) {
+ // no encapsulation needed - write out the original value
+ out.append(value, start, end);
+ return;
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unexpected Quote value: " + quoteModePolicy);
+ }
+
+ if (!quote) {
+ // no encapsulation needed - write out the original value
+ out.append(value, start, end);
+ return;
+ }
+
+ // we hit something that needed encapsulation
+ out.append(quoteChar);
+
+ // Pick up where we left off: pos should be positioned on the first character that caused
+ // the need for encapsulation.
+ while (pos < end) {
+ final char c = value.charAt(pos);
+ if (c == quoteChar) {
+ // write out the chunk up until this point
+
+ // add 1 to the length to write out the encapsulator also
+ out.append(value, start, pos + 1);
+ // put the next starting position on the encapsulator so we will
+ // write it out again with the next string (effectively doubling it)
+ start = pos;
+ }
+ pos++;
+ }
+
+ // write the last segment
+ out.append(value, start, pos);
+ out.append(quoteChar);
+ }
+
+ /**
+ * Outputs the record separator.
+ *
+ * @param out
+ * where to write
+ *
+ * @throws IOException
+ * If an I/O error occurs
+ * @since 1.4
+ */
+ public void println(final Appendable out) throws IOException {
+ if (getTrailingDelimiter()) {
+ out.append(getDelimiter());
+ }
+ if (recordSeparator != null) {
+ out.append(recordSeparator);
+ }
+ }
+
+ /**
+ * Prints the given values a single record of delimiter separated values followed by the record separator.
+ *
+ * <p>
+ * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
+ * separator to the output after printing the record, so there is no need to call {@link #println(Appendable)}.
+ * </p>
+ *
+ * @param out where to write
+ * @param values
+ * values to output.
+ * @throws IOException
+ * If an I/O error occurs
+ * @since 1.4
+ */
+ public void printRecord(final Appendable out, final Object... values) throws IOException {
+ for (int i = 0; i < values.length; i++) {
+ print(values[i], out, i == 0);
+ }
+ println(out);
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -917,6 +1156,23 @@ public final class CSVFormat implements
return strings;
}
+ private CharSequence trim(final CharSequence charSequence) {
+ if (charSequence instanceof String) {
+ return ((String) charSequence).trim();
+ }
+ final int count = charSequence.length();
+ int len = count;
+ int pos = 0;
+
+ while ((pos < len) && (charSequence.charAt(pos) <= ' ')) {
+ pos++;
+ }
+ while ((pos < len) && (charSequence.charAt(len - 1) <= ' ')) {
+ len--;
+ }
+ return (pos > 0) || (len < count) ? charSequence.subSequence(pos, len) : charSequence;
+ }
+
/**
* Verifies the consistency of the parameters and throws an IllegalArgumentException if necessary.
*
@@ -1083,6 +1339,7 @@ public final class CSVFormat implements
* <p>
* Calling this method is equivalent to calling:
* </p>
+ *
* <pre>
* CSVFormat format = aFormat.withHeader().withSkipHeaderRecord();
* </pre>
@@ -1114,8 +1371,8 @@ public final class CSVFormat implements
* </p>
*
* @param headerEnum
- * the enum defining the header, {@code null} if disabled, empty if parsed automatically, user
- * specified otherwise.
+ * the enum defining the header, {@code null} if disabled, empty if parsed automatically, user specified
+ * otherwise.
*
* @return A new CSVFormat that is equal to this but with the specified header
* @see #withHeader(String...)
Modified: commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java
URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java?rev=1743430&r1=1743429&r2=1743430&view=diff
==============================================================================
--- commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java (original)
+++ commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java Wed May 11 21:09:48 2016
@@ -17,7 +17,6 @@
package org.apache.commons.csv;
-import static org.apache.commons.csv.Constants.COMMENT;
import static org.apache.commons.csv.Constants.CR;
import static org.apache.commons.csv.Constants.LF;
import static org.apache.commons.csv.Constants.SP;
@@ -120,204 +119,10 @@ public final class CSVPrinter implements
* If an I/O error occurs
*/
public void print(final Object value) throws IOException {
- // null values are considered empty
- // Only call CharSequence.toString() if you have to, helps GC-free use cases.
- CharSequence charSequence;
- if (value == null) {
- final String nullString = format.getNullString();
- charSequence = nullString == null ? Constants.EMPTY : nullString;
- } else {
- charSequence = value instanceof CharSequence ? (CharSequence) value : value.toString();
- }
- charSequence = format.getTrim() ? trim(charSequence) : charSequence;
- this.print(value, charSequence, 0, charSequence.length());
- }
-
- private CharSequence trim(final CharSequence charSequence) {
- if (charSequence instanceof String) {
- return ((String) charSequence).trim();
- }
- final int count = charSequence.length();
- int len = count;
- int pos = 0;
-
- while ((pos < len) && (charSequence.charAt(pos) <= ' ')) {
- pos++;
- }
- while ((pos < len) && (charSequence.charAt(len - 1) <= ' ')) {
- len--;
- }
- return (pos > 0) || (len < count) ? charSequence.subSequence(pos, len) : charSequence;
- }
-
- private void print(final Object object, final CharSequence value, final int offset, final int len)
- throws IOException {
- if (!newRecord) {
- out.append(format.getDelimiter());
- }
- if (object == null) {
- out.append(value);
- } else if (format.isQuoteCharacterSet()) {
- // the original object is needed so can check for Number
- printAndQuote(object, value, offset, len);
- } else if (format.isEscapeCharacterSet()) {
- printAndEscape(value, offset, len);
- } else {
- out.append(value, offset, offset + len);
- }
+ format.print(value, out, newRecord);
newRecord = false;
}
- /*
- * Note: must only be called if escaping is enabled, otherwise will generate NPE
- */
- private void printAndEscape(final CharSequence value, final int offset, final int len) throws IOException {
- int start = offset;
- int pos = offset;
- final int end = offset + len;
-
- final char delim = format.getDelimiter();
- final char escape = format.getEscapeCharacter().charValue();
-
- while (pos < end) {
- char c = value.charAt(pos);
- if (c == CR || c == LF || c == delim || c == escape) {
- // write out segment up until this char
- if (pos > start) {
- out.append(value, start, pos);
- }
- if (c == LF) {
- c = 'n';
- } else if (c == CR) {
- c = 'r';
- }
-
- out.append(escape);
- out.append(c);
-
- start = pos + 1; // start on the current char after this one
- }
-
- pos++;
- }
-
- // write last segment
- if (pos > start) {
- out.append(value, start, pos);
- }
- }
-
- /*
- * Note: must only be called if quoting is enabled, otherwise will generate NPE
- */
- // the original object is needed so can check for Number
- private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len)
- throws IOException {
- boolean quote = false;
- int start = offset;
- int pos = offset;
- final int end = offset + len;
-
- final char delimChar = format.getDelimiter();
- final char quoteChar = format.getQuoteCharacter().charValue();
-
- QuoteMode quoteModePolicy = format.getQuoteMode();
- if (quoteModePolicy == null) {
- quoteModePolicy = QuoteMode.MINIMAL;
- }
- switch (quoteModePolicy) {
- case ALL:
- quote = true;
- break;
- case NON_NUMERIC:
- quote = !(object instanceof Number);
- break;
- case NONE:
- // Use the existing escaping code
- printAndEscape(value, offset, len);
- return;
- case MINIMAL:
- if (len <= 0) {
- // always quote an empty token that is the first
- // on the line, as it may be the only thing on the
- // line. If it were not quoted in that case,
- // an empty line has no tokens.
- if (newRecord) {
- quote = true;
- }
- } else {
- char c = value.charAt(pos);
-
- // TODO where did this rule come from?
- if (newRecord && (c < '0' || c > '9' && c < 'A' || c > 'Z' && c < 'a' || c > 'z')) {
- quote = true;
- } else if (c <= COMMENT) {
- // Some other chars at the start of a value caused the parser to fail, so for now
- // encapsulate if we start in anything less than '#'. We are being conservative
- // by including the default comment char too.
- quote = true;
- } else {
- while (pos < end) {
- c = value.charAt(pos);
- if (c == LF || c == CR || c == quoteChar || c == delimChar) {
- quote = true;
- break;
- }
- pos++;
- }
-
- if (!quote) {
- pos = end - 1;
- c = value.charAt(pos);
- // Some other chars at the end caused the parser to fail, so for now
- // encapsulate if we end in anything less than ' '
- if (c <= SP) {
- quote = true;
- }
- }
- }
- }
-
- if (!quote) {
- // no encapsulation needed - write out the original value
- out.append(value, start, end);
- return;
- }
- break;
- default:
- throw new IllegalStateException("Unexpected Quote value: " + quoteModePolicy);
- }
-
- if (!quote) {
- // no encapsulation needed - write out the original value
- out.append(value, start, end);
- return;
- }
-
- // we hit something that needed encapsulation
- out.append(quoteChar);
-
- // Pick up where we left off: pos should be positioned on the first character that caused
- // the need for encapsulation.
- while (pos < end) {
- final char c = value.charAt(pos);
- if (c == quoteChar) {
- // write out the chunk up until this point
-
- // add 1 to the length to write out the encapsulator also
- out.append(value, start, pos + 1);
- // put the next starting position on the encapsulator so we will
- // write it out again with the next string (effectively doubling it)
- start = pos;
- }
- pos++;
- }
-
- // write the last segment
- out.append(value, start, pos);
- out.append(quoteChar);
- }
-
/**
* Prints a comment on a new line among the delimiter separated values.
*
@@ -370,13 +175,7 @@ public final class CSVPrinter implements
* If an I/O error occurs
*/
public void println() throws IOException {
- if (format.getTrailingDelimiter()) {
- out.append(format.getDelimiter());
- }
- final String recordSeparator = format.getRecordSeparator();
- if (recordSeparator != null) {
- out.append(recordSeparator);
- }
+ format.println(out);
newRecord = true;
}
@@ -414,10 +213,8 @@ public final class CSVPrinter implements
* If an I/O error occurs
*/
public void printRecord(final Object... values) throws IOException {
- for (final Object value : values) {
- print(value);
- }
- println();
+ format.printRecord(out, values);
+ newRecord = true;
}
/**