File: BeanConnector.java

package info (click to toggle)
libglazedlists-java 1.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,024 kB
  • ctags: 4,252
  • sloc: java: 22,561; xml: 818; sh: 51; makefile: 5
file content (238 lines) | stat: -rw-r--r-- 10,903 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.beans;

import ca.odell.glazedlists.ObservableElementList;
import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.Matchers;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EventListener;

/**
 * An {@link ObservableElementList.Connector} for the Java beans'
 * {@link PropertyChangeListener}.
 *
 * @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
 * @author James Lemieux
 */
public class BeanConnector<E> implements ObservableElementList.Connector<E> {

    /** The method to use when installing a PropertyChangeListener on an object. */
    private Method addListenerMethod;

    /** The method to use when uninstalling a PropertyChangeListener on an object. */
    private Method removeListenerMethod;

    /** The list which contains the elements being observed via this {@link ObservableElementList.Connector}. */
    private ObservableElementList<? extends E> list;

    /** The PropertyChangeListener to install on each list element. */
    protected PropertyChangeListener propertyChangeListener = this.createPropertyChangeListener();

    /** Matches PropertyChangeEvents to deliver to the ObservableElementList. */
    private Matcher<PropertyChangeEvent> eventMatcher = Matchers.trueMatcher();    
    
    /**
     * Reflection is used to install/uninstall the {@link #propertyChangeListener}
     * on list elements, so we cache the Object[] used in the reflection call
     * for a speed increase.
     */
    private final Object[] reflectionParameters = {propertyChangeListener};

    /**
     * The types taken by the methods which add and remove PropertyChangeListeners.
     */
    private static final Class[] REFLECTION_TYPES = {PropertyChangeListener.class};

    /**
     * Constructs a new Connector which uses reflection to add and remove a
     * PropertyChangeListener from instances of the <code>beanClass</code>. The
     * methods for adding and removing PropertyChangeListener from instances of
     * the given <code>beanClass</code> are assumed to follow the naming
     * convention:
     *
     * <ul>
     *  <li> add*(PropertyChangeListener) to add PropertyChangeListeners
     *  <li> remove*(PropertyChangeListener) to remove PropertyChangeListeners
     * </ul>
     *
     * where the * may be replaced with any string of valid java identifier
     * characters.
     *
     * @param beanClass the Class of all list elements within the {@link ObservableElementList}
     * @throws IllegalArgumentException if <code>beanClass</code> does not contain methods
     *      matching the format described
     */
    public BeanConnector(Class<E> beanClass) {
        final Method[] methods = beanClass.getMethods();
        for (int m = 0; m < methods.length; m++) {
            if(methods[m].getParameterTypes().length != 1) continue;
            if(methods[m].getParameterTypes()[0] != PropertyChangeListener.class) continue;
            if(methods[m].getName().startsWith("add")) this.addListenerMethod = methods[m];
            if(methods[m].getName().startsWith("remove")) this.removeListenerMethod = methods[m];
        }

        if (this.addListenerMethod == null || this.removeListenerMethod == null)
            throw new IllegalArgumentException("Couldn't find listener methods for " + beanClass.getName());
    }

    /**
     * Constructs a new Connector which uses reflection to add and remove a
     * PropertyChangeListener from instances of the <code>beanClass</code>. The
     * methods for adding and removing PropertyChangeListener from instances of
     * the given <code>beanClass</code> are assumed to follow the naming
     * convention:
     *
     * <ul>
     *  <li> add*(PropertyChangeListener) to add PropertyChangeListeners
     *  <li> remove*(PropertyChangeListener) to remove PropertyChangeListeners
     * </ul>
     *
     * where the * may be replaced with any string of valid java identifier
     * characters.
     *
     * @param beanClass the Class of all list elements within the {@link ObservableElementList}
     * @param eventMatcher the matcher for matching those PropertyChangeEvents, which should be
     *        delivered to the ObservableElementList
     * @throws IllegalArgumentException if <code>beanClass</code> does not contain methods
     *      matching the format described
     */
    public BeanConnector(Class<E> beanClass, Matcher<PropertyChangeEvent> eventMatcher) {
        this(beanClass);
        setEventMatcher(eventMatcher);
    }


