FileDocCategorySizeDatePackage
TableSearcher.javaAPI DocExample11390Mon Jan 09 11:01:58 GMT 2006None

TableSearcher

public class TableSearcher extends AbstractTableModel
This is a TableModel that encapsulates Lucene search logic within a TableModel implementation. It is implemented as a TableModel decorator, similar to the TableSorter demo from Sun that decorates a TableModel and provides sorting functionality. The benefit of this architecture is that you can decorate any TableModel implementation with this searching table model -- making it easy to add searching functionaliy to existing JTables -- or making new search capable table models. This decorator works by holding a reference to a decorated ot inner TableModel. All data is stored within that table model, not this table model. Rather, this table model simply manages links to data in the inner table model according to the search. All methods on TableSearcher forward to the inner table model with subtle filtering or alteration according to the search criteria. Using the table model: Pass the TableModel you want to decorate in at the constructor. When the TableModel initializes, it displays all search results. Call the search methid with any vaid Lucene search String and the data will be filtered by the search string. Users can always clear the search at any time by searching with an empty string. Additionally, you can add a button calling the clearSearch() method.
author
Jonathan Simon - jonathan_s_simon@yahoo.com

Fields Summary
protected TableModel
tableModel
The inner table model we are decorating
private TableModelListener
tableModelListener
This listener is used to register this class as a listener to the decorated table model for update events
private ArrayList
rowToModelIndex
these keeps reference to the decorated table model for data only rows that match the search criteria are linked
private RAMDirectory
directory
In memory lucene index
private Analyzer
analyzer
Cached lucene analyzer
private static final String
ROW_NUMBER
Links between this table model and the decorated table model are maintained through links based on row number. This is a key constant to denote "row number" for indexing
private String
searchString
Cache the current search String. Also used internally to key whether there is an active search running or not. i.e. if searchString is null, there is no active search.
Constructors Summary
public TableSearcher(TableModel tableModel)

param
tableModel The table model to decorate


                
       
        analyzer = new WhitespaceAnalyzer();
        tableModelListener = new TableModelHandler();
        setTableModel(tableModel);
        tableModel.addTableModelListener(tableModelListener);
        clearSearchingState();
    
Methods Summary
private voidclearSearchingState()
Clear the currently active search Resets the complete dataset of the decorated table model.

        searchString = null;
        rowToModelIndex.clear();
        for (int t=0; t<tableModel.getRowCount(); t++){
            rowToModelIndex.add(new Integer(t));
        }
    
public org.apache.lucene.analysis.AnalyzergetAnalyzer()

return
The current lucene analyzer

        return analyzer;
    
public java.lang.ClassgetColumnClass(int column)

        return tableModel.getColumnClass(column);
    
public intgetColumnCount()

        return (tableModel == null) ? 0 : tableModel.getColumnCount();
    
public java.lang.StringgetColumnName(int column)

        return tableModel.getColumnName(column);
    
private intgetModelRow(int row)

        return ((Integer) rowToModelIndex.get(row)).intValue();
    
public intgetRowCount()

        return (tableModel == null) ? 0 : rowToModelIndex.size();
    
public javax.swing.table.TableModelgetTableModel()

return
The inner table model this table model is decorating

        return tableModel;
    
public java.lang.ObjectgetValueAt(int row, int column)

        return tableModel.getValueAt(getModelRow(row), column);
    
public booleanisCellEditable(int row, int column)

        return tableModel.isCellEditable(getModelRow(row), column);
    
private booleanisSearching()

        return searchString != null;
    
private voidreindex()
Reset the search results and links to the decorated (inner) table model from this table model.

        try {
            // recreate the RAMDirectory
            directory = new RAMDirectory();
            IndexWriter writer = new IndexWriter(directory, analyzer, true);

            // iterate through all rows
            for (int row=0; row < tableModel.getRowCount(); row++){

                //for each row make a new document
                Document document = new Document();
                //add the row number of this row in the decorated table model
                //this will allow us to retrive the results later
                //and map this table model's row to a row in the decorated
                //table model
                document.add(new Field(ROW_NUMBER, "" + row, true, true, true));
                //iterate through all columns
                //index the value keyed by the column name
                //NOTE: there could be a problem with using column names with spaces
                for (int column=0; column < tableModel.getColumnCount(); column++){
                    String columnName = tableModel.getColumnName(column);
                    String columnValue = String.valueOf(tableModel.getValueAt(row, column)).toLowerCase();
                    document.add(new Field(columnName, columnValue, true, true, true));
                }
                writer.addDocument(document);
            }
            writer.optimize();
            writer.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    
private voidresetSearchResults(org.apache.lucene.search.Hits hits)

param
hits The new result set to set this table to.

        try {
            //clear our index mapping this table model rows to
            //the decorated inner table model
            rowToModelIndex.clear();
            //iterate through the hits
            //get the row number stored at the index
            //that number is the row number of the decorated
            //tabble model row that we are mapping to
            for (int t=0; t<hits.length(); t++){
                Document document = hits.doc(t);
                Field field = document.getField(ROW_NUMBER);
                rowToModelIndex.add(new Integer(field.stringValue()));
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    
public voidsearch(java.lang.String searchString)
Run a new search.

param
searchString Any valid lucene search string


        //if search string is null or empty, clear the search == search all
        if (searchString == null || searchString.equals("")){
            clearSearchingState();
            fireTableDataChanged();
            return;
        }


        try {
            //cache search String
            this.searchString = searchString;

            //make a new index searcher with the in memory (RAM) index.
            IndexSearcher is = new IndexSearcher(directory);

            //make an array of fields - one for each column
            String[] fields = new String[tableModel.getColumnCount()];
            for (int t=0; t<tableModel.getColumnCount(); t++){
                fields[t]=tableModel.getColumnName(t);
            }

            //build a query based on the fields, searchString and cached analyzer
            //NOTE: This is an area for improvement since the MultiFieldQueryParser
            // has some weirdness.
            Query query = MultiFieldQueryParser.parse(searchString, fields, analyzer);
            //run the search
            Hits hits = is.search(query);
            //reset this table model with the new results
            resetSearchResults(hits);
        } catch (Exception e){
            e.printStackTrace();
        }

        //notify all listeners that the table has been changed
        fireTableStructureChanged();
    
public voidsetAnalyzer(org.apache.lucene.analysis.Analyzer analyzer)

param
analyzer The new analyzer to use

        this.analyzer = analyzer;
        //reindex from the model with the new analyzer
        reindex();

        //rerun the search if there is an active search
        if (isSearching()){
            search(searchString);
        }
    
public voidsetTableModel(javax.swing.table.TableModel tableModel)
Set the table model used by this table model

param
tableModel The new table model to decorate


        //remove listeners if there...
        if (this.tableModel != null) {
            this.tableModel.removeTableModelListener(tableModelListener);
        }

        this.tableModel = tableModel;
        if (this.tableModel != null) {
            this.tableModel.addTableModelListener(tableModelListener);
        }

        //recalculate the links between this table model and
        //the inner table model since the decorated model just changed
        reindex();

        // let all listeners know the table has changed
        fireTableStructureChanged();
    
public voidsetValueAt(java.lang.Object aValue, int row, int column)

        tableModel.setValueAt(aValue, getModelRow(row), column);