Skip to content
This repository was archived by the owner on Sep 28, 2022. It is now read-only.

Commit 25b056d

Browse files
authored
Merge pull request #6 from scijava/improve-tests
2 parents e09a9e0 + f9f4e9c commit 25b056d

File tree

3 files changed

+187
-170
lines changed

3 files changed

+187
-170
lines changed

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.scijava</groupId>
77
<artifactId>pom-scijava</artifactId>
8-
<version>28.0.0</version>
8+
<version>29.0.0-beta-3</version>
99
<relativePath />
1010
</parent>
1111

@@ -95,6 +95,8 @@ Wisconsin-Madison and University of Konstanz.</license.copyrightOwners>
9595

9696
<!-- NB: Deploy releases to the SciJava Maven repository. -->
9797
<releaseProfiles>deploy-to-scijava</releaseProfiles>
98+
<scijava-table.version>0.6.0</scijava-table.version>
99+
<scijava-optional.version>1.0.0</scijava-optional.version>
98100
</properties>
99101

100102
<dependencies>
@@ -106,6 +108,11 @@ Wisconsin-Madison and University of Konstanz.</license.copyrightOwners>
106108
<groupId>org.scijava</groupId>
107109
<artifactId>scijava-table</artifactId>
108110
</dependency>
111+
<dependency>
112+
<groupId>org.scijava</groupId>
113+
<artifactId>scijava-optional</artifactId>
114+
<version>${scijava-optional.version}</version>
115+
</dependency>
109116

110117
<!-- Test scope dependencies -->
111118
<dependency>

src/main/java/org/scijava/table/DefaultTableIOPlugin.java

Lines changed: 90 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,24 @@
3535
import java.util.ArrayList;
3636
import java.util.Arrays;
3737
import java.util.Collections;
38+
import java.util.HashMap;
3839
import java.util.HashSet;
3940
import java.util.List;
41+
import java.util.Map;
4042
import java.util.Set;
4143
import java.util.function.Function;
42-
import java.util.stream.IntStream;
4344

4445
import org.scijava.Priority;
45-
import org.scijava.io.AbstractIOPlugin;
4646
import org.scijava.io.IOPlugin;
4747
import org.scijava.io.handle.DataHandle;
4848
import org.scijava.io.handle.DataHandleService;
4949
import org.scijava.io.location.Location;
5050
import org.scijava.io.location.LocationService;
5151
import org.scijava.plugin.Parameter;
5252
import org.scijava.plugin.Plugin;
53+
import org.scijava.table.io.ColumnTableIOOptions;
54+
import org.scijava.table.io.TableIOOptions;
55+
import org.scijava.table.io.TableIOPlugin;
5356
import org.scijava.util.FileUtils;
5457