    /**
     * Constructs a new Connector which uses reflection to add and remove a
     * PropertyChangeListener from instances of the <code>beanClass</code>
     * using the named methods.
     *
     * @param beanClass the Class of all list elements within the {@link ObservableElementList}
     * @param addListenerMethodName the name of the method which adds PropertyChangeListeners
     *      to the elements within the {@link ObservableElementList}
     * @param removeListenerMethodName the name of the method which removes PropertyChangeListeners
     *      from the elements within the {@link ObservableElementList}
     * @throws IllegalArgumentException if <code>beanClass</code> does not contain the named
     *      methods or if the methods do no take a PropertyChangeListener as the single parameter
     */
    public BeanConnector(Class<E> beanClass, String addListenerMethodName, String removeListenerMethodName) {
        try {
            this.addListenerMethod = beanClass.getMethod(addListenerMethodName, REFLECTION_TYPES);
            this.removeListenerMethod = beanClass.getMethod(removeListenerMethodName, REFLECTION_TYPES);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Failed to find method " + e.getMessage() + " in " + beanClass);
        }
    }
    
    /**
     * Constructs a new Connector which uses reflection to add and remove a PropertyChangeListener
     * from instances of the <code>beanClass</code> using the named methods.
     * 
     * @param beanClass the Class of all list elements within the {@link ObservableElementList}
     * @param addListenerMethodName the name of the method which adds PropertyChangeListeners to the
     *        elements within the {@link ObservableElementList}
     * @param removeListenerMethodName the name of the method which removes PropertyChangeListeners
     *        from the elements within the {@link ObservableElementList}
     * @param eventMatcher the matcher for matching those PropertyChangeEvents, which should be
     *        delivered to the ObservableElementList
     * @throws IllegalArgumentException if <code>beanClass</code> does not contain the named
     *         methods or if the methods do no take a PropertyChangeListener as the single parameter
     */
    public BeanConnector(Class<E> beanClass, String addListenerMethodName,
            String removeListenerMethodName, Matcher<PropertyChangeEvent> eventMatcher) {
        this(beanClass, addListenerMethodName, removeListenerMethodName);
        setEventMatcher(eventMatcher);
    }

    /**
     * Start listening for PropertyChangeEvents from the specified
     * <code>element</code>. The PropertyChangeListener is installed using
     * reflection.
     *
     * @param element the element to be observed
     * @return the listener that was installed on the <code>element</code>
     *      to be used as a parameter to {@link #uninstallListener(Object, EventListener)}
     * @throws RuntimeException if the reflection call fails to successfully
     *      install the PropertyChangeListener
     */
    public EventListener installListener(E element) {
        try {
            this.addListenerMethod.invoke(element, this.reflectionParameters);
            return this.propertyChangeListener;
        } catch (IllegalAccessException iae) {
            throw new RuntimeException(iae);
        } catch (InvocationTargetException ite) {
            throw new RuntimeException(ite.getCause());
        }
    }

    /**
     * Stop listening for PropertyChangeEvents from the specified
     * <code>element</code>. The PropertyChangeListener is uninstalled using
     * reflection.
     *
     * @param element the observed element
     * @param listener the listener that was installed on the <code>element</code>
     *      in {@link #installListener(Object)}
     * @throws RuntimeException if the reflection call fails to successfully
     *      uninstall the PropertyChangeListener
     */
    public void uninstallListener(E element, EventListener listener) {
        try {
            this.removeListenerMethod.invoke(element, this.reflectionParameters);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getCause());
        }
    }

    /** {@inheritDoc} */
    public void setObservableElementList(ObservableElementList<? extends E> list) {
        this.list = list;
    }

    /**
     * Returns the event matcher. It matches those PropertyChangeEvents, which should be delivered
     * to the ObservableElementList. In other words, it serves as a filter for PropertyChangeEvents.
     */
    public final Matcher<PropertyChangeEvent> getEventMatcher() {
        return eventMatcher;
    }

    /**
     * Sets the event matcher, may not be <code>null</code>. It matches those
     * PropertyChangeEvents, which should be delivered to the ObservableElementList. In other words,
     * it serves as a filter for PropertyChangeEvents.
     */
    private void setEventMatcher(Matcher<PropertyChangeEvent> eventMatcher) {
        if (eventMatcher == null) throw new IllegalArgumentException("Event matcher may not be null."); 
        this.eventMatcher = eventMatcher;
    }

    /**
     * A local factory method to produce the PropertyChangeListener which will
     * be installed on list elements.
     */
    protected PropertyChangeListener createPropertyChangeListener() {
        return new PropertyChangeHandler();
    }

    /**
     * The PropertyChangeListener which notifies the {@link ObservableElementList} within this
     * Connector of changes to list elements.
     */
    public class PropertyChangeHandler implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent event) {
            if (getEventMatcher().matches(event)) {
            	list.elementChanged(event.getSource());
            }
        }
    }
}