From 219bcea236d1ea5c11cd860ed219cb5ea1f2fe5f Mon Sep 17 00:00:00 2001 From: frauzufall Date: Thu, 13 Aug 2020 10:25:35 +0200 Subject: [PATCH] WIP Upgrade to pom-scijava 29 #41 - upgrade pom-scijava to 29.2.1 - upgrade dropwizard from 1.2.0 to 2.0.9 - upgrade jersey-media-multipart from 2.23.2 to 2.31 - use jackson version 2.11.2 - delete DefaultTableIOPlugin, use TableIOService instead - make TableIOPluginTest use new options API for reading and writing tables - use BytesHandle instead of ByteArrayHandle --- pom.xml | 54 ++- .../server/external/DefaultTableIOPlugin.java | 327 ------------------ .../server/resources/ObjectsResource.java | 84 +++-- ...hmark.java => TableIOPluginBenchmark.java} | 30 +- ...PluginTest.java => TableIOPluginTest.java} | 189 +++++----- 5 files changed, 191 insertions(+), 493 deletions(-) delete mode 100644 src/main/java/net/imagej/server/external/DefaultTableIOPlugin.java rename src/test/java/net/imagej/server/{DefaultTableIOPluginBenchmark.java => TableIOPluginBenchmark.java} (82%) rename src/test/java/net/imagej/server/{DefaultTableIOPluginTest.java => TableIOPluginTest.java} (60%) diff --git a/pom.xml b/pom.xml index 1aebc18c..bf6d2da7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.scijava pom-scijava - 29.0.0-beta-2 + 29.2.1 @@ -93,11 +93,16 @@ Wisconsin-Madison. ImageJ server for RESTful access to ImageJ. - 1.2.0 - 2.23.2 + 2.0.9 + 2.11.2 + 2.31 + 0.6.2-frauzufall + 0.3.1-frauzufall + 2.84.0-frauzufall deploy-to-scijava + @@ -121,18 +126,47 @@ Wisconsin-Madison. commons-logging commons-logging + + + javax.servlet + javax.servlet-api + io.dropwizard dropwizard-core ${dropwizard.version} + + + + + + org.glassfish.jersey.media jersey-media-multipart ${jersey-media-multipart.version} + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + junit junit @@ -164,21 +198,17 @@ Wisconsin-Madison. io.dropwizard dropwizard-testing + ${dropwizard.version} org.ow2.asm asm-debug-all + + + + - - - - io.dropwizard - dropwizard-testing - ${dropwizard.version} - - - diff --git a/src/main/java/net/imagej/server/external/DefaultTableIOPlugin.java b/src/main/java/net/imagej/server/external/DefaultTableIOPlugin.java deleted file mode 100644 index 7fac516d..00000000 --- a/src/main/java/net/imagej/server/external/DefaultTableIOPlugin.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * #%L - * ImageJ server for RESTful access to ImageJ. - * %% - * Copyright (C) 2013 - 2016 Board of Regents of the University of - * Wisconsin-Madison. - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -package net.imagej.server.external; - -import io.scif.io.IRandomAccess; -import io.scif.io.VirtualHandle; -import io.scif.services.LocationService; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.IntStream; - -import org.scijava.Priority; -import org.scijava.io.AbstractIOPlugin; -import org.scijava.io.IOPlugin; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; -import org.scijava.table.DefaultGenericTable; -import org.scijava.table.GenericTable; -import org.scijava.util.FileUtils; - -/** - * Plugin for reading/writing {@link GenericTable}s. - * - * @author Leon Yang - */ -@Plugin(type = IOPlugin.class, priority = Priority.LOW) -public class DefaultTableIOPlugin extends AbstractIOPlugin { - - @Parameter - private LocationService locationService; - - /** Reads the first row of the input file as column headers. */ - @Parameter(required = false) - private boolean readColHeaders = true; - - /** Writes column headers to file if there exists at least one. */ - @Parameter(required = false) - private boolean writeColHeaders = true; - - /** Reads the first column of the input file as row headers. */ - @Parameter(required = false) - private boolean readRowHeaders = false; - - /** Writes row headers to file if there exists at least one. */ - @Parameter(required = false) - private boolean writeRowHeaders = true; - - /** Regex pattern that separates cells in each row of the table. */ - @Parameter(required = false) - private char separator = ','; - - /** End of line when writing to file. */ - @Parameter(required = false) - private String eol = System.lineSeparator(); - - /** - * Quote character used for escaping separator and empty strings. Use two - * consecutive quotes to escape one. - */ - @Parameter(required = false) - private char quote = '"'; - - /** - * Text that appears at the top left corner when both column and row headers - * present. - */ - @Parameter(required = false) - private String cornerText = "\\"; - - /** - * Lambda function that converts the string of a cell to an appropriate value. - */ - @Parameter(required = false) - private Function parser = s -> s; - - /** Lambda function that convert the cell content to a string. */ - @Parameter(required = false) - private Function formatter = o -> o.toString(); - - // FIXME: The "txt" extension is extremely general and will conflict with - // other plugins. Consider another way to check supportsOpen/Close. - private static final Set SUPPORTED_EXTENSIONS = Collections - .unmodifiableSet(new HashSet<>(Arrays.asList("csv", "txt", "prn", "dif", - "rtf"))); - - @Override - public Class getDataType() { - return GenericTable.class; - } - - @Override - public boolean supportsOpen(final String source) { - final String ext = FileUtils.getExtension(source).toLowerCase(); - return SUPPORTED_EXTENSIONS.contains(ext); - } - - @Override - public boolean supportsSave(final String source) { - return supportsOpen(source); - } - - /** - * Process a given line into a list of tokens. - */ - private ArrayList processRow(final String line) throws IOException { - final ArrayList row = new ArrayList<>(); - final StringBuilder sb = new StringBuilder(); - int idx = 0; - int start = idx; - while (idx < line.length()) { - if (line.charAt(idx) == quote) { - sb.append(line.substring(start, idx)); - boolean quoted = true; - idx++; - start = idx; - // find quoted string - while (idx < line.length()) { - if (line.charAt(idx) == quote) { - sb.append(line.substring(start, idx)); - if (idx + 1 < line.length() && line.charAt(idx + 1) == quote) { - sb.append(quote); - idx += 2; - start = idx; - } - else { - idx++; - start = idx; - quoted = false; - break; - } - } - else { - idx++; - } - } - if (quoted) { - throw new IOException(String.format( - "Unbalanced quote at position %d: %s", idx, line)); - } - } - else if (line.charAt(idx) == separator) { - sb.append(line.substring(start, idx)); - row.add(sb.toString()); - sb.setLength(0); - idx++; - start = idx; - } - else { - idx++; - } - } - sb.append(line.substring(start, idx)); - row.add(sb.toString()); - return row; - } - - @Override - public GenericTable open(final String source) throws IOException { - final IRandomAccess handle = locationService.getHandle(source); - if (handle instanceof VirtualHandle) { - throw new IOException("Cannot open source"); - } - handle.seek(0); - final byte[] buffer = new byte[(int) handle.length()]; - handle.read(buffer); - final String text = new String(buffer); - - final GenericTable table = new DefaultGenericTable(); - - // split by any line delimiter - final String[] lines = text.split("\\R"); - if (lines.length == 0) return table; - // process first line to get number of cols - { - final ArrayList tokens = processRow(lines[0]); - if (readColHeaders) { - final List colHeaders; - if (readRowHeaders) colHeaders = tokens.subList(1, tokens.size()); - else colHeaders = tokens; - final String[] colHeadersArr = new String[colHeaders.size()]; - table.appendColumns(colHeaders.toArray(colHeadersArr)); - } - else { - final List cols; - if (readRowHeaders) { - cols = tokens.subList(1, tokens.size()); - table.appendColumns(cols.size()); - table.appendRow(tokens.get(0)); - } - else { - cols = tokens; - table.appendColumns(cols.size()); - table.appendRow(); - } - for (int i = 0; i < cols.size(); i++) { - table.set(i, 0, parser.apply(cols.get(i))); - } - } - } - for (int lineNum = 1; lineNum < lines.length; lineNum++) { - final String line = lines[lineNum]; - final ArrayList tokens = processRow(line); - final List cols; - if (readRowHeaders) { - cols = tokens.subList(1, tokens.size()); - table.appendRow(tokens.get(0)); - } - else { - cols = tokens; - table.appendRow(); - } - if (cols.size() != table.getColumnCount()) { - throw new IOException("Line " + table.getRowCount() + - " is not the same length as the first line."); - } - for (int i = 0; i < cols.size(); i++) { - table.set(i, lineNum - 1, parser.apply(cols.get(i))); - } - } - return table; - } - - @Override - public void save(final GenericTable table, final String source) - throws IOException - { - final IRandomAccess handle = locationService.getHandle(source, true); - if (handle instanceof VirtualHandle) { - throw new IOException("Cannot open source"); - } - handle.seek(0); - - final boolean writeRH = this.writeRowHeaders && table.getRowCount() > 0 && - IntStream.range(0, table.getRowCount()).allMatch(row -> table - .getRowHeader(row) != null); - final boolean writeCH = this.writeColHeaders && table - .getColumnCount() > 0 && table.stream().allMatch(col -> col - .getHeader() != null); - - final StringBuilder sb = new StringBuilder(); - // write column headers - if (writeCH) { - if (writeRH) { - sb.append(tryQuote(cornerText)); - if (table.getColumnCount() > 0) { - sb.append(separator); - sb.append(tryQuote(table.getColumnHeader(0))); - } - } - // avoid adding extra separator when there is 0 column - else if (table.getColumnCount() > 0) { - sb.append(tryQuote(table.getColumnHeader(0))); - } - for (int col = 1; col < table.getColumnCount(); col++) { - sb.append(separator); - sb.append(tryQuote(table.getColumnHeader(col))); - } - sb.append(eol); - handle.writeBytes(sb.toString()); - sb.setLength(0); - } - // write each row - for (int row = 0; row < table.getRowCount(); row++) { - if (writeRH) { - sb.append(tryQuote(table.getRowHeader(row))); - if (table.getColumnCount() > 0) { - sb.append(separator); - sb.append(tryQuote(formatter.apply(table.get(0, row)))); - } - } - // avoid adding extra separator when there is 0 column - else if (table.getColumnCount() > 0) { - sb.append(tryQuote(formatter.apply(table.get(0, row)))); - } - for (int col = 1; col < table.getColumnCount(); col++) { - sb.append(separator); - sb.append(tryQuote(formatter.apply(table.get(col, row)))); - } - sb.append(eol); - handle.writeBytes(sb.toString()); - sb.setLength(0); - } - } - - /** - * Try to quote a string if: - *
  • it is null or empty
  • - *
  • it has quotes inside
  • - *
  • it has separators or EOL inside
  • - * - * @param str string to quote - * @return string, possibly quoted - */ - private String tryQuote(final String str) { - if (str == null || str.length() == 0) return "" + quote + quote; - if (str.indexOf(quote) != -1) return quote + str.replace("" + quote, "" + - quote + quote) + quote; - if (str.indexOf(separator) != -1) return quote + str + quote; - return str; - } -} diff --git a/src/main/java/net/imagej/server/resources/ObjectsResource.java b/src/main/java/net/imagej/server/resources/ObjectsResource.java index 463f5640..af885583 100644 --- a/src/main/java/net/imagej/server/resources/ObjectsResource.java +++ b/src/main/java/net/imagej/server/resources/ObjectsResource.java @@ -24,16 +24,23 @@ import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; - import io.scif.config.SCIFIOConfig; -import io.scif.io.ByteArrayHandle; import io.scif.services.DatasetIOService; -import io.scif.services.LocationService; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Set; +import net.imagej.Dataset; +import net.imagej.DatasetService; +import net.imagej.server.Utils; +import net.imagej.server.services.ObjectInfo; +import net.imagej.server.services.ObjectService; +import net.imglib2.img.Img; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.scijava.io.IOService; +import org.scijava.io.handle.BytesHandle; +import org.scijava.io.location.BytesLocation; +import org.scijava.plugin.Parameter; +import org.scijava.table.Table; +import org.scijava.table.io.TableIOOptions; +import org.scijava.table.io.TableIOService; import javax.inject.Inject; import javax.ws.rs.Consumes; @@ -51,19 +58,10 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; - -import net.imagej.Dataset; -import net.imagej.DatasetService; -import net.imagej.server.Utils; -import net.imagej.server.services.ObjectInfo; -import net.imagej.server.services.ObjectService; -import net.imglib2.img.Img; - -import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.glassfish.jersey.media.multipart.FormDataParam; -import org.scijava.io.IOService; -import org.scijava.plugin.Parameter; -import org.scijava.table.Table; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Set; /** * Server resource for managing data structures that could not be easily handled @@ -82,7 +80,7 @@ public class ObjectsResource { private DatasetIOService datasetIOService; @Parameter - private LocationService locationService; + private TableIOService tableIOService; @Parameter private IOService ioService; @@ -167,18 +165,15 @@ public JsonNode uploadFile( @FormDataParam("file") final FormDataContentDisposition fileDetail, @QueryParam("type") final String typeHint) { - final ByteArrayHandle bah; + BytesHandle bah; try { - bah = readFileInputStream(fileInputStream); + bah = readFileInputStream(fileInputStream, fileDetail.getFileName()); } catch (IOException exc) { throw new WebApplicationException(exc, Status.BAD_REQUEST); } - // Maps a filename to a byte array in memory - final String filename = Utils.randomString(8) + "_" + fileDetail - .getFileName(); - locationService.mapFile(filename, bah); + final String filename = fileDetail.getFileName(); final String type; if (typeHint != null && typeHint.length() != 0) { @@ -189,14 +184,14 @@ public JsonNode uploadFile( type = mt.substring(0, mt.indexOf('/')); } - final Object obj; + Object obj = null; try { switch (type) { case "image": - obj = datasetIOService.open(filename); + obj = datasetIOService.open(bah.get()); break; case "text": - obj = ioService.open(filename); + obj = ioService.open(bah.get()); break; default: throw new WebApplicationException("Unrecognized format", @@ -209,9 +204,6 @@ public JsonNode uploadFile( catch (final IOException exc) { throw new WebApplicationException(exc, Status.CONFLICT); } - finally { - locationService.getIdMap().remove(filename, bah); - } final String id = objectService.register(obj, "uploadFile:filename=" + fileDetail.getFileName()); @@ -240,11 +232,11 @@ public Response getObject(@PathParam("id") final String id, final String filename = String.format("%s.%s", Utils.timestampedId(8), format); - final ByteArrayHandle bah = new ByteArrayHandle(); - locationService.mapFile(filename, bah); + final BytesHandle bah = new BytesHandle(); try { final Object obj = objectService.find(id).getObject(); + bah.set(new BytesLocation(8192, filename)); if (obj instanceof Img) { final Dataset ds; if (obj instanceof Dataset) { @@ -257,11 +249,12 @@ public Response getObject(@PathParam("id") final String id, } // TODO: inject query parameters into config final SCIFIOConfig config = new SCIFIOConfig(); - datasetIOService.save(ds, filename, config); + datasetIOService.save(ds, bah.get(), config); } else if (obj instanceof Table) { // TODO: inject query parameters into IOPlugin (DefaultTableIOPlugin) - ioService.save(obj, filename); + TableIOOptions options = TableIOOptions.options(); + tableIOService.save((Table)obj, bah.get(), options); } else { final String type = obj == null ? "null" : obj.getClass().getName(); @@ -276,7 +269,11 @@ else if (obj instanceof Table) { public void write(OutputStream output) throws IOException, WebApplicationException { - output.write(bah.getBytes(), 0, (int) bah.length()); + byte[] buffer = new byte[1024]; + int len; + while ((len = bah.read(buffer)) != -1) { + output.write(buffer, 0, len); + } } }; final String mt = Utils.getMimetype(filename); @@ -288,20 +285,17 @@ public void write(OutputStream output) throws IOException, catch (final IOException exc) { throw new WebApplicationException(exc, Status.CONFLICT); } - finally { - locationService.getIdMap().remove(filename, bah); - } } // -- helper methods -- - private ByteArrayHandle readFileInputStream(final InputStream fileInputStream) + private BytesHandle readFileInputStream(final InputStream fileInputStream, String fileName) throws IOException { - final ByteArrayHandle bah = new ByteArrayHandle(); - // Reads input file into memory final int BUFFER_SIZE = 8192; + final BytesHandle bah = new BytesHandle(); + bah.set(new BytesLocation(BUFFER_SIZE, fileName)); final byte[] buffer = new byte[BUFFER_SIZE]; int n = 0; while ((n = fileInputStream.read(buffer)) != -1) { diff --git a/src/test/java/net/imagej/server/DefaultTableIOPluginBenchmark.java b/src/test/java/net/imagej/server/TableIOPluginBenchmark.java similarity index 82% rename from src/test/java/net/imagej/server/DefaultTableIOPluginBenchmark.java rename to src/test/java/net/imagej/server/TableIOPluginBenchmark.java index ad8c2ab2..69adff28 100644 --- a/src/test/java/net/imagej/server/DefaultTableIOPluginBenchmark.java +++ b/src/test/java/net/imagej/server/TableIOPluginBenchmark.java @@ -21,18 +21,8 @@ package net.imagej.server; -import static org.junit.Assume.assumeTrue; - import com.carrotsearch.junitbenchmarks.BenchmarkOptions; import com.carrotsearch.junitbenchmarks.BenchmarkRule; - -import io.scif.io.ByteArrayHandle; -import io.scif.services.LocationService; - -import java.io.IOException; - -import net.imagej.server.external.DefaultTableIOPlugin; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -42,14 +32,20 @@ import org.scijava.Context; import org.scijava.io.IOPlugin; import org.scijava.io.IOService; -import org.scijava.table.GenericTable; +import org.scijava.io.handle.BytesHandle; +import org.scijava.table.Table; +import org.scijava.table.io.TableIOPlugin; import org.scijava.util.MersenneTwisterFast; +import java.io.IOException; + +import static org.junit.Assume.assumeTrue; + /** * @author Leon Yang */ @BenchmarkOptions(benchmarkRounds = 20, warmupRounds = 1) -public class DefaultTableIOPluginBenchmark { +public class TableIOPluginBenchmark { private boolean benchmarkTestsEnabled = "enabled".equals(System.getProperty( "imagej.server.benchmark.tests")); @@ -62,7 +58,7 @@ public void skipBenchmarksByDefault() { private static Context ctx; @BeforeClass - public static void prepare() { + public static void prepare() throws IOException { ctx = new Context(); final StringBuilder sb = new StringBuilder(10 * 1024 * 1024); @@ -77,8 +73,8 @@ public static void prepare() { } sb.append(String.format("%.6f\r\n", r.nextFloat())); } - final ByteArrayHandle bah = new ByteArrayHandle(sb.toString().getBytes()); - ctx.getService(LocationService.class).mapFile("large.csv", bah); + final BytesHandle bah = new BytesHandle(); + bah.write(sb.toString().getBytes()); } @AfterClass @@ -92,8 +88,8 @@ public static void cleanup() { @Test public void openLarge() { - final IOPlugin tableIO = ctx.service(IOService.class) - .getInstance(DefaultTableIOPlugin.class); + final IOPlugin tableIO = ctx.service(IOService.class) + .getInstance(TableIOPlugin.class); try { tableIO.open("large.csv"); } diff --git a/src/test/java/net/imagej/server/DefaultTableIOPluginTest.java b/src/test/java/net/imagej/server/TableIOPluginTest.java similarity index 60% rename from src/test/java/net/imagej/server/DefaultTableIOPluginTest.java rename to src/test/java/net/imagej/server/TableIOPluginTest.java index 4be0d909..4b7e8594 100644 --- a/src/test/java/net/imagej/server/DefaultTableIOPluginTest.java +++ b/src/test/java/net/imagej/server/TableIOPluginTest.java @@ -24,31 +24,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import io.scif.io.ByteArrayHandle; -import io.scif.services.LocationService; - +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; import java.util.function.Function; -import net.imagej.server.external.DefaultTableIOPlugin; - import org.junit.Test; import org.scijava.Context; -import org.scijava.io.IOPlugin; -import org.scijava.io.IOService; -import org.scijava.plugin.Parameter; -import org.scijava.table.GenericTable; -import org.scijava.util.ClassUtils; +import org.scijava.io.handle.BytesHandle; +import org.scijava.io.location.BytesLocation; +import org.scijava.table.Table; +import org.scijava.table.io.TableIOOptions; +import org.scijava.table.io.TableIOPlugin; +import org.scijava.table.io.TableIOService; /** - * Tests for {@link DefaultTableIOPlugin}. + * Tests for {@link TableIOPlugin}. * * @author Leon Yang */ -public class DefaultTableIOPluginTest { +public class TableIOPluginTest { private static final Context ctx = new Context(); @@ -71,20 +65,27 @@ public void testParser() { "123.000\t-123.000\t123.000\t123.000\t0.000\n" + "0.000\t1234567890.099\tNaN\t-Infinity\t0.000\n"; - final IOPlugin tableIO = ctx.service(IOService.class) - .getInstance(DefaultTableIOPlugin.class); + final TableIOService tableIO = ctx.service(TableIOService.class); try { final Function parser = Double::valueOf; - final Function formatter = val -> String.format("%.3f", - val); - setValues(tableIO, new String[] { "readColHeaders", "writeColHeaders", - "readRowHeaders", "writeRowHeaders", "separator", "eol", "quote", - "cornerText", "parser", "formatter" }, new Object[] { true, true, false, - true, "\t", "\n", "\"", "\\", parser, formatter }); - - final GenericTable table = openTable(tableSource, tableIO); + final Function formatter = val -> String.format("%.3f", + (Double)val); + + TableIOOptions options = TableIOOptions.options() + .readColumnHeaders(true) + .writeColumnHeaders(true) + .readRowHeaders(false) + .writeRowHeaders(false) + .columnDelimiter('\t') + .rowDelimiter("\n") + .quote('\"') + .cornerText("\\") + .parser(parser) + .formatter(formatter); + + final Table table = openTable(tableSource, tableIO, options); assertTableEquals(colHeaders, rowHeaders, content, table); - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); } catch (final Exception exc) { exc.printStackTrace(); @@ -116,20 +117,25 @@ public void testQuote() { "'should\tnot,break',unnecessary_quotes,should,break\r\n" + "'some,empty,cells','','',''\r\n"; - final IOPlugin tableIO = ctx.service(IOService.class) - .getInstance(DefaultTableIOPlugin.class); + final TableIOService tableIO = ctx.service(TableIOService.class); try { - setValues(tableIO, new String[] { "readColHeaders", "writeColHeaders", - "readRowHeaders", "writeRowHeaders", "separator", "eol", "quote", - "cornerText", "parser", "formatter" }, new Object[] { true, true, true, - true, " ", "\r\n", '\'', "CORNER_TEXT", Function.identity(), Function - .identity() }); - - final GenericTable table = openTable(tableSource, tableIO); + TableIOOptions options = TableIOOptions.options() + .readColumnHeaders(true) + .writeColumnHeaders(true) + .readRowHeaders(true) + .writeRowHeaders(true) + .columnDelimiter(' ') + .rowDelimiter("\r\n") + .quote('\'') + .cornerText("CORNER_TEXT") + .parser(Function.identity()) + .formatter(Object::toString); + + final Table table = openTable(tableSource, tableIO, options); assertTableEquals(colHeaders, rowHeaders, content, table); - setValues(tableIO, new String[] { "separator" }, new Object[] { ',' }); - assertEquals(expected, saveTable(table, tableIO)); + options.columnDelimiter(','); + assertEquals(expected, saveTable(table, tableIO, options)); } catch (final Exception exc) { exc.printStackTrace(); @@ -157,53 +163,61 @@ public void testSmallTables() { final Double[][] content = { { 3.1415926 } }; final Double[][] emptyContent = { {} }; - final IOPlugin tableIO = ctx.service(IOService.class) - .getInstance(DefaultTableIOPlugin.class); + final TableIOService tableIO = ctx.service(TableIOService.class); try { - GenericTable table; + Table table; String expected; final Function parser = Double::valueOf; - final Function formatter = val -> String.format("%.3f", - val); - setValues(tableIO, new String[] { "readColHeaders", "writeColHeaders", - "readRowHeaders", "writeRowHeaders", "separator", "eol", "quote", - "cornerText", "parser", "formatter" }, new Object[] { false, true, true, - true, ",", "\n", "'", "CORNER TEXT", parser, formatter }); - table = openTable(makeTableSource(singleRow, ",", "\n"), tableIO); + final Function formatter = val -> String.format("%.3f", + (Double)val); + TableIOOptions options = TableIOOptions.options() + .readColumnHeaders(false) + .writeColumnHeaders(false) + .readRowHeaders(true) + .writeRowHeaders(true) + .columnDelimiter(',') + .rowDelimiter("\n") + .quote('\'') + .cornerText("CORNER TEXT") + .parser(parser) + .formatter(formatter); + table = openTable(makeTableSource(singleRow, ",", "\n"), tableIO, options); assertTableEquals(emptyHeader, singleRowHeader, content, table); expected = "Row Header,3.142\n"; - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); - setValues(tableIO, new String[] { "readRowHeaders" }, new Object[] { - false }); - table = openTable(makeTableSource(singleCell, ",", "\n"), tableIO); + options.readRowHeaders(false); + options.writeRowHeaders(false); + table = openTable(makeTableSource(singleCell, ",", "\n"), tableIO, options); assertTableEquals(emptyHeader, emptyHeader, content, table); expected = "3.142\n"; - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); - setValues(tableIO, new String[] { "readColHeaders" }, new Object[] { - true }); - table = openTable(makeTableSource(singleCol, ",", "\n"), tableIO); + options.readColumnHeaders(true); + options.writeColumnHeaders(true); + table = openTable(makeTableSource(singleCol, ",", "\n"), tableIO, options); assertTableEquals(singleColHeader, emptyHeader, content, table); expected = "Col Header\n3.142\n"; - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); - setValues(tableIO, new String[] { "readRowHeaders" }, new Object[] { - true }); - table = openTable(makeTableSource(onlyColHeader, ",", "\n"), tableIO); + options.readRowHeaders(true); + table = openTable(makeTableSource(onlyColHeader, ",", "\n"), tableIO, options); assertTableEquals(singleColHeader, empty, emptyContent, table); expected = "Col Header\n"; - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); - table = openTable(makeTableSource(onlyRowHeader, ",", "\n"), tableIO); + options.writeColumnHeaders(false); + options.writeRowHeaders(true); + table = openTable(makeTableSource(onlyRowHeader, ",", "\n"), tableIO, options); assertTableEquals(empty, singleRowHeader, emptyContent, table); expected = "Row Header\n"; - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); - table = openTable(makeTableSource(full, ",", "\n"), tableIO); + options.writeColumnHeaders(true); + table = openTable(makeTableSource(full, ",", "\n"), tableIO, options); assertTableEquals(singleColHeader, singleRowHeader, content, table); expected = "CORNER TEXT,Col Header\nRow Header,3.142\n"; - assertEquals(expected, saveTable(table, tableIO)); + assertEquals(expected, saveTable(table, tableIO, options)); } catch (final Exception exc) { exc.printStackTrace(); @@ -214,8 +228,7 @@ public void testSmallTables() { @Test(expected = IOException.class) public void testOpenNonExist() throws IOException { - final IOPlugin tableIO = ctx.service(IOService.class) - .getInstance(DefaultTableIOPlugin.class); + final TableIOService tableIO = ctx.service(TableIOService.class); tableIO.open("fake.csv"); } @@ -226,7 +239,7 @@ public void testOpenNonExist() throws IOException { */ private void assertTableEquals(final String[] colHeaders, final String[] rowHeaders, final Object[][] content, - final GenericTable table) + final Table table) { assertEquals(colHeaders.length, table.getColumnCount()); assertEquals(rowHeaders.length, table.getRowCount()); @@ -241,36 +254,28 @@ private void assertTableEquals(final String[] colHeaders, } } - private GenericTable openTable(final String tableSource, - final IOPlugin tableIO) throws IOException + private Table openTable(final String tableSource, + final TableIOService tableIO, final TableIOOptions options) throws IOException { - final ByteArrayHandle bah = new ByteArrayHandle(tableSource.getBytes()); - ctx.service(LocationService.class).mapFile("table.txt", bah); - return tableIO.open("table.txt"); + final BytesHandle bah = new BytesHandle(); + BytesLocation data = new BytesLocation(tableSource.getBytes(), "table.csv"); + bah.set(data); + return tableIO.open(bah.get(), options); } - private String saveTable(final GenericTable table, - final IOPlugin tableIO) throws IOException + private String saveTable(final Table table, + final TableIOService tableIO, TableIOOptions options) throws IOException { - final ByteArrayHandle bah = new ByteArrayHandle(); - ctx.service(LocationService.class).mapFile("table.txt", bah); - tableIO.save(table, "table.txt"); - return new String(bah.getBytes(), 0, (int) bah.length()); - } - - private void setValues(final Object instance, final String[] fieldNames, - final Object[] values) throws SecurityException - { - final Class cls = instance.getClass(); - final List fields = ClassUtils.getAnnotatedFields(cls, - Parameter.class); - final HashMap fieldMap = new HashMap<>(); - for (final Field field : fields) { - fieldMap.put(field.getName(), field); - } - for (int i = 0; i < fieldNames.length; i++) { - ClassUtils.setValue(fieldMap.get(fieldNames[i]), instance, values[i]); + final BytesHandle bah = new BytesHandle(); + bah.set(new BytesLocation(1024, "table.csv")); + tableIO.save(table, bah.get(), options); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = bah.read(buffer)) != -1) { + out.write(buffer, 0, len); } + return new String(out.toByteArray(), 0, (int) bah.length()); } private String makeTableSource(final String[][] cells, final String separator,