package com.github.martincooper.datatable; import java.lang.reflect.Type; import io.vavr.collection.Seq; import io.vavr.collection.Vector; import io.vavr.control.Try; import static io.vavr.API.$; import static io.vavr.API.Case; import static io.vavr.API.Match; import static io.vavr.Patterns.$Failure; import static io.vavr.Patterns.$Success; /** * DataColumn. Handles the data for a single column. * Created by Martin Cooper on 08/07/2017. */ public class DataColumn<T> implements IDataColumn { private final Class<T> type; private final String name; private final Vector<T> data; /** * DataColumn constructor. * * @param type Stores the type of data stored in this column. * @param columnName The column name. */ public DataColumn(Class<T> type, String columnName) { this(type, columnName, Vector.empty()); } /** * DataColumn constructor. * * @param type Stores the type of data stored in this column. * @param columnName The column name. * @param data The data items stored in the column. */ public DataColumn(Class<T> type, String columnName, T[] data) { this(type, columnName, Vector.of(data)); } /** * DataColumn constructor. * * @param type Stores the type of data stored in this column. * @param columnName The column name. * @param data The data items stored in the column. */ public DataColumn(Class<T> type, String columnName, Iterable<T> data) { this(type, columnName, Vector.ofAll(data)); } /** * DataColumn constructor. * * @param type Stores the type of data stored in this column. * @param columnName The column name. * @param data The data items stored in the column. */ public DataColumn(Class<T> type, String columnName, Vector<T> data) { this.type = type; this.name = columnName; this.data = data; } /** * Gets the column name. * * @return Returns the column name. */ @Override public String name() { return this.name; } /** * Gets the column type. * * @return Returns the column type. */ @Override public Type type() { return this.type; } /** * Returns the underlying data. * * @return Returns access to the underlying data. */ @Override public Vector<T> data() { return this.data; } /** * Returns the value at the specified index. * * @param rowIndex The row index. * @return Returns the value. */ @Override public T valueAt(Integer rowIndex) { return this.data.get(rowIndex); } /** * Returns the generic data column as it's typed implementation. * If the types don't match, then it'll return Failure. * * @param type The type of the column. * @param <V> The type. * @return Returns the typed Data Column wrapped in a Try. */ @Override @SuppressWarnings("unchecked") public <V> Try<DataColumn<V>> asType(Class<V> type) { return this.type == type ? Try.success((DataColumn<V>)this) : DataTableException.tryError("Column type doesn't match type requested."); } /** * Builds a new DataColumn from the data at the specified row indexes. * * @param rowIndexes The rows which the new column data is to be built from. * @return Returns a new IDataColumn with just the rows specified. */ @Override public Try<IDataColumn> buildFromRows(Seq<Integer> rowIndexes) { Seq<T> rowData = rowIndexes.map(this.data::get); return Try.success(new DataColumn<>(this.type, this.name, rowData)); } /** * Attempts to add / append a new item to the end of the column. * A type check is performed before addition. * * @param value The item required to be added. * @return Returns a Success with the new modified DataColumn, or a Failure. */ @Override public Try<IDataColumn> add(Object value) { return Match(GenericExtensions.tryCast(this.type, value)).of( Case($Success($()), typedVal -> Try.of(() -> createColumn(this.data.append(typedVal)))), Case($Failure($()), DataTableException.tryError("tryAdd failed. Item of invalid type passed.")) ); } /** * Attempts to insert a new item into the column. * A type check is performed before insertion. * * @param index The index the item is to be inserted at. * @param value The item required to be inserted. * @return Returns a Success with the new modified DataColumn, or a Failure. */ @Override public Try<IDataColumn> insert(Integer index, Object value) { return Match(GenericExtensions.tryCast(this.type, value)).of( Case($Success($()), typedVal -> Try.of(() -> createColumn(this.data.insert(index, typedVal)))), Case($Failure($()), DataTableException.tryError("tryInsert failed. Item of invalid type passed.")) ); } /** * Attempts to replace an existing item with a new item in the column. * A type check is performed before replacement. * * @param index The index the item is to be replaced at. * @param value The new item. * @return Returns a Success with the new modified DataColumn, or a Failure. */ @Override public Try<IDataColumn> replace(Integer index, Object value) { return Match(GenericExtensions.tryCast(this.type, value)).of( Case($Success($()), typedVal -> Try.of(() -> createColumn(this.data.update(index, typedVal)))), Case($Failure($()), DataTableException.tryError("tryReplace failed. Item of invalid type passed.")) ); } /** * Attempts to remove an existing item at the specified index. * * @param index The index to remove the item at. * @return Returns a Success with the new modified DataColumn, or a Failure. */ @Override public Try<IDataColumn> remove(Integer index) { return Try.of(() -> createColumn(this.data.removeAt(index))); } /** * Adds / Appends and item to the end of the column. * * @param value The value to append. * @return Returns a new DataColumn with the new item appended. */ public Try<DataColumn<T>> addItem(T value) { return Try.success(createColumn(this.data.append(value))); } /** * Inserts the item at the specified index. * * @param index The index to insert the item at. * @param value The item to insert. * @return Returns a new DataColumn with the new item inserted. */ public Try<DataColumn<T>> insertItem(Integer index, T value) { return Try.of(() -> createColumn(this.data.insert(index, value))); } /** * Replaces the existing item at the specified index with the new item. * * @param index The index to replace the existing item. * @param value The new item to replace the existing one. * @return Returns a new DataColumn with the specified item replaced. */ public Try<DataColumn<T>> replaceItem(Integer index, T value) { return Try.of(() -> createColumn(this.data.update(index, value))); } /** * Removes the item at the specified index. * * @param index The index to remove the item at. * @return Returns a new DataColumn with the specified item removed. */ public Try<DataColumn<T>> removeItem(Integer index) { return Try.of(() -> createColumn(this.data.removeAt(index))); } /** * Creates a new DataColumn based on this one, with modified data. * * @param data The new data set. * @return Returns the new DataColumn. */ private DataColumn<T> createColumn(Vector<T> data) { return new DataColumn<>(this.type, this.name, data); } /** * Checks if the data column supports comparable for sorting. * * @return Returns true if the type supports Comparable. */ @Override public boolean IsComparable() { return Comparable.class.isAssignableFrom(type); } }