KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > decorator > FilterPipeline


1 /*
2  * $Id: FilterPipeline.java,v 1.3 2005/02/18 11:22:47 kleopatra Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  */

7
8 package org.jdesktop.swing.decorator;
9
10 import java.util.BitSet JavaDoc;
11 import java.util.List JavaDoc;
12 import java.util.Vector JavaDoc;
13
14 import javax.swing.event.EventListenerList JavaDoc;
15
16 /**
17  * <p>A <b><code>FilterPipeline</code></b> is used to define the set of
18  * {@link org.jdesktop.swing.decorator.Filter filters}
19  * for a data-aware component such as a {@link org.jdesktop.swing.JXList} or a
20  * {@link org.jdesktop.swing.JXTable}. Filtering involves interposing one or
21  * more filters in a {@link org.jdesktop.swing.decorator.FilterPipeline} between
22  * a data model and a view to change the apparent order and/or number of records
23  * in the data model. The order of filters in the filter pipeline determines the
24  * order in which each filter is applied. The output from one filter in the
25  * pipeline is piped as the input to the next filter in the pipeline.</p>
26  *
27  * <pre>
28  * {@link org.jdesktop.swing.decorator.Filter}[] filters = new {@link org.jdesktop.swing.decorator.Filter}[] {
29  * new {@link org.jdesktop.swing.decorator.PatternFilter}("S.*", 0, 1), // regex, matchflags, column
30  * new {@link org.jdesktop.swing.decorator.ShuttleSorter}(1, false), // column 1, descending
31  * new {@link org.jdesktop.swing.decorator.ShuttleSorter}(0, true), // column 0, ascending
32  * };
33  * {@link org.jdesktop.swing.decorator.FilterPipeline} pipeline = new {@link org.jdesktop.swing.decorator.FilterPipeline}(filters);
34  * {@link org.jdesktop.swing.JXTable} table = new {@link org.jdesktop.swing.JXTable}(model);
35  * table.setFilters(pipeline);
36  * </pre>
37  *
38  * This is all you need to do in order to use <code>FilterPipeline</code>. Most
39  * of the methods in this class are only for advanced developers who want to write
40  * their own filter subclasses and want to override the way a filter pipeline works.
41  *
42  * @author Ramesh Gupta
43  * @see org.jdesktop.swing.decorator.Filter
44  */

45 public class FilterPipeline {
46     protected EventListenerList JavaDoc listenerList = new EventListenerList JavaDoc();
47     private ComponentAdapter adapter = null;
48     private Sorter sorter = null;
49     private final Filter[] filters;
50
51     /**
52      * Constructs a new <code>FilterPipeline</code> populated with the specified
53      * filters that are applied in the order they appear in the list. Since filters
54      * maintain state about the view to which they are attached, an instance of
55      * a filter may not ever be used in more than one pipeline.
56      *
57      * @param inList array of filters
58      */

59     public FilterPipeline(Filter[] inList) {
60         filters = reorderSorters(inList, locateSorters(inList));
61
62         for (int i = 0; i < filters.length; i++) {
63             if (filters[i].order < 0) {
64                 filters[i].order = i;
65             }
66             else {
67                 throw new IllegalArgumentException JavaDoc("Element " + i +
68                     " is part of another pipeline.");
69             }
70         }
71         // individual filters not bound until adapter is bound
72
}
73
74     /**
75      * Sets the sorter that the output of the filter pipeline is piped through.
76      * This is the sorter that is installed interactively on a view by a user
77      * action. This is an implementation detail.
78      *
79      * @param sorter the interactive sorter, if any; null otherwise.
80      */

81     void setSorter(Sorter sorter) {
82         this.sorter = sorter;
83     }
84
85     /**
86      * Assigns a {@link org.jdesktop.swing.decorator.ComponentAdapter} to this
87      * pipeline if no adapter has previously been assigned to the pipeline. Once an
88      * adapter has been assigned to this pipeline, any attempt to change that will
89      * cause an exception to be thrown.
90      *
91      * @param adapter the <code>ComponentAdapter</code> to assign
92      * @throws IllegalArgumentException if adapter is null
93      * @throws IllegalStateException if an adapter is already assigned to this
94      * pipeline and the new adapter is not the same the existing adapter
95      */

96      public final void assign(ComponentAdapter adapter) {
97         if (adapter == null) {
98             throw new IllegalArgumentException JavaDoc("null adapter");
99         }
100
101     // also assign individual filters when adapter is bound
102
if (this.adapter == null) {
103             this.adapter = adapter;
104             for (int i = 0; i < filters.length; i++) {
105                 filters[i].assign(this);
106                 filters[i].assign(adapter);
107             }
108         }
109         else if (this.adapter != adapter){
110             throw new IllegalStateException JavaDoc("Can't bind to a different adapter");
111         }
112     }
113
114     /**
115      * Returns true if this pipeline contains the specified filter;
116      * otherwise it returns false.
117      *
118      * @param filter filter whose membership in this pipeline is tested
119      * @return true if this pipeline contains the specified filter;
120      * otherwise it returns false
121      */

122     boolean contains(Filter filter) {
123         return (filter.order >= 0) &&
124                 (filters.length > 0) && (filters[filter.order] == filter);
125     }
126
127     /**
128      * Returns the first filter, if any, in this pipeline, or null, if there are
129      * no filters in this pipeline.
130      *
131      * @return the first filter, if any, in this pipeline, or null, if there are
132      * no filters in this pipeline
133      */

134     Filter first() {
135         return (filters.length > 0) ? filters[0] : null;
136     }
137
138     /**
139      * Returns the last filter, if any, in this pipeline, or null, if there are
140      * no filters in this pipeline.
141      *
142      * @return the last filter, if any, in this pipeline, or null, if there are
143      * no filters in this pipeline
144      */

145     Filter last() {
146         return (filters.length > 0) ? filters[filters.length - 1] : null;
147     }
148
149     /**
150      * Returns the filter after the supplied filter in this pipeline,
151      * or null, if there aren't any filters after the supplied filter.
152      *
153      * @param filter a filter in this pipeline
154      * @return the filter after the supplied filter in this pipeline,
155      * or null, if there aren't any filters after the supplied filter
156      */

157     Filter next(Filter filter) {
158         return last().equals(filter) ? null : filters[filter.order + 1];
159     }
160
161     /**
162      * Returns the filter before the supplied filter in this pipeline,
163      * or null, if there aren't any filters before the supplied filter.
164      *
165      * @param filter a filter in this pipeline
166      * @return the filter before the supplied filter in this pipeline,
167      * or null, if there aren't any filters before the supplied filter
168      */

169     Filter previous(Filter filter) {
170         return first().equals(filter) ? null : filters[filter.order - 1];
171     }
172
173     /**
174      * Called when the specified filter has changed.
175      * Cascades <b><code>filterChanged</code></b> notifications to the next
176      * filter in the pipeline after the specified filter. If the specified filter
177      * is the last filter in the pipeline, this method broadcasts a
178      * <b><code>filterChanged</code></b> notification to all
179      * <b><code>PipelineListener</code></b> objects registered with this pipeline.
180      *
181      * @param filter a filter in this pipeline that has changed in any way
182      */

183     protected void filterChanged(Filter filter) {
184         Filter next = contains(filter) ? next(filter) : null;
185         if (next == null) {
186             if (sorter == null) {
187                 fireContentsChanged();
188             }
189             else {
190                 sorter.refresh(); // cascade to interposed sorter
191
}
192         }
193         else {
194             next.refresh(); // Cascade to next filter
195
}
196     }
197
198 /*
199     public int getInputSize() {
200         return adapter.getRowCount();
201     }
202 */

203     int getInputSize(Filter filter) {
204         if (contains(filter)) {
205             Filter previous = previous(filter);
206             if (previous == null) {
207                 return adapter.getRowCount();
208             }
209             else {
210                 return previous.getSize();
211             }
212         }
213         else if (filter.getPipeline() == this) {
214             return getOutputSize();
215         }
216
217         return 0;
218     }
219
220     /**
221      * Returns the number of records in the filtered view.
222      *
223      * @return the number of records in the filtered view
224      */

225     public int getOutputSize() {
226         Filter last = last();
227         return (last == null) ? adapter.getRowCount() : last.getSize();
228     }
229
230 /*
231     public int getOutputSize(Filter filter) {
232         return (isMember(filter)) ? filter.getSize() : 0;
233     }
234 */

235     /**
236      * Convert row index from view coordinates to model coordinates
237      * accounting for the presence of sorters and filters. This is essentially
238      * a pass-through to the {@link org.jdesktop.swing.decorator.Filter#convertRowIndexToModel(int) convertRowIndexToModel}
239      * method of the <em>last</em> {@link org.jdesktop.swing.decorator.Filter},
240      * if any, in this pipeline.
241      *
242      * @param row row index in view coordinates
243      * @return row index in model coordinates
244      */

245     public int convertRowIndexToModel(int row) {
246         Filter last = last();
247         return (last == null) ? row : last.convertRowIndexToModel(row);
248     }
249
250     int convertRowIndexToModel(Filter filter, int row) {
251         if (contains(filter)) {
252             Filter previous = previous(filter);
253             if (previous == null) {
254                 return row;
255             }
256             else {
257                 return previous.convertRowIndexToModel(row);
258             }
259         }
260         else {
261             Filter last = last();
262             if (last == null) {
263                 return row;
264             }
265             else {
266                 return last.convertRowIndexToModel(row);
267             }
268         }
269     }
270
271     /**
272      * Convert row index from model coordinates to view coordinates
273      * accounting for the presence of sorters and filters. This is essentially
274      * a pass-through to the {@link org.jdesktop.swing.decorator.Filter#convertRowIndexToView(int) convertRowIndexToModel}
275      * method of the <em>last</em> {@link org.jdesktop.swing.decorator.Filter},
276      * if any, in this pipeline.
277      *
278      * @param row row index in model coordinates
279      * @return row index in view coordinates
280      */

281     public int convertRowIndexToView(int row) {
282         Filter last = last();
283         return (last == null) ? row : last.convertRowIndexToView(row);
284     }
285
286     int convertRowIndexToView(Filter filter, int row) {
287         if (contains(filter)) {
288             Filter previous = previous(filter);
289             if (previous == null) {
290                 return row;
291             }
292             else {
293                 return previous.convertRowIndexToView(row);
294             }
295         }
296         else {
297             Filter last = last();
298             if (last == null) {
299                 return row;
300             }
301             else {
302                 return last.convertRowIndexToView(row);
303             }
304         }
305     }
306
307     /**
308      * Returns the value of the cell at the specified coordinates.
309      *
310      * @param row in view coordinates
311      * @param column in model coordinates
312      * @return the value of the cell at the specified coordinates
313      */

314     public Object JavaDoc getValueAt(int row, int column) {
315         Filter last = last();
316         return (last == null) ? null : last.getValueAt(row, column);
317     }
318
319
320     Object JavaDoc getInputValueFor(Filter filter, int row, int column) {
321         if (contains(filter)) {
322             Filter previous = previous(filter);
323             if (previous == null) {
324                 return adapter.getValueAt(row, adapter.modelToView(column));
325             }
326             else {
327                 return previous.getValueAt(row, column);
328             }
329         }
330         else {
331             Filter last = last();
332             if (last == null) {
333                 return adapter.getValueAt(row, adapter.modelToView(column));
334             }
335             else {
336                 return last.getValueAt(row, column);
337             }
338         }
339     }
340
341     public void setValueAt(Object JavaDoc aValue, int row, int column) {
342         Filter last = last();
343         if (last != null) {
344             last.setValueAt(aValue, row, column);
345         }
346     }
347
348     void setInputValueFor(Object JavaDoc aValue, Filter filter, int row, int column) {
349         if (contains(filter)) {
350             Filter previous = previous(filter);
351             if (previous == null) {
352                 adapter.setValueAt(aValue, row, adapter.modelToView(column));
353             }
354             else {
355                 previous.setValueAt(aValue, row, column);
356             }
357         }
358         else {
359             Filter last = last();
360             if (last == null) {
361                 adapter.setValueAt(aValue, row, adapter.modelToView(column));
362             }
363             else {
364                 last.setValueAt(aValue, row, column);
365             }
366         }
367     }
368
369     public boolean isCellEditable(int row, int column) {
370         Filter last = last();
371         return (last == null) ? false : last.isCellEditable(row, column);
372     }
373
374     boolean isInputEditableFor(Filter filter, int row, int column) {
375         /** @todo test this. Why are we calling translateToPreviousFilter? */
376         // JW: because Filter isCellEditable didn't!
377
// int inputRow = filter.translateToPreviousFilter(row);
378
int inputRow = row;
379         if (contains(filter)) {
380             Filter previous = previous(filter);
381             if (previous == null) {
382                 return adapter.isCellEditable(inputRow, adapter.modelToView(column));
383             }
384             else {
385                 return previous.isCellEditable(inputRow, column);
386             }
387         }
388         else {
389             Filter last = last();
390             if (last == null) {
391                 return adapter.isCellEditable(inputRow, adapter.modelToView(column));
392             }
393             else {
394                 return last.isCellEditable(inputRow, column);
395             }
396         }
397     }
398
399     /**
400      * Flushes the pipeline by initiating a {@link org.jdesktop.swing.decorator.Filter#refresh() refresh}
401      * on the <em>first</em> {@link org.jdesktop.swing.decorator.Filter filter},
402      * if any, in this pipeline. After that filter has refreshed itself, it sends a
403      * {@link #filterChanged(org.jdesktop.swing.decorator.Filter) filterChanged}
404      * notification to this pipeline, and the pipeline responds by initiating a
405      * {@link org.jdesktop.swing.decorator.Filter#refresh() refresh}
406      * on the <em>next</em> {@link org.jdesktop.swing.decorator.Filter filter},
407      * if any, in this pipeline. Eventualy, when there are no more filters left
408      * in the pipeline, it broadcasts a {@link org.jdesktop.swing.decorator.PipelineEvent}
409      * signaling a {@link org.jdesktop.swing.decorator.PipelineEvent#CONTENTS_CHANGED}
410      * message to all {@link org.jdesktop.swing.decorator.PipelineListener} objects
411      * registered with this pipeline.
412      */

413     public void flush() {
414         if ((filters != null) && (filters.length > 0)) {
415             filters[0].refresh();
416         }
417         else if (sorter != null) {
418             sorter.refresh();
419         }
420     }
421
422     /**
423      * Adds a listener to the list that's notified each time there is a change
424      * to this pipeline.
425      *
426      * @param l the <code>PipelineListener</code> to be added
427      */

428     public void addPipelineListener(PipelineListener l) {
429         listenerList.add(PipelineListener.class, l);
430     }
431
432     /**
433      * Removes a listener from the list that's notified each time there is a change
434      * to this pipeline.
435      *
436      * @param l the <code>PipelineListener</code> to be removed
437      */

438     public void removePipelineListener(PipelineListener l) {
439         listenerList.remove(PipelineListener.class, l);
440     }
441
442     /**
443      * Returns an array of all the pipeline listeners
444      * registered on this <code>FilterPipeline</code>.
445      *
446      * @return all of this pipeline's <code>PipelineListener</code>s,
447      * or an empty array if no pipeline listeners
448      * are currently registered
449      *
450      * @see #addPipelineListener
451      * @see #removePipelineListener
452      */

453     public PipelineListener[] getPipelineListeners() {
454         return (PipelineListener[]) listenerList.getListeners(
455             PipelineListener.class);
456     }
457
458     /**
459      * Notifies all registered {@link org.jdesktop.swing.decorator.PipelineListener}
460      * objects that the contents of this pipeline has changed. The event instance
461      * is lazily created.
462      */

463     protected void fireContentsChanged() {
464         Object JavaDoc[] listeners = listenerList.getListenerList();
465         PipelineEvent e = null;
466
467         for (int i = listeners.length - 2; i >= 0; i -= 2) {
468             if (listeners[i] == PipelineListener.class) {
469                 if (e == null) {
470                     e = new PipelineEvent(this, PipelineEvent.CONTENTS_CHANGED);
471                 }
472                 ( (PipelineListener) listeners[i + 1]).contentsChanged(e);
473             }
474         }
475     }
476
477     private List JavaDoc locateSorters(Filter[] inList) {
478         BitSet JavaDoc sortableColumns = new BitSet JavaDoc(); // temporary structure for checking
479
List JavaDoc sorterLocations = new Vector JavaDoc();
480         for (int i = 0; i < inList.length; i++) {
481             if (inList[i] instanceof Sorter) {
482                 int columnIndex = inList[i].getColumnIndex();
483                 if (columnIndex < 0) {
484                     throw new IndexOutOfBoundsException JavaDoc(
485                         "Negative column index for filter: " + inList[i]);
486                 }
487
488                 if (sortableColumns.get(columnIndex)) {
489                     throw new IllegalArgumentException JavaDoc(
490                         "Filter "+ i +" attempting to overwrite sorter for column "
491                         + columnIndex);
492                 }
493
494                 sortableColumns.set(columnIndex); // mark column index
495
sorterLocations.add(new Integer JavaDoc(i)); // mark sorter index
496
//columnSorterMap.put(new Integer(columnIndex), inList[i]);
497
}
498         }
499         return sorterLocations;
500     }
501
502     private Filter[] reorderSorters(Filter[] inList, List JavaDoc sorterLocations) {
503         // always returns a new copy of inList
504
Filter[] outList = (Filter[]) inList.clone();
505
506         // Invert the order of sorters, if any, in outList
507
int max = sorterLocations.size() - 1;
508         for (int i = 0; i <= max; i++) {
509             int orig = ((Integer JavaDoc) sorterLocations.get(max - i)).intValue();
510             int copy = ((Integer JavaDoc) sorterLocations.get(i)).intValue();
511             outList[copy] = inList[orig];
512         }
513
514         return outList;
515     }
516 /*
517     private void bind(Filter filter) {
518         filter.bind(this);
519         filter.bind(adapter);
520     }
521 */

522 }
523
Popular Tags