Skip to content

Commit 322fad2

Browse files
committed
[CSV-234] Add support for java.sql.Clob.
1 parent 0ab2b08 commit 322fad2

5 files changed

Lines changed: 308 additions & 90 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<action issue="CSV-233" type="add" dev="ggregory" due-to="Gary Gregory">Add predefined CSVFormats for printing MongoDB CSV and TSV.</action>
4343
<action issue="CSV-208" type="fix" dev="ggregory" due-to="Jurrie Overgoor">Fix escape character for POSTGRESQL_TEXT and POSTGRESQL_CSV formats.</action>
4444
<action issue="CSV-232" type="fix" dev="ggregory" due-to="Jurrie Overgoor, Gary Gregory">Site link "Source Repository" does not work.</action>
45+
<action issue="CSV-234" type="add" dev="ggregory" due-to="Roberto Benedetti, Gary Gregory">Add support for java.sql.Clob.</action>
4546
</release>
4647
<release version="1.6" date="2018-09-22" description="Feature and bug fix release">
4748
<action issue="CSV-231" type="update" dev="britter">Add more documentation to CSVPrinter.</action>

src/main/java/org/apache/commons/csv/CSVFormat.java

Lines changed: 166 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
import static org.apache.commons.csv.Constants.BACKSLASH;
2121
import static org.apache.commons.csv.Constants.COMMA;
2222
import static org.apache.commons.csv.Constants.COMMENT;
23-
import static org.apache.commons.csv.Constants.EMPTY;
2423
import static org.apache.commons.csv.Constants.CR;
2524
import static org.apache.commons.csv.Constants.CRLF;
2625
import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR;
26+
import static org.apache.commons.csv.Constants.EMPTY;
2727
import static org.apache.commons.csv.Constants.LF;
2828
import static org.apache.commons.csv.Constants.PIPE;
2929
import static org.apache.commons.csv.Constants.SP;
@@ -36,6 +36,7 @@
3636
import java.io.Reader;
3737
import java.io.Serializable;
3838
import java.io.StringWriter;
39+
import java.io.Writer;
3940
import java.nio.charset.Charset;
4041
import java.nio.file.Files;
4142
import java.nio.file.Path;
@@ -398,7 +399,7 @@ public CSVFormat getFormat() {
398399
.withQuoteMode(QuoteMode.MINIMAL)
399400
.withSkipHeaderRecord(false);
400401
// @formatter:off
401-
402+
402403
/**
403404
* Default MongoDB TSV format used by the {@code mongoexport} operation.
404405
* <p>
@@ -434,7 +435,7 @@ public CSVFormat getFormat() {
434435
.withQuoteMode(QuoteMode.MINIMAL)
435436
.withSkipHeaderRecord(false);
436437
// @formatter:off
437-
438+
438439
/**
439440
* Default MySQL format used by the {@code SELECT INTO OUTFILE} and {@code LOAD DATA INFILE} operations.
440441
*
@@ -707,6 +708,8 @@ public static CSVFormat valueOf(final String format) {
707708

708709
private final Character quoteCharacter; // null if quoting is disabled
709710

711+
private final String quotedNullString;
712+
710713
private final QuoteMode quoteMode;
711714

712715
private final String recordSeparator; // for outputs
@@ -759,11 +762,11 @@ public static CSVFormat valueOf(final String format) {
759762
* if the delimiter is a line break character
760763
*/
761764
private CSVFormat(final char delimiter, final Character quoteChar, final QuoteMode quoteMode,
762-
final Character commentStart, final Character escape, final boolean ignoreSurroundingSpaces,
763-
final boolean ignoreEmptyLines, final String recordSeparator, final String nullString,
764-
final Object[] headerComments, final String[] header, final boolean skipHeaderRecord,
765-
final boolean allowMissingColumnNames, final boolean ignoreHeaderCase, final boolean trim,
766-
final boolean trailingDelimiter, final boolean autoFlush) {
765+
final Character commentStart, final Character escape, final boolean ignoreSurroundingSpaces,
766+
final boolean ignoreEmptyLines, final String recordSeparator, final String nullString,
767+
final Object[] headerComments, final String[] header, final boolean skipHeaderRecord,
768+
final boolean allowMissingColumnNames, final boolean ignoreHeaderCase, final boolean trim,
769+
final boolean trailingDelimiter, final boolean autoFlush) {
767770
this.delimiter = delimiter;
768771
this.quoteCharacter = quoteChar;
769772
this.quoteMode = quoteMode;
@@ -781,6 +784,7 @@ private CSVFormat(final char delimiter, final Character quoteChar, final QuoteMo
781784
this.trailingDelimiter = trailingDelimiter;
782785
this.trim = trim;
783786
this.autoFlush = autoFlush;
787+
this.quotedNullString = quoteCharacter + nullString + quoteCharacter;
784788
validate();
785789
}
786790

@@ -1172,32 +1176,41 @@ public void print(final Object value, final Appendable out, final boolean newRec
11721176
charSequence = EMPTY;
11731177
} else {
11741178
if (QuoteMode.ALL == quoteMode) {
1175-
charSequence = quoteCharacter + nullString + quoteCharacter;
1179+
charSequence = quotedNullString;
11761180
} else {
11771181
charSequence = nullString;
11781182
}
11791183
}
11801184
} else {
1181-
charSequence = value instanceof CharSequence ? (CharSequence) value : value.toString();
1185+
if (value instanceof CharSequence) {
1186+
charSequence = (CharSequence) value;
1187+
} else if (value instanceof Reader) {
1188+
print((Reader) value, out, newRecord);
1189+
return;
1190+
} else {
1191+
charSequence = value.toString();
1192+
}
11821193
}
11831194
charSequence = getTrim() ? trim(charSequence) : charSequence;
1184-
this.print(value, charSequence, 0, charSequence.length(), out, newRecord);
1195+
print(value, charSequence, out, newRecord);
11851196
}
11861197

