KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > prefuse > data > CascadedTable


1 package prefuse.data;
2
3 import java.util.ArrayList JavaDoc;
4 import java.util.Iterator JavaDoc;
5
6 import javax.swing.event.TableModelEvent JavaDoc;
7
8 import prefuse.data.column.Column;
9 import prefuse.data.column.ColumnMetadata;
10 import prefuse.data.event.EventConstants;
11 import prefuse.data.event.ExpressionListener;
12 import prefuse.data.event.ProjectionListener;
13 import prefuse.data.event.TableListener;
14 import prefuse.data.expression.BooleanLiteral;
15 import prefuse.data.expression.Expression;
16 import prefuse.data.expression.Predicate;
17 import prefuse.data.tuple.TableTuple;
18 import prefuse.data.util.AcceptAllColumnProjection;
19 import prefuse.data.util.CascadedRowManager;
20 import prefuse.data.util.ColumnProjection;
21 import prefuse.util.collections.CompositeIterator;
22 import prefuse.util.collections.IntIterator;
23
24
25 /**
26  * <p>Table subclass featuring a "cascaded" table design - a CascadedTable can
27  * have a parent table, from which it inherits a potentially filtered set of
28  * rows and columns. Child tables may override the columns of the parent by
29  * having a column of the same name as that of the parent, in which case the
30  * parent's column will not be accessible.</p>
31  *
32  * <p>Table rows of the parent table can be selectively included by providing
33  * a {@link prefuse.data.expression.Predicate} that filters the parent rows.
34  * Columns of the parent table can be selectively included by providing
35  * a {@link prefuse.data.util.ColumnProjection} indicating the columns to
36  * include.</p>
37  *
38  * <p>Tuple instances backed by a CascadedTable will be not be equivalent to
39  * the tuples backed by the parent table. However, setting a value in a
40  * CascadedTable that is inherited from a parent table <em>will</em> update
41  * the value in the parent table.</p>
42  *
43  * @author <a HREF="http://jheer.org">jeffrey heer</a>
44  */

