Repository: jtablesaw/tablesaw
Pull request #491 (2019-03-10 06:22:24)

Introduce ReaderRegistry and new DataFrameReader methods
- [X] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)


Patch:
diff --git a/core/pom.xml b/core/pom.xml
index 194f0408..8489f425 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -110,6 +110,11 @@
             <artifactId>univocity-parsers</artifactId>
             <version>2.7.5</version>
         </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.11</version>
+        </dependency>
         <dependency>
             <groupId>tech.tablesaw</groupId>
             <artifactId>tablesaw-excel</artifactId>
diff --git a/core/src/main/java/tech/tablesaw/api/Table.java b/core/src/main/java/tech/tablesaw/api/Table.java
index 07507a1d..1edecbd4 100644
--- a/core/src/main/java/tech/tablesaw/api/Table.java
+++ b/core/src/main/java/tech/tablesaw/api/Table.java
@@ -14,8 +14,24 @@
 
 package tech.tablesaw.api;
 
+import static java.util.stream.Collectors.toList;
+import static tech.tablesaw.aggregate.AggregateFunctions.countMissing;
+import static tech.tablesaw.selection.Selection.selectNRowsAtRandom;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import org.reflections.Reflections;
+
 import com.google.common.base.Preconditions;
 import com.google.common.primitives.Ints;
+
 import it.unimi.dsi.fastutil.ints.IntArrays;
 import it.unimi.dsi.fastutil.ints.IntComparator;
 import tech.tablesaw.aggregate.AggregateFunction;
@@ -25,7 +41,10 @@ import tech.tablesaw.aggregate.Summarizer;
 import tech.tablesaw.columns.Column;
 import tech.tablesaw.io.DataFrameReader;
 import tech.tablesaw.io.DataFrameWriter;
-import tech.tablesaw.io.html.HtmlTableWriter;
+import tech.tablesaw.io.DataReader;
+import tech.tablesaw.io.DataWriter;
+import tech.tablesaw.io.ReaderRegistry;
+import tech.tablesaw.io.WriterRegistry;
 import tech.tablesaw.joining.DataFrameJoiner;
 import tech.tablesaw.selection.BitmapBackedSelection;
 import tech.tablesaw.selection.Selection;
@@ -37,18 +56,6 @@ import tech.tablesaw.table.Rows;
 import tech.tablesaw.table.StandardTableSliceGroup;
 import tech.tablesaw.table.TableSliceGroup;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-import static java.util.stream.Collectors.toList;