1187-
private void print(final Object object, final CharSequence value, final int offset, final int len,
1188-
final Appendable out, final boolean newRecord) throws IOException {
1198+
private void print(final Object object, final CharSequence value, final Appendable out, final boolean newRecord)
1199+
throws IOException {
1200+
final int offset = 0;
1201+
final int len = value.length();
11891202
if (!newRecord) {
11901203
out.append(getDelimiter());
11911204
}
11921205
if (object == null) {
11931206
out.append(value);
11941207
} else if (isQuoteCharacterSet()) {
11951208
// the original object is needed so can check for Number
1196-
printAndQuote(object, value, offset, len, out, newRecord);
1209+
printWithQuotes(object, value, out, newRecord);
11971210
} else if (isEscapeCharacterSet()) {
1198-
printAndEscape(value, offset, len, out);
1211+
printWithEscapes(value, out);
11991212
} else {
1200-
out.append(value, offset, offset + len);
1213+
out.append(value, offset, len);
12011214
}
12021215
}
12031216

@@ -1221,14 +1234,90 @@ public CSVPrinter print(final Path out, final Charset charset) throws IOExceptio
12211234
return print(Files.newBufferedWriter(out, charset));
12221235
}
12231236

1237+
private void print(final Reader reader, final Appendable out, final boolean newRecord) throws IOException {
1238+
// Reader is never null
1239+
if (!newRecord) {
1240+
out.append(getDelimiter());
1241+
}
1242+
if (isQuoteCharacterSet()) {
1243+
// the original object is needed so can check for Number
1244+
printWithQuotes(reader, out, newRecord);
1245+
} else if (isEscapeCharacterSet()) {
1246+
printWithEscapes(reader, out);
1247+
} else if (out instanceof Writer) {
1248+
IOUtils.copyLarge(reader, (Writer) out);
1249+
} else {
1250+
IOUtils.copy(reader, out);
1251+
}
1252+
1253+
}
1254+
1255+
/**
1256+
* Prints to the {@link System#out}.
1257+
*
1258+
* <p>
1259+
* See also {@link CSVPrinter}.
1260+
* </p>
1261+
*
1262+
* @return a printer to {@link System#out}.
1263+
* @throws IOException
1264+
* thrown if the optional header cannot be printed.
1265+
* @since 1.5
1266+
*/
1267+
public CSVPrinter printer() throws IOException {
1268+
return new CSVPrinter(System.out, this);
1269+
}
1270+
1271+
/**
1272+
* Outputs the trailing delimiter (if set) followed by the record separator (if set).
1273+
*
1274+
* @param out
1275+
* where to write
1276+
* @throws IOException
1277+
* If an I/O error occurs
1278+
* @since 1.4
1279+
*/
1280+
public void println(final Appendable out) throws IOException {
1281+
if (getTrailingDelimiter()) {
1282+
out.append(getDelimiter());
1283+
}
1284+
if (recordSeparator != null) {
1285+
out.append(recordSeparator);
1286+
}
1287+
}
1288+
1289+
/**
1290+
* Prints the given {@code values} to {@code out} as a single record of delimiter separated values followed by the
1291+
* record separator.
1292+
*
1293+
* <p>
1294+
* The values will be quoted if needed. Quotes and new-line characters will be escaped. This method adds the record
1295+
* separator to the output after printing the record, so there is no need to call {@link #println(Appendable)}.
1296+
* </p>
1297+
*
1298+
* @param out
1299+
* where to write.
1300+
* @param values
1301+
* values to output.
1302+
* @throws IOException
1303+
* If an I/O error occurs.
1304+
* @since 1.4
1305+
*/
1306+
public void printRecord(final Appendable out, final Object... values) throws IOException {
1307+
for (int i = 0; i < values.length; i++) {
1308+
print(values[i], out, i == 0);
1309+
}
1310+
println(out);
1311+
}
1312+
12241313
/*
12251314
* Note: must only be called if escaping is enabled, otherwise will generate NPE
12261315
*/
1227-
private void printAndEscape(final CharSequence value, final int offset, final int len, final Appendable out)
1228-
throws IOException {
1229-
int start = offset;
1230-
int pos = offset;
1231-
final int end = offset + len;
1316+
private void printWithEscapes(final CharSequence value, final Appendable out) throws IOException {
1317+
int start = 0;
1318+
int pos = 0;
1319+
final int len = value.length();
1320+
final int end = len;
12321321

12331322
final char delim = getDelimiter();
12341323
final char escape = getEscapeCharacter().charValue();
@@ -1251,7 +1340,6 @@ private void printAndEscape(final CharSequence value, final int offset, final in
12511340

12521341
start = pos + 1; // start on the current char after this one
12531342
}
1254-
12551343
pos++;
12561344
}
12571345

@@ -1261,16 +1349,54 @@ private void printAndEscape(final CharSequence value, final int offset, final in
12611349
}
12621350
}
12631351

1352+
private void printWithEscapes(final Reader reader, final Appendable out) throws IOException {
1353+
int start = 0;
1354+
int pos = 0;
1355+
1356+
final char delim = getDelimiter();
1357+
final char escape = getEscapeCharacter().charValue();
1358+
final StringBuilder builder = new StringBuilder(IOUtils.DEFAULT_BUFFER_SIZE);
1359+
1360+
int c;
1361+
while (-1 != (c = reader.read())) {
1362+
builder.append((char) c);
1363+
if (c == CR || c == LF || c == delim || c == escape) {
1364+
// write out segment up until this char
1365+
if (pos > start) {
1366+
out.append(builder.substring(start, pos));
1367+
builder.setLength(0);
1368+
}
1369+
if (c == LF) {
1370+
c = 'n';
1371+
} else if (c == CR) {
1372+
c = 'r';
1373+
}
1374+
1375+
out.append(escape);
1376+
out.append((char) c);
1377+
1378+
start = pos + 1; // start on the current char after this one
1379+
}
1380+
pos++;
1381+
}
1382+
1383+
// write last segment
1384+
if (pos > start) {
1385+
out.append(builder.substring(start, pos));
1386+
}
1387+
}
1388+
12641389
/*
12651390
* Note: must only be called if quoting is enabled, otherwise will generate NPE
12661391
*/
12671392
// the original object is needed so can check for Number
1268-
private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len,
1269-
final Appendable out, final boolean newRecord) throws IOException {
1393+
private void printWithQuotes(final Object object, final CharSequence value, final Appendable out,
1394+
final boolean newRecord) throws IOException {
12701395
boolean quote = false;
1271-
int start = offset;
1272-
int pos = offset;
1273-
final int end = offset + len;
1396+
int start = 0;
1397+
int pos = 0;
1398+
final int len = value.length();
1399+
final int end = len;
12741400

12751401
final char delimChar = getDelimiter();
12761402
final char quoteChar = getQuoteCharacter().charValue();
@@ -1289,7 +1415,7 @@ private void printAndQuote(final Object object, final CharSequence value, final
12891415
break;
12901416
case NONE:
12911417
// Use the existing escaping code
1292-
printAndEscape(value, offset, len, out);
1418+
printWithEscapes(value, out);
12931419
return;
12941420
case MINIMAL:
12951421
if (len <= 0) {
@@ -1371,61 +1497,21 @@ private void printAndQuote(final Object object, final CharSequence value, final
13711497
}
13721498

13731499
/**
1374-
* Prints to the {@link System#out}.
1375-
*
1376-
* <p>
1377-
* See also {@link CSVPrinter}.
1378-
* </p>
1500+
* Always use quotes unless QuoteMode is NONE, so we not have to look ahead.
13791501
*
1380-
* @return a printer to {@link System#out}.
13811502
* @throws IOException
1382-
* thrown if the optional header cannot be printed.
1383-
* @since 1.5
13841503
*/
1385-
public CSVPrinter printer() throws IOException {
1386-
return new CSVPrinter(System.out, this);
1387-
}
1504+
private void printWithQuotes(final Reader reader, final Appendable out, final boolean newRecord) throws IOException {
1505+
final char quoteChar = getQuoteCharacter().charValue();
13881506

1389-
/**
1390-
* Outputs the trailing delimiter (if set) followed by the record separator (if set).
1391-
*
1392-
* @param out
1393-
* where to write
1394-
* @throws IOException
1395-
* If an I/O error occurs
1396-
* @since 1.4
1397-
*/
1398-
public void println(final Appendable out) throws IOException {
1399-
if (getTrailingDelimiter()) {
1400-
out.append(getDelimiter());
1401-
}
1402-
if (recordSeparator != null) {
1403-
out.append(recordSeparator);
1507+
if (getQuoteMode() == QuoteMode.NONE) {
1508+
printWithEscapes(reader, out);
1509+
return;
14041510
}
1405-
}
14061511

1407-
/**
1408-
* Prints the given {@code values} to {@code out} as a single record of delimiter separated values followed by the
1409-
* record separator.
1410-
*
1411-
* <p>
1412-
* The values will be quoted if needed. Quotes and new-line characters will be escaped. This method adds the record
1413-
* separator to the output after printing the record, so there is no need to call {@link #println(Appendable)}.
1414-
* </p>
1415-
*
1416-
* @param out
1417-
* where to write.
1418-
* @param values
1419-
* values to output.
1420-
* @throws IOException
1421-
* If an I/O error occurs.
1422-
* @since 1.4
1423-
*/
1424-
public void printRecord(final Appendable out, final Object... values) throws IOException {
1425-
for (int i = 0; i < values.length; i++) {
1426-
print(values[i], out, i == 0);
1427-
}
1428-
println(out);
1512+
out.append(quoteChar);
1513+
IOUtils.copy(reader, out);
1514+
out.append(quoteChar);
14291515
}
14301516

14311517
@Override
@@ -1589,8 +1675,8 @@ public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNam
15891675
*/
15901676
public CSVFormat withAutoFlush(final boolean autoFlush) {
15911677
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
1592-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
1593-
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter, autoFlush);
1678+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
1679+
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter, autoFlush);
15941680
}
15951681

15961682
/**
@@ -1703,6 +1789,7 @@ public CSVFormat withFirstRecordAsHeader() {
17031789
* <p>
17041790
* Example:
17051791
* </p>
1792+
*
17061793
* <pre>
17071794
* public enum Header {
17081795
* Name, Email, Phone

0 commit comments

Comments
 (0)