5558
/**
@@ -59,73 +62,20 @@
5962
*/
6063
@SuppressWarnings("rawtypes")
6164
@Plugin(type = IOPlugin.class, priority = Priority.LOW)
62-
public class DefaultTableIOPlugin extends AbstractIOPlugin<Table> {
65+
public class DefaultTableIOPlugin extends TableIOPlugin {
6366

6467
@Parameter
6568
private LocationService locationService;
6669

6770
@Parameter
6871
private DataHandleService dataHandleService;
6972

70-
/** Reads the first row of the input file as column headers. */
71-
@Parameter(required = false)
72-
private boolean readColHeaders = true;
73-
74-
/** Writes column headers to file if there exists at least one. */
75-
@Parameter(required = false)
76-
private boolean writeColHeaders = true;
77-
78-
/** Reads the first column of the input file as row headers. */
79-
@Parameter(required = false)
80-
private boolean readRowHeaders = false;
81-
82-
/** Writes row headers to file if there exists at least one. */
83-
@Parameter(required = false)
84-
private boolean writeRowHeaders = true;
85-
86-
/** Regex pattern that separates cells in each row of the table. */
87-
@Parameter(required = false)
88-
private char separator = ',';
89-
90-
/** End of line when writing to file. */
91-
@Parameter(required = false)
92-
private String eol = System.lineSeparator();
93-
94-
/**
95-
* Quote character used for escaping separator and empty strings. Use two
96-
* consecutive quotes to escape one.
97-
*/
98-
@Parameter(required = false)
99-
private char quote = '"';
100-
101-
/**
102-
* Text that appears at the top left corner when both column and row headers
103-
* present.
104-
*/
105-
@Parameter(required = false)
106-
private String cornerText = "\\";
107-
108-
/**
109-
* Lambda function that converts the string of a cell to an appropriate value.
110-
*/
111-
@Parameter(required = false)
112-
private Function<String, Object> parser = s -> s;
113-
114-
/** Lambda function that convert the cell content to a string. */
115-
@Parameter(required = false)
116-
private Function<Object, String> formatter = o -> o.toString();
117-
11873
// FIXME: The "txt" extension is extremely general and will conflict with
11974
// other plugins. Consider another way to check supportsOpen/Close.
12075
private static final Set<String> SUPPORTED_EXTENSIONS = Collections
12176
.unmodifiableSet(new HashSet<>(Arrays.asList("csv", "txt", "prn", "dif",
12277
"rtf")));
12378

124-
@Override
125-
public Class<Table> getDataType() {
126-
return Table.class;
127-
}
128-
12979
@Override
13080
public boolean supportsOpen(final String source) {
13181
final String ext = FileUtils.getExtension(source).toLowerCase();
@@ -140,21 +90,21 @@ public boolean supportsSave(final String source) {
14090
/**
14191
* Process a given line into a list of tokens.
14292
*/
143-
private ArrayList<String> processRow(final String line) throws IOException {
93+
private ArrayList<String> processRow(final String line, char separator, char quote) throws IOException {
14494
final ArrayList<String> row = new ArrayList<>();
14595
final StringBuilder sb = new StringBuilder();
14696
int idx = 0;
14797
int start = idx;
14898
while (idx < line.length()) {
14999
if (line.charAt(idx) == quote) {
150-
sb.append(line.substring(start, idx));
100+
sb.append(line, start, idx);
151101
boolean quoted = true;
152102
idx++;
153103
start = idx;
154104
// find quoted string
155105
while (idx < line.length()) {
156106
if (line.charAt(idx) == quote) {
157-
sb.append(line.substring(start, idx));
107+
sb.append(line, start, idx);
158108
if (idx + 1 < line.length() && line.charAt(idx + 1) == quote) {
159109
sb.append(quote);
160110
idx += 2;
@@ -177,7 +127,7 @@ private ArrayList<String> processRow(final String line) throws IOException {
177127
}
178128
}
179129
else if (line.charAt(idx) == separator) {
180-
sb.append(line.substring(start, idx));
130+
sb.append(line, start, idx);
181131
row.add(sb.toString());
182132
sb.setLength(0);
183133
idx++;
@@ -187,13 +137,18 @@ else if (line.charAt(idx) == separator) {
187137
idx++;
188138
}
189139
}
190-
sb.append(line.substring(start, idx));
140+
sb.append(line, start, idx);
191141
row.add(sb.toString());
192142
return row;
193143
}
194144

195145
@Override
196-
public GenericTable open(final String source) throws IOException {
146+
public GenericTable open(final String source, TableIOOptions options) throws IOException {
147+
return open(source, options.values);
148+
}
149+
150+
private GenericTable open(final String source, TableIOOptions.Values options) throws IOException {
151+
197152
final Location sourceLocation;
198153
try {
199154
sourceLocation = locationService.resolve(source);
@@ -216,13 +171,18 @@ public GenericTable open(final String source) throws IOException {
216171

217172
final String text = new String(buffer);
218173

174+
final char separator = options.columnDelimiter();
175+
final char quote = options.quote();
176+
boolean readRowHeaders = options.readRowHeaders();
177+
boolean readColHeaders = options.readColumnHeaders();
219178

220179
// split by any line delimiter
221180
final String[] lines = text.split("\\R");
222181
if (lines.length == 0) return table;
223182
// process first line to get number of cols
183+
Map<Integer, Function<String, Object>> columnParsers = new HashMap<>();
224184
{
225-
final ArrayList<String> tokens = processRow(lines[0]);
185+
final ArrayList<String> tokens = processRow(lines[0], separator, quote);
226186
if (readColHeaders) {
227187
final List<String> colHeaders;
228188
if (readRowHeaders) colHeaders = tokens.subList(1, tokens.size());
@@ -243,13 +203,15 @@ public GenericTable open(final String source) throws IOException {
243203
table.appendRow();
244204
}
245205
for (int i = 0; i < cols.size(); i++) {
206+
Function<String, Object> parser = getParser(cols.get(i), i, options);
207+
columnParsers.put(i, parser);
246208
table.set(i, 0, parser.apply(cols.get(i)));
247209
}
248210
}
249211
}
250212
for (int lineNum = 1; lineNum < lines.length; lineNum++) {
251213
final String line = lines[lineNum];
252-
final ArrayList<String> tokens = processRow(line);
214+
final ArrayList<String> tokens = processRow(line, separator, quote);
253215
final List<String> cols;
254216
if (readRowHeaders) {
255217
cols = tokens.subList(1, tokens.size());
@@ -264,17 +226,56 @@ public GenericTable open(final String source) throws IOException {
264226
" is not the same length as the first line.");
265227
}
266228
for (int i = 0; i < cols.size(); i++) {
267-
table.set(i, lineNum - 1, parser.apply(cols.get(i)));
229+
if(lineNum == 1 && readColHeaders) {
230+
columnParsers.put(i, getParser(cols.get(i), i, options));
231+
}
232+
table.set(i, lineNum - 1, columnParsers.get(i).apply(cols.get(i)));
268233
}
269234
}
270235
}
271236
return table;
272237
}
273238

239+
private static Function<String, Object> getParser(String content, int column, TableIOOptions.Values options) {
240+
ColumnTableIOOptions.Values colOptions = options.column(column);
241+
if(colOptions != null) return colOptions.parser();
242+
if(options.guessParser()) return guessParser(content);
243+
return options.parser();
244+
}
245+
246+
static Function<String, Object> guessParser(String content) {
247+
try {
248+
Integer.valueOf(content);
249+
return Integer::valueOf;
250+
} catch(NumberFormatException ignored) {}
251+
try {
252+
Long.valueOf(content);
253+
return Long::valueOf;
254+
} catch(NumberFormatException ignored) {}
255+
try {
256+
Double.valueOf(content);
257+
return Double::valueOf;
258+
} catch(NumberFormatException ignored) {}
259+
if(content.equalsIgnoreCase("true")||content.equalsIgnoreCase("false")) {
260+
return Boolean::valueOf;
261+
}
262+
return String::valueOf;
263+
}
264+
274265
@Override
275266
public void save(final Table table, final String destination)
276-
throws IOException
277-
{
267+
throws IOException {
268+
save(table, destination, new TableIOOptions().values);
269+
}
270+
271+
@Override
272+
public void save(final Table table, final String destination, final TableIOOptions options)
273+
throws IOException {
274+
save(table, destination, options.values);
275+
}
276+
277+
private void save(final Table table, final String destination, final TableIOOptions.Values options)
278+
throws IOException {
278279
final Location dstLocation;
279280
try {
280281
dstLocation = locationService.resolve(destination);
@@ -286,53 +287,52 @@ public void save(final Table table, final String destination)
286287
try (final DataHandle<Location> handle = //
287288
dataHandleService.create(dstLocation))
288289
{
289-
final boolean writeRH = this.writeRowHeaders && //
290-
table.getRowCount() > 0 && //
291-
IntStream.range(0, table.getRowCount()).allMatch(row -> table
292-
.getRowHeader(row) != null);
293-
final boolean writeCH = this.writeColHeaders && //
294-
table.getColumnCount() > 0 && //
295-
IntStream.range(0, table.getColumnCount()).allMatch(col -> table
296-
.getColumnHeader(col) != null);
290+
final boolean writeRH = options.writeRowHeaders();
291+
final boolean writeCH = options.writeColumnHeaders();
292+
final char separator = options.columnDelimiter();
293+
final String eol = options.rowDelimiter();
294+
final char quote = options.quote();
297295

298296
final StringBuilder sb = new StringBuilder();
299297
// write column headers
300298
if (writeCH) {
301299
if (writeRH) {
302-
sb.append(tryQuote(cornerText));
300+
sb.append(tryQuote(options.cornerText(), separator, quote));
303301
if (table.getColumnCount() > 0) {
304302
sb.append(separator);
305-
sb.append(tryQuote(table.getColumnHeader(0)));
303+
sb.append(tryQuote(table.getColumnHeader(0), separator, quote));
306304
}
307305
}
308306
// avoid adding extra separator when there is 0 column
309307
else if (table.getColumnCount() > 0) {
310-
sb.append(tryQuote(table.getColumnHeader(0)));
308+
sb.append(tryQuote(table.getColumnHeader(0), separator, quote));
311309
}
312310
for (int col = 1; col < table.getColumnCount(); col++) {
313311
sb.append(separator);
314-
sb.append(tryQuote(table.getColumnHeader(col)));
312+
sb.append(tryQuote(table.getColumnHeader(col), separator, quote));
315313
}
316314
sb.append(eol);
317315
handle.writeBytes(sb.toString());
318316
sb.setLength(0);
319317
}
320318
// write each row
321319
for (int row = 0; row < table.getRowCount(); row++) {
320+
Function<Object, String> formatter = getFormatter(options, 0);
322321
if (writeRH) {
323-
sb.append(tryQuote(table.getRowHeader(row)));
322+
sb.append(tryQuote(table.getRowHeader(row), separator, quote));
324323
if (table.getColumnCount() > 0) {
325324
sb.append(separator);
326-
sb.append(tryQuote(formatter.apply(table.get(0, row))));
325+
sb.append(tryQuote(formatter.apply(table.get(0, row)), separator, quote));
327326
}
328327
}
329328
// avoid adding extra separator when there is 0 column
330329
else if (table.getColumnCount() > 0) {
331-
sb.append(tryQuote(formatter.apply(table.get(0, row))));
330+
sb.append(tryQuote(formatter.apply(table.get(0, row)), separator, quote));
332331
}
333332
for (int col = 1; col < table.getColumnCount(); col++) {
333+
formatter = getFormatter(options, col);
334334
sb.append(separator);
335-
sb.append(tryQuote(formatter.apply(table.get(col, row))));
335+
sb.append(tryQuote(formatter.apply(table.get(col, row)), separator, quote));
336336
}
337337
sb.append(eol);
338338
handle.writeBytes(sb.toString());
@@ -342,6 +342,12 @@ else if (table.getColumnCount() > 0) {
342342

343343
}
344344

345+
private Function<Object, String> getFormatter(TableIOOptions.Values options, int i) {
346+
ColumnTableIOOptions.Values columnOptions = options.column(i);
347+
if(columnOptions != null) return columnOptions.formatter();
348+
return options.formatter();
349+
}
350+
345351
/**
346352
* Try to quote a string if:
347353
* <li>it is null or empty</li>
@@ -351,7 +357,7 @@ else if (table.getColumnCount() > 0) {
351357
* @param str string to quote
352358
* @return string, possibly quoted
353359
*/
354-
private String tryQuote(final String str) {
360+
private String tryQuote(final String str, char separator, char quote) {
355361
if (str == null || str.length() == 0) return "" + quote + quote;
356362
if (str.indexOf(quote) != -1) return quote + str.replace("" + quote, "" +
357363
quote + quote) + quote;

0 commit comments

Comments
 (0)