ca.odell.glazedlists.impl.matchers.WeakReferenceMatcherEditor Maven / Gradle / Ivy
/* 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.
*
* 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.
*
*
Common usage of this class resembles:
*
* MatcherEditor myCustomMatcherEditor = ...
* MatcherEditor weakRefMatcherEditor = Matchers.weakReferenceProxy(myCustomMatcherEditor);
*
* // customMatcherEditorListener will be removed when it is garbage collected
* MatcherEditor.Listener customMatcherEditorListener = ...
* weakRefMatcherEditor.addMatcherEditorListener(customMatcherEditorListener);
*
*
* @author James Lemieux
*/
public final class WeakReferenceMatcherEditor implements MatcherEditor, MatcherEditor.Listener {
/** The Listeners for this MatcherEditor. */
private final EventListenerList listenerList = new EventListenerList();
/** The last Matcher that was broadcast from this MatcherEditor. */
private MatcherEditor source;
/**
* Construct a MatcherEditor which acts as a weak proxy for the given
* source
. That is, it rebroadcasts MatcherEvents it receives
* from the source
to its own weak listeners until it, itself,
* is no longer reachable, at which time it stops listening to the
* source
.
*
* @param source the MatcherEditor to decorate with weak proxying
*/
public WeakReferenceMatcherEditor(MatcherEditor source) {
this.source = source;
// listen to the source weakly so we clean ourselves up when we're extinct
source.addMatcherEditorListener(new WeakMatcherEditorListener(source, this));
}
/**
* Return the current {@link Matcher} specified by the decorated
* {@link MatcherEditor}.
*
* @return a non-null {@link Matcher}
*/
public Matcher getMatcher() {
return this.source.getMatcher();
}
/**
* Wrap the given listener
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 listener) {
this.listenerList.add(Listener.class, new WeakMatcherEditorListener(this, listener));
}
/** {@inheritDoc} */
public void removeMatcherEditorListener(Listener 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 weakMatcherEditorListener = (WeakMatcherEditorListener) currentObject;
final Listener 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 matcherEvent) {
final Object[] listeners = this.listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2)
((Listener) 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 class WeakMatcherEditorListener implements Listener {
/** The WeakReference housing the true MatcherEditor.Listener. */
private final WeakReference> weakListener;
/** The editor that this Listener is listening to. */
private final MatcherEditor editor;
/**
* Construct a WeakMatcherEditorListener which wraps the given
* listener
, which is assumed to listen to the given
* editor
, 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 editor, Listener listener) {
this.weakListener = new WeakReference>(listener);
this.editor = editor;
}
/**
* Return the underlying {@link Listener} from the {@link WeakReference}.
*/
public Listener 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 matcherEvent
. 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 matcherEvent) {
// fetch the underlying MatcherEditor.Listener
final Listener matcherEditorListener = this.weakListener.get();
// if it has been garbage collected, stop listening to the MatcherEditor
if (matcherEditorListener == null) {
this.editor.removeMatcherEditorListener(this);
} else {
// otherwise fire the event as though it originated from this WeakReferenceMatcherEditor
matcherEvent = new MatcherEditor.Event(WeakReferenceMatcherEditor.this, matcherEvent.getType(), matcherEvent.getMatcher());
matcherEditorListener.changedMatcher(matcherEvent);
}
}
}
}