001: package prefuse.data.column;
002:
003: import java.util.BitSet;
004: import java.util.Iterator;
005: import java.util.Set;
006:
007: import prefuse.data.DataTypeException;
008: import prefuse.data.Table;
009: import prefuse.data.event.ColumnListener;
010: import prefuse.data.event.EventConstants;
011: import prefuse.data.event.ExpressionListener;
012: import prefuse.data.expression.Expression;
013: import prefuse.data.expression.ExpressionAnalyzer;
014:
015: /**
016: * <p>Column instance that stores values provided by an Expression
017: * instance. These expressions can reference other column values within the
018: * same table. Values are evaluated when first requested and then cached to
019: * increase performance. This column maintains listeners for all referenced
020: * columns discovered in the expression and for the expression itself,
021: * invalidating all cached entries when an update to either occurs.</p>
022: *
023: * <p>
024: * WARNING: Infinite recursion, eventually resulting in a StackOverflowError,
025: * could occur if an expression refers to its own column, or if two
026: * ExpressionColumns have expressions referring to each other. The
027: * responsibility for avoiding such situations is left with client programmers.
028: * Note that it is fine for one ExpressionColumn to reference another;
029: * however, the graph induced by such references must not contain any cycles.
030: * </p>
031: *
032: * @author <a href="http://jheer.org">jeffrey heer</a>
033: * @see prefuse.data.expression
034: */
035: public class ExpressionColumn extends AbstractColumn {
036:
037: private Expression m_expr;
038: private Table m_table;
039: private Set m_columns;
040:
041: private BitSet m_valid;
042: private Column m_cache;
043: private Listener m_lstnr;
044:
045: /**
046: * Create a new ExpressionColumn.
047: * @param table the table this column is a member of
048: * @param expr the expression used to provide the column values
049: */
050: public ExpressionColumn(Table table, Expression expr) {
051: super (expr.getType(table.getSchema()));
052: m_table = table;
053: m_expr = expr;
054: m_lstnr = new Listener();
055:
056: init();
057:
058: int nrows = m_table.getRowCount();
059: m_cache = ColumnFactory.getColumn(getColumnType(), nrows);
060: m_valid = new BitSet(nrows);
061: m_expr.addExpressionListener(m_lstnr);
062: }
063:
064: protected void init() {
065: // first remove listeners on any current columns
066: if (m_columns != null && m_columns.size() > 0) {
067: Iterator iter = m_columns.iterator();
068: while (iter.hasNext()) {
069: String field = (String) iter.next();
070: Column col = m_table.getColumn(field);
071: col.removeColumnListener(m_lstnr);
072: }
073: }
074: // now get the current set of columns
075: m_columns = ExpressionAnalyzer.getReferencedColumns(m_expr);
076:
077: // sanity check table and expression
078: Iterator iter = m_columns.iterator();
079: while (iter.hasNext()) {
080: String name = (String) iter.next();
081: if (m_table.getColumn(name) == null)
082: throw new IllegalArgumentException(
083: "Table must contain all "
084: + "columns referenced by the expression."
085: + " Bad column name: " + name);
086:
087: }
088:
089: // passed check, so now listen to columns
090: iter = m_columns.iterator();
091: while (iter.hasNext()) {
092: String field = (String) iter.next();
093: Column col = m_table.getColumn(field);
094: col.addColumnListener(m_lstnr);
095: }
096: }
097:
098: // ------------------------------------------------------------------------
099: // Column Metadata
100:
101: /**
102: * @see prefuse.data.column.Column#getRowCount()
103: */
104: public int getRowCount() {
105: return m_cache.getRowCount();
106: }
107:
108: /**
109: * @see prefuse.data.column.Column#setMaximumRow(int)
110: */
111: public void setMaximumRow(int nrows) {
112: m_cache.setMaximumRow(nrows);
113: }
114:
115: // ------------------------------------------------------------------------
116: // Cache Management
117:
118: /**
119: * Check if this ExpressionColumn has a valid cached value at the given
120: * row.
121: * @param row the row to check for a valid cache entry
122: * @return true if the cache row is valid, false otherwise
123: */
124: public boolean isCacheValid(int row) {
125: return m_valid.get(row);
126: }
127:
128: /**
129: * Invalidate a range of the cache.
130: * @param start the start of the range to invalidate
131: * @param end the end of the range to invalidate, inclusive
132: */
133: public void invalidateCache(int start, int end) {
134: m_valid.clear(start, end + 1);
135: }
136:
137: // ------------------------------------------------------------------------
138: // Data Access Methods
139:
140: /**
141: * Has no effect, as all values in this column are derived.
142: * @param row the row to revert
143: */
144: public void revertToDefault(int row) {
145: // do nothing, as we don't have default values.
146: }
147:
148: /**
149: * @see prefuse.data.column.AbstractColumn#canSet(java.lang.Class)
150: */
151: public boolean canSet(Class type) {
152: return false;
153: }
154:
155: /**
156: * @see prefuse.data.column.Column#get(int)
157: */
158: public Object get(int row) {
159: rangeCheck(row);
160: if (isCacheValid(row)) {
161: return m_cache.get(row);
162: }
163: Object val = m_expr.get(m_table.getTuple(row));
164: Class type = val == null ? Object.class : val.getClass();
165: if (m_cache.canSet(type)) {
166: m_cache.set(val, row);
167: m_valid.set(row);
168: }
169: return val;
170: }
171:
172: /**
173: * @see prefuse.data.column.Column#set(java.lang.Object, int)
174: */
175: public void set(Object val, int row) throws DataTypeException {
176: throw new UnsupportedOperationException();
177: }
178:
179: private void rangeCheck(int row) {
180: if (row < 0 || row >= getRowCount())
181: throw new IndexOutOfBoundsException();
182: }
183:
184: // ------------------------------------------------------------------------
185:
186: /**
187: * @see prefuse.data.column.Column#getBoolean(int)
188: */
189: public boolean getBoolean(int row) throws DataTypeException {
190: if (!canGetBoolean())
191: throw new DataTypeException(boolean.class);
192: rangeCheck(row);
193:
194: if (isCacheValid(row)) {
195: return m_cache.getBoolean(row);
196: } else {
197: boolean value = m_expr.getBoolean(m_table.getTuple(row));
198: m_cache.setBoolean(value, row);
199: m_valid.set(row);
200: return value;
201: }
202: }
203:
204: private void computeNumber(int row) {
205: if (m_columnType == int.class || m_columnType == byte.class) {
206: m_cache.setInt(m_expr.getInt(m_table.getTuple(row)), row);
207: } else if (m_columnType == long.class) {
208: m_cache.setLong(m_expr.getLong(m_table.getTuple(row)), row);
209: } else if (m_columnType == float.class) {
210: m_cache.setFloat(m_expr.getFloat(m_table.getTuple(row)),
211: row);
212: } else {
213: m_cache.setDouble(m_expr.getDouble(m_table.getTuple(row)),
214: row);
215: }
216: m_valid.set(row);
217: }
218:
219: /**
220: * @see prefuse.data.column.Column#getInt(int)
221: */
222: public int getInt(int row) throws DataTypeException {
223: if (!canGetInt())
224: throw new DataTypeException(int.class);
225: rangeCheck(row);
226:
227: if (!isCacheValid(row))
228: computeNumber(row);
229: return m_cache.getInt(row);
230: }
231:
232: /**
233: * @see prefuse.data.column.Column#getDouble(int)
234: */
235: public double getDouble(int row) throws DataTypeException {
236: if (!canGetDouble())
237: throw new DataTypeException(double.class);
238: rangeCheck(row);
239:
240: if (!isCacheValid(row))
241: computeNumber(row);
242: return m_cache.getDouble(row);
243: }
244:
245: /**
246: * @see prefuse.data.column.Column#getFloat(int)
247: */
248: public float getFloat(int row) throws DataTypeException {
249: if (!canGetFloat())
250: throw new DataTypeException(float.class);
251: rangeCheck(row);
252:
253: if (!isCacheValid(row))
254: computeNumber(row);
255: return m_cache.getFloat(row);
256: }
257:
258: /**
259: * @see prefuse.data.column.Column#getLong(int)
260: */
261: public long getLong(int row) throws DataTypeException {
262: if (!canGetLong())
263: throw new DataTypeException(long.class);
264: rangeCheck(row);
265:
266: if (!isCacheValid(row))
267: computeNumber(row);
268: return m_cache.getLong(row);
269: }
270:
271: // ------------------------------------------------------------------------
272: // Listener Methods
273:
274: private class Listener implements ColumnListener,
275: ExpressionListener {
276:
277: public void columnChanged(int start, int end) {
278: // for a single index change with a valid cache value,
279: // propagate a change event with the previous value
280: if (start == end && isCacheValid(start)) {
281: if (!m_table.isValidRow(start))
282: return;
283:
284: // invalidate the cache index
285: invalidateCache(start, end);
286: // fire change event including previous value
287: Class type = getColumnType();
288: if (int.class == type) {
289: fireColumnEvent(start, m_cache.getInt(start));
290: } else if (long.class == type) {
291: fireColumnEvent(start, m_cache.getLong(start));
292: } else if (float.class == type) {
293: fireColumnEvent(start, m_cache.getFloat(start));
294: } else if (double.class == type) {
295: fireColumnEvent(start, m_cache.getDouble(start));
296: } else if (boolean.class == type) {
297: fireColumnEvent(start, m_cache.getBoolean(start));
298: } else {
299: fireColumnEvent(start, m_cache.get(start));
300: }
301:
302: // otherwise send a generic update
303: } else {
304: // invalidate cache indices
305: invalidateCache(start, end);
306: // fire change event
307: fireColumnEvent(EventConstants.UPDATE, start, end);
308: }
309: }
310:
311: public void columnChanged(Column src, int idx, boolean prev) {
312: columnChanged(idx, idx);
313: }
314:
315: public void columnChanged(Column src, int idx, double prev) {
316: columnChanged(idx, idx);
317: }
318:
319: public void columnChanged(Column src, int idx, float prev) {
320: columnChanged(idx, idx);
321: }
322:
323: public void columnChanged(Column src, int type, int start,
324: int end) {
325: columnChanged(start, end);
326: }
327:
328: public void columnChanged(Column src, int idx, int prev) {
329: columnChanged(idx, idx);
330: }
331:
332: public void columnChanged(Column src, int idx, long prev) {
333: columnChanged(idx, idx);
334: }
335:
336: public void columnChanged(Column src, int idx, Object prev) {
337: columnChanged(idx, idx);
338: }
339:
340: public void expressionChanged(Expression expr) {
341: // mark everything as changed
342: columnChanged(0, m_cache.getRowCount() - 1);
343: // re-initialize our setup
344: init();
345: }
346: }
347:
348: } // end of class DerivedColumn
|