-import static tech.tablesaw.aggregate.AggregateFunctions.countMissing;
-import static tech.tablesaw.selection.Selection.selectNRowsAtRandom;
-
 /**
  * A table of data, consisting of some number of columns, each of which has the same number of rows.
  * All the data in a column has the same type: integer, float, category, etc., but a table may contain an arbitrary
@@ -58,6 +65,13 @@ import static tech.tablesaw.selection.Selection.selectNRowsAtRandom;
  */
 public class Table extends Relation implements Iterable<Row> {
 
+    public static final ReaderRegistry defaultReaderRegistry = new ReaderRegistry();
+    public static final WriterRegistry defaultWriterRegistry = new WriterRegistry();
+
+    static {
+	autoRegisterReadersAndWriters();
+    }
+
     /**
      * The columns that hold the data in this table
      */
@@ -67,6 +81,12 @@ public class Table extends Relation implements Iterable<Row> {
      */
     private String name;
 
+    /**
+     * Returns a new table
+     */
+    private Table() {
+    }
+
     /**
      * Returns a new table initialized with the given name
      */
@@ -74,6 +94,15 @@ public class Table extends Relation implements Iterable<Row> {
         this.name = name;
     }
 
+    /**
+     * Returns a new Table initialized with the given columns
+     *
+     * @param columns One or more columns, all of which must have either the same length or size 0
+     */
+    protected Table(Column<?>... columns) {
+        this(null, columns);
+    }
+
     /**
      * Returns a new Table initialized with the given names and columns
      *
@@ -87,6 +116,35 @@ public class Table extends Relation implements Iterable<Row> {
         }
     }
 
+    private static void autoRegisterReadersAndWriters() {
+	Reflections reflections = new Reflections("tech.tablesaw.io");
+	@SuppressWarnings("rawtypes")
+	Set<Class<? extends DataWriter>> writerClasses = reflections.getSubTypesOf(DataWriter.class);
+	for (Class<?> clazz : writerClasses) {
+	    try {
+		Class.forName(clazz.getCanonicalName());
+	    } catch (ClassNotFoundException e) {
+		new IllegalStateException(e);
+	    }
+	}	
+	@SuppressWarnings("rawtypes")
+	Set<Class<? extends DataReader>> readerClasses = reflections.getSubTypesOf(DataReader.class);
+	for (Class<?> clazz : readerClasses) {
+	    try {
+		Class.forName(clazz.getCanonicalName());
+	    } catch (ClassNotFoundException e) {
+		new IllegalStateException(e);
+	    }
+	}	
+    }
+
+    /**
+     * Returns a new, empty table (without rows or columns)
+     */
+    public static Table create() {
+        return new Table();
+    }
+
     /**
      * Returns a new, empty table (without rows or columns) with the given name
      */
@@ -94,13 +152,23 @@ public class Table extends Relation implements Iterable<Row> {
         return new Table(tableName);
     }
 
+    /**
+     * Returns a new table with the given columns
+     *
+     * @param columns one or more columns, all of the same @code{column.size()}
+     */
+    public static Table create(Column<?>... columns) {
+        return new Table(columns);
+    }
+
     /**
      * Returns a new table with the given columns and given name
      *
-     * @param columns One or more columns, all of the same @code{column.size()}
+     * @param name the name for this table 
+     * @param columns one or more columns, all of the same @code{column.size()}
      */
-    public static Table create(final String tableName, final Column<?>... columns) {
-        return new Table(tableName, columns);
+    public static Table create(String name, Column<?>... columns) {
+        return new Table(name, columns);
     }
 
     /**
@@ -128,11 +196,11 @@ public class Table extends Relation implements Iterable<Row> {
     }
 
     public static DataFrameReader read() {
-        return new DataFrameReader();
+        return new DataFrameReader(defaultReaderRegistry);
     }
 
     public DataFrameWriter write() {
-        return new DataFrameWriter(this);
+        return new DataFrameWriter(defaultWriterRegistry, this);
     }
 
     /**
@@ -690,10 +758,6 @@ public class Table extends Relation implements Iterable<Row> {
         return StandardTableSliceGroup.create(this, columns);
     }
 
-    public String printHtml() {
-        return HtmlTableWriter.write(this);
-    }
-
     public Table structure() {
         Table t = new Table("Structure of " + name());
 
diff --git a/core/src/main/java/tech/tablesaw/io/DataFrameReader.java b/core/src/main/java/tech/tablesaw/io/DataFrameReader.java
index 352e1a13..383faf01 100644
--- a/core/src/main/java/tech/tablesaw/io/DataFrameReader.java
+++ b/core/src/main/java/tech/tablesaw/io/DataFrameReader.java
@@ -21,12 +21,15 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.List;
 import java.util.Scanner;
 
+import com.google.common.io.Files;
+
 import tech.tablesaw.api.Table;
 import tech.tablesaw.io.csv.CsvReadOptions;
 import tech.tablesaw.io.csv.CsvReader;
@@ -42,13 +45,82 @@ import tech.tablesaw.io.xlsx.XlsxReader;
 
 public class DataFrameReader {
 
+    private final ReaderRegistry registry;
+
+    public DataFrameReader(ReaderRegistry registry) {
+	this.registry = registry;
+    }
+
+    /**
+     * Reads the given URL into a table using default options
+     * Uses appropriate converter based on mime-type
+     * Use {@link #withOptions(ReadOptions) withOptions} to use non-default options
+     */
+    public Table url(String url) throws IOException {
+	return url(new URL(url));
+    }
+
+    /**
+     * Reads the given URL into a table using default options
+     * Uses appropriate converter based on mime-type
+     * Use {@link #withOptions(ReadOptions) withOptions} to use non-default options
+     */
+    public Table url(URL url) throws IOException {
+	URLConnection connection = url.openConnection();
+	String mimeType = connection.getContentType();
+	DataReader<?> reader = registry.getReaderForMimeType(mimeType);
+	return reader.read(new Source(connection.getInputStream()));
+    }
+
+    /**
+     * Reads the given string contents into a table using default options
+     * Uses converter specified based on given file extension
+     * Use {@link #withOptions(ReadOptions) withOptions} to use non-default options
+     */
+    public Table string(String s, String fileExtension) throws IOException {
+	DataReader<?> reader = registry.getReaderForExtension(fileExtension);
+	return reader.read(Source.fromString(s));
+    }
+
+    /**
+     * Reads the given file into a table using default options
+     * Uses converter specified based on given file extension
+     * Use {@link #withOptions(ReadOptions) withOptions} to use non-default options
+     */
+    public Table file(String file) throws IOException {
+        return file(new File(file));
+    }
+
+    /**
+     * Reads the given file into a table using default options
+     * Uses converter specified based on given file extension
+     * Use {@link #withOptions(ReadOptions) withOptions} to use non-default options
+     */
+    public Table file(File file) throws IOException {
+	String extension = Files.getFileExtension(file.getCanonicalPath());
+	DataReader<?> reader = registry.getReaderForExtension(extension);
+	return reader.read(new Source(file));
+    }
+
+    public Table usingOptions(ReadOptions options) throws IOException {
+	DataReader<?> reader = registry.getReaderForOptions(options);
+	return reader.read(options.source());
+    }
+
+    public Table usingOptions(ReadOptions.Builder builder) throws IOException {
+	return usingOptions(builder.build());
+    }
+
+
+    // Legacy reader methods for backwards-compatibility
+    
     public Table csv(String file) throws IOException {
         return csv(CsvReadOptions.builder(file));
     }
 
     public Table csv(String contents, String tableName) {
         try {
-            return csv(new StringReader(contents), tableName);
+            return csv(CsvReadOptions.builder(new StringReader(contents)).tableName(tableName));
         } catch (Exception e) {
             throw new IllegalStateException(e);
         }
@@ -58,12 +130,12 @@ public class DataFrameReader {
         return csv(CsvReadOptions.builder(file));
     }
 
-    public Table csv(InputStream stream, String tableName) throws IOException {
-        return csv(CsvReadOptions.builder(stream, tableName));
+    public Table csv(InputStream stream) throws IOException {
+        return csv(CsvReadOptions.builder(stream));
     }
 
-    public Table csv(Reader reader, String tableName) throws IOException {
-        return csv(CsvReadOptions.builder(reader, tableName));
+    public Table csv(Reader reader) throws IOException {
+        return csv(CsvReadOptions.builder(reader));
     }
 
     public Table csv(CsvReadOptions.Builder options) throws IOException {
@@ -71,31 +143,23 @@ public class DataFrameReader {
     }
 
     public Table csv(CsvReadOptions options) throws IOException {
-        return new CsvReader().read(options);
+	return new CsvReader().read(options);
     }
 
     public Table fixedWidth(String file) throws IOException {
         return fixedWidth(FixedWidthReadOptions.builder(file));
     }
 
-    public Table fixedWidth(String contents, String tableName) {
-        try {
-            return fixedWidth(new StringReader(contents), tableName);
-        } catch (Exception e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     public Table fixedWidth(File file) throws IOException {
         return fixedWidth(FixedWidthReadOptions.builder(file));
     }
 
-    public Table fixedWidth(InputStream stream, String tableName) throws IOException {
-        return fixedWidth(FixedWidthReadOptions.builder(stream, tableName));
+    public Table fixedWidth(InputStream stream) throws IOException {
+        return fixedWidth(FixedWidthReadOptions.builder(stream));
     }
 
-    public Table fixedWidth(Reader reader, String tableName) throws IOException {
-        return fixedWidth(FixedWidthReadOptions.builder(reader, tableName));
+    public Table fixedWidth(Reader reader) throws IOException {
+        return fixedWidth(FixedWidthReadOptions.builder(reader));
     }
 
     public Table fixedWidth(FixedWidthReadOptions.Builder options) throws IOException {
@@ -106,8 +170,14 @@ public class DataFrameReader {
         return new FixedWidthReader().read(options);
     }
 
+    public Table db(ResultSet resultSet) throws SQLException {
+        return SqlResultSetReader.read(resultSet);
+    }
+
     public Table db(ResultSet resultSet, String tableName) throws SQLException {
-        return SqlResultSetReader.read(resultSet, tableName);
+        Table table = SqlResultSetReader.read(resultSet);
+        table.setName(tableName);
+        return table;
     }
 
     /**
@@ -142,7 +212,7 @@ public class DataFrameReader {
      * Modules that call this method must add the optional dependency tech.tablesaw:tablesaw-excel
      */
     public List<Table> xlsx(XlsxReadOptions options) throws IOException {
-        return new XlsxReader().read(options);
+        return new XlsxReader().readMultiple(options);
     }
 
     /**
diff --git a/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java b/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java
index 284f397e..b6b7522e 100644
--- a/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java
+++ b/core/src/main/java/tech/tablesaw/io/DataFrameWriter.java
@@ -28,65 +28,93 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.io.Writer;
 
+import com.google.common.io.Files;
+
 public class DataFrameWriter {
 
+    private final WriterRegistry registry;
     private final Table table;
 
-    public DataFrameWriter(Table table) {
+    public DataFrameWriter(WriterRegistry registry, Table table) {
+	this.registry = registry;
         this.table = table;
     }
 
+    public void toFile(String file) throws IOException {
+	toFile(new File(file));
+    }
+
+    public void toFile(File file) throws IOException {
+	String extension = Files.getFileExtension(file.getCanonicalPath());
+	DataWriter<?> dataWriter = registry.getWriterForExtension(extension);
+	dataWriter.write(table, new Destination(file));
+    }
+
+    public void toStream(OutputStream stream, String extension) {
+	DataWriter<?> dataWriter = registry.getWriterForExtension(extension);
+	dataWriter.write(table, new Destination(stream));
+    }
+
+    public void toWriter(Writer writer, String extension) {
+	DataWriter<?> dataWriter = registry.getWriterForExtension(extension);
+	dataWriter.write(table, new Destination(writer));
+    }
+ 
+    public void usingOptions(WriteOptions options) {
+	DataWriter<?> dataWriter = registry.getWriterForOptions(options);
+	dataWriter.write(table, options.destination());
+    }
+
     public void csv(String file) throws IOException {
-        CsvWriteOptions options = new CsvWriteOptions.Builder(file).build();
-        new CsvWriter(table, options).write();
+        CsvWriteOptions options = CsvWriteOptions.builder(file).build();
+        new CsvWriter().write(table, options);
     }
 
     public void csv(File file) throws IOException {
-        CsvWriteOptions options = new CsvWriteOptions.Builder(file).build();
-        new CsvWriter(table, options).write();
+        CsvWriteOptions options = CsvWriteOptions.builder(file).build();
+        new CsvWriter().write(table, options);
     }
 
     public void csv(CsvWriteOptions options) {
-        new CsvWriter(table, options).write();
+        new CsvWriter().write(table, options);
     }
 
     public void csv(OutputStream stream) {
-        CsvWriteOptions options = new CsvWriteOptions.Builder(stream).build();
-        new CsvWriter(table, options).write();
+        CsvWriteOptions options = CsvWriteOptions.builder(stream).build();
+        new CsvWriter().write(table, options);
     }
 
     public void csv(Writer writer) {
-        CsvWriteOptions options = new CsvWriteOptions.Builder(writer).build();
-        new CsvWriter(table, options).write();
+        CsvWriteOptions options = CsvWriteOptions.builder(writer).build();
+        new CsvWriter().write(table, options);
     }
 
     public void fixedWidth(String file) throws IOException {
-        FixedWidthWriteOptions options = new FixedWidthWriteOptions.Builder(file).build();
-        new FixedWidthWriter(table, options).write();
+        FixedWidthWriteOptions options = FixedWidthWriteOptions.builder(file).build();
+        new FixedWidthWriter(options).write(table);
     }
 
     public void fixedWidth(File file) throws IOException {
-        FixedWidthWriteOptions options = new FixedWidthWriteOptions.Builder(file).build();
-        new FixedWidthWriter(table, options).write();
+        FixedWidthWriteOptions options = FixedWidthWriteOptions.builder(file).build();
+        new FixedWidthWriter(options).write(table);
     }
 
     public void fixedWidth(FixedWidthWriteOptions options) {
-        new FixedWidthWriter(table, options).write();
+        new FixedWidthWriter(options).write(table);
     }
 
     public void fixedWidth(OutputStream stream) {
-        FixedWidthWriteOptions options = new FixedWidthWriteOptions.Builder(stream).build();
-        new FixedWidthWriter(table, options).write();
+        FixedWidthWriteOptions options = FixedWidthWriteOptions.builder(stream).build();
+        new FixedWidthWriter(options).write(table);
     }
 
     public void fixedWidth(Writer writer) {
-        FixedWidthWriteOptions options = new FixedWidthWriteOptions.Builder(writer).build();
-        new FixedWidthWriter(table, options).write();
+        FixedWidthWriteOptions options = FixedWidthWriteOptions.builder(writer).build();
+        new FixedWidthWriter(options).write(table);
     }
 
-
     public void html(OutputStream stream) {
-        HtmlTableWriter.write(table, stream);
+        new HtmlTableWriter().write(table, stream);
     }
 
     public String json() throws IOException {
diff --git a/core/src/main/java/tech/tablesaw/io/DataReader.java b/core/src/main/java/tech/tablesaw/io/DataReader.java
new file mode 100644
index 00000000..39345fcf
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/DataReader.java
@@ -0,0 +1,13 @@
+package tech.tablesaw.io;
+
+import java.io.IOException;
+
+import tech.tablesaw.api.Table;
+
+public interface DataReader<O extends ReadOptions> {
+
+    Table read(Source source) throws IOException;
+
+    Table read(O options) throws IOException;
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/DataWriter.java b/core/src/main/java/tech/tablesaw/io/DataWriter.java
new file mode 100644
index 00000000..ace3ca07
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/DataWriter.java
@@ -0,0 +1,11 @@
+package tech.tablesaw.io;
+
+import tech.tablesaw.api.Table;
+
+public interface DataWriter<O extends WriteOptions> {
+
+    void write(Table table, Destination dest);
+
+    void write(Table table, O options);
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/Destination.java b/core/src/main/java/tech/tablesaw/io/Destination.java
new file mode 100644
index 00000000..86f67b82
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/Destination.java
@@ -0,0 +1,42 @@
+package tech.tablesaw.io;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+public class Destination {
+
+    protected final OutputStream stream;
+    protected final Writer writer;
+
+    public Destination(File file) throws IOException {
+	this.stream = new FileOutputStream(file);
+	this.writer = null;
+    }
+
+    public Destination(Writer writer) {
+	this.stream = null;
+	this.writer = writer;
+    }
+
+    public Destination(OutputStream stream) {
+	this.stream = stream;
+	this.writer = null;
+    }
+
+    public OutputStream stream() {
+	return stream;
+    }
+
+    public Writer writer() {
+	return writer;
+    }
+
+    public Writer createWriter() {
+	return writer != null ? writer : new OutputStreamWriter(stream);
+    }
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/ReadOptions.java b/core/src/main/java/tech/tablesaw/io/ReadOptions.java
index 0e9ca77b..b8c00264 100644
--- a/core/src/main/java/tech/tablesaw/io/ReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/ReadOptions.java
@@ -20,8 +20,10 @@ import com.google.common.collect.Lists;
 import tech.tablesaw.api.ColumnType;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.net.URL;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.Locale;
@@ -59,11 +61,7 @@ public class ReadOptions {
                     STRING,
                     TEXT);
 
-    // we always have one of these (file, reader, or inputStream)
-    protected final File file;
-    protected final Reader reader;
-    protected final InputStream inputStream;
-
+    protected final Source source;
     protected final String tableName;
     protected final boolean sample;
     protected final String dateFormat;
@@ -75,21 +73,7 @@ public class ReadOptions {
     protected final boolean header;
 
     protected ReadOptions(ReadOptions.Builder builder) {
-
-        int sourceCount = 0;
-        if (builder.file != null) sourceCount++;
-        if (builder.reader != null) sourceCount++;
-        if (builder.inputStream != null) sourceCount++;
-
-        if (sourceCount == 0) {
-            throw new IllegalArgumentException("ReadOptions Builder configured with no data source");
-        } else if (sourceCount > 1) {
-            throw new IllegalArgumentException("ReadOptions Builder configured with more than one data source");
-        }
-
-        file = builder.file;
-        reader = builder.reader;
-        inputStream = builder.inputStream;
+	source = builder.source;
         tableName = builder.tableName;
         sample = builder.sample;
         dateFormat = builder.dateFormat;
@@ -106,16 +90,23 @@ public class ReadOptions {
         }
     }
 
+    public Source source() {
+        return source;
+    }
+
+    @Deprecated
     public File file() {
-        return file;
+        return source.file();
     }
 
+    @Deprecated
     public Reader reader() {
-        return reader;
+        return source.reader();
     }
 
+    @Deprecated
     public InputStream inputStream() {
-        return inputStream;
+        return source.inputStream();
     }
 
     public String tableName() {
@@ -165,9 +156,7 @@ public class ReadOptions {
 
     protected static class Builder {
 
-        protected final InputStream inputStream;
-        protected final File file;
-        protected final Reader reader;
+        protected final Source source;
         protected String tableName = "";
         protected boolean sample = true;
         protected String dateFormat;
@@ -178,37 +167,30 @@ public class ReadOptions {
         protected boolean minimizeColumnSizes = false;
         protected boolean header = true;
 
+        protected Builder() {
+            source = null;
+        }
+        
+        protected Builder(Source source) {
+      	    this.source = source;
+        }
+
         protected Builder(File file) {
-      	    this.inputStream = null;
-            this.file = file;
-            this.reader = null;
+      	    this.source = new Source(file);
             this.tableName = file.getName();
         }
 
-        protected Builder(Reader reader) {
-      	    this.inputStream = null;
-            this.file = null;
-            this.reader = reader;
+        protected Builder(URL url) throws IOException {
+      	    this.source = new Source(url.openStream());
+            this.tableName = url.toString();
         }
 
         protected Builder(InputStream stream) {
-            this.inputStream = stream;
-            this.file = null;
-            this.reader = null;
-        }
-
-        protected Builder(Reader reader, String tableName) {
-            this.inputStream = null;
-            this.file = null;
-            this.reader = reader;
-            this.tableName = tableName;
+            this.source = new Source(stream);
         }
 
-        protected Builder(InputStream stream, String tableName) {
-            this.inputStream = stream;
-            this.file = null;
-            this.reader = null;
-            this.tableName = tableName;
+        protected Builder(Reader reader) {
+            this.source = new Source(reader);
         }
 
         public Builder tableName(String tableName) {
@@ -255,6 +237,10 @@ public class ReadOptions {
             this.minimizeColumnSizes = minimize;
             return this;
         }
+        
+        public ReadOptions build() {
+            return new ReadOptions(this);
+        }
     }
 
 }
diff --git a/core/src/main/java/tech/tablesaw/io/ReaderRegistry.java b/core/src/main/java/tech/tablesaw/io/ReaderRegistry.java
new file mode 100644
index 00000000..5f78a3ea
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/ReaderRegistry.java
@@ -0,0 +1,39 @@
+package tech.tablesaw.io;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ReaderRegistry {
+
+    private final Map<String, DataReader<?>> optionTypesRegistry = new HashMap<>();
+
+    private final Map<String, DataReader<?>> extensionsRegistry = new HashMap<>();
+
+    private final Map<String, DataReader<?>> mimeTypesRegistry = new HashMap<>();
+
+
+    public void registerOptions(Class<? extends ReadOptions> optionsType, DataReader<?> reader) {
+	optionTypesRegistry.put(optionsType.getCanonicalName(), reader);
+    }
+
+    public void registerExtension(String extension, DataReader<?> reader) {
+	extensionsRegistry.put(extension, reader);
+    }
+
+    public void registerMimeType(String mimeType, DataReader<?> reader) {
+	mimeTypesRegistry.put(mimeType, reader);
+    }
+
+    public DataReader<?> getReaderForOptions(ReadOptions options) {
+	return optionTypesRegistry.get(options.getClass().getCanonicalName());
+    }
+
+    public DataReader<?> getReaderForExtension(String extension) {
+	return extensionsRegistry.get(extension);
+    }
+
+    public DataReader<?> getReaderForMimeType(String mimeType) {
+	return mimeTypesRegistry.get(mimeType);
+    }
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/Source.java b/core/src/main/java/tech/tablesaw/io/Source.java
new file mode 100644
index 00000000..7f50df0f
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/Source.java
@@ -0,0 +1,86 @@
+package tech.tablesaw.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Scanner;
+
+public class Source {
+
+    protected final File file;
+    protected final Reader reader;
+    protected final InputStream inputStream;
+
+    public Source(File file) {
+	this.file = file;
+	this.reader = null;
+	this.inputStream = null;
+    }
+
+    public Source(Reader reader) {
+	this.file = null;
+	this.reader = reader;
+	this.inputStream = null;
+    }
+
+    public Source(InputStream inputStream) {
+	this.file = null;
+	this.reader = null;
+	this.inputStream = inputStream;
+    }
+
+    public static Source fromString(String s) {
+	return new Source(new StringReader(s));
+    }
+
+    public static Source fromFile(String file) {
+	return new Source(new File(file));
+    }
+
+    public static Source fromUrl(String url) throws IOException {
+	return new Source(new StringReader(loadUrl(url)));
+    }
+
+    public File file() {
+	return file;
+    }
+
+    public Reader reader() {
+	return reader;
+    }
+
+    public InputStream inputStream() {
+	return inputStream;
+    }
+
+    /**
+     * If cachedBytes are not null, returns a Reader created from the cachedBytes.
+     * Otherwise, returns a Reader from the underlying source.
+     */
+    public Reader createReader(byte[] cachedBytes) throws IOException {
+	if (cachedBytes != null) {
+	    return new InputStreamReader(new ByteArrayInputStream(cachedBytes));
+	}
+        if (inputStream != null) {
+            return new InputStreamReader(inputStream);
+        }
+        if (reader != null) {
+            return reader;
+        }
+        return new FileReader(file);
+    }
+
+    private static String loadUrl(String url) throws IOException {
+        try (Scanner scanner = new Scanner(new URL(url).openStream(), StandardCharsets.UTF_8.toString())) {
+            scanner.useDelimiter("\\A");
+            return scanner.hasNext() ? scanner.next() : "";
+	}
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java b/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java
index 03707a2a..5f03b120 100644
--- a/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java
+++ b/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java
@@ -1,29 +1,18 @@
 package tech.tablesaw.io;
 
-import tech.tablesaw.api.ColumnType;
-import tech.tablesaw.api.Table;
-
-import java.io.Reader;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.ByteArrayInputStream;
-import java.io.FileReader;
+import java.io.Reader;
 import java.util.Iterator;
 import java.util.List;
 
+import tech.tablesaw.api.ColumnType;
+import tech.tablesaw.api.Table;
+
 public class TableBuildingUtils {
 
-    public static Reader createReader(ReadOptions options, byte[] cachedBytes) throws IOException {
-	if (cachedBytes != null) {
-	    return new InputStreamReader(new ByteArrayInputStream(cachedBytes));
-	}
-        if (options.inputStream() != null) {
-            return new InputStreamReader(options.inputStream());
-        }
-        if (options.reader() != null) {
-            return options.reader();
-        }
-        return new FileReader(options.file());
+    @Deprecated
+    public static Reader createReader(ReadOptions options, byte[] cachedBytes) throws IOException {	
+	return options.source().createReader(cachedBytes);
     }
 
     public static Table build(List<String> columnNames, List<String[]> dataRows, ReadOptions options) {
diff --git a/core/src/main/java/tech/tablesaw/io/WriteOptions.java b/core/src/main/java/tech/tablesaw/io/WriteOptions.java
new file mode 100644
index 00000000..68becb14
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/WriteOptions.java
@@ -0,0 +1,41 @@
+package tech.tablesaw.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+public class WriteOptions {
+
+    protected final Destination dest;
+
+    protected WriteOptions(Builder builder) {
+	this.dest = builder.dest;
+    }
+
+    public Destination destination() {
+	return dest;
+    }
+
+    public static class Builder {
+
+	protected Destination dest;
+
+	protected Builder(Destination dest) {
+            this.dest = dest;
+	}
+	
+	protected Builder(OutputStream dest) {
+            this.dest = new Destination(dest);
+	}
+
+	protected Builder(Writer dest) {
+            this.dest = new Destination(dest);
+	}
+
+	protected Builder(File dest) throws IOException {
+            this.dest = new Destination(dest);
+	}
+    }
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/WriterRegistry.java b/core/src/main/java/tech/tablesaw/io/WriterRegistry.java
new file mode 100644
index 00000000..ed79a8e9
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/WriterRegistry.java
@@ -0,0 +1,28 @@
+package tech.tablesaw.io;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class WriterRegistry {
+
+    private final Map<String, DataWriter<?>> optionTypesRegistry = new HashMap<>();
+
+    private final Map<String, DataWriter<?>> extensionsRegistry = new HashMap<>();
+
+    public void registerOptions(Class<? extends WriteOptions> optionsType, DataWriter<?> writer) {
+	optionTypesRegistry.put(optionsType.getCanonicalName(), writer);
+    }
+
+    public void registerExtension(String extension, DataWriter<?> writer) {
+	extensionsRegistry.put(extension, writer);
+    }
+
+    public DataWriter<?> getWriterForOptions(WriteOptions options) {
+	return optionTypesRegistry.get(options.getClass().getCanonicalName());
+    }
+
+    public DataWriter<?> getWriterForExtension(String extension) {
+	return extensionsRegistry.get(extension);
+    }
+
+}
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
index 1eb18943..3915c958 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
@@ -15,12 +15,16 @@
 package tech.tablesaw.io.csv;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.io.StringReader;
+import java.net.URL;
 import java.util.Locale;
 
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.io.ReadOptions;
+import tech.tablesaw.io.Source;
 
 public class CsvReadOptions extends ReadOptions {
 
@@ -42,6 +46,14 @@ public class CsvReadOptions extends ReadOptions {
         lineSeparatorDetectionEnabled = builder.lineSeparatorDetectionEnabled;
     }
 
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static Builder builder(Source source) {
+        return new Builder(source);
+    }
+
     public static Builder builder(File file) {
         return new Builder(file).tableName(file.getName());
     }
@@ -50,6 +62,22 @@ public class CsvReadOptions extends ReadOptions {
         return new Builder(new File(fileName));
     }
 
+    public static Builder builder(URL url) throws IOException {
+        return new Builder(url);
+    }
+
+    public static Builder builderFromFile(String fileName) {
+        return new Builder(new File(fileName));
+    }
+
+    public static Builder builderFromString(String contents) {
+        return new Builder(new StringReader(contents));
+    }
+
+    public static Builder builderFromUrl(String url) throws IOException {
+        return new Builder(new URL(url));
+    }
+
     /**
      * This method may cause tablesaw to buffer the entire InputStream.
      * <p>
@@ -58,8 +86,8 @@ public class CsvReadOptions extends ReadOptions {
      * 2. Provide the array of column types as an option. If you provide the columnType array,
      * we skip type detection and can avoid reading the entire file
      */
-    public static Builder builder(InputStream stream, String tableName) {
-        return new Builder(stream).tableName(tableName);
+    public static Builder builder(InputStream stream) {
+        return new Builder(stream);
     }
 
     /**
@@ -71,9 +99,8 @@ public class CsvReadOptions extends ReadOptions {
      * 2. Provide the array of column types as an option. If you provide the columnType array,
      * we skip type detection and can avoid reading the entire file
      */
-    public static Builder builder(Reader reader, String tableName) {
-        Builder builder = new Builder(reader);
-        return builder.tableName(tableName);
+    public static Builder builder(Reader reader) {
+        return new Builder(reader);
     }
 
     public ColumnType[] columnTypes() {
@@ -109,6 +136,18 @@ public class CsvReadOptions extends ReadOptions {
         private Character commentPrefix;
         private boolean lineSeparatorDetectionEnabled = true;
 
+        protected Builder() {
+            super();
+        }
+
+        protected Builder(Source source) {
+            super(source);
+        }
+
+        protected Builder(URL url) throws IOException {
+            super(url);
+        }
+
         protected Builder(File file) {
             super(file);
         }
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
index 2c421dc6..e1d38faf 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
@@ -14,26 +14,41 @@
 
 package tech.tablesaw.io.csv;
 
+import java.io.IOException;
+import java.io.Reader;
+import java.util.List;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.apache.commons.math3.util.Pair;
+
 import com.google.common.io.CharStreams;
 import com.univocity.parsers.common.AbstractParser;
 import com.univocity.parsers.csv.CsvFormat;
 import com.univocity.parsers.csv.CsvParser;
 import com.univocity.parsers.csv.CsvParserSettings;
+
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.api.Table;
+import tech.tablesaw.io.DataReader;
 import tech.tablesaw.io.FileReader;
-import tech.tablesaw.io.TableBuildingUtils;
+import tech.tablesaw.io.ReaderRegistry;
+import tech.tablesaw.io.Source;
 
-import javax.annotation.concurrent.Immutable;
+@Immutable
+public class CsvReader extends FileReader implements DataReader<CsvReadOptions> {
 
-import org.apache.commons.math3.util.Pair;
+    private static final CsvReader INSTANCE = new CsvReader();
 
-import java.io.IOException;
-import java.io.Reader;
-import java.util.List;
+    static {
+	register(Table.defaultReaderRegistry);
+    }
 
-@Immutable
-public class CsvReader extends FileReader {
+    public static void register(ReaderRegistry registry) {
+	registry.registerExtension("csv", INSTANCE);
+	registry.registerMimeType("text/csv", INSTANCE);
+	registry.registerOptions(CsvReadOptions.class, INSTANCE);
+    }
 
     /**
      * Constructs a CsvReader
@@ -55,21 +70,21 @@ public class CsvReader extends FileReader {
      * Determines column types if not provided by the user
      * Reads all input into memory unless File was provided
      */
-    private Pair<Reader, ColumnType[]> getReaderAndColumnTypes(CsvReadOptions options) throws IOException {
+    private Pair<Reader, ColumnType[]> getReaderAndColumnTypes(Source source, CsvReadOptions options) throws IOException {
         ColumnType[] types = options.columnTypes();
         byte[] bytesCache = null;
 
         if (types == null) {
-            Reader reader = TableBuildingUtils.createReader(options, bytesCache);
-            if (options.file() == null) {
+            Reader reader = source.createReader(bytesCache);
+            if (source.file() == null) {
                 bytesCache = CharStreams.toString(reader).getBytes();
                 // create a new reader since we just exhausted the existing one
-                reader = TableBuildingUtils.createReader(options, bytesCache);
+                reader = source.createReader(bytesCache);
             }
             types = detectColumnTypes(reader, options);
         }
 
-        return Pair.create(TableBuildingUtils.createReader(options, bytesCache), types);
+        return Pair.create(source.createReader(bytesCache), types);
     }
 
     public Table read(CsvReadOptions options) throws IOException {
@@ -77,7 +92,7 @@ public class CsvReader extends FileReader {
     }
 
     private Table read(CsvReadOptions options, boolean headerOnly) throws IOException {
-        Pair<Reader, ColumnType[]> pair = getReaderAndColumnTypes(options);
+        Pair<Reader, ColumnType[]> pair = getReaderAndColumnTypes(options.source(), options);
         Reader reader = pair.getKey();
         ColumnType[] types = pair.getValue();
 
@@ -86,7 +101,7 @@ public class CsvReader extends FileReader {
         try {
             return parseRows(options, headerOnly, reader, types, parser);
         } finally {
-            if (options.reader() == null) {
+            if (options.source().reader() == null) {
                 // if we get a reader back from options it means the client opened it, so let the client close it
                 // if it's null, we close it here.
                 parser.stopParsing();
@@ -115,7 +130,6 @@ public class CsvReader extends FileReader {
      * @throws IOException if file cannot be read
      */
     public String printColumnTypes(CsvReadOptions options) throws IOException {
-
         Table structure = read(options, true).structure();
         return getTypeString(structure);
     }
@@ -132,7 +146,6 @@ public class CsvReader extends FileReader {
      * used to explicitly specify the correct column types.
      */
     protected ColumnType[] detectColumnTypes(Reader reader, CsvReadOptions options) {
-
         boolean header = options.header();
         int linesToSkip = header ? 1 : 0;
 
@@ -170,4 +183,9 @@ public class CsvReader extends FileReader {
         return format;
     }
 
+    @Override
+    public Table read(Source source) throws IOException {
+	return read(CsvReadOptions.builder(source).build());
+    }
+
 }
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java b/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java
index 6e60a8a1..ee4a18f8 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvWriteOptions.java
@@ -1,16 +1,16 @@
 package tech.tablesaw.io.csv;
 
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.file.Paths;
 
-public class CsvWriteOptions {
+import tech.tablesaw.io.Destination;
+import tech.tablesaw.io.WriteOptions;
+
+public class CsvWriteOptions extends WriteOptions {
 
-    private final Writer writer;
     private final boolean header;
     private final char separator;
     private final char quotechar;
@@ -18,7 +18,7 @@ public class CsvWriteOptions {
     private final String lineEnd;
 
     private CsvWriteOptions(Builder builder) {
-        this.writer = builder.writer;
+        super(builder);
         this.header = builder.header;
         this.separator = builder.separator;
         this.quotechar = builder.quoteChar;
@@ -26,10 +26,6 @@ public class CsvWriteOptions {
         this.lineEnd = builder.lineEnd;
     }
 
-    public Writer writer() {
-        return writer;
-    }
-
     public boolean header() {
         return header;
     }
@@ -50,38 +46,52 @@ public class CsvWriteOptions {
         return lineEnd;
     }
 
-    public static Builder builder(File file) throws IOException {
-        return new Builder(file);
+    public static Builder builder(Destination dest) {
+	return new Builder(dest);
+    }
+
+    public static Builder builder(OutputStream dest) {
+	return new Builder(dest);
+    }
+
+    public static Builder builder(Writer dest) {
+	return new Builder(dest);
+    }
+
+    public static Builder builder(File dest) throws IOException {
+	return new Builder(dest);
     }
 
     public static Builder builder(String fileName) throws IOException {
         return builder(new File(fileName));
     }
 
-    public static class Builder {
+    public static class Builder extends WriteOptions.Builder {
 
-        private Writer writer;
         private boolean header = true;
         private char separator = ',';
         private String lineEnd = System.lineSeparator();
         private char escapeChar = '\\';
         private char quoteChar = '"';
 
-        public Builder(String fileName) throws IOException {
-            File file = Paths.get(fileName).toFile();
-            this.writer = new FileWriter(file);
+        protected Builder(String fileName) throws IOException {
+            super(Paths.get(fileName).toFile());
+        }
+
+        protected Builder(Destination dest) {
+            super(dest);
         }
 
-        public Builder(File file) throws IOException {
-            this.writer = new FileWriter(file);
+        protected Builder(File file) throws IOException {
+            super(file);
         }
 
-        public Builder(Writer writer) {
-            this.writer = writer;
+        protected Builder(Writer writer) {
+            super(writer);
         }
 
-        public Builder(OutputStream stream) {
-            this.writer = new OutputStreamWriter(stream);
+        protected Builder(OutputStream stream) {
+            super(stream);
         }
 
         public CsvWriteOptions.Builder separator(char separator) {
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvWriter.java b/core/src/main/java/tech/tablesaw/io/csv/CsvWriter.java
index 691361f6..d5d37584 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvWriter.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvWriter.java
@@ -14,53 +14,42 @@
 
 package tech.tablesaw.io.csv;
 
+import javax.annotation.concurrent.Immutable;
+
 import com.univocity.parsers.csv.CsvWriterSettings;
-import tech.tablesaw.api.Table;
 
-import javax.annotation.concurrent.Immutable;
-import java.io.Writer;
+import tech.tablesaw.api.Table;
+import tech.tablesaw.io.DataWriter;
+import tech.tablesaw.io.Destination;
+import tech.tablesaw.io.WriterRegistry;
 
 /**
  * Class that writes tables and individual columns to CSV files
  */
 @Immutable
-final public class CsvWriter {
+final public class CsvWriter implements DataWriter<CsvWriteOptions> {
 
+    private static final CsvWriter INSTANCE = new CsvWriter();
     private static final String nullValue = "";
 
-    private final Table table;
-    private final boolean header;
-    private final Writer writer;
-    private final CsvWriterSettings settings;
-
-    /**
-     * Private constructor to prevent instantiation
-     */
-    public CsvWriter(Table table, CsvWriteOptions options) {
-        this.table = table;
-        this.header = options.header();
-        this.writer = options.writer();
-
-        this.settings = new CsvWriterSettings();
-        // Sets the character sequence to write for the values that are null.
-        settings.setNullValue(nullValue);
-        settings.getFormat().setDelimiter(options.separator());
-        settings.getFormat().setQuote(options.quoteChar());
-        settings.getFormat().setQuoteEscape(options.escapeChar());
-        settings.getFormat().setLineSeparator(options.lineEnd());
-        // writes empty lines as well.
-        settings.setSkipEmptyLines(false);
-
+    static {
+	register(Table.defaultWriterRegistry);
     }
 
-    public void write() {
+    public static void register(WriterRegistry registry) {
+	registry.registerExtension("csv", INSTANCE);
+	registry.registerOptions(CsvWriteOptions.class, INSTANCE);
+    }
 
+    public void write(Table table, CsvWriteOptions options) {
+	CsvWriterSettings settings = createSettings(options);
+	
         com.univocity.parsers.csv.CsvWriter csvWriter = null;
         // Creates a writer with the above settings;
         try {
+            csvWriter = new com.univocity.parsers.csv.CsvWriter(options.destination().createWriter(), settings);
 
-            csvWriter = new com.univocity.parsers.csv.CsvWriter(writer, settings);
-            if (header) {
+            if (options.header()) {
                 String[] header = new String[table.columnCount()];
                 for (int c = 0; c < table.columnCount(); c++) {
                     header[c] = table.column(c).name();
@@ -83,32 +72,23 @@ final public class CsvWriter {
         }
     }
 
-    public String getNullValue() {
-        return settings.getNullValue();
-    }
-
-    public Table getTable() {
-        return table;
-    }
-
-    public char getQuoteCharacter() {
-        return settings.getFormat().getQuote();
-    }
-
-    public boolean getHeader() {
-        return header;
-    }
-
-    public char getEscapeChar() {
-        return settings.getFormat().getQuoteEscape();
-    }
-
-    public char getSeparator() {
-        return settings.getFormat().getDelimiter();
+    protected static CsvWriterSettings createSettings(CsvWriteOptions options) {
+	CsvWriterSettings settings = new CsvWriterSettings();
+        // Sets the character sequence to write for the values that are null.
+        settings.setNullValue(nullValue);
+        settings.getFormat().setDelimiter(options.separator());
+        settings.getFormat().setQuote(options.quoteChar());
+        settings.getFormat().setQuoteEscape(options.escapeChar());
+        settings.getFormat().setLineSeparator(options.lineEnd());
+        // writes empty lines as well.
+        settings.setSkipEmptyLines(false);
+        return settings;
     }
 
-    public String getLineEnd() {
-        return new String(settings.getFormat().getLineSeparator());
+    @Override
+    public void write(Table table, Destination dest) {
+	write(table, CsvWriteOptions.builder(dest).build());
     }
+    
 }
 
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
index 34b483c1..e51e0173 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
@@ -20,8 +20,10 @@ import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.io.ReadOptions;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.net.URL;
 import java.util.Locale;
 
 public class FixedWidthReadOptions extends ReadOptions {
@@ -68,8 +70,8 @@ public class FixedWidthReadOptions extends ReadOptions {
      * 2. Provide the array of column types as an option. If you provide the columnType array,
      * we skip type detection and can avoid reading the entire file
      */
-    public static Builder builder(InputStream stream, String tableName) {
-        return new Builder(stream).tableName(tableName);
+    public static Builder builder(InputStream stream) {
+        return new Builder(stream);
     }
     /**
      * This method may cause tablesaw to buffer the entire InputStream.
@@ -80,9 +82,8 @@ public class FixedWidthReadOptions extends ReadOptions {
      * 2. Provide the array of column types as an option. If you provide the columnType array,
      * we skip type detection and can avoid reading the entire file
      */
-    public static Builder builder(Reader reader, String tableName) {
-        Builder builder = new Builder(reader);
-        return builder.tableName(tableName);
+    public static Builder builder(Reader reader) {
+        return new Builder(reader);
     }
 
     public ColumnType[] columnTypes() {
@@ -133,6 +134,10 @@ public class FixedWidthReadOptions extends ReadOptions {
         protected ColumnType[] columnTypes;
         protected Integer maxNumberOfColumns = 10_000;
 
+        protected Builder(URL url) throws IOException {
+            super(url);
+        }
+
         protected Builder(File file) {
             super(file);
         }
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java
index 59d2c66a..f75067a2 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java
@@ -22,7 +22,6 @@ import com.univocity.parsers.fixed.FixedWidthParserSettings;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.io.FileReader;
-import tech.tablesaw.io.TableBuildingUtils;
 
 import javax.annotation.concurrent.Immutable;
 
@@ -60,16 +59,16 @@ public class FixedWidthReader extends FileReader {
         byte[] bytesCache = null;
 
         if (types == null) {
-            Reader reader = TableBuildingUtils.createReader(options, bytesCache);
-            if (options.file() == null) {
+            Reader reader = options.source().createReader(bytesCache);
+            if (options.source().file() == null) {
                 bytesCache = CharStreams.toString(reader).getBytes();
                 // create a new reader since we just exhausted the existing one
-                reader = TableBuildingUtils.createReader(options, bytesCache);
+                reader = options.source().createReader(bytesCache);
             }
             types = detectColumnTypes(reader, options);
         }
 
-        return Pair.create(TableBuildingUtils.createReader(options, bytesCache), types);
+        return Pair.create(options.source().createReader(bytesCache), types);
     }
 
     public Table read(FixedWidthReadOptions options) throws IOException {
@@ -86,7 +85,7 @@ public class FixedWidthReader extends FileReader {
         try {
             return parseRows(options, headerOnly, reader, types, parser);
         } finally {
-            if (options.reader() == null) {
+            if (options.source().reader() == null) {
                 // if we get a reader back from options it means the client opened it, so let the client close it
                 // if it's null, we close it here.
                 parser.stopParsing();
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriteOptions.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriteOptions.java
index 98d8da01..7406446a 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriteOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriteOptions.java
@@ -156,6 +156,14 @@ public class FixedWidthWriteOptions {
         return builder(new File(fileName));
     }
 
+    public static Builder builder(OutputStream stream) {
+        return new Builder(stream);
+    }
+
+    public static Builder builder(Writer writer) {
+        return new Builder(writer);
+    }
+
     public static class Builder {
 
         private Writer writer;
@@ -181,21 +189,20 @@ public class FixedWidthWriteOptions {
         private char lookupWildcard = '?';
         private char normalizedNewline = '\n';
 
-
-        public Builder(String fileName) throws IOException {
+        protected Builder(String fileName) throws IOException {
             File file = Paths.get(fileName).toFile();
             this.writer = new FileWriter(file);
         }
 
-        public Builder(File file) throws IOException {
+        protected Builder(File file) throws IOException {
             this.writer = new FileWriter(file);
         }
 
-        public Builder(Writer writer) {
+        protected Builder(Writer writer) {
             this.writer = writer;
         }
 
-        public Builder(OutputStream stream) {
+        protected Builder(OutputStream stream) {
             this.writer = new OutputStreamWriter(stream);
         }
 
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriter.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriter.java
index 05b7cff8..fc21cdba 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriter.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthWriter.java
@@ -27,7 +27,6 @@ import java.io.Writer;
 @Immutable
 final public class FixedWidthWriter {
 
-    private final Table table;
     private final boolean header;
     private final Writer writer;
     private final FixedWidthWriterSettings settings;
@@ -36,8 +35,7 @@ final public class FixedWidthWriter {
     /**
      * Private constructor to prevent instantiation
      */
-    public FixedWidthWriter(Table table, FixedWidthWriteOptions options) {
-        this.table = table;
+    public FixedWidthWriter(FixedWidthWriteOptions options) {
         this.header = options.header();
         this.writer = options.writer();
         this.settings = fixedWidthWriterSettings(options);
@@ -45,7 +43,7 @@ final public class FixedWidthWriter {
         settings.setFormat(format);
     }
 
-    public void write() {
+    public void write(Table table) {
 
         com.univocity.parsers.fixed.FixedWidthWriter fixedWidthWriter = null;
         // Creates a writer with the above settings;
@@ -75,10 +73,6 @@ final public class FixedWidthWriter {
         }
     }
 
-    public Table getTable() {
-        return table;
-    }
-
     public boolean getHeader() {
         return header;
     }
@@ -91,7 +85,6 @@ final public class FixedWidthWriter {
         return format;
     }
 
-
     private FixedWidthFormat fixedWidthFormat(FixedWidthWriteOptions options) {
         FixedWidthFormat format = new FixedWidthFormat();
 
diff --git a/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java b/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java
index effba8b3..c8e95821 100644
--- a/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java
+++ b/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java
@@ -87,10 +87,10 @@ public class SqlResultSetReader {
      *
      * @throws SQLException if there is a problem detected in the database
      */
-    public static Table read(ResultSet resultSet, String tableName) throws SQLException {
+    public static Table read(ResultSet resultSet) throws SQLException {
 
         ResultSetMetaData metaData = resultSet.getMetaData();
-        Table table = Table.create(tableName);
+        Table table = Table.create();
 
         // Setup the columns and add to the table
         for (int i = 1; i <= metaData.getColumnCount(); i++) {
diff --git a/core/src/main/java/tech/tablesaw/io/string/DataFramePrinter.java b/core/src/main/java/tech/tablesaw/io/string/DataFramePrinter.java
index 9b734ad7..85979b05 100644
--- a/core/src/main/java/tech/tablesaw/io/string/DataFramePrinter.java
+++ b/core/src/main/java/tech/tablesaw/io/string/DataFramePrinter.java
@@ -127,7 +127,9 @@ public class DataFramePrinter {
                 capacity = 0;
             }
             final StringBuilder text = new StringBuilder(capacity);
-            text.append(tableName(frame, totalWidth)).append(System.lineSeparator());
+            if (frame.name() != null) {
+                text.append(tableName(frame, totalWidth)).append(System.lineSeparator());
+            }
             final String headerLine = String.format(headerTemplate, (Object[]) headers);
             text.append(headerLine).append(System.lineSeparator());
             for (int j = 0; j < totalWidth; j++) {
diff --git a/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java b/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java
index d70cfeb3..14378ffd 100644
--- a/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java
+++ b/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReader.java
@@ -45,7 +45,15 @@ import tech.tablesaw.columns.Column;
 @Immutable
 public class XlsxReader {
 
-    public List<Table> read(XlsxReadOptions options) throws IOException {
+    public Table read(XlsxReadOptions options) throws IOException {
+        List<Table> tables = readMultiple(options);
+        if (tables.isEmpty()) {
+            throw new IllegalArgumentException("No tables found.");            
+        }
+        return tables.get(0);
+    }
+
+    public List<Table> readMultiple(XlsxReadOptions options) throws IOException {
         byte[] bytes = null;
         InputStream input = getInputStream(options, bytes);
         List<Table> tables = new ArrayList<Table>();
diff --git a/html/src/main/java/tech/tablesaw/io/html/HtmlTableWriter.java b/html/src/main/java/tech/tablesaw/io/html/HtmlTableWriter.java
index fb912045..41956fe2 100644
--- a/html/src/main/java/tech/tablesaw/io/html/HtmlTableWriter.java
+++ b/html/src/main/java/tech/tablesaw/io/html/HtmlTableWriter.java
@@ -14,7 +14,6 @@
 
 package tech.tablesaw.io.html;
 
-import com.google.common.annotations.VisibleForTesting;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.columns.Column;
 
@@ -25,18 +24,9 @@ import java.util.List;
 import static tech.tablesaw.io.ParsingUtils.splitCamelCase;
 import static tech.tablesaw.io.ParsingUtils.splitOnUnderscore;
 
-/**
- * Static utility that writes Tables in HTML format for display
- */
-public final class HtmlTableWriter {
-
-    /**
-     * Private constructor to prevent instantiation
-     */
-    private HtmlTableWriter() {
-    }
+public class HtmlTableWriter {
 
-    public static String write(Table table) {
+    public String write(Table table) {
         StringBuilder builder = new StringBuilder();
         builder.append(header(table.columnNames()));
         builder.append("<tbody>")
@@ -48,7 +38,7 @@ public final class HtmlTableWriter {
         return builder.toString();
     }
 
-    public static void write(Table table, OutputStream outputStream) {
+    public void write(Table table, OutputStream outputStream) {
         try (PrintWriter p = new PrintWriter(outputStream)) {
             p.println(write(table));
         }
@@ -57,7 +47,6 @@ public final class HtmlTableWriter {
     /**
      * Returns a string containing the html output of one table row
      */
-    @VisibleForTesting
     private static String row(int row, Table table) {
         StringBuilder builder = new StringBuilder()
                 .append("<tr>");
@@ -74,7 +63,6 @@ public final class HtmlTableWriter {
         return builder.toString();
     }
 
-    @VisibleForTesting
     private static String header(List<String> columnNames) {
         StringBuilder builder = new StringBuilder()
                 .append("<thead>")


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/TestData.java b/core/src/test/java/tech/tablesaw/TestData.java
index 0d3f3078..dfd288d9 100644
--- a/core/src/test/java/tech/tablesaw/TestData.java
+++ b/core/src/test/java/tech/tablesaw/TestData.java
@@ -79,7 +79,7 @@ public enum TestData {
         this.columnNames = columnNames;
         try {
             this.table = Table.read().csv(CsvReadOptions
-                    .builder(new FileInputStream(csvSource),"")
+                    .builder(new FileInputStream(csvSource))
                     .columnTypes(columnTypes));
         } catch (IOException e) {
             throw new IllegalStateException("Unable to read from CSV file", e);
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
index fc2d8ae4..90298b31 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
@@ -134,7 +134,7 @@ public class CsvReaderTest {
     @Test
     public void testDataTypeDetection() throws IOException {
         Reader reader = new FileReader("../data/bus_stop_test.csv");
-        CsvReadOptions options = CsvReadOptions.builder(reader, "")
+        CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(true)
                 .minimizeColumnSizes(true)
                 .separator(',')
@@ -170,7 +170,7 @@ public class CsvReaderTest {
         final char delimiter = ',';
         final boolean useSampling = true;
 
-        CsvReadOptions options = CsvReadOptions.builder(reader, "")
+        CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(header)
                 .separator(delimiter)
                 .sample(useSampling)
@@ -198,7 +198,7 @@ public class CsvReaderTest {
         final char delimiter = ',';
         final boolean useSampling = true;
 
-        CsvReadOptions options = CsvReadOptions.builder(reader, "")
+        CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(header)
                 .separator(delimiter)
                 .sample(useSampling)
@@ -227,7 +227,7 @@ public class CsvReaderTest {
         final char delimiter = ',';
         final boolean useSampling = true;
 
-        CsvReadOptions options = CsvReadOptions.builder(reader, "")
+        CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(header)
                 .separator(delimiter)
                 .sample(useSampling)
@@ -255,7 +255,7 @@ public class CsvReaderTest {
         final char delimiter = ',';
         final boolean useSampling = true;
 
-        CsvReadOptions options = CsvReadOptions.builder(reader, "")
+        CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(header)
                 .separator(delimiter)
                 .sample(useSampling)
@@ -293,7 +293,7 @@ public class CsvReaderTest {
             + "foobar5\r\n"
             + "foobar6\r\n");
 
-        Table t = Table.read().csv(reader, "test table");
+        Table t = Table.read().csv(reader);
         assertEquals(1, t.columnCount());
         assertEquals(6, t.rowCount());
     }
@@ -349,7 +349,7 @@ public class CsvReaderTest {
     @Test
     public void testDataTypeDetection2() throws IOException {
         Reader reader = new FileReader("../data/bush.csv");
-        CsvReadOptions options = CsvReadOptions.builder(reader, "")
+        CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(true)
                 .separator(',')
                 .sample(false)
@@ -368,7 +368,8 @@ public class CsvReaderTest {
         Table table;
         try (InputStream input = new File("../data/bush.csv").toURI().toURL().openStream()) {
             table = Table.read().csv(CsvReadOptions
-                    .builder(input, "Bush approval ratings")
+                    .builder(input)
+                    .tableName("Bush approval ratings")
                     .columnTypes(types));
         }
         assertNotNull(table);
@@ -383,7 +384,8 @@ public class CsvReaderTest {
         Table table;
         try (InputStream input = new File("../data/bush.csv").toURI().toURL().openStream()) {
             table = Table.read().csv(CsvReadOptions
-                    .builder(input, "Bush approval ratings"));
+                    .builder(input)
+                    .tableName("Bush approval ratings"));
         }
         assertNotNull(table);
         assertEquals(3, table.columnCount());
@@ -399,7 +401,8 @@ public class CsvReaderTest {
         File file = Paths.get(location).toFile();
         try (InputStream input = new FileInputStream(file)) {
             table = Table.read().csv(CsvReadOptions
-                    .builder(input, "Bush approval ratings"));
+                    .builder(input)
+                    .tableName("Bush approval ratings"));
         }
         assertNotNull(table);
         assertEquals(3, table.columnCount());
@@ -415,7 +418,8 @@ public class CsvReaderTest {
         File file = Paths.get(location).toFile();
         try (Reader reader = new FileReader(file)) {
             table = Table.read().csv(CsvReadOptions
-                    .builder(reader, "Bush approval ratings"));
+                    .builder(reader)
+                    .tableName("Bush approval ratings"));
         }
         assertNotNull(table);
         assertEquals(3, table.columnCount());
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvWriteOptionsTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvWriteOptionsTest.java
index c4c36341..520160fa 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvWriteOptionsTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvWriteOptionsTest.java
@@ -7,19 +7,14 @@ import java.io.ByteArrayOutputStream;
 
 import org.junit.jupiter.api.Test;
 
-import tech.tablesaw.api.StringColumn;
-import tech.tablesaw.api.Table;
+import com.univocity.parsers.csv.CsvWriterSettings;
 
 public class CsvWriteOptionsTest {
 
     @Test
     public void testSettingsPropagation() {
-
-        Table test = Table.create("test", StringColumn.create("t"));
-        test.stringColumn(0).appendCell("testing");
-
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        CsvWriteOptions options = new CsvWriteOptions.Builder(stream)
+        CsvWriteOptions options = CsvWriteOptions.builder(stream)
                 .escapeChar('~')
                 .header(true)
                 .lineEnd("\r\n")
@@ -30,12 +25,12 @@ public class CsvWriteOptionsTest {
         assertTrue(options.header());
         assertEquals('"', options.quoteChar());
         assertEquals('.', options.separator());
+        
+        CsvWriterSettings settings = CsvWriter.createSettings(options);
 
-        CsvWriter writer = new CsvWriter(test, options);
-        assertEquals('~', writer.getEscapeChar());
-        assertTrue(writer.getHeader());
-        assertEquals("\r\n", writer.getLineEnd());
-        assertEquals('"', writer.getQuoteCharacter());
-        assertEquals('.', writer.getSeparator());
+        assertEquals('~', settings.getFormat().getQuoteEscape());
+        assertEquals("\r\n", settings.getFormat().getLineSeparatorString());
+        assertEquals('"', settings.getFormat().getQuote());
+        assertEquals('.', settings.getFormat().getDelimiter());
     }
 }
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java
new file mode 100644
index 00000000..8eba63cd
--- /dev/null
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java
@@ -0,0 +1,26 @@
+package tech.tablesaw.io.csv;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.StringWriter;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import tech.tablesaw.api.StringColumn;
+import tech.tablesaw.api.Table;
+
+public class CsvWriterTest {
+
+    @Test
+    public void toWriterWithExtension() {
+	StringColumn colA = StringColumn.create("colA", ImmutableList.of("a","b"));
+	StringColumn colB = StringColumn.create("colB", ImmutableList.of("1","2"));
+	Table table = Table.create("testTable", colA, colB);
+	StringWriter writer = new StringWriter();
+	table.write().toWriter(writer, "csv");
+	assertEquals("colA,colB\na,1\nb,2\n", writer.toString());
+    }
+
+}
diff --git a/core/src/test/java/tech/tablesaw/io/csv/UnicodeBomHandlingTest.java b/core/src/test/java/tech/tablesaw/io/csv/UnicodeBomHandlingTest.java
index 85630237..a734c224 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/UnicodeBomHandlingTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/UnicodeBomHandlingTest.java
@@ -26,11 +26,11 @@ public class UnicodeBomHandlingTest {
     @Test
     public void javaBehaviour() throws IOException {
 
-        Table t = new CsvReader().read(CsvReadOptions.builder(
-                new InputStreamReader(new ByteArrayInputStream(CONTENT)), "R").header(false).build());
+        Table t = Table.read().csv(CsvReadOptions.builder(
+                new InputStreamReader(new ByteArrayInputStream(CONTENT))).tableName("R").header(false).build());
         assertEquals(1, t.get(0, 0));
-        t = new CsvReader().read(CsvReadOptions.builder(
-                new InputStreamReader(new ByteArrayInputStream(UTF8_BOM_CONTENT)), "R").header(false).build());
+        t = Table.read().csv(CsvReadOptions.builder(
+                new InputStreamReader(new ByteArrayInputStream(UTF8_BOM_CONTENT))).tableName("R").header(false).build());
         assertEquals(1, t.get(0, 0));
     }
 
diff --git a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
index c5fa59a2..dda3edf3 100644
--- a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
@@ -106,7 +106,7 @@ public class FixedWidthReaderTest {
     public void testDataTypeDetection() throws Exception {
 
         InputStream stream = new FileInputStream(new File("../data/fixed_width_cars_test.txt"));
-        FixedWidthReadOptions options = FixedWidthReadOptions.builder(stream, "")
+        FixedWidthReadOptions options = FixedWidthReadOptions.builder(stream)
                 .header(true)
                 .columnSpecs(car_fields_specs)
                 .padding('_')
@@ -125,7 +125,7 @@ public class FixedWidthReaderTest {
     public void testWithMissingValue() throws Exception {
 
         Reader reader = new FileReader("../data/fixed_width_missing_values.txt");
-        FixedWidthReadOptions options = FixedWidthReadOptions.builder(reader, "")
+        FixedWidthReadOptions options = FixedWidthReadOptions.builder(reader)
                 .header(true)
                 .columnSpecs(car_fields_specs)
                 .padding('_')
diff --git a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthWriteOptionsTest.java b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthWriteOptionsTest.java
index 8eb24712..c2c02408 100644
--- a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthWriteOptionsTest.java
+++ b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthWriteOptionsTest.java
@@ -7,17 +7,10 @@ import java.io.ByteArrayOutputStream;
 
 import org.junit.jupiter.api.Test;
 
-import tech.tablesaw.api.StringColumn;
-import tech.tablesaw.api.Table;
-
 public class FixedWidthWriteOptionsTest {
 
     @Test
     public void testSettingsPropagation() {
-
-        Table test = Table.create("test", StringColumn.create("t"));
-        test.stringColumn(0).appendCell("testing");
-
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         FixedWidthWriteOptions options = new FixedWidthWriteOptions.Builder(stream)
                 .header(true)
@@ -27,7 +20,7 @@ public class FixedWidthWriteOptionsTest {
 
         assertTrue(options.header());
 
-        FixedWidthWriter writer = new FixedWidthWriter(test, options);
+        FixedWidthWriter writer = new FixedWidthWriter(options);
         assertTrue(writer.getHeader());
         assertEquals("\r\n", writer.getFormat().getLineSeparatorString());
         assertEquals('~', writer.getFormat().getPadding());
@@ -35,10 +28,6 @@ public class FixedWidthWriteOptionsTest {
 
     @Test
     public void testSettingsAutoConfigurationPropagation() {
-
-        Table test = Table.create("test", StringColumn.create("t"));
-        test.stringColumn(0).appendCell("testing");
-
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         FixedWidthWriteOptions options = new FixedWidthWriteOptions.Builder(stream)
                 .autoConfigurationEnabled(true)
@@ -48,7 +37,7 @@ public class FixedWidthWriteOptionsTest {
 
         assertTrue(options.header());
 
-        FixedWidthWriter writer = new FixedWidthWriter(test, options);
+        FixedWidthWriter writer = new FixedWidthWriter(options);
         assertTrue(writer.getHeader());
     }
 
diff --git a/core/src/test/java/tech/tablesaw/io/html/HtmlTableWriterTest.java b/core/src/test/java/tech/tablesaw/io/html/HtmlTableWriterTest.java
index 75c710e9..d9089d54 100644
--- a/core/src/test/java/tech/tablesaw/io/html/HtmlTableWriterTest.java
+++ b/core/src/test/java/tech/tablesaw/io/html/HtmlTableWriterTest.java
@@ -38,6 +38,6 @@ public class HtmlTableWriterTest {
         StringColumn byColumn = table.stringColumn("who");
         TableSliceGroup group = StandardTableSliceGroup.create(table, byColumn);
         Table result = group.aggregate("approval", AggregateFunctions.mean);
-        HtmlTableWriter.write(result);
+        new HtmlTableWriter().write(result);
     }
 }
diff --git a/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java b/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java
index a392371a..62257d68 100644
--- a/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java
@@ -68,28 +68,28 @@ public class SqlResultSetReaderTest {
 
             sql = "SELECT * FROM coffee";
             try (ResultSet rs = stmt.executeQuery(sql)) {
-                Table coffee = SqlResultSetReader.read(rs, "Coffee");
+                Table coffee = SqlResultSetReader.read(rs);
                 assertEquals(4, coffee.columnCount());
                 assertEquals(18, coffee.rowCount());
             }
 
             sql = "SELECT * FROM Customer";
             try (ResultSet rs = stmt.executeQuery(sql)) {
-                Table customer = SqlResultSetReader.read(rs, "Customer");
+                Table customer = SqlResultSetReader.read(rs);
                 assertEquals(7, customer.columnCount());
                 assertEquals(3, customer.rowCount());
             }
 
             sql = "SELECT * FROM UnpaidOrder";
             try (ResultSet rs = stmt.executeQuery(sql)) {
-                Table unpaidInvoice = SqlResultSetReader.read(rs, "Unpaid Invoice");
+                Table unpaidInvoice = SqlResultSetReader.read(rs);
                 assertEquals(5, unpaidInvoice.columnCount());
                 assertEquals(0, unpaidInvoice.rowCount());
             }
 
             sql = "SELECT * FROM Numbers";
             try (ResultSet rs = stmt.executeQuery(sql)) {
-                Table numbers = SqlResultSetReader.read(rs, "Numbers");
+                Table numbers = SqlResultSetReader.read(rs);
                 assertEquals(13, numbers.columnCount());
                 assertEquals(3, numbers.rowCount());
                 assertTrue(numbers.column("Description").type() instanceof StringColumnType);