CSV.java

243 lines | 6.486 kB Blame History Raw Download
package br.ufrgs.inf.prosoft.cache.tools;

import br.ufrgs.inf.prosoft.cache.tools.CSV.Row;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

public class CSV implements Iterable<Row> {

  private final List<Header> headers;
  private final List<Row> rows;
  private final List<Column> columns;
  private final Map<Header, Map<Reference, Value>> indexes;

  public CSV(String... headers) {
    this(headers, new String[]{});
  }

  public CSV(String[] headers, String[] indexes) {
    this.headers = new ArrayList<>();
    this.indexes = new HashMap<>();
    this.columns = new ArrayList<>();
    this.rows = new ArrayList<>();

    ArrayList<String> indexesList = new ArrayList<>(Arrays.asList(indexes));

    for (String strHeader : headers) {
      Header header = new Header(strHeader);
      this.headers.add(header);
      if (indexesList.contains(header.toString())) {
        indexesList.remove(header.toString());
        this.indexes.put(header, new HashMap<>());
      }
      this.columns.add(new Column());
    }
    if (!indexesList.isEmpty()) throw new RuntimeException("indexes not present on headers: " + indexesList);
  }

  private static String[] process(String line) {
    if (line.contains("'") || line.contains("\"")) throw new UnsupportedOperationException("Scape values is not supported yet");
    return line.split(",");
  }

  public static CSV read(String path) {
    return read(path, new String[]{});
  }

  public static CSV read(String path, String... indexes) {
    try {
      List<String> lines = Files.readAllLines(Paths.get(path));
      if (lines.isEmpty()) throw new RuntimeException("Empty file");
      String headers = lines.remove(0);
      CSV csv = new CSV(process(headers), indexes);
      lines.forEach(line -> csv.append(process(line)));
      return csv;
    } catch (IOException ex) {
      throw new RuntimeException("Cannot read file");
    }
  }

  public CSV append(String... values) {
    if (values.length != this.headers.size()) throw new RuntimeException("Values not matching headers length");
    List<Value> list = new ArrayList<>();
    Row row = new Row(list);
    Iterator<Column> columnsIterator = this.columns.iterator();
    Iterator<Header> headersIterator = this.headers.iterator();
    for (String strValue : values) {
      Header header = headersIterator.next();
      Reference reference = new Reference(strValue);
      Value value = new Value(header, reference, row, columnsIterator.next());
      list.add(value);
      if (this.indexes.containsKey(header)) {
        if (this.indexes.get(header).get(reference) != null) throw new RuntimeException("duplicate index " + reference + " for " + header);
        this.indexes.get(header).put(reference, value);
      }
    }
    this.rows.add(row);
    return this;
  }

  public Optional<Row> selectFirst(String property, String value) {
    Header mockProperty = new Header(property);
    Reference mockValue = new Reference(value);
    if (!this.headers.contains(mockProperty)) throw new RuntimeException("header not present");
    Map<Reference, Value> values = this.indexes.get(mockProperty);
    if (values != null) return Optional.ofNullable(values.get(mockValue).getRow());

    for (Row row : this) if (row.get(property).equals(value)) return Optional.ofNullable(row);
    return Optional.empty();
  }

  public List<Row> select(String property, String value) {
    Header mockProperty = new Header(property);
    Reference mockValue = new Reference(value);
    if (!this.headers.contains(mockProperty)) throw new RuntimeException("header not present");
    List<Row> select = new ArrayList<>();

    Map<Reference, Value> values = this.indexes.get(mockProperty);
    if (values != null) {
      select.add(values.get(mockValue).getRow());
      return select;
    }

    for (Row row : this) if (row.get(property).equals(value)) select.add(row);
    return select;
  }

  @Override
  public Iterator<Row> iterator() {
    return this.rows.iterator();
  }

  public Stream<Row> stream() {
    return this.rows.stream();
  }

  private static class Reference {

    String value;

    public Reference() {
    }

    public Reference(String value) {
      this.value = value;
    }

    @Override
    public int hashCode() {
      int hash = 7;
      hash = 11 * hash + Objects.hashCode(this.value);
      return hash;
    }

    @Override
    public boolean equals(Object obj) {
      if (this.value == null) return obj == null;
      if (obj instanceof Reference) return this.value.equals(((Reference) obj).value);
      if (obj instanceof String) return this.value.equals(obj);
      return false;
    }

    @Override
    public String toString() {
      return value;
    }
  }

  private static class Header extends Reference {

    public Header(String value) {
      super(value);
    }

  }

  private class Value {

    private final Header header;
    private final Row row;
    private final Column column;
    private Reference reference;

    public Value(Header header, Row row, Column column) {
      this(header, null, row, column);
    }

    public Value(Header header, Reference reference, Row row, Column column) {
      this.header = header;
      this.reference = reference;
      this.row = row;
      this.column = column;
    }

    public String getProperty() {
      return header.value;
    }

    public String getValue() {
      return reference.value;
    }

    private void setValue(Reference reference) {
      this.reference = reference;
    }

    public Row getRow() {
      return row;
    }

    public Column getColumn() {
      return column;
    }

    @Override
    public String toString() {
      return getProperty() + ": " + getValue();
    }

  }

  public class Row implements Iterable<Value> {

    private final List<Value> values;

    public Row(List<Value> values) {
      if (values == null) throw new RuntimeException("Tried to create a null row");
      this.values = values;
    }

    public String get(String property) {
      for (Value value : this) if (value.getProperty().equals(property)) return value.getValue();
      throw new RuntimeException("data does not contain property " + property);
    }

    @Override
    public Iterator<Value> iterator() {
      return this.values.iterator();
    }

    @Override
    public String toString() {
      return values.toString();
    }

  }

  private class Column extends Row {

    public Column() {
      this(new ArrayList<>());
    }

    public Column(List<Value> values) {
      super(values);
    }
  }

}