wrote a class, sharing it

View: New views
4 Messages — Rating Filter:   Alert me  

wrote a class, sharing it

by ymajoros :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hello,

After using GlazedLists for some time, which is a really nice project, I wanted to share some class I wrote.

For filters which need to change Matcher frequently, you typically write a MatcherEditor. The problem is, if you have a list of employees and a list of clients, you'll have relatively similar MatcherEditors, which just install an Employee Matcher or a Client Matcher.

I wrote this class, DynamicMatcherEditor, which simply takes a matcher and install it. It can also create a Matcher from a TextFilterator and a JTextComponent (and install the needed DocumentListener), which is similar to TextComponentMatcherEditor but lets you change your TextFilterator dynamically, without having to instanciating a new TextComponentMatcherEditor.

So, here it is. Hope someone will check the usefullness, comment it, etc. I tried to document it enough to be quite usable.

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package be.ucl.invrech.client;

import ca.odell.glazedlists.TextFilterator;
import ca.odell.glazedlists.impl.matchers.TrueMatcher;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.MatcherEditor;
import ca.odell.glazedlists.matchers.MatcherEditor.Listener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

/**
 * Creates a MatcherEditor which can accept a given new Matcher anytime.
 * Can also create a Matcher from a JTextComponent and a TextFilterator
 * @author Yannick Majoros
 * @param T Type of the elements to match
 */
public class DynamicMatcherEditor<T> implements MatcherEditor<T> {

    private final List<Listener<T>> listeners = new ArrayList<Listener<T>>();
    private Matcher<T> matcher;
    private DynamicMatcherEditor.TextComponentListener textComponentListener;

    /**
     * Creates a new DynamicMatcherEditor which initially matches everything
     */
    public DynamicMatcherEditor() {
        this.matcher = new TrueMatcher<T>();
    }

    /**
     * Creates this MatcherEditor with the specified Matcher set up as initial
     * Matcher
     * @param matcher
     */
    public DynamicMatcherEditor(Matcher<T> matcher) {
        this.matcher = matcher;
    }

    /***
     * Creates a matcher from a TextFilterator and a JTextComponent, which filters
     * entries matching the text component's document
     * @param textComponent
     * @param textFilterator
     */
    public void setupTextFilter(JTextComponent textComponent, TextFilterator<T> textFilterator) {
        unregisterTextComponentListener();
        textComponentListener = new TextComponentListener(textComponent, textFilterator);        
        registerTextComponentListener();
    }
   
    /***
     * Dispose the component, releasing all registered listeners
     */
    public void dispose() {
        unregisterTextComponentListener();
    }

    /**
     * Set the given Matcher for this MatcherEditor and notify all listeners
     * @param matcher
     */
    public void setMatcher(Matcher<T> matcher) {
        this.matcher = matcher;
        Event<T> event = new Event(this, Event.CHANGED, matcher);
        for (Listener<T> listener : listeners) {
            listener.changedMatcher(event);
        }
    }

    @Override
    public void addMatcherEditorListener(Listener<T> listener) {
        listeners.add(listener);
    }

    @Override
    public void removeMatcherEditorListener(Listener<T> listener) {
        listeners.remove(listener);
    }

    /***
     * Get the current Matcher
     * @return
     */
    @Override
    public Matcher<T> getMatcher() {
        return matcher;
    }

    private static class TextMatcher<T> implements Matcher<T> {

        private String[] searchStrings;
        private TextFilterator<T> textFilterator;

        public TextMatcher(String[] textFilters, TextFilterator<T> textFilterator) {
            this.searchStrings = textFilters;
            this.textFilterator = textFilterator;
        }

        @Override
        public boolean matches(T item) {
            List<String> baseList = new ArrayList<String>();
            textFilterator.getFilterStrings(baseList, item);
            boolean match;
            for (String searchString : searchStrings) {
                match = false;
                String trimmedLowerSearchString = searchString.trim().toLowerCase();
                for (String baseString : baseList) {
                    if (baseString != null && baseString.trim().toLowerCase().contains(trimmedLowerSearchString)) {
                        match = true;
                        break;
                    }
                }
                // search term not found in baseList?
                if (!match) {
                    return false;
                }
            }
            return true;
        }

    }

    synchronized
    private void registerTextComponentListener() {
        JTextComponent textComponent = textComponentListener.textComponent;
        textComponent.getDocument().addDocumentListener(textComponentListener);
    }

    synchronized
    private void unregisterTextComponentListener() {
        if (textComponentListener == null) {
            return;
        }
        JTextComponent textComponent = textComponentListener.textComponent;
        textComponent.getDocument().removeDocumentListener(textComponentListener);
        textComponentListener = null;
    }

    private class TextComponentListener implements DocumentListener {

        private final TextFilterator<T> textFilterator;
        private JTextComponent textComponent;

