package org.radargun.utils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Random-accessible table iterable over both rows and columns * * @author Radim Vansa <rvansa@redhat.com> */ public class Table<TRowKey, TColumnKey, TValue> { private List<List<TValue>> data = new ArrayList<List<TValue>>(); private LinkedHashMap<TRowKey, Integer> rowIndices = new LinkedHashMap<TRowKey, Integer>(); private LinkedHashMap<TColumnKey, Integer> columnIndices = new LinkedHashMap<TColumnKey, Integer>(); private TValue defaultValue = null; public Table() { } public Table(TValue defaultValue) { this.defaultValue = defaultValue; } public Set<TRowKey> rowKeys() { return Collections.unmodifiableSet(rowIndices.keySet()); } public Set<TColumnKey> columnKeys() { return Collections.unmodifiableSet(columnIndices.keySet()); } public TValue put(TRowKey row, TColumnKey column, TValue value) { List<TValue> rowList = ensureRow(row); int columnIndex = ensureColumn(column, rowList); TValue previous = rowList.get(columnIndex); rowList.set(columnIndex, value); return previous; } protected TValue set(int rowIndex, int columnIndex, TValue value) { if (data.size() <= rowIndex) throw new IllegalArgumentException(); List<TValue> rowList = data.get(rowIndex); for (int i = rowList.size(); i <= columnIndex; ++i) { rowList.add(defaultValue); } return rowList.set(columnIndex, value); } private int ensureColumn(TColumnKey column, List<TValue> row) { Integer index = columnIndices.get(column); if (index == null) { index = columnIndices.size(); columnIndices.put(column, index); } for (int i = row.size(); i <= index; ++i) { row.add(defaultValue); } return index; } private List<TValue> ensureRow(TRowKey row) { Integer index = rowIndices.get(row); if (index == null) { index = rowIndices.size(); if (index != data.size()) { throw new IllegalStateException(); } data.add(new ArrayList<TValue>()); rowIndices.put(row, index); } return data.get(index); } public TValue get(TRowKey row, TColumnKey column) { Integer rowIndex = rowIndices.get(row); if (rowIndex == null) return defaultValue; Integer columnIndex = columnIndices.get(column); if (columnIndex == null) return defaultValue; List<TValue> rowList = data.get(rowIndex); if (rowList.size() <= columnIndex) return defaultValue; return rowList.get(columnIndex); } protected TValue get(int rowIndex, int columnIndex) { if (data.size() <= rowIndex) return defaultValue; List<TValue> rowList = data.get(rowIndex); if (rowList.size() > columnIndex) { return rowList.get(columnIndex); } else { return defaultValue; } } public Map<TRowKey, TValue> getColumn(TColumnKey column) { return new ColumnMap(column); } public Map<TColumnKey, TValue> getRow(TRowKey row) { return new RowMap(row); } public boolean columnContains(int columnIndex, Object value) { for (List<TValue> rowList : data) { if (rowList.size() >= columnIndex) { return areEqual(rowList.get(columnIndex), value); } } return false; } public boolean rowContains(int rowIndex, Object value) { if (data.size() > rowIndex) throw new IllegalArgumentException(); List<TValue> rowList = data.get(rowIndex); return rowList.contains(value) || (rowList.size() < columnIndices.size() && areEqual(value, defaultValue)); } protected boolean areEqual(Object v1, Object v2) { if (v1 == null) return v2 == null; else return v1.equals(v2); } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (TColumnKey columnKey : columnIndices.keySet()) { sb.append(";").append(columnKey.toString()); } Iterator<TRowKey> rowKeyIterator = rowIndices.keySet().iterator(); for (List<TValue> row : data) { sb.append('\n').append(rowKeyIterator.next()); for (TValue value : row) { sb.append(";").append(value); } } return sb.toString(); } protected abstract class AbstractColumn { protected TColumnKey column; protected int columnIndex = -1; private AbstractColumn(TColumnKey column) { this.column = column; } public int size() { return rowIndices.size(); } public boolean isEmpty() { return rowIndices.size() == 0; } public void clear() { if (ensure()) { for (List<TValue> rowList : data) { if (rowList.size() > columnIndex) { rowList.set(columnIndex, defaultValue); } } } } protected boolean ensure() { if (columnIndex < 0) { Integer index = columnIndices.get(column); if (index != null) { columnIndex = index; } else { return false; } } return true; } } protected class ColumnMap extends AbstractColumn implements Map<TRowKey, TValue> { private ColumnMap(TColumnKey column) { super(column); } @Override public boolean containsKey(Object key) { return rowIndices.containsKey(key); } @Override public boolean containsValue(Object value) { if (ensure()) { if (columnContains(columnIndex, value)) return true; } return false; } @Override public TValue get(Object key) { if (ensure()) { Integer rowIndex = rowIndices.get(key); if (rowIndex == null) return defaultValue; return data.get(rowIndex).get(columnIndex); } return defaultValue; } @Override public TValue put(TRowKey key, TValue value) { return Table.this.put(key, column, value); } @Override public TValue remove(Object key) { return Table.this.put((TRowKey) key, column, defaultValue); } @Override public void putAll(Map<? extends TRowKey, ? extends TValue> m) { for (Map.Entry<? extends TRowKey, ? extends TValue> entry : m.entrySet()) { Table.this.put(entry.getKey(), column, entry.getValue()); } } @Override public Set<TRowKey> keySet() { return Collections.unmodifiableSet(rowIndices.keySet()); } @Override public Collection<TValue> values() { return new ColumnCollection(column); } @Override public Set<Map.Entry<TRowKey, TValue>> entrySet() { return new ColumnSet(column); } } protected class ColumnCollection extends AbstractColumn implements Collection<TValue> { private ColumnCollection(TColumnKey column) { super(column); } @Override public boolean contains(Object value) { if (ensure()) { return columnContains(columnIndex, value); } else { return false; } } @Override public Iterator<TValue> iterator() { return new Iterator<TValue>() { private int rowIndex = 0; @Override public boolean hasNext() { if (ensure()) { return rowIndex < data.size(); } else { return false; } } @Override public TValue next() { if (ensure()) { return Table.this.get(rowIndex++, columnIndex); } else { throw new IllegalStateException(); } } @Override public void remove() { if (rowIndex == 0) throw new IllegalStateException(); if (ensure()) { Table.this.set(rowIndex - 1, columnIndex, defaultValue); } else { throw new IllegalStateException(); } } }; } @Override public Object[] toArray() { if (ensure()) { Object[] array = new Object[data.size()]; return toArray(array); } return new Object[0]; } @Override public <T> T[] toArray(T[] a) { if (ensure()) { if (a.length < data.size()) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), data.size()); } for (int i = 0; i < data.size(); ++i) { a[i] = (T) Table.this.get(i, columnIndex); } return a; } else { return (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), 0); } } @Override public boolean add(TValue tValue) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { if (ensure()) { for (Object value : c) { if (!columnContains(columnIndex, value)) return false; } return true; } else { return c.isEmpty(); } } @Override public boolean addAll(Collection<? extends TValue> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } } protected class ColumnSet extends AbstractColumn implements Set<Map.Entry<TRowKey, TValue>> { public ColumnSet(TColumnKey column) { super(column); } @Override public boolean contains(Object o) { Map.Entry<TRowKey, TValue> entry = (Map.Entry<TRowKey, TValue>) o; if (ensure()) { Integer rowIndex = rowIndices.get(entry.getKey()); if (rowIndex == null) return false; List<TValue> rowList = data.get(rowIndex); if (rowList.size() <= columnIndex) return areEqual(entry.getValue(), defaultValue); TValue item = rowList.get(columnIndex); return areEqual(item, entry.getValue()); } else return false; } @Override public Iterator<Map.Entry<TRowKey, TValue>> iterator() { return new Iterator<Map.Entry<TRowKey, TValue>>() { private int rowIndex = 0; private Iterator<TRowKey> rowKeyIterator; private boolean ensure() { if (ColumnSet.this.ensure()) { if (rowKeyIterator == null) { rowKeyIterator = rowIndices.keySet().iterator(); } return true; } return false; } @Override public boolean hasNext() { return ensure() && rowIndex < data.size(); } @Override public Map.Entry<TRowKey, TValue> next() { if (ensure()) { return new Entry<TRowKey, TValue>(rowKeyIterator.next(), get(rowIndex++, columnIndex)); } else { throw new IllegalStateException(); } } @Override public void remove() { if (rowIndex == 0) throw new IllegalStateException(); if (ensure()) { set(rowIndex - 1, columnIndex, defaultValue); } else { throw new IllegalStateException(); } } }; } @Override public Object[] toArray() { if (ensure()) { return toArray(new Map.Entry[data.size()]); } else { return new Object[0]; } } @Override public <T> T[] toArray(T[] a) { if (ensure()) { if (a.length < data.size()) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), data.size()); } Iterator<TRowKey> rowKeyIterator = rowIndices.keySet().iterator(); for (int i = 0; i < data.size(); ++i) { a[i] = (T) new Entry(rowKeyIterator.next(), Table.this.get(i, columnIndex)); } return a; } else { return (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), 0); } } @Override public boolean add(Map.Entry<TRowKey, TValue> entry) { return !areEqual(Table.this.put(entry.getKey(), column, entry.getValue()), entry.getValue()); } @Override public boolean remove(Object o) { if (ensure()) { Map.Entry<TRowKey, TValue> entry = (Map.Entry<TRowKey, TValue>) o; Integer rowIndex = rowIndices.get(entry.getKey()); if (rowIndex == null) return false; if (areEqual(Table.this.get(rowIndex, columnIndex), entry.getValue())) { Table.this.set(rowIndex, columnIndex, defaultValue); return true; } } return false; } @Override public boolean containsAll(Collection<?> c) { for (Object o : c) { if (!contains(o)) return false; } return true; } @Override public boolean addAll(Collection<? extends Map.Entry<TRowKey, TValue>> c) { boolean changed = false; for (Map.Entry<TRowKey, TValue> entry : c) { changed = add(entry) || changed; } return changed; } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { boolean changed = false; for (Object o : c) { changed = remove(o) || changed; } return changed; } } protected static class Entry<TKey, TValue> implements Map.Entry<TKey, TValue> { private TKey key; private TValue value; public Entry(TKey key, TValue value) { this.key = key; this.value = value; } @Override public TKey getKey() { return key; } @Override public TValue getValue() { return value; } @Override public TValue setValue(TValue value) { throw new UnsupportedOperationException(); } } protected abstract class AbstractRow { protected TRowKey row; protected int rowIndex = -1; protected List<TValue> rowList; protected AbstractRow(TRowKey row) { this.row = row; } public int size() { return columnIndices.size(); } public boolean isEmpty() { return columnIndices.size() == 0; } public void clear() { if (ensure()) { rowList.clear(); } } protected boolean ensure() { if (rowIndex < 0) { Integer index = rowIndices.get(row); if (index != null) { rowIndex = index; rowList = data.get(rowIndex); } else { return false; } } return true; } } protected class RowMap extends AbstractRow implements Map<TColumnKey, TValue> { public RowMap(TRowKey row) { super(row); } @Override public boolean containsKey(Object key) { return columnIndices.containsKey(key); } @Override public boolean containsValue(Object value) { if (ensure()) { return rowContains(rowIndex, value); } else { return false; } } @Override public TValue get(Object key) { if (ensure()) { Integer columnIndex = columnIndices.get(key); if (columnIndex != null) { return Table.this.get(rowIndex, columnIndex); } } return defaultValue; } @Override public TValue put(TColumnKey key, TValue value) { return Table.this.put(row, key, value); } @Override public TValue remove(Object key) { return Table.this.put(row, (TColumnKey) key, defaultValue); } @Override public void putAll(Map<? extends TColumnKey, ? extends TValue> m) { for (Entry<? extends TColumnKey, ? extends TValue> entry : m.entrySet()) { Table.this.put(row, entry.getKey(), entry.getValue()); } } @Override public Set<TColumnKey> keySet() { return Collections.unmodifiableSet(columnIndices.keySet()); } @Override public Collection<TValue> values() { return new RowCollection(row); } @Override public Set<Entry<TColumnKey, TValue>> entrySet() { return new RowSet(row); } } public class RowCollection extends AbstractRow implements Collection<TValue> { protected RowCollection(TRowKey row) { super(row); } @Override public boolean contains(Object o) { if (ensure()) { return rowContains(rowIndex, o); } else { return false; } } @Override public Iterator<TValue> iterator() { return new Iterator<TValue>() { int index = 0; @Override public boolean hasNext() { if (ensure()) { return index < rowList.size(); } else { return false; } } @Override public TValue next() { if (ensure()) { return rowList.get(index++); } else { throw new IllegalStateException(); } } @Override public void remove() { if (index <= 0) throw new IllegalStateException(); if (ensure()) { rowList.set(index - 1, defaultValue); } else { throw new IllegalStateException(); } } }; } @Override public Object[] toArray() { if (ensure()) { return toArray(new Object[columnIndices.size()]); } else { return new Object[0]; } } @Override public <T> T[] toArray(T[] a) { if (ensure()) { if (a.length < columnIndices.size()) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), columnIndices.size()); } for (int i = 0; i < rowList.size(); ++i) { a[i] = (T) rowList.get(i); } for (int i = rowList.size(); i < columnIndices.size(); ++i) { a[i] = (T) defaultValue; } return a; } else { return (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), 0); } } @Override public boolean add(TValue tValue) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { if (ensure()) { for (Object o : c) { if (rowContains(rowIndex, o)) return false; } return true; } else { return c.isEmpty(); } } @Override public boolean addAll(Collection<? extends TValue> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } } public class RowSet extends AbstractRow implements Set<Map.Entry<TColumnKey, TValue>> { protected RowSet(TRowKey row) { super(row); } @Override public boolean contains(Object o) { if (ensure()) { Map.Entry<TColumnKey, TValue> entry = (Map.Entry<TColumnKey, TValue>) o; Integer columnIndex = columnIndices.get(entry.getKey()); if (columnIndex != null) { return areEqual(rowList.get(columnIndex), entry.getValue()); } } return false; } @Override public Iterator<Map.Entry<TColumnKey, TValue>> iterator() { return new Iterator<Map.Entry<TColumnKey, TValue>>() { int index = 0; Iterator<TColumnKey> columnKeyIterator; private boolean ensure() { if (RowSet.this.ensure()) { if (columnKeyIterator == null) { columnKeyIterator = columnIndices.keySet().iterator(); } return true; } return false; } @Override public boolean hasNext() { if (ensure()) { return index < rowList.size(); } else { return false; } } @Override public Map.Entry<TColumnKey, TValue> next() { if (ensure()) { return new Entry(columnKeyIterator.next(), rowList.get(index++)); } else { throw new IllegalStateException(); } } @Override public void remove() { if (index <= 0) throw new IllegalStateException(); if (ensure()) { rowList.set(index - 1, defaultValue); } else { throw new IllegalStateException(); } } }; } @Override public Object[] toArray() { if (ensure()) { return toArray(new Object[columnIndices.size()]); } else { return new Object[0]; } } @Override public <T> T[] toArray(T[] a) { if (ensure()) { if (a.length < columnIndices.size()) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), columnIndices.size()); } Iterator<TColumnKey> columnKeyIterator = columnIndices.keySet().iterator(); for (int i = 0; i < rowList.size(); ++i) { a[i] = (T) new Entry(columnKeyIterator.next(), rowList.get(i)); } for (int i = rowList.size(); i < columnIndices.size(); ++i) { a[i] = (T) new Entry(columnKeyIterator.next(), defaultValue); } return a; } else { return (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), 0); } } @Override public boolean add(Map.Entry<TColumnKey, TValue> entry) { return !areEqual(Table.this.put(row, entry.getKey(), entry.getValue()), entry.getValue()); } @Override public boolean remove(Object o) { if (ensure()) { Map.Entry<TColumnKey, TValue> entry = (Map.Entry<TColumnKey, TValue>) o; Integer columnIndex = columnIndices.get(entry.getKey()); if (columnIndex != null) { if (rowList.size() <= columnIndex) { return false; } if (!areEqual(entry.getValue(), rowList.get(columnIndex))) { rowList.set(columnIndex, entry.getValue()); return true; } } } return false; } @Override public boolean containsAll(Collection<?> c) { for (Object o : c) { if (!contains(o)) return false; } return true; } @Override public boolean addAll(Collection<? extends Map.Entry<TColumnKey, TValue>> c) { boolean changed = false; for (Map.Entry<TColumnKey, TValue> entry : c) { changed = add(entry) || changed; } return changed; } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { boolean changed = false; for (Object o : c) { changed = remove(o) || changed; } return changed; } } }