CSV.java

282 lines | 7.85 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.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public class CSV implements Iterable<Row> {

    private 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((String) obj);
            }
            return false;
        }

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

    private class Header extends Reference {

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

    }

    private class Value {

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

        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();
        }

    }

    protected 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);
        }
    }

    private 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);
        }
    }

    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;
    }

    private static String[] process(String line) {
        if (line.contains("'") || line.contains("\"")) {
            throw new UnsupportedOperationException("Scaped values 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 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();
    }

}