class Builder {

    protected final Source source;
    protected String tableName = "";
    protected List<ColumnType> columnTypesToDetect = DEFAULT_TYPES;
    protected boolean sample = true;
    protected String dateFormat;
    protected DateTimeFormatter dateFormatter;
    protected String timeFormat;
    protected DateTimeFormatter timeFormatter;
    protected String dateTimeFormat;
    protected DateTimeFormatter dateTimeFormatter;
    protected Locale locale = Locale.getDefault();
    protected String[] missingValueIndicators = new String[0];
    protected boolean minimizeColumnSizes = false;
    protected boolean header = true;
    protected int maxCharsPerColumn = 4096;
    protected boolean ignoreZeroDecimal = DEFAULT_IGNORE_ZERO_DECIMAL;
    protected boolean skipRowsWithInvalidColumnCount = DEFAULT_SKIP_ROWS_WITH_INVALID_COLUMN_COUNT;
    private boolean allowDuplicateColumnNames = false;
    protected ColumnType[] columnTypes;
    protected Map<String, ColumnType> columnTypeMap = new HashMap<>();
    protected Function<String, Optional<ColumnType>> columnTypeFunction;
    protected Function<String, ColumnType> completeColumnTypeFunction;

    protected Builder() {
      source = null;
    }

    protected Builder(Source source) {
      this.source = source;
    }

    protected Builder(File file) {
      this.source = new Source(file);
      this.tableName = file.getName();
    }

    protected Builder(URL url) throws IOException {
      this.source = new Source(url.openStream());
      this.tableName = url.toString();
    }

    protected Builder(InputStream stream) {
      this.source = new Source(stream);
    }

    protected Builder(InputStreamReader reader) {
      this.source = new Source(reader);
    }

    protected Builder(Reader reader) {
      this.source = new Source(reader);
    }

    public Builder tableName(String tableName) {
      this.tableName = tableName;
      return this;
    }

    public Builder header(boolean hasHeader) {
      this.header = hasHeader;
      return this;
    }

    public Builder dateFormat(DateTimeFormatter dateFormat) {
      this.dateFormatter = dateFormat;
      return this;
    }

    public Builder allowDuplicateColumnNames(Boolean allow) {
      this.allowDuplicateColumnNames = allow;
      return this;
    }

    public Builder timeFormat(DateTimeFormatter dateFormat) {
      this.timeFormatter = dateFormat;
      return this;
    }

    public Builder dateTimeFormat(DateTimeFormatter dateFormat) {
      this.dateTimeFormatter = dateFormat;
      return this;
    }

    public Builder missingValueIndicator(String... missingValueIndicators) {
      this.missingValueIndicators = missingValueIndicators;
      return this;
    }

    public Builder maxCharsPerColumn(int maxCharsPerColumn) {
      this.maxCharsPerColumn = maxCharsPerColumn;
      return this;
    }

    /** Ignore zero value decimals in data values. Defaults to {@code true}. */
    public Builder ignoreZeroDecimal(boolean ignoreZeroDecimal) {
      this.ignoreZeroDecimal = ignoreZeroDecimal;
      return this;
    }

    /** Skip the rows with invalid column count in data values. Defaluts to {@code false}. */
    public Builder skipRowsWithInvalidColumnCount(boolean skipRowsWithInvalidColumnCount) {
      this.skipRowsWithInvalidColumnCount = skipRowsWithInvalidColumnCount;
      return this;
    }

    public Builder sample(boolean sample) {
      this.sample = sample;
      return this;
    }

    public Builder locale(Locale locale) {
      this.locale = locale;
      return this;
    }

    /** @see ColumnTypeDetector */
    public Builder columnTypesToDetect(List<ColumnType> columnTypesToDetect) {<FILL_FUNCTION_BODY>}

    /**
     * Allow the {@link ColumnTypeDetector} to choose shorter column types such as float instead of
     * double when the data will fit in a smaller type
     */
    public Builder minimizeColumnSizes() {
      this.columnTypesToDetect = EXTENDED_TYPES;
      return this;
    }

    /**
     * Provide column types for all columns skipping autodetect column type logic. The array must
     * contain a ColumnType for each column in the table. An error will be thrown if they don't
     * match up
     */
    public Builder columnTypes(ColumnType[] columnTypes) {
      if (columnTypeOptionsAlreadySet()) {
        throw new IllegalStateException("columnTypes already set");
      }
      this.columnTypes = columnTypes;
      return this;
    }

    /**
     * Provide a function that determines ColumnType for all column names. To provide only for some
     * use {@link #columnTypesPartial(Function)}
     *
     * <p>This method is generally more efficient because it skips column type detection
     */
    public Builder columnTypes(Function<String, ColumnType> columnTypeFunction) {
      if (columnTypeOptionsAlreadySet()) {
        throw new IllegalStateException("columnTypes already set");
      }
      this.completeColumnTypeFunction = columnTypeFunction;
      return this;
    }

    /**
     * Provide a function that determines ColumnType for some column names. To provide for all
     * column names use {@link #columnTypes(Function)} that generally is more efficient because it
     * skips column type detection
     */
    public Builder columnTypesPartial(Function<String, Optional<ColumnType>> columnTypeFunction) {
      if (columnTypeOptionsAlreadySet()) {
        throw new IllegalStateException("columnTypes already set");
      }
      this.columnTypeFunction = columnTypeFunction;
      return this;
    }

    /**
     * Provide a map that determines ColumnType for given column names. Types for not present column
     * names will be autodetected. To provide type for all column names use {@link
     * #columnTypes(Function)} that generally is more efficient because it skips column type
     * detection
     */
    public Builder columnTypesPartial(Map<String, ColumnType> columnTypeByName) {
      if (columnTypeOptionsAlreadySet()) {
        throw new IllegalStateException("columnTypes already set");
      }
      if (columnTypeByName != null) {
        this.columnTypeMap = columnTypeByName;
      }
      return this;
    }

    private boolean columnTypeOptionsAlreadySet() {
      return columnTypes != null
          || columnTypeFunction != null
          || completeColumnTypeFunction != null
          || !columnTypeMap.isEmpty();
    }

    public ReadOptions build() {
      return new ReadOptions(this);
    }
  }

      // Types need to be in certain order as more general types like string come last
      // Otherwise everything will be parsed as a string
      List<ColumnType> orderedTypes = new ArrayList<>();
      for (ColumnType t : EXTENDED_TYPES) {
        if (columnTypesToDetect.contains(t)) {
          orderedTypes.add(t);
        }
      }
      this.columnTypesToDetect = orderedTypes;
      return this;
    
<no_super_class>