45 public class CascadedTable extends Table {
46
47     /** Cascaded parent table */
48     protected Table m_parent;
49     /** List of included parent column names */
50     protected ArrayList JavaDoc m_pnames;
51     
52     /** ColumnProjection determining which columns of the parent table
53      * are included in this table. */

54     protected ColumnProjection m_colFilter;
55     /** Selection Predicate determining which rows of the parent table
56      * are included in this table. */

57     protected Predicate m_rowFilter;
58     
59     /** An internal listener class */
60     protected Listener m_listener;
61     
62     // ------------------------------------------------------------------------
63
// Constructor
64

65     /**
66      * Create a new CascadedTable. By default all rows and columns of the
67      * parent table are included in this one.
68      * @param parent the parent Table to use
69      */

70     public CascadedTable(Table parent) {
71         this(parent, null, null);
72     }
73     
74     /**
75      * Create a new CascadedTable. By default all columns of the parent
76      * table are included in this one.
77      * @param parent the parent Table to use
78      * @param rowFilter a Predicate determining which rows of the parent
79      * table to include in this one.
80      */

81     public CascadedTable(Table parent, Predicate rowFilter) {
82         this(parent, rowFilter, null);
83     }
84     
85     /**
86      * Create a new CascadedTable. By default all rows of the parent
87      * table are included in this one.
88      * @param parent the parent Table to use
89      * @param colFilter a ColumnProjection determining which columns of the
90      * parent table to include in this one.
91      */

92     public CascadedTable(Table parent, ColumnProjection colFilter) {
93         this(parent, null, colFilter);
94     }
95     
96     /**
97      * Create a new CascadedTable.
98      * @param parent the parent Table to use
99      * @param rowFilter a Predicate determining which rows of the parent
100      * table to include in this one.
101      * @param colFilter a ColumnProjection determining which columns of the
102      * parent table to include in this one.
103      */

104     public CascadedTable(Table parent, Predicate rowFilter,
105                          ColumnProjection colFilter)
106     {
107         this(parent, rowFilter, colFilter, TableTuple.class);
108     }
109     
110     /**
111      * Create a new CascadedTable.
112      * @param parent the parent Table to use
113      * @param rowFilter a Predicate determining which rows of the parent
114      * table to include in this one.
115      * @param colFilter a ColumnProjection determining which columns of the
116      * parent table to include in this one.
117      * @param tupleType the class type of the Tuple instances to use
118      */

119     protected CascadedTable(Table parent, Predicate rowFilter,
120             ColumnProjection colFilter, Class JavaDoc tupleType)
121     {
122         super(0, 0, tupleType);
123         m_parent = parent;
124         m_pnames = new ArrayList JavaDoc();
125         m_rows = new CascadedRowManager(this);
126         m_listener = new Listener();
127         
128         setColumnProjection(colFilter);
129         setRowFilter(rowFilter);
130         m_parent.addTableListener(m_listener);
131     }
132     
133     // -- non-cascading version -----------------------------------------------
134

135     /**
136      * Create a CascadedTable without a backing parent table.
137      */

138     protected CascadedTable() {
139         this(TableTuple.class);
140     }
141
142     /**
143      * Create a CascadedTable without a backing parent table.
144      * @param tupleType the class type of the Tuple instances to use
145      */

146     protected CascadedTable(Class JavaDoc tupleType) {
147         super(0, 0, tupleType);
148         m_pnames = new ArrayList JavaDoc();
149     }
150     
151     // ------------------------------------------------------------------------
152
// Filter Methods
153

154     /**
155      * Determines which columns are inherited from the backing parent table.
156      */

157     protected void filterColumns() {
158         if ( m_parent == null ) return;
159         
160         for ( int i=0; i<m_pnames.size(); ++i ) {
161             String JavaDoc name = (String JavaDoc)m_pnames.get(i);
162             Column col = m_parent.getColumn(i);
163             boolean contained = m_names.contains(name);
164             if ( !m_colFilter.include(col, name) || contained ) {
165                 m_pnames.remove(i--);
166                 if ( !contained ) {
167                     ((ColumnEntry)m_entries.get(name)).dispose();
168                     m_entries.remove(name);
169                 }
170                 
171                 // fire notification
172
fireTableEvent(m_rows.getMinimumRow(),
173                                m_rows.getMaximumRow(),
174                                i, EventConstants.DELETE);
175             }
176         }
177         
178         m_pnames.clear();
179
180         Iterator JavaDoc pcols = m_parent.getColumnNames();
181         for ( int i=0, j=m_columns.size(); pcols.hasNext(); ++i ) {
182             String JavaDoc name = (String JavaDoc)pcols.next();
183             Column col = m_parent.getColumn(i);
184             
185             if ( m_colFilter.include(col, name) && !m_names.contains(name) ) {
186                 m_pnames.add(name);
187                 ColumnEntry entry = (ColumnEntry)m_entries.get(name);
188                 if ( entry == null ) {
189                     entry = new ColumnEntry(j++, col,
190                             new ColumnMetadata(this, name));
191                     m_entries.put(name, entry);
192                     // fire notification
193
fireTableEvent(m_rows.getMinimumRow(),
194                                    m_rows.getMaximumRow(),
195                                    i, EventConstants.INSERT);
196                 } else {
197                     entry.colnum = j++;
198                 }
199                 m_lastCol = m_columns.size()-1;
200             }
201         }
202         
203     }
204     
205     /**
206      * Manually trigger a re-filtering of the rows of this table. If the
207      * filtering predicate concerns only items within this table, calling
208      * this method should be unnecessary. It is only when the filtering
209      * predicate references data outside of this table that a manual
210      * re-filtering request may be necessary. For example, filtering
211      * valid edges of a graph from a pool of candidate edges will depend
212      * on the available nodes.
213      * @see prefuse.data.util.ValidEdgePredicate
214      */

215     public void filterRows() {
216         if ( m_parent == null ) return;
217         
218         CascadedRowManager rowman = (CascadedRowManager)m_rows;
219         IntIterator crows = m_rows.rows();
220         while ( crows.hasNext() ) {
221             int crow = crows.nextInt();
222             if ( !m_rowFilter.getBoolean(
223                     m_parent.getTuple(rowman.getParentRow(crow))) )
224             {
225                 removeCascadedRow(crow);
226             }
227         }
228         
229         Iterator JavaDoc ptuples = m_parent.tuples(m_rowFilter);
230         while ( ptuples.hasNext() ) {
231             Tuple pt = (Tuple)ptuples.next();
232             int prow = pt.getRow();
233             if ( rowman.getChildRow(prow) == -1 )
234                 addCascadedRow(prow);
235         }
236     }
237     
238     /**
239      * Get the ColumnProjection determining which columns of the
240      * parent table are included in this one.
241      * @return the ColumnProjection of this CascadedTable
242      */

243     public ColumnProjection getColumnProjection() {
244         return m_colFilter;
245     }
246     
247     /**
248      * Sets the ColumnProjection determining which columns of the
249      * parent table are included in this one.
250      * @param colFilter a ColumnProjection determining which columns of the
251      * parent table to include in this one.
252      */

253     public void setColumnProjection(ColumnProjection colFilter) {
254         if ( m_colFilter != null ) {
255             m_colFilter.removeProjectionListener(m_listener);
256         }
257         m_colFilter = colFilter==null ? new AcceptAllColumnProjection() : colFilter;
258         m_colFilter.addProjectionListener(m_listener);
259         filterColumns();
260     }
261     
262     /**
263      * Gets ths Predicate determining which rows of the parent
264      * table are included in this one.
265      * @return the row filtering Predicate of this CascadedTable
266      */

267     public Predicate getRowFilter() {
268         return m_rowFilter;
269     }
270     
271     /**
272      * Sets the Predicate determining which rows of the parent
273      * table are included in this one.
274      * @param rowFilter a Predicate determining which rows of the parent
275      * table to include in this one.
276      */

277     public void setRowFilter(Predicate rowFilter) {
278         if ( m_rowFilter != null ) {
279             m_rowFilter.removeExpressionListener(m_listener);
280         }
281         m_rowFilter = rowFilter==null ? BooleanLiteral.TRUE : rowFilter;
282         if ( m_rowFilter != BooleanLiteral.TRUE )
283             m_rowFilter.addExpressionListener(m_listener);
284         filterRows();
285     }
286     
287     // ------------------------------------------------------------------------
288
// Table Metadata
289

290     /**
291      * @see prefuse.data.Table#getColumnCount()
292      */

293     public int getColumnCount() {
294         return m_columns.size() + m_pnames.size();
295     }
296     
297     /**
298      * Get the number of columns explicitly stored by this table (i.e., all
299      * columns that are not inherited from the parent table).
300      * @return the number of locally stored columns
301      */

302     public int getLocalColumnCount() {
303         return m_columns.size();
304     }
305     
306     // ------------------------------------------------------------------------
307
// Parent Table Methods
308

309     /**
310      * Get the parent table from which this cascaded table inherits values.
311      * @return the parent table
312      */

313     public Table getParentTable() {
314         return m_parent;
315     }
316     
317     /**
318      * Given a row in this table, return the corresponding row in the parent
319      * table.
320      * @param row a row in this table
321      * @return the corresponding row in the parent table
322      */

323     public int getParentRow(int row) {
324         return ((CascadedRowManager)m_rows).getParentRow(row);
325     }
326     
327     /**
328      * Given a row in the parent table, return the corresponding row, if any,
329      * in this table.
330      * @param prow a row in the parent table
331      * @return the corresponding row in this table, or -1 if the given parent
332      * row is not inherited by this table
333      */

334     public int getChildRow(int prow) {
335         return ((CascadedRowManager)m_rows).getChildRow(prow);
336     }
337     
338     // ------------------------------------------------------------------------
339
// Row Operations
340

341     /**
342      * @see prefuse.data.Table#addRow()
343      */

344     public int addRow() {
345         if ( m_parent != null ) {
346             throw new IllegalStateException JavaDoc(
347                 "Add row not supported for CascadedTable.");
348         } else {
349             return super.addRow();
350         }
351     }
352     
353     /**
354      * @see prefuse.data.Table#addRows(int)
355      */

356     public void addRows(int nrows) {
357         if ( m_parent != null ) {
358             throw new IllegalStateException JavaDoc(
359                 "Add rows not supported for CascadedTable.");
360         } else {
361             super.addRows(nrows);
362         }
363     }
364     
365     /**
366      * @see prefuse.data.Table#removeRow(int)
367      */

368     public boolean removeRow(int row) {
369         if ( m_parent != null ) {
370             throw new IllegalStateException JavaDoc(
371                 "Remove row not supported for CascadedTable.");
372         } else {
373             return super.removeRow(row);
374         }
375     }
376
377     /**
378      * Internal method for adding a new cascaded row backed by
379      * the given parent row.
380      * @param prow the parent row to inherit
381      * @return the row number ofr the newly added row in this table
382      */

383     protected int addCascadedRow(int prow) {
384         int r = m_rows.addRow();
385         ((CascadedRowManager)m_rows).put(r, prow);
386         updateRowCount();
387         
388         fireTableEvent(r, r, TableModelEvent.ALL_COLUMNS,
389                        TableModelEvent.INSERT);
390         return r;
391     }
392     
393     /**
394      * Internal method for removing a cascaded row from this table.
395      * @param row the row to remove
396      * @return true if the row was successfully removed, false otherwise
397      */

398     protected boolean removeCascadedRow(int row) {
399         boolean rv = super.removeRow(row);
400         if ( rv )
401             ((CascadedRowManager)m_rows).remove(row);
402         return rv;
403     }
404     
405     // ------------------------------------------------------------------------
406
// Column Operations
407

408     /**
409      * @see prefuse.data.Table#getColumnName(int)
410      */

411     public String JavaDoc getColumnName(int col) {
412         int local = m_names.size();
413         if ( col >= local ) {
414             return (String JavaDoc)m_pnames.get(col-local);
415         } else {
416             return (String JavaDoc)m_names.get(col);
417         }
418     }
419     
420     /**
421      * @see prefuse.data.Table#getColumnNumber(prefuse.data.column.Column)
422      */

423     public int getColumnNumber(Column col) {
424         int idx = m_columns.indexOf(col);
425         if ( idx == -1 && m_parent != null ) {
426             idx = m_parent.getColumnNumber(col);
427             if ( idx == -1 ) return idx;
428             String JavaDoc name = m_parent.getColumnName(idx);
429             idx = m_pnames.indexOf(name);
430             if ( idx != -1 ) idx += m_columns.size();
431         }
432         return idx;
433      }
434     
435     /**
436      * @see prefuse.data.Table#getColumn(int)
437      */

438     public Column getColumn(int col) {
439         m_lastCol = col;
440         int local = m_names.size();
441         if ( col >= local && m_parent != null ) {
442             return m_parent.getColumn((String JavaDoc)m_pnames.get(col-local));
443         } else {
444             return (Column)m_columns.get(col);
445         }
446     }
447     
448     /**
449      * @see prefuse.data.Table#hasColumn(java.lang.String)
450      */

451     protected boolean hasColumn(String JavaDoc name) {
452         int idx = getColumnNumber(name);
453         return idx >= 0 && idx < getLocalColumnCount();
454     }
455     
456     /**
457      * @see prefuse.data.Table#getColumnNames()
458      */

459     protected Iterator JavaDoc getColumnNames() {
460         if ( m_parent == null ) {
461             return m_names.iterator();
462         } else {
463             return new CompositeIterator(m_names.iterator(),
464                                          m_pnames.iterator());
465         }
466     }
467     
468     /**
469      * Invalidates this table's cached schema. This method should be called
470      * whenever columns are added or removed from this table.
471      */

472     protected void invalidateSchema() {
473         super.invalidateSchema();
474         this.filterColumns();
475     }
476     
477     // ------------------------------------------------------------------------
478
// Listener Methods
479

480     /**
481      * Internal listener class handling updates from the backing parent table,
482      * the column projection, or the row selection predicate.
483      */

484     private class Listener
485         implements TableListener, ProjectionListener, ExpressionListener
486     {
487         public void tableChanged(Table t, int start, int end, int col, int type) {
488             // must come from parent
489
if ( t != m_parent )
490                 return;
491             
492             CascadedRowManager rowman = (CascadedRowManager)m_rows;
493             
494             // switch on the event type
495
switch ( type ) {
496             case EventConstants.UPDATE:
497             {
498                 // do nothing if update on all columns, as this is only
499
// used to indicate a non-measurable update.
500
if ( col == EventConstants.ALL_COLUMNS ) {
501                     break;
502                 }
503                 
504                 // process each update, check if filtered state changes
505
for ( int r=start, cr=-1; r<=end; ++r ) {
506                     if ( (cr=rowman.getChildRow(r)) != -1 ) {
507                         // the parent row has a corresponding row in this table
508
if ( m_rowFilter.getBoolean(m_parent.getTuple(r)) ) {
509                             // row still passes the filter, check the column
510
int idx = getColumnNumber(m_parent.getColumnName(col));
511                             if ( idx >= getLocalColumnCount() )
512                                 fireTableEvent(cr, cr, idx, EventConstants.UPDATE);
513                         } else {
514                             // row no longer passes the filter, remove it
515
removeCascadedRow(cr);
516                         }
517                     } else {
518                         // does it now pass the filter due to the update?
519
if ( m_rowFilter.getBoolean(m_parent.getTuple(r)) ) {
520                             if ( (cr=rowman.getChildRow(r)) < 0 )
521                                 addCascadedRow(r);
522                         }
523                     }
524                 }
525                 break;
526             }
527             case EventConstants.DELETE:
528             {
529                 if ( col == EventConstants.ALL_COLUMNS ) {
530                     // entire rows deleted
531
for ( int r=start, cr=-1; r<=end; ++r ) {
532                         if ( (cr=rowman.getChildRow(r)) != -1 )
533                             removeCascadedRow(cr);
534                     }
535                 } else {
536                     // column deleted
537
filterColumns();
538                 }
539                 break;
540             }
541             case EventConstants.INSERT:
542                 if ( col == EventConstants.ALL_COLUMNS ) {
543                     // entire rows added
544
for ( int r=start; r<=end; ++r ) {
545                         if ( m_rowFilter.getBoolean(m_parent.getTuple(r)) ) {
546                             if ( rowman.getChildRow(r) < 0 )
547                                 addCascadedRow(r);
548                         }
549                     }
550                 } else {
551                     // column added
552
filterColumns();
553                 }
554                 break;
555             }
556         }
557     
558         public void projectionChanged(ColumnProjection projection) {
559             if ( projection == m_colFilter )
560                 filterColumns();
561         }
562     
563         public void expressionChanged(Expression expr) {
564             if ( expr == m_rowFilter )
565                 filterRows();
566         }
567     }
568     
569 } // end of class CascadedTable
570
Popular Tags