blob: 9dd8d46bd8fe42a2a2276741b4ff375cb5445e1d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2012 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.common.utility.internal.model.value;
import java.util.EventListener;
import org.eclipse.jpt.common.utility.internal.model.AbstractModel;
import org.eclipse.jpt.common.utility.internal.model.ChangeSupport;
import org.eclipse.jpt.common.utility.internal.model.SingleAspectChangeSupport;
import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent;
import org.eclipse.jpt.common.utility.model.listener.PropertyChangeAdapter;
import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener;
import org.eclipse.jpt.common.utility.model.value.PropertyValueModel;
/**
* This abstract extension of {@link AbstractModel} provides a base for adding
* change listeners (property change, collection change, list change, tree change)
* to a subject and converting the subject's change notifications into a single
* set of change notifications for a common aspect
* (e.g. {@link PropertyValueModel#VALUE}).
* <p>
* The adapter will only listen to the subject (and subject model) when the
* adapter itself actually has listeners. This will allow the adapter to be
* garbage collected when appropriate.
*
* @param <S> the type of the model's subject
* @param <A> the type of the subject's aspect
*/
public abstract class AspectAdapter<S, A>
extends AbstractModel
{
/**
* The subject that holds the aspect and fires
* change notification when the aspect changes.
* We need to hold on to this directly so we can
* disengage it when it changes.
*/
protected volatile S subject;
/**
* A value model that holds the {@link #subject}
* that holds the aspect and provides change notification.
* This is useful when there are a number of aspect adapters
* that have the same subject and that subject can change.
* All the aspect adapters can share the same subject model.
* For now, this is can only be set upon construction and is
* immutable.
*/
protected final PropertyValueModel<? extends S> subjectHolder;
/** A listener that keeps us in sync with the subject model. */
protected final PropertyChangeListener subjectListener;
// ********** constructors **********
/**
* Construct an aspect adapter for the specified subject.
*/
protected AspectAdapter(S subject) {
this(new StaticPropertyValueModel<S>(subject));
}
/**
* Construct an aspect adapter for the specified subject model.
* The subject model cannot be <code>null</code>.
*/
protected AspectAdapter(PropertyValueModel<? extends S> subjectModel) {
super();
if (subjectModel == null) {
throw new NullPointerException();
}
this.subjectHolder = subjectModel;
this.subjectListener = this.buildSubjectListener();
// the subject is null when we are not listening to it
// this will typically result in our value being null
this.subject = null;
}
// ********** initialization **********
@Override
protected ChangeSupport buildChangeSupport() {
return new LocalChangeSupport(this, this.getListenerClass(), this.getListenerAspectName());
}
protected PropertyChangeListener buildSubjectListener() {
return new SubjectListener();
}
protected class SubjectListener
extends PropertyChangeAdapter
{
/**
* The subject model's value has changed, keep our subject in sync.
*/
@Override
public void propertyChanged(PropertyChangeEvent event) {
AspectAdapter.this.subjectChanged();
}
}
// ********** behavior **********
/**
* The subject has changed.
* Move our subject listener and
* notify listeners that the value has changed.
*/
protected synchronized void subjectChanged() {
A old = this.getAspectValue();
boolean hasListeners = this.hasListeners();
if (hasListeners) {
this.disengageSubject();
}
this.subject = this.subjectHolder.getValue();
if (hasListeners) {
this.engageSubject();
this.fireAspectChanged(old, this.getAspectValue());
}
}
/**
* Return the aspect's current value.
*/
protected abstract A getAspectValue();
/**
* Return the class of listener that is interested in the aspect adapter's
* changes.
*/
protected abstract Class<? extends EventListener> getListenerClass();
/**
* Return the name of the aspect adapter's aspect
* (e.g. {@link PropertyValueModel#VALUE}).
* This is the name of the aspect adapter's single aspect, not the
* name of the subject's aspect(s) the aspect adapter is adapting.
*/
protected abstract String getListenerAspectName();
/**
* Return whether there are any listeners for the aspect.
*/
protected abstract boolean hasListeners();
/**
* Return whether there are no listeners for the aspect.
*/
protected boolean hasNoListeners() {
return ! this.hasListeners();
}
/**
* The aspect has changed, notify listeners appropriately.
*/
protected abstract void fireAspectChanged(A oldValue, A newValue);
// ********** engage/disengage models **********
/**
* Called by {@link LocalChangeSupport}
*/
protected void engageModels() {
this.engageSubjectHolder();
this.engageSubject();
}
protected void engageSubjectHolder() {
this.subjectHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.subjectListener);
// sync our subject *after* we start listening to the subject holder,
// since its value might change when a listener is added
this.subject = this.subjectHolder.getValue();
}
protected void engageSubject() {
// check for nothing to listen to
if (this.subject != null) {
this.engageSubject_();
}
}
/**
* The {@link #subject} is not <code>null</code> - add our listener.
*/
protected abstract void engageSubject_();
/**
* Called by {@link LocalChangeSupport}
*/
protected void disengageModels() {
this.disengageSubject();
this.disengageSubjectHolder();
}
protected void disengageSubject() {
// check for nothing to listen to
if (this.subject != null) {
this.disengageSubject_();
}
}
/**
* The {@link #subject} is not <code>null</code> - remove our listener.
*/
protected abstract void disengageSubject_();
protected void disengageSubjectHolder() {
this.subjectHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.subjectListener);
// clear out the subject when we are not listening to its holder
this.subject = null;
}
// ********** local change support **********
/**
* Extend change support to start listening to the aspect adapter's
* models (the subject holder and the subject itself) when the first
* relevant listener is added.
* Conversely, stop listening to the aspect adapter's models when the
* last relevant listener is removed.
* A relevant listener is a listener of the relevant type and aspect or a
* general-purpose listener.
*/
protected class LocalChangeSupport
extends SingleAspectChangeSupport
{
private static final long serialVersionUID = 1L;
public LocalChangeSupport(AspectAdapter<S, A> source, Class<? extends EventListener> validListenerClass, String validAspectName) {
super(source, validListenerClass, validAspectName);
}
protected boolean hasNoListeners() {
return this.hasNoListeners(this.validListenerClass, this.validAspectName);
}
// ********** overrides **********
@Override
protected synchronized <T extends EventListener> void addListener(Class<T> listenerClass, T listener) {
if (this.hasNoListeners()) {
AspectAdapter.this.engageModels();
}
super.addListener(listenerClass, listener);
}
@Override
protected synchronized <T extends EventListener> void addListener(Class<T> listenerClass, String aspectName, T listener) {
if (this.hasNoListeners()) {
AspectAdapter.this.engageModels();
}
super.addListener(listenerClass, aspectName, listener);
}
@Override
protected synchronized <T extends EventListener> void removeListener(Class<T> listenerClass, T listener) {
super.removeListener(listenerClass, listener);
if (this.hasNoListeners()) {
AspectAdapter.this.disengageModels();
}
}
@Override
protected synchronized <T extends EventListener> void removeListener(Class<T> listenerClass, String aspectName, T listener) {
super.removeListener(listenerClass, aspectName, listener);
if (this.hasNoListeners()) {
AspectAdapter.this.disengageModels();
}
}
}
}