Skip to content

Commit

Permalink
Merge branch 'release-1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
charphi committed Apr 15, 2020
2 parents bd9bfbc + a655275 commit eb19575
Show file tree
Hide file tree
Showing 7 changed files with 353 additions and 49 deletions.
78 changes: 67 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>be.nbb.rd</groupId>
<artifactId>picocsv</artifactId>
<version>1.1.0</version>
<version>1.2.0</version>
<packaging>jar</packaging>

<name>picocsv</name>
Expand Down Expand Up @@ -32,13 +32,13 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.14.0</version>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -98,8 +98,8 @@
</property>
</activation>
<properties>
<lombok.version>1.18.10</lombok.version>
<jmh.version>1.22</jmh.version>
<lombok.version>1.18.12</lombok.version>
<jmh.version>1.23</jmh.version>
</properties>
<dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -214,18 +214,60 @@
</build>
</profile>

<!-- Optional checks -->
<!-- Enforce dependency rules -->
<profile>
<id>optional-checks</id>
<id>enforce-dependency-rules</id>
<activation>
<property>
<name>!skipEnforceDependencyRules</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<requireMavenVersion>
<version>3.3.9</version>
</requireMavenVersion>
<DependencyConvergence/>
<requireReleaseDeps>
<onlyWhenRelease>true</onlyWhenRelease>
</requireReleaseDeps>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

<!-- Enforce modern API -->
<profile>
<id>enforce-modern-api</id>
<activation>
<property>
<name>!skipEnforceModernAPI</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>2.0.0</version>
<version>2.1.0</version>
<configuration>
<javaVersion>1.8</javaVersion>
<!--<failOnViolations>false</failOnViolations>-->
</configuration>
<executions>
<execution>
Expand All @@ -237,6 +279,20 @@
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

<!-- Enforce code coverage -->
<profile>
<id>enforce-code-coverage</id>
<activation>
<property>
<name>!skipEnforceCodeCoverage</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
Expand Down Expand Up @@ -279,7 +335,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>verify</phase>
Expand Down Expand Up @@ -312,7 +368,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>verify</phase>
Expand Down
173 changes: 156 additions & 17 deletions src/main/java/nbbrd/picocsv/Csv.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,64 @@ public Format build() {
}
}

/**
* Specifies the reader options.
*/
public static final class Parsing {

public static final Parsing STRICT = new Parsing(false);
public static final Parsing LENIENT = new Parsing(true);

private final boolean lenientSeparator;

private Parsing(boolean lenientSeparator) {
this.lenientSeparator = lenientSeparator;
}

/**
* Determine if the separator is parsed leniently or not. If set to
* true, the reader follows the same behavior as BufferedReader: <i>a
* line is considered to be terminated by any one of a line feed ('\n'),
* a carriage return ('\r'), a carriage return followed immediately by a
* line feed, or by reaching the end-of-file (EOF)</i>.
*
* @return true if lenient parsing of separator, false otherwise
*/
public boolean isLenientSeparator() {
return lenientSeparator;
}

@Override
public int hashCode() {
int hash = 7;
hash = 37 * hash + Boolean.hashCode(lenientSeparator);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Parsing other = (Parsing) obj;
if (this.lenientSeparator != other.lenientSeparator) {
return false;
}
return true;
}

@Override
public String toString() {
return "ReaderOptions{" + "lenientSeparator=" + lenientSeparator + '}';
}
}

/**
* Reads CSV files.
*/
Expand All @@ -238,17 +296,34 @@ public static final class Reader implements Closeable, CharSequence {
* @throws IOException if an I/O error occurs
*/
public static Reader of(Path file, Charset encoding, Format format) throws IllegalArgumentException, IOException {
return of(file, encoding, format, Parsing.STRICT);
}

/**
* Creates a new instance from a file.
*
* @param file a non-null file
* @param encoding a non-null encoding
* @param format a non-null format
* @param options non-null options
* @return a new CSV reader
* @throws IllegalArgumentException if the format contains an invalid
* combination of options
* @throws IOException if an I/O error occurs
*/
public static Reader of(Path file, Charset encoding, Format format, Parsing options) throws IllegalArgumentException, IOException {
Objects.requireNonNull(file, "file");
Objects.requireNonNull(encoding, "encoding");
Objects.requireNonNull(format, "format");
Objects.requireNonNull(options, "options");

if (!format.isValid()) {
throw new IllegalArgumentException("format");
}

CharsetDecoder decoder = encoding.newDecoder();
BufferSizes sizes = BufferSizes.of(file, decoder);
return make(format, sizes.chars, newCharReader(file, decoder, sizes.bytes));
return make(format, sizes.chars, newCharReader(file, decoder, sizes.bytes), options);
}

/**
Expand All @@ -263,17 +338,34 @@ public static Reader of(Path file, Charset encoding, Format format) throws Illeg
* @throws IOException if an I/O error occurs
*/
public static Reader of(InputStream stream, Charset encoding, Format format) throws IllegalArgumentException, IOException {
return of(stream, encoding, format, Parsing.STRICT);
}

/**
* Creates a new instance from a stream.
*
* @param stream a non-null stream
* @param encoding a non-null encoding
* @param format a non-null format
* @param options non-null options
* @return a new CSV reader
* @throws IllegalArgumentException if the format contains an invalid
* combination of options
* @throws IOException if an I/O error occurs
*/
public static Reader of(InputStream stream, Charset encoding, Format format, Parsing options) throws IllegalArgumentException, IOException {
Objects.requireNonNull(stream, "stream");
Objects.requireNonNull(encoding, "encoding");
Objects.requireNonNull(format, "format");
Objects.requireNonNull(options, "options");

if (!format.isValid()) {
throw new IllegalArgumentException("format");
}

CharsetDecoder decoder = encoding.newDecoder();
BufferSizes sizes = BufferSizes.of(stream, decoder);
return make(format, sizes.chars, new InputStreamReader(stream, decoder));
return make(format, sizes.chars, new InputStreamReader(stream, decoder), options);
}

/**
Expand All @@ -287,23 +379,39 @@ public static Reader of(InputStream stream, Charset encoding, Format format) thr
* @throws IOException if an I/O error occurs
*/
public static Reader of(java.io.Reader charReader, Format format) throws IllegalArgumentException, IOException {
return of(charReader, format, Parsing.STRICT);
}

/**
* Creates a new instance from a char reader.
*
* @param charReader a non-null char reader
* @param format a non-null format
* @param options non-null options
* @return a new CSV reader
* @throws IllegalArgumentException if the format contains an invalid
* combination of options
* @throws IOException if an I/O error occurs
*/
public static Reader of(java.io.Reader charReader, Format format, Parsing options) throws IllegalArgumentException, IOException {
Objects.requireNonNull(charReader, "charReader");
Objects.requireNonNull(format, "format");
Objects.requireNonNull(options, "options");

if (!format.isValid()) {
throw new IllegalArgumentException("format");
}

BufferSizes sizes = BufferSizes.EMPTY;
return make(format, sizes.chars, charReader);
return make(format, sizes.chars, charReader, options);
}

private static Reader make(Format format, OptionalInt charBufferSize, java.io.Reader charReader) {
private static Reader make(Format format, OptionalInt charBufferSize, java.io.Reader charReader, Parsing options) {
int size = BufferSizes.getSize(charBufferSize, BufferSizes.DEFAULT_CHAR_BUFFER_SIZE);
return new Reader(
format.getSeparator() == NewLine.WINDOWS ? new ReadAheadInput(charReader, size) : new Input(charReader, size),
ReadAheadInput.isNeeded(format, options) ? new ReadAheadInput(charReader, size) : new Input(charReader, size),
format.getQuote(), format.getDelimiter(),
EndOfLineReader.of(format.getSeparator()));
EndOfLineReader.of(format, options));
}

private final Input input;
Expand Down Expand Up @@ -543,6 +651,10 @@ public int read() throws IOException {

private static final class ReadAheadInput extends Input {

static boolean isNeeded(Format format, Parsing options) {
return options.isLenientSeparator() || format.getSeparator() == NewLine.WINDOWS;
}

private static final int NULL = -2;
private int readAhead;

Expand Down Expand Up @@ -578,24 +690,51 @@ private interface EndOfLineReader {
static final int CR_CODE = NewLine.CR;
static final int LF_CODE = NewLine.LF;

static EndOfLineReader of(NewLine newLine) {
switch (newLine) {
static EndOfLineReader of(Format format, Parsing options) {
if (options.isLenientSeparator()) {
return EndOfLineReader::isLenient;
}
switch (format.getSeparator()) {
case MACINTOSH:
return (code, input) -> code == CR_CODE;
return EndOfLineReader::isMacintosh;
case UNIX:
return (code, input) -> code == LF_CODE;
return EndOfLineReader::isUnix;
case WINDOWS:
return (code, input) -> {
if (code == CR_CODE && ((ReadAheadInput) input).peek(LF_CODE)) {
((ReadAheadInput) input).discardAheadOfTimeChar();
return true;
}
return false;
};
return EndOfLineReader::isWindows;
default:
throw new RuntimeException();
}
}

static boolean isLenient(int code, Input input) throws IOException {
switch (code) {
case LF_CODE:
return true;
case CR_CODE:
if (((ReadAheadInput) input).peek(LF_CODE)) {
((ReadAheadInput) input).discardAheadOfTimeChar();
}
return true;
default:
return false;
}
}

static boolean isMacintosh(int code, Input input) throws IOException {
return code == CR_CODE;
}

static boolean isUnix(int code, Input input) throws IOException {
return code == LF_CODE;
}

static boolean isWindows(int code, Input input) throws IOException {
if (code == CR_CODE && ((ReadAheadInput) input).peek(LF_CODE)) {
((ReadAheadInput) input).discardAheadOfTimeChar();
return true;
}
return false;
}
}

private static java.io.Reader newCharReader(Path file, CharsetDecoder decoder, OptionalInt byteBufferSize) throws IOException {
Expand Down
Loading

0 comments on commit eb19575

Please sign in to comment.