Tuesday, May 3, 2011

Multiple Table filters in Java

As mentioned in the previous post, I have been working with Java TableFilters and RowSorters for some time now and as it turns out, they are more customizable than I imagined in the beginning. Anyways, this time however, I wanted to combine these filters as in use multiple filters together. e.g. Let's say I have a table as follows:
Name Question Answer CorrectAnswer
A Q1 A1 A1
B Q1 A1 A1
C Q1 A1 A1
D Q1 A1 A1
A Q2 A3 A2 <-- wrong answer
B Q2 A2 A2
C Q2 A3 A2 <-- wrong answer

Now let's say I want to filter/sort this data to
1. Display only selected Names
2.Display only rows where Answer matches CorrectAnswer
3. Do 1 and 2 above together or individually

It makes sense to write separate filters for 1 and 2 above and then use them separately or combine them. With my architecture, I was invoking a new filter of required type whenever a related event occurs on the control. But the problem with this approach was, a new filter would be applied on the rowSorter and since the changes made by filter on the visible table are no persistant and in most of the cases you wouldn't want them to be.
So there has to be a way to combine these filters. There's obviously a way you can do that by means of an andFilter as follows:

compoundRowFilter = RowFilter.andFilter(compoundFilterList);

rowSorter.setRowFilter(compoundRowFilter);

Where compoundFilterList is a list of filters you are trying to make work together.

So, what you can do instead of the following:


protected void newFilter(String filterRegExp) {

textFilter = null;

// If current expression doesn't parse, don't update.

try {

textFilter = RowFilter.regexFilter(filterRegExp);

} catch (java.util.regex.PatternSyntaxException e) {

return;

}

rowSorter.setRowFilter(textFilter);

}

is this:

protected void newFilter(String filterRegExp) {

textFilter = null;

// If current expression doesn't parse, don't update.

try {

textFilter = RowFilter.regexFilter(filterRegExp);

} catch (java.util.regex.PatternSyntaxException e) {

return;

}

filterList.add(textFilter);

compoundRowFilter = RowFilter.andFilter(filterList);

rowSorter.setRowFilter(compoundRowFilter);

}
I got this idea from here

Now, this will work fine in most of the cases, unless, your filters are dynamic. e.g. in my case, my filter changes as I type. So, each filter will be "added" to the filterList instead of being updated. This will keep on adding filters to the table as there's no way (or at least it's very difficult to) remove a specific object from a list. Also, I could not find any method that removes the filter altogether from rowSorter.
So, I figured, I should be able update a particular filter object in the list rather than to add it. Again, I found it difficult to update it in the filterList itself. So I decided to have a fixed number of filters in an array of filters and whenever a filter updates, update in it's position in the array and construct a list out of it. Note that, null filters cannot be applied, so I had to make sure that the filter objects aren't null, before adding them to the list:
This is how I did it:

private RowFilter[] compoundFilterArray = new RowFilter [numberOfFiltersYouWant];

And later in the filter code:

protected void newFilter(String filterRegExp) {

textFilter = null;

// If current expression doesn't parse, don't update.

try {

textFilter = RowFilter.regexFilter(filterRegExp);

} catch (java.util.regex.PatternSyntaxException e) {

return;

}

this.compoundFilterArray[1] = textFilter;

applyCompoundFilter();

}


protected void newFilter(final int userAnswerColumnIndex,

final int expectedAnswerColumnIndex) {

incorrectResponseFilter = new RowFilter() {

@Override

public boolean include(Entry entry) {

String actualResponse = entry

.getStringValue(userAnswerColumnIndex);

String expectedResponse = entry

.getStringValue(expectedAnswerColumnIndex);

return expectedResponse.contains(actualResponse);

}

};

// replace the filter with new object, each time the filter is updated

this.compoundFilterArray[0] = incorrectResponseFilter;

applyCompoundFilter();

}



where:

private void applyCompoundFilter() {

ArrayList> compoundFilterList = new ArrayList>();

for(int i = 0; i< compoundFilterArray.length; i++){

if(compoundFilterArray[i] != null){

compoundFilterList.add(compoundFilterArray[i]);

}

}

compoundRowFilter = RowFilter.andFilter(compoundFilterList);

rowSorter.setRowFilter(compoundRowFilter);

}

Sunday, May 1, 2011

The Awesomeness of Java table filters

While working on a coding project related to my thesis, I ran into java table filters. Initially, I was quite hesitant to use those, merely for the obscureness bundled with them, but soon I was proven wrong. At first I thought, I'd be better off creating a database and constructing the queries as we go. Apart from the fact that I have historically hated databases, for slowness, dependencies and the time required to design them and lack of visibility in terms of wtf is exactly happening inside, I decided to give java filters a shot, simply because all I needed to do was deal with a visible table and have user play with it to his/her heart's content.
The first thing I look for when using something I have never used before is the working example, e.g. on roseindia.com or something instead of looking at the javadocs. In short, I cheat because I am impatient and I do not wish to learn the language, but instead get the work done! Recently, I have been avoiding roseindia.com because of poor coding style, but those guys are bang on in terms of how to use something, no bullshit! I don't exactly remember, but I guess there's not much about filters on roseindia.com so I settled for java tutorials example which i found here.
Example is pretty much self explanatory, in the sense that it gives you at least two key hints:
1. You have to add a DocumentListener to a text field and override all the update methods that come with the DocumentListener interface. But where this example or any other example anywhere on the internet including sun (now oracle) website fails is that these guys end up defining the listener at the exact same place where it was added on the field. This is what I am talking about:

filterText.getDocument().addDocumentListener(

new DocumentListener() {

public void changedUpdate(DocumentEvent e) {

newFilter();

}

public void insertUpdate(DocumentEvent e) {

newFilter();

}

public void removeUpdate(DocumentEvent e) {

newFilter();

}

});


And I seriously believe that this is a very bad way of doing it, because the objects and fields and methods of current class are simple inaccessible within these update method definitions, because this space actually belongs to the new class DocumentListener. In my opinion, a better way of doing it is

filterText.getDocument().addDocumentListener(new YourListenerImplementation(Object1, Object2...));


and


class YourListenerImplementation{


public YourListenerImplementation(Object1, Object2....){


}

@Override

public void changedUpdate(DocumentEvent event) {

// do stuff here

//create new filter each time

}


@Override

public void insertUpdate(DocumentEvent event) {

//do stuff here

// create new filter each time

}


@Override

public void removeUpdate(DocumentEvent event) {

// do stuff here

// create new filter each time


}


}

So, now you have a freedom to parameterize the filter. This way, filter can be reused as well.

2. Regular expression is the key:
Now comes the filter part. Against my expectations, this was very easy! All I needed to do was to make sure that I am receiving a valid regular expression from the caller. What example also fails to demonstrate is that you can actually search over the entire row instead of sticking to a particular column. Had I found that out earlier, it would have saved me writing an additional function, but better late than never! I ended up implementing a filter, which takes multiple comma separated values from a text field and lists the row that contain either of those values. And it all happens as you type. I was blown away by this feature. So here's how you can construct queries and apply filter:

// Read the text field, construct and return a regular expression:


private static final String COMMA = ",";

private static final String OR = "|";


private String getSearchRegularExpression(){

String filterText = filterControlsPanelHandlerObject.getFilterText();

return filterText.replace(COMMA, OR);

}

// call the filter in update methods

@Override

public void changedUpdate(DocumentEvent event) {

yourTableClassWithFilterMethod.newFilter(getSearchRegularExpression());

}

And here's how the filter method looks like:

private TableRowSorter rowSorter;

/*

* initialize rowsorter etc...

*/

protected void newFilter(String filterRegExp) {

RowFilter rowFilter = null;

//If current expression doesn't parse, don't update.

try {

rowFilter = RowFilter.regexFilter(filterRegExp);

} catch (java.util.regex.PatternSyntaxException e) {

return;

}

rowSorter.setRowFilter(rowFilter);

}



And the functionality is ready to be used!! Enjoy :)