File: WeakReferenceMatcherEditor.java

package info (click to toggle)
libglazedlists-java 1.8.0.dfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 3,016 kB
  • sloc: java: 21,991; xml: 860; sh: 48; makefile: 5
file content (175 lines) | stat: -rw-r--r-- 7,907 bytes parent folder | download | duplicates (2)
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
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.matchers;

import ca.odell.glazedlists.matchers.Matcher;
import ca.odell.glazedlists.matchers.MatcherEditor;

import javax.swing.event.EventListenerList;
import java.lang.ref.WeakReference;

/**
 * This {@link MatcherEditor} exists to aid with garbage collection of
 * {@link Listener} objects. It is particularly useful when a long-lived
 * {@link MatcherEditor} exists and many short-lived {@link Listener} objects
 * must be added and removed.
 *
 * <p>Rather than attaching each {@link Listener} to the long-lived
 * {@link MatcherEditor} with hard references and managing the {@link Listener}
 * registrations manually, it is considerably easier to contruct a
 * {@link WeakReferenceMatcherEditor} which removes {@link Listener}s after
 * they are unreachable and have been garbage collected.
 *
 * <p>Common usage of this class resembles:
 * <pre>
 * MatcherEditor myCustomMatcherEditor = ...
 * MatcherEditor weakRefMatcherEditor = Matchers.weakReferenceProxy(myCustomMatcherEditor);
 *
 * // customMatcherEditorListener will be removed when it is garbage collected
 * MatcherEditor.Listener customMatcherEditorListener = ...
 * weakRefMatcherEditor.addMatcherEditorListener(customMatcherEditorListener);
 * </pre>
 *
 * @author James Lemieux
 */
public final class WeakReferenceMatcherEditor<E> implements MatcherEditor<E>, MatcherEditor.Listener<E> {

    /** The Listeners for this MatcherEditor. */
    private final EventListenerList listenerList = new EventListenerList();

    /** The last Matcher that was broadcast from this MatcherEditor. */
    private MatcherEditor<E> source;

    /**
     * Construct a MatcherEditor which acts as a weak proxy for the given
     * <code>source</code>. That is, it rebroadcasts MatcherEvents it receives
     * from the <code>source</code> to its own weak listeners until it, itself,
     * is no longer reachable, at which time it stops listening to the
     * <code>source</code>.
     *
     * @param source the MatcherEditor to decorate with weak proxying
     */
    public WeakReferenceMatcherEditor(MatcherEditor<E> source) {
        this.source = source;

        // listen to the source weakly so we clean ourselves up when we're extinct
        source.addMatcherEditorListener(new WeakMatcherEditorListener<E>(source, this));
    }

    /**
     * Return the current {@link Matcher} specified by the decorated
     * {@link MatcherEditor}.
     *
     * @return a non-null {@link Matcher}
     */
    public Matcher<E> getMatcher() {
        return this.source.getMatcher();
    }

    /**
     * Wrap the given <code>listener</code> in a {@link WeakReference} and
     * notify it when the decorated {@link MatcherEditor} fires {@link Matcher}
     * changes. The weak listener will only be notified while it is reachable
     * via hard references, and will be cleaned up the next time a new
     * {@link MatcherEditor.Event} is fired.
     */
    public void addMatcherEditorListener(Listener<E> listener) {
        this.listenerList.add(Listener.class, new WeakMatcherEditorListener<E>(this, listener));
    }

    /** {@inheritDoc} */
    public void removeMatcherEditorListener(Listener<E> listener) {
        final Object[] listeners = this.listenerList.getListenerList();

        // we remove the given listener by identity
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            final Object currentObject = listeners[i+1];
            if (currentObject == listener)
                this.listenerList.remove(MatcherEditor.Listener.class, listener);

            // if the given listener is a WeakMatcherEditorListener, check if
            // the currentObject is actually its referent
            if (currentObject instanceof WeakMatcherEditorListener) {
                final WeakMatcherEditorListener<E> weakMatcherEditorListener = (WeakMatcherEditorListener<E>) currentObject;
                final Listener<E> currentListener = weakMatcherEditorListener.getDecoratedListener();
                if (currentListener == listener)
                    this.listenerList.remove(MatcherEditor.Listener.class, weakMatcherEditorListener);
            }
        }
    }

    /**
     * Indicates a changes has occurred in the delegate Matcher produced by the
     * MatcherEditor.
     *
     * @param matcherEvent a MatcherEditor.Event describing the change in the
     *      delegate Matcher produced by the MatcherEditor
     */
    public void changedMatcher(Event<E> matcherEvent) {
        final Object[] listeners = this.listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2)
            ((Listener<E>) listeners[i+1]).changedMatcher(matcherEvent);
    }

    /**
     * This is the crux of this MatcherEditor. It wraps a {@link Listener} in a
     * {@link WeakReference} so that its garbage collection is not affected by
     * being registered with a {@link MatcherEditor}. Instead, each time it is
     * notified that the {@link Matcher} changed it must test the availability
     * of the underlying listener. If it is available, it is notified. If it is
     * unavailable, it removes itself from listening.
     */
    private static class WeakMatcherEditorListener<E> implements Listener<E> {

        /** The WeakReference housing the true MatcherEditor.Listener. */
        private final WeakReference<Listener<E>> weakListener;

        /** The editor that this Listener is listening to. */
        private final MatcherEditor<E> editor;

        /**
         * Construct a WeakMatcherEditorListener which wraps the given
         * <code>listener</code>, which is assumed to listen to the given
         * <code>editor</code>, in a {@link WeakReference}.
         *
         * @param editor the {@link MatcherEditor} from which to remove the
         *      listener after it has been garbage collected
         * @param listener the {@link Listener} containing the true logic for
         *      reacting to matcher changes
         */
        public WeakMatcherEditorListener(MatcherEditor<E> editor, Listener<E> listener) {
            this.weakListener = new WeakReference<Listener<E>>(listener);
            this.editor = editor;
        }

        /**
         * Return the underlying {@link Listener} from the {@link WeakReference}.
         */
        public Listener<E> getDecoratedListener() {
            return this.weakListener.get();
        }

        /**
         * This method tests for the existence of the underlying {@link Listener}
         * and if it still exists (i.e. has not been garbage collected) it is
         * notified of the <code>matcherEvent</code>. Otherwise, it is removed
         * from the {@link MatcherEditor} and will never be notified again.
         *
         * @param matcherEvent a MatcherEditor.Event describing the change in the
         *      Matcher produced by the MatcherEditor
         */
        public void changedMatcher(Event<E> matcherEvent) {
            // fetch the underlying MatcherEditor.Listener
            final Listener<E> matcherEditorListener = this.weakListener.get();

            // if it has been garbage collected, stop listening to the MatcherEditor
            // otherwise, fire the event as normal
            if (matcherEditorListener == null) {
                this.editor.removeMatcherEditorListener(this);
            } else {
                matcherEditorListener.changedMatcher(matcherEvent);
            }
        }
    }
}