Though, as I wrote before, it's very hard to come up a universal dynamic data structure to fit all needs, sometimes we still need a somewhat general dynamic data structure due to requirements. For example, the spreadsheet could have as many columns during runtime, we can't determine the number of columns during compile time.
I have a very bad example of an implementation at work, it has some 3000 lines of code. I think it's for me to think about how it ends up like this and what are the better alternatives. Another motivation is still from the blog, I want to see how close I can get to ultimate - universal dynamic data structure, though I don't believe we can reach there. But the old saying says, you can't reach the North Star, but it always points to the right direction.
So here is my journey.
It's a data matrix, it has headers that we call fields, it has rows and columns. Each column has same data type. This class is going across wires as well. And it has 3000 lines, for God's sake.
- stripped out the code for memory allocation, replace them with Collection classes
- stripped out the type meta data, removed all the type-safe getters/setters, they should be on the caller side, the meta data should be in the headers
- stripped out the methods related to cell/row/column computations. This should be a dedicated class later on since the same kind of logic spreads in a few places.
- stripped out the toXml() and other output methods.
After consolidate method signatures, The new class is like this
java 代码
- import java.util.List;
- import java.util.Map;
- import java.util.ArrayList;
- import java.util.HashMap;
-
-
-
-
-
-
-
- public class DataMatrix
- {
- private List columns;
- private List fields;
-
- private Map fieldToColumns;
-
- public DataMatrix()
- {
- fields = new ArrayList();
- columns = new ArrayList();
- fieldToColumns = new HashMap();
- }
-
- public DataMatrix(List fields)
- {
- this.fields = new ArrayList(fields);
- columns = new ArrayList(fields.size());
- fieldToColumns = new HashMap(fields.size());
- }
-
- public DataMatrix(DataMatrix dataMatrix)
- {
- this.columns = new ArrayList(dataMatrix.columns);
- this.fields = new ArrayList(dataMatrix.fields);
- this.fieldToColumns = new HashMap(dataMatrix.fieldToColumns);
- }
-
-
-
-
- public List getColumnFields() { return fields; }
-
- public void setColumnFields(List fields) { this.fields = new ArrayList(fields); }
-
- public Field getField(int col) { return (Field)fields.get(col); }
-
- public int getColumnNumber(Field field)
- {
- Integer index = (Integer)fieldToColumns.get(field);
-
- if (index == null) throw new IllegalArgumentException("field not found: " + field.toString());
-
- return index.intValue();
- }
-
-
-
-
- public int getNumberOfColumns() { return columns.size(); }
-
- public List getColumn(int col) { return (List) columns.get(col); }
-
- public List getColumn(Field key)
- {
- return (List) columns.get(getColumnNumber(key));
- }
-
- public void addColumn(Field field, List column)
- {
- checkNullAndEqualSize(field, column);
-
- fieldToColumns.put(field, new Integer(fields.size()));
- fields.add(field);
- columns.add(new ArrayList(column));
- }
-
- public void insertColumn(int position, Field field, List column)
- {
- checkNullAndEqualSize(field, column);
-
- fieldToColumns.put(field, new Integer(position));
- fields.add(position, field);
- columns.add(position, column);
- }
-
- private void checkNullAndEqualSize(Field field, List column)
- {
- if (field == null) throw new IllegalArgumentException("field is null");
-
- if (column == null) throw new IllegalArgumentException("column is null");
-
- if (columns.size() > 0)
- {
- List existingRow = (List)columns.get(0);
- if (existingRow.size() != column.size())
- {
- throw new IllegalArgumentException("column size doesn't match: "
- + " existing column size=" + existingRow.size()
- + " to be added column size=" + column.size());
- }
- }
- }
-
- public void deleteColumn(int position)
- {
- fieldToColumns.remove(fields.get(position));
- fields.remove(position);
- columns.remove(position);
- }
-
- public void deleteColumn(Field field)
- {
- Integer index = (Integer)fieldToColumns.get(field);
- if (index == null) return;
-
- fieldToColumns.remove(field);
- fields.remove(index.intValue());
- columns.remove(index.intValue());
- }
-
-
-
- public void reorder(Field[] fieldsInNewOrder)
- {
-
-
- List newFields = new ArrayList();
- List newColumns = new ArrayList();
- Map newFieldToColumns = new HashMap();
- for (int i=0; i
- {
- newFields.add(fieldsInNewOrder[i]);
- int index = ((Integer)fieldToColumns.get(fieldsInNewOrder[i])).intValue();
- newColumns.add(columns.get(index));
- newFieldToColumns.put(fieldsInNewOrder, new Integer(i));
- }
-
- this.fields = newFields;
- this.columns = newColumns;
- this.fieldToColumns = newFieldToColumns;
- }
-
-
-
-
- public int getNumberOfRows()
- {
- if (columns.size() == 0) return 0;
-
- List firstColumn = (List)columns.get(0);
- return firstColumn.size();
- }
-
- public List getRow(int row)
- {
- List ret = new ArrayList();
- for (int i=0; i
- {
- List columnList = (List) columns.get(i);
- ret.add(columnList.get(row));
- }
-
- return ret;
- }
-
- public void addRow(List row)
- {
- checkSizeMatch(row);
-
- for (int i=0; i
- {
- List columnList = getExistingOrNewColumn(i);
- columnList.add(row.get(i));
- }
- }
-
- public void insertRow(int position, List row)
- {
- checkSizeMatch(row);
-
- for (int i=0; i
- {
- List columnList = getExistingOrNewColumn(i);
- columnList.add(position, row.get(i));
- }
- }
-
- private void checkSizeMatch(List row)
- {
- if (row == null)
- {
- throw new IllegalArgumentException("row is null");
- }
-
- if (fields.size() == 0)
- {
- throw new IllegalStateException("set the fields before adding rows");
- }
-
-
- if (columns.size() != 0 && row.size() != columns.size())
- {
- throw new IllegalArgumentException("mismatched column size: " +
- "datamatrix num of columns=" + columns.size() + " to be "
- + "added row size=" + row.size());
- }
- }
-
- private List getExistingOrNewColumn(int i)
- {
- List columnList;
- if (i >= columns.size())
- {
- columnList = new ArrayList();
- columns.add(columnList);
- }
- else
- {
- columnList = (List) columns.get(i);
- }
- return columnList;
- }
-
-
-
- public void deleteRow(int rowNumber)
- {
- for (int i=0; i
- {
- List columnList = (List) columns.get(i);
- columnList.remove(rowNumber);
- }
- }
-
-
-
-
-
-
- public void setCellValueAt(int row, int col, Object value)
- {
- if (col >= columns.size())
- {
- throw new IllegalArgumentException("try to set value at an uninitialized cell");
- }
-
- ((List)columns.get(col)).set(row, value);
- }
-
- public Object getCellValue(int row, int col)
- {
- if (col >= columns.size())
- {
- throw new IllegalArgumentException("try to get value at an uninitialized cell");
- }
-
- List column = (List)columns.get(col);
- if (column == null || row >= column.size())
- {
- throw new IllegalArgumentException("try to get value at an uninitialized cell");
- }
- return ((List)columns.get(col)).get(row);
- }
-
-
-
-
-
- public void concatenateRows(DataMatrix dataMatrix)
- {
-
- if (columns.size() != dataMatrix.getNumberOfColumns())
- {
- throw new IllegalArgumentException("Two matrices have different "
- + "number of columns: " + columns.size() + " != " +
- dataMatrix.getNumberOfColumns());
- }
-
- for (int j=0; j
- {
- ((List)columns.get(j)).addAll(dataMatrix.getColumn(j));
- }
- }
-
-
- public void concatenateColumns(DataMatrix dataMatrix)
- {
-
- if (this.getNumberOfRows() != dataMatrix.getNumberOfRows())
- {
- throw new IllegalArgumentException("Two matrices have different "
- + "number of rows: " + getNumberOfRows() + " != " +
- dataMatrix.getNumberOfRows());
- }
-
- int startCol = columns.size();
- for (int j=0; j
- {
- fieldToColumns.put(dataMatrix.getField(j), new Integer(j + startCol));
- fields.add(dataMatrix.getField(j));
- columns.add(dataMatrix.getColumn(j));
- }
- }
-
-
-
- public DataMatrix[] splitFrom(Field field)
- {
- int colNum = getColumnNumber(field);
-
- DataMatrix leftMatrix = new DataMatrix();
- DataMatrix rightMatrix = new DataMatrix();
-
- for (int i=0; i
- {
- leftMatrix.addColumn((Field)fields.get(i), (List)columns.get(i));
- }
-
- for (int i=colNum; i
- {
- rightMatrix.addColumn((Field)fields.get(i), (List)columns.get(i));
- }
- return new DataMatrix[] {leftMatrix, rightMatrix};
- }
-
- public String toString()
- {
- String ret = "";
-
- if (fields == null) return ret;
-
-
- for (int i=0; i
- {
- ret += fields.get(i).toString() + " | ";
- }
- ret += "\n";
-
- if (columns == null) return ret;
-
-
- for (int i=0; i
- {
- for (int j=0; j
- {
- ret += ((List)columns.get(j)).get(i).toString() + " | ";
- }
- ret += "\n";
- }
-
- return ret;
- }
I am not surprised to see that the only dependency on the headers is just an empty interface, as a flag.
java 代码
- public interface Field
- {
- }
If you compare the Swing's TableModel implementations to this one, it's almost the same, except the event handling portion, plus we have several extra methods. This is a good sign that we abstract the responsibility at the right level, not too many responsibilities.
The only relevant missing piece is the serialization. We can either implements Serializable or Externalizable.
In this implementation, I delegate the dynamic memory allocation to Collection classes. I could use a Map that maps fields to columns, but I instead use two Lists for speeding.
I also supplied a toString() method for debugging purpose, but left out other formatters, like toXml(), toCvs() etc.
Since this is more of a design issue, I left out most of the code untested. For simple testing, I created a simple field class:
java 代码
- public class StringField implements Field
- {
- private String name;
-
- public StringField(String name)
- {
- this.name = name;
- }
-
- public String getName() { return name; }
-
- public boolean equals(Object obj)
- {
- if (!(obj instanceof StringField)) return false;
-
- StringField field = (StringField)obj;
- return this.name.equals(field.name);
- }
-
- public int hashCode() { return name.hashCode(); }
-
- public String toString() { return getName(); }
- }
A real world Field class would have dependencies on date, currency and other factors.