        public TextComponentListener(JTextComponent textComponent, TextFilterator<T> textFilterator) {
            this.textComponent = textComponent;
            this.textFilterator = textFilterator;
            textChanged();
        }

        @Override
        public void insertUpdate(DocumentEvent e) {
            textChanged();
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            textChanged();
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            textChanged();
        }

        private void textChanged() {
            String[] searchStrings = textComponent.getText().split("[ \t]");
            setMatcher(new TextMatcher(searchStrings, textFilterator));
        }

    }
   
}

Re: wrote a class, sharing it

by ymajoros :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Well, nobody interested? Should I share further code?

Re: wrote a class, sharing it

by James Lemieux :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Yannick,

   To be honest, I'm not really "getting" the benefit of your class. Here are the two observations I have. Perhaps you can give me more information.

1) Your String search is actually much slower than the one in stock Glazed Lists.

calls to trim() and toLowerCase() are going to kill performance with the number of intermediate, throwaway objects that are created. Consider using one of the TextSearchStrategies already present in GL which implement much faster String-matching algorithms (Boyer-Moore).

2) I haven't encountered a case where I needed to dynamically change the TextFilterator. Can you describe that use case more?

Specifically, this part of your explanation confused me:

"The problem is, if you have a list of employees and a list of clients, you'll have relatively similar MatcherEditors, which just install an Employee Matcher or a Client Matcher."

3) a public version of setMatcher() seems out of place on a MatcherEditor. Typically the MatcherEditor itself is the only source of Matchers, some external code to the MatcherEditor shouldn't "tell it" what Matcher to use. If they want to prompt a change, they should do something like change the Document behind the JTextComponent and let the normal course of actions take place (MatcherEvent is created and broadcast)

Thanks,

James

On Nov 21, 2007 1:11 AM, ymajoros <yannick.majoros@...> wrote:

Well, nobody interested? Should I share further code?
--
View this message in context: http://www.nabble.com/wrote-a-class%2C-sharing-it-tf4834031.html#a13873449
Sent from the GlazedLists - Dev mailing list archive at Nabble.com.

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@...
For additional commands, e-mail: dev-help@...



Re: wrote a class, sharing it

by ymajoros :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


Yannick,

James Lemieux wrote:
   To be honest, I'm not really "getting" the benefit of your class. Here
are the two observations I have. Perhaps you can give me morce information.
 Thanks for your honesty, it could help me rethink my way of doing business; I could just have reinvented the wheel once again ;-)

James Lemieux wrote:
1) Your String search is actually much slower than the one in stock Glazed
Lists.

calls to trim() and toLowerCase() are going to kill performance with the
number of intermediate, throwaway objects that are created. Consider using
one of the TextSearchStrategies already present in GL which implement much
faster String-matching algorithms (Boyer-Moore).
Ok, I didn't try to make it fast, as I did it to suit my only needs in the first place and my lists are small; it seemed fast enough, but just using GL algorithm should be easy to change.

James Lemieux wrote:
2) I haven't encountered a case where I needed to dynamically change the
TextFilterator. Can you describe that use case more?
Well, I just made a list which could be searched on 5 columns. I have 5 checkboxes which enable/disable those search criteria. So, when they change, I create a new TextFilterator. I could use the same, but how to be sure the TextFilterator will be queried? Maybe I'm missing some point here.

James Lemieux wrote:
Specifically, this part of your explanation confused me:

"The problem is, if you have a list of employees and a list of clients,
you'll have relatively similar MatcherEditors, which just install an
Employee Matcher or a Client Matcher."
Well, I have two dialogs, one with a list of clients, and one with a list of employees. I have them filtered using their own MatcherEditors, one ClientMatcherEditor and one EmployeeMatcherEditor. In fact, what those MatcherEditors do is just installing a new ClientMatcher or an EmployeeMatcher, which match Clients and Employees using a ClientFilterDescription (respectively EmployeeFilterDescription). Those last 2 classes are just simple beans describing filters ; they could e.g. be persisted to a property file (the user can "save" a filter).

 What I'm saying is that ClientMatcherEditor and EmployeeMatcherEditor are quite similar, in fact almost the same. So, the next natural step for me was to replace them with a generic class...

Now, you make me think: should I better just make a generic FilterMatcherEditor<T> which takes FilterDescription<T> as a parameter, internally making the Matchers? Hoping I'm clear enough and not too boring

James Lemieux wrote:
3) a public version of setMatcher() seems out of place on a MatcherEditor.
Typically the MatcherEditor itself is the only source of Matchers, some
external code to the MatcherEditor shouldn't "tell it" what Matcher to use.
If they want to prompt a change, they should do something like change the
Document behind the JTextComponent and let the normal course of actions take
place (MatcherEvent is created and broadcast)
Well, I just checked my application (didn't work in this part for some days now). I don't have a public setMatcher(...), it just takes filter descriptions and creates matchers out of it. But anyway, I've about 10 lists of different items which are really quite similar. So, I think I'll just write a generic class.