package tc.oc.commons.core.collection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.collect.Tables;
import tc.oc.commons.core.util.SupersetView;
/**
* Unmodifiable {@link Table} view of a {@link Map} of {@link Map}s
*
* Empty rows are ignored
*/
public class TableView<R, C, V> implements Table<R, C, V> {
private final Map<R, Map<C, V>> map;
public TableView(Map<R, Map<C, V>> map) {
this.map = map;
}
@Override
public boolean containsRow(@Nullable Object rowKey) {
return map.containsKey(rowKey) && !map.get(rowKey).isEmpty();
}
@Override
public boolean containsColumn(@Nullable Object columnKey) {
return map.values().stream().anyMatch(row -> row.containsKey(columnKey));
}
@Override
public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) {
return map.containsKey(rowKey) && map.get(rowKey).containsKey(columnKey);
}
@Override
public boolean containsValue(@Nullable Object value) {
return map.values().stream().anyMatch(row -> row.values().stream().anyMatch(v -> Objects.equals(v, value)));
}
@Override
public V get(@Nullable Object rowKey, @Nullable Object columnKey) {
return map.containsKey(rowKey) ? map.get(rowKey).get(columnKey) : null;
}
@Override
public boolean isEmpty() {
return map.values().stream().allMatch(Map::isEmpty);
}
@Override
public int size() {
return map.values().stream().mapToInt(Map::size).sum();
}
@Override
public Map<R, Map<C, V>> rowMap() {
return map;
}
@Override
public Map<C, V> row(R rowKey) {
return map.get(rowKey);
}
@Override
public Set<R> rowKeySet() {
return Sets.filter(map.keySet(), this::containsRow);
}
@Override
public Map<C, Map<R, V>> columnMap() {
return Maps.asMap(columnKeySet(), this::column);
}
@Override
public Map<R, V> column(C columnKey) {
return Maps.asMap(Sets.filter(rowKeySet(), rowKey -> contains(rowKey, columnKey)),
rowKey -> map.get(rowKey).get(columnKey));
}
@Override
public Set<C> columnKeySet() {
return new SupersetView<>(Collections2.transform(map.values(), Map::keySet));
}
@Override
public Collection<V> values() {
return new FlatCollection<>(Collections2.transform(map.values(), Map::values));
}
@Override
public Set<Cell<R, C, V>> cellSet() {
return new AbstractSet<Cell<R, C, V>>() {
@Override
public int size() {
return TableView.this.size();
}
@Override
public boolean isEmpty() {
return TableView.this.isEmpty();
}
@Override
public boolean contains(Object o) {
if(!(o instanceof Cell)) return false;
final Cell cell = (Cell) o;
return Objects.equals(get(cell.getRowKey(), cell.getColumnKey()), cell.getValue());
}
@Override
public Stream<Cell<R, C, V>> stream() {
return map.entrySet()
.stream()
.flatMap(row -> row.getValue()
.entrySet()
.stream()
.map(col -> Tables.immutableCell(row.getKey(),
col.getKey(),
col.getValue())));
}
@Override
public Spliterator<Cell<R, C, V>> spliterator() {
return stream().spliterator();
}
@Override
public Iterator<Cell<R, C, V>> iterator() {
return Spliterators.iterator(spliterator());
}
};
}
@Override
public void clear() {
unsupported();
}
@Override
public V put(R rowKey, C columnKey, V value) {
throw unsupported();
}
@Override
public void putAll(Table<? extends R, ? extends C, ? extends V> table) {
unsupported();
}
@Override
public V remove(@Nullable Object rowKey, @Nullable Object columnKey) {
throw unsupported();
}
private UnsupportedOperationException unsupported() {
throw new UnsupportedOperationException("This object is immutable");
}
}