Skip to content

Commit 74bcb35

Browse files
committed
Annotations for CSV version 1.9.0
1 parent aa5b3bc commit 74bcb35

7 files changed

Lines changed: 49 additions & 40 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ buildNumber.properties
1414
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
1515
.mvn/wrapper/maven-wrapper.jar
1616

17+
1718
.classpath
1819
.project
1920
.settings/

pom.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@
312312
</annotationProcessors>
313313
<compilerArgs>
314314
<arg>-J-Xbootclasspath/p:${errorProneJavac}</arg>
315-
<arg>-Xbootclasspath/p:${annotatedJdk}</arg>
316315
<!-- -Awarns turns type-checking warnings into errors. -->
317316
<!-- <arg>-Awarns</arg> -->
318317
</compilerArgs>
@@ -649,7 +648,6 @@
649648
<configuration>
650649
<fork>true</fork>
651650
<compilerArgs combine.children="append">
652-
<arg>-Xbootclasspath/p:${annotatedJdk}</arg>
653651
<arg>-J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar</arg>
654652
</compilerArgs>
655653
</configuration>

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

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.commons.csv;
1919

20+
import org.checkerframework.checker.nullness.qual.PolyNull;
2021
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
2122
import org.checkerframework.checker.nullness.qual.AssertNonNullIfNonNull;
2223
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
@@ -203,31 +204,31 @@ public static Builder create(final CSVFormat csvFormat) {
203204

204205
private boolean autoFlush;
205206

206-
private Character commentMarker;
207+
private @Nullable Character commentMarker;
207208

208209
private String delimiter;
209210

210-
private Character escapeCharacter;
211+
private @Nullable Character escapeCharacter;
211212

212-
private String[] headerComments;
213+
private @Nullable String @Nullable [] headerComments;
213214

214-
private String[] headers;
215+
private String @Nullable[] headers;
215216

216217
private boolean ignoreEmptyLines;
217218

218219
private boolean ignoreHeaderCase;
219220

220221
private boolean ignoreSurroundingSpaces;
221222

222-
private String nullString;
223+
private @Nullable String nullString;
223224

224-
private Character quoteCharacter;
225+
private @Nullable Character quoteCharacter;
225226

226227
private String quotedNullString;
227228

228-
private QuoteMode quoteMode;
229+
private @Nullable QuoteMode quoteMode;
229230

230-
private String recordSeparator;
231+
private @Nullable String recordSeparator;
231232

232233
private boolean skipHeaderRecord;
233234

@@ -324,7 +325,7 @@ public Builder setCommentMarker(final char commentMarker) {
324325
* @return This instance.
325326
* @throws IllegalArgumentException thrown if the specified character is a line break
326327
*/
327-
public Builder setCommentMarker(final Character commentMarker) {
328+
public Builder setCommentMarker(final @Nullable Character commentMarker) {
328329
if (isLineBreak(commentMarker)) {
329330
throw new IllegalArgumentException("The comment start marker character cannot be a line break");
330331
}
@@ -375,7 +376,7 @@ public Builder setEscape(final char escapeCharacter) {
375376
* @return This instance.
376377
* @throws IllegalArgumentException thrown if the specified character is a line break
377378
*/
378-
public Builder setEscape(final Character escapeCharacter) {
379+
public Builder setEscape(final @Nullable Character escapeCharacter) {
379380
if (isLineBreak(escapeCharacter)) {
380381
throw new IllegalArgumentException("The escape character cannot be a line break");
381382
}
@@ -404,10 +405,11 @@ public Builder setEscape(final Character escapeCharacter) {
404405
* @param headerEnum the enum defining the header, {@code null} if disabled, empty if parsed automatically, user specified otherwise.
405406
* @return This instance.
406407
*/
407-
public Builder setHeader(final Class<? extends Enum<?>> headerEnum) {
408+
public Builder setHeader(final @Nullable Class<? extends Enum<?>> headerEnum) {
408409
String[] header = null;
409410
if (headerEnum != null) {
410-
final Enum<?>[] enumValues = headerEnum.getEnumConstants();
411+
@SuppressWarnings({"dereference.of.nullable", "assignment"}) // headerEnum is an enum, so getEnumConstants is non-null
412+
final Enum<?> @NonNull [] enumValues = headerEnum.getEnumConstants();
411413
header = new String[enumValues.length];
412414
for (int i = 0; i < enumValues.length; i++) {
413415
header[i] = enumValues[i].name();
@@ -436,7 +438,7 @@ public Builder setHeader(final Class<? extends Enum<?>> headerEnum) {
436438
* @return This instance.
437439
* @throws SQLException SQLException if a database access error occurs or this method is called on a closed result set.
438440
*/
439-
public Builder setHeader(final ResultSet resultSet) throws SQLException {
441+
public Builder setHeader(final @Nullable ResultSet resultSet) throws SQLException {
440442
return setHeader(resultSet != null ? resultSet.getMetaData() : null);
441443
}
442444

@@ -460,7 +462,7 @@ public Builder setHeader(final ResultSet resultSet) throws SQLException {
460462
* @return This instance.
461463
* @throws SQLException SQLException if a database access error occurs or this method is called on a closed result set.
462464
*/
463-
public Builder setHeader(final ResultSetMetaData resultSetMetaData) throws SQLException {
465+
public Builder setHeader(final @Nullable ResultSetMetaData resultSetMetaData) throws SQLException {
464466
String[] labels = null;
465467
if (resultSetMetaData != null) {
466468
final int columnCount = resultSetMetaData.getColumnCount();
@@ -491,7 +493,7 @@ public Builder setHeader(final ResultSetMetaData resultSetMetaData) throws SQLEx
491493
* @param header the header, {@code null} if disabled, empty if parsed automatically, user specified otherwise.
492494
* @return This instance.
493495
*/
494-
public Builder setHeader(final String... header) {
496+
public Builder setHeader(final String @Nullable ... header) {
495497
this.headers = CSVFormat.clone(header);
496498
return this;
497499
}
@@ -506,7 +508,7 @@ public Builder setHeader(final String... header) {
506508
* @param headerComments the headerComments which will be printed by the Printer before the actual CSV data.
507509
* @return This instance.
508510
*/
509-
public Builder setHeaderComments(final Object... headerComments) {
511+
public Builder setHeaderComments(final @Nullable Object @Nullable ... headerComments) {
510512
this.headerComments = CSVFormat.clone(toStringArray(headerComments));
511513
return this;
512514
}
@@ -521,7 +523,7 @@ public Builder setHeaderComments(final Object... headerComments) {
521523
* @param headerComments the headerComments which will be printed by the Printer before the actual CSV data.
522524
* @return This instance.
523525
*/
524-
public Builder setHeaderComments(final String... headerComments) {
526+
public Builder setHeaderComments(final @Nullable String @Nullable ... headerComments) {
525527
this.headerComments = CSVFormat.clone(headerComments);
526528
return this;
527529
}
@@ -572,7 +574,7 @@ public Builder setIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces)
572574
* @param nullString the String to convert to and from {@code null}. No substitution occurs if {@code null}.
573575
* @return This instance.
574576
*/
575-
public Builder setNullString(final String nullString) {
577+
public Builder setNullString(final @Nullable String nullString) {
576578
this.nullString = nullString;
577579
this.quotedNullString = quoteCharacter + nullString + quoteCharacter;
578580
return this;
@@ -595,7 +597,7 @@ public Builder setQuote(final char quoteCharacter) {
595597
* @param quoteCharacter the quote character, use {@code null} to disable.
596598
* @return This instance.
597599
*/
598-
public Builder setQuote(final Character quoteCharacter) {
600+
public Builder setQuote(final @Nullable Character quoteCharacter) {
599601
if (isLineBreak(quoteCharacter)) {
600602
throw new IllegalArgumentException("The quoteChar cannot be a line break");
601603
}
@@ -1161,7 +1163,7 @@ public CSVFormat getFormat() {
11611163
* @return the cloned array.
11621164
*/
11631165
@SafeVarargs
1164-
static <T> T[] clone(final T... values) {
1166+
static <T> T @PolyNull [] clone(final T @PolyNull ... values) {
11651167
return values == null ? null : values.clone();
11661168
}
11671169

@@ -1231,7 +1233,8 @@ public static CSVFormat newFormat(final char delimiter) {
12311233
true);
12321234
}
12331235

1234-
private @Nullable String @Nullable [] toStringArray(@UnknownInitialization(java.lang.Object.class) @NonNull CSVFormat this, final @Nullable Object @Nullable [] values) {
1236+
@SuppressWarnings("nullness:argument") // Objects.toString(..., ...) needs @PolyNull (has it after CF 3.17.0)
1237+
static @Nullable String @PolyNull [] toStringArray(final @Nullable Object @PolyNull [] values) {
12351238
if (values == null) {
12361239
return null;
12371240
}
@@ -1284,7 +1287,7 @@ public static CSVFormat valueOf(final String format) {
12841287

12851288
private final String @MonotonicNonNull [] header; // array of header column names
12861289

1287-
private final @Nullable String @MonotonicNonNull [] headerComments; // array of header comment lines
1290+
private final @Nullable String @Nullable [] headerComments; // array of header comment lines
12881291

12891292
private final boolean ignoreEmptyLines;
12901293

@@ -1353,7 +1356,7 @@ private CSVFormat(final Builder builder) {
13531356
* @param autoFlush TODO Doc me.
13541357
* @throws IllegalArgumentException if the delimiter is a line break character.
13551358
*/
1356-
@SuppressWarnings("nullness:assignment.type.incompatible") // initializing @MonotonicNonNull: https://github.com/typetools/checker-framework/issues/2215
1359+
@SuppressWarnings("nullness:assignment") // initializing @MonotonicNonNull: https://github.com/typetools/checker-framework/issues/2215
13571360
private CSVFormat(final String delimiter, final @Nullable Character quoteChar, final @Nullable QuoteMode quoteMode,
13581361
final @Nullable Character commentStart, final @Nullable Character escape, final boolean ignoreSurroundingSpaces,
13591362
final boolean ignoreEmptyLines, final @Nullable String recordSeparator, final @Nullable String nullString,
@@ -1514,6 +1517,7 @@ public char getDelimiter() {
15141517
*
15151518
* @return the delimiter.
15161519
*/
1520+
@Pure
15171521
public String getDelimiterString() {
15181522
return delimiter;
15191523
}
@@ -1547,7 +1551,7 @@ public String getDelimiterString() {
15471551
@AssertNonNullIfNonNull("headerComments")
15481552
@Pure // not actually pure, but pure up to .equals, and this suppresses a warning elsewhere
15491553
@SuppressWarnings("nullness") // return @MonotonicNonNull field: https://github.com/typetools/checker-framework/issues/2216
1550-
public @Nullable String @MonotonicNonNull [] getHeaderComments() {
1554+
public @Nullable String @Nullable [] getHeaderComments() {
15511555
return headerComments != null ? headerComments.clone() : null;
15521556
}
15531557

@@ -1873,7 +1877,7 @@ public CSVPrinter print(final Path out, final Charset charset) throws IOExceptio
18731877
return print(Files.newBufferedWriter(out, charset));
18741878
}
18751879

1876-
@SuppressWarnings("contracts.precondition.not.satisfied") // Maybe a bug in commmons-csv:
1880+
@SuppressWarnings("contracts.precondition") // Maybe a bug in commmons-csv:
18771881
// if quoteCharacter==NONE and isEscapeCharacterSet()==false, an exception is thrown.
18781882
private void print(final Reader reader, final Appendable out, final boolean newRecord) throws IOException {
18791883
// Reader is never null
@@ -2588,7 +2592,7 @@ public CSVFormat withHeader(final String @Nullable ... header) {
25882592
* @deprecated Use {@link Builder#setHeaderComments(Object...)}
25892593
*/
25902594
@Deprecated
2591-
public CSVFormat withHeaderComments(final Object... headerComments) {
2595+
public CSVFormat withHeaderComments(final @Nullable Object @Nullable ... headerComments) {
25922596
return builder().setHeaderComments(headerComments).build();
25932597
}
25942598

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public final class CSVParser implements Iterable<CSVRecord>, Closeable {
148148
class CSVRecordIterator implements Iterator<CSVRecord> {
149149
private @Nullable CSVRecord current;
150150

151-
@SuppressWarnings("contracts.precondition.not.satisfied") // TODO: how to express @RequiresNonNull({"CSVParser.this.format"})
151+
// @SuppressWarnings("contracts.precondition") // TODO: how to express @RequiresNonNull({"CSVParser.this.format"})
152152
private @Nullable CSVRecord getNextRecord() {
153153
try {
154154
return CSVParser.this.nextRecord();
@@ -437,13 +437,14 @@ public CSVParser(final Reader reader, final CSVFormat format, final long charact
437437
this.format = format.copy();
438438
this.lexer = new Lexer(format, new ExtendedBufferedReader(reader));
439439
this.csvRecordIterator = new CSVRecordIterator();
440-
this.headers = createHeaders();
440+
@SuppressWarnings("method.invocation") // receiver is not yet initialized; likely bug; see comment at createHeaders.
441+
Headers _headers = createHeaders();
442+
this.headers = _headers;
441443
this.characterOffset = characterOffset;
442444
this.recordNumber = recordNumber - 1;
443445
}
444446

445-
@RequiresNonNull("format")
446-
private void addRecordValue(@UnknownInitialization(Object.class) CSVParser this, final boolean lastRecord) {
447+
private void addRecordValue(final boolean lastRecord) {
447448
final String input = this.reusableToken.content.toString();
448449
final String inputClean = this.format.getTrim() ? input.trim() : input;
449450
if (lastRecord && inputClean.isEmpty() && this.format.getTrailingDelimiter()) {
@@ -478,8 +479,8 @@ private Map<String, Integer> createEmptyHeaderMap(@UnknownInitialization(Object.
478479
* @return null if the format has no header.
479480
* @throws IOException if there is a problem reading the header or skipping the first record
480481
*/
481-
@RequiresNonNull({"format", "lexer"})
482-
private Headers createHeaders(@UnknownInitialization(Object.class) CSVParser this) throws IOException {
482+
// Possible bug in commons-csv: This is called from the constructor, when characterOffset and recordNumber are not yet set. This method calls nextRecord(), which reads characterOffset.
483+
private Headers createHeaders() throws IOException {
483484
Map<String, Integer> hdrMap = null;
484485
List<String> headerNames = null;
485486
final String[] formatHeader = this.format.getHeader();
@@ -648,7 +649,7 @@ public List<CSVRecord> getRecords() throws IOException {
648649
* the cell data to further processed
649650
* @return null if input is parsed as null, or input itself if input isn't parsed as null
650651
*/
651-
private String handleNull(final String input) {
652+
private @Nullable String handleNull(final String input) {
652653
final boolean isQuoted = this.reusableToken.isQuoted;
653654
final String nullString = format.getNullString();
654655
final boolean strictQuoteMode = isStrictQuoteMode();
@@ -704,8 +705,7 @@ public Iterator<CSVRecord> iterator() {
704705
* @throws IOException
705706
* on parse error or input read-failure
706707
*/
707-
@RequiresNonNull({"lexer", "format"})
708-
@Nullable CSVRecord nextRecord(@UnknownInitialization(Object.class) CSVParser this) throws IOException {
708+
@Nullable CSVRecord nextRecord() throws IOException {
709709
CSVRecord result = null;
710710
this.recordList.clear();
711711
StringBuilder sb = null;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void print(final @Nullable Object value) throws IOException {
191191
* If an I/O error occurs
192192
*/
193193
@SuppressWarnings("nullness") // getCommentMarker() isn't affected by side effects in this method
194-
public void printComment(final String comment) throws IOException {
194+
public void printComment(final @Nullable String comment) throws IOException {
195195
if (comment == null || !format.isCommentMarkerSet()) {
196196
return;
197197
}
@@ -229,6 +229,7 @@ public void printComment(final String comment) throws IOException {
229229
* @throws SQLException If a database access error occurs or this method is called on a closed result set.
230230
* @since 1.9.0
231231
*/
232+
@SuppressWarnings("nullness:argument") // getHeader() returns non-null because setHeader() was called on non-null
232233
public void printHeaders(final ResultSet resultSet) throws IOException, SQLException {
233234
printRecord((Object[]) format.builder().setHeader(resultSet).build().getHeader());
234235
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ public boolean isConsistent() {
227227
* the name of the column to be retrieved.
228228
* @return whether a given column is mapped.
229229
*/
230+
@SuppressWarnings({"expression.unparsable", "flowexpr.parse.error", "contracts.conditional.postcondition"}) // https://github.com/typetools/checker-framework/issues/4858
230231
@EnsuresKeyForIf(expression="#1", map="getHeaderMapRaw()", result=true)
231232
@EnsuresNonNullIf(expression="getHeaderMapRaw()", result=true)
232233
public boolean isMapped(final String name) {
@@ -253,6 +254,7 @@ public boolean isSet(final int index) {
253254
* the name of the column to be retrieved.
254255
* @return whether a given columns is mapped and has a value
255256
*/
257+
@SuppressWarnings({"expression.unparsable", "flowexpr.parse.error.postcondition", "dereference.of.nullable"}) // https://github.com/typetools/checker-framework/issues/4858
256258
public boolean isSet(final String name) {
257259
return isMapped(name) && getHeaderMapRaw().get(name).intValue() < values.length;
258260
}
@@ -275,6 +277,7 @@ public boolean isSet(final String name) {
275277
* @return the given map.
276278
* @since 1.9.0
277279
*/
280+
@SuppressWarnings({"lambda.param", "expression.unparsable"}) // https://github.com/typetools/checker-framework/issues/4858
278281
public <M extends Map<String, @Nullable String>> M putIn(final M map) {
279282
if (getHeaderMapRaw() == null) {
280283
return map;
@@ -303,7 +306,7 @@ public int size() {
303306
* @return the new stream.
304307
* @since 1.9.0
305308
*/
306-
public Stream<String> stream() {
309+
public Stream<@Nullable String> stream() {
307310
return Stream.of(values);
308311
}
309312

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
package org.apache.commons.csv;
1919

20+
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
2021
import org.checkerframework.checker.nullness.qual.Nullable;
2122
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
23+
2224
import static org.apache.commons.csv.Constants.BACKSPACE;
2325
import static org.apache.commons.csv.Constants.CR;
2426
import static org.apache.commons.csv.Constants.END_OF_STREAM;
@@ -200,7 +202,7 @@ boolean isStartOfLine(final int ch) {
200202
return ch == LF || ch == CR || ch == UNDEFINED;
201203
}
202204

203-
private char mapNullToDisabled(final @Nulable Character c) {
205+
private char mapNullToDisabled(@UnknownInitialization Lexer this, final @Nullable Character c) {
204206
return c == null ? DISABLED : c.charValue();
205207
}
206208

0 commit comments

Comments
 (0)