/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.beanfabrics.event.ModelProviderEvent;
import org.beanfabrics.event.ModelProviderListener;
import org.beanfabrics.model.PresentationModel;
/**
* The default implementation of a {@link IModelProvider}.
*
* @author Michael Karneim
* @beaninfo
*/
@SuppressWarnings("serial")
public class ModelProvider extends AbstractBean implements IModelProvider, Serializable {
private List<Subscription> subscriptions = new LinkedList<Subscription>();
private PresentationModel presentationModel;
private Class<? extends PresentationModel> presentationModelType = PresentationModel.class;
/**
* Constructs an empty <code>ModelProvider</code>.
*/
public ModelProvider() {
//
}
// Serialization support.
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
}
/**
* Constructs an empty <code>ModelProvider</code> for the given presentationModel type.
*/
public ModelProvider(Class<? extends PresentationModel> presentationModelType) {
if (presentationModelType == null) {
throw new IllegalArgumentException("presentationModelType may not be null");
}
this.setPresentationModelType(presentationModelType);
}
/**
* Constructs a <code>ModelProvider</code> with the given presentationModel.
*/
public ModelProvider(PresentationModel presentationModel) {
if (presentationModel != null) {
this.setPresentationModelType(presentationModel.getClass());
}
this.setPresentationModel(presentationModel);
}
@SuppressWarnings("unchecked")
public <T extends PresentationModel> T getPresentationModel() {
return (T) this.presentationModel;
}
/** {@inheritDoc} */
public void setPresentationModel(PresentationModel newPresentationModel) {
if (newPresentationModel != null) {
if (this.presentationModelType.isInstance(newPresentationModel) == false) {
throw new IllegalArgumentException("the new presentationModel must be instance of "
+ presentationModelType.getName() + " but was " + newPresentationModel.getClass().getName());
}
}
PresentationModel old = this.presentationModel;
if (old == newPresentationModel) {
return;
}
if (old != null) {
for (Subscription b : subscriptions) {
b.stopObservation();
}
}
this.presentationModel = newPresentationModel;
if (this.presentationModel != null) {
for (Subscription b : subscriptions) {
b.startObservation();
}
}
this.getPropertyChangeSupport().firePropertyChange("presentationModel", old, newPresentationModel);
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public <T extends PresentationModel> T getPresentationModel(Path path) {
PathEvaluation eval = new PathEvaluation(this.presentationModel, path);
if (eval.isCompletelyResolved()) {
return (T) eval.getResult().getValue();
} else {
return null;
}
}
/** {@inheritDoc} */
public Class<? extends PresentationModel> getPresentationModelType() {
if (presentationModel != null) {
return presentationModel.getClass();
}
return presentationModelType;
}
/** {@inheritDoc} */
public void setPresentationModelType(Class<? extends PresentationModel> newType) {
if (newType == null) {
throw new IllegalArgumentException("presentationModelType must not be null");
}
Class<? extends PresentationModel> old = this.presentationModelType;
if (newType.equals(old)) {
return;
}
if (presentationModel != null && !newType.isInstance(presentationModel)) {
throw new IllegalArgumentException(
"the presentationModel is already set; a new presentationModel type must be superclass of "
+ this.presentationModel.getClass().getName());
}
this.presentationModelType = newType;
this.getPropertyChangeSupport().firePropertyChange("presentationModelType", old, newType);
}
/** {@inheritDoc} */
public void addModelProviderListener(Path path, ModelProviderListener l) {
if (this.subscriptions == null) {
throw new IllegalStateException();
}
Subscription binding = new Subscription(path, l);
List<Subscription> newBindings = new LinkedList<Subscription>(this.subscriptions);
newBindings.add(binding);
this.subscriptions = newBindings;
if (this.presentationModel != null) {
binding.startObservation();
}
}
/** {@inheritDoc} */
public void removeModelProviderListener(Path path, ModelProviderListener l) {
List<Subscription> newBindings = new LinkedList<Subscription>(this.subscriptions);
Iterator<Subscription> it = newBindings.iterator();
Subscription toRemove = null;
while (it.hasNext()) {
Subscription b = it.next();
if (b.path.equals(path) && b.listener == l) {
it.remove();
toRemove = b;
break;
}
}
if (toRemove != null) {
subscriptions = newBindings;
toRemove.stopObservation();
}
}
/**
* A subscription is responsible for notifying a {@link ModelProviderListener} whenever a reference on a given
* {@link Path} changes.
*/
private class Subscription implements PropertyChangeListener, Serializable {
final Path path;
final ModelProviderListener listener;
private PathObservation observation;
/**
* Creates a new subscription.
*
* @param path
* @param listener
*/
public Subscription(Path path, ModelProviderListener listener) {
super();
this.path = path;
this.listener = listener;
}
/**
* Starts the observation of the presentation model (along the path)
*/
public void startObservation() {
this.observation = new PathObservation(presentationModel, this.path);
this.observation.addPropertyChangeListener("target", this);
if (this.observation.getTarget() != null) {
listener.modelGained(new ModelProviderEvent(ModelProvider.this, ModelProvider.this.presentationModel,
path));
}
}
/**
* Stops the observation.
*/
public void stopObservation() {
if (this.observation != null) {
this.observation.removePropertyChangeListener("target", this);
if (this.observation.getTarget() != null) {
listener.modelLost(new ModelProviderEvent(ModelProvider.this, ModelProvider.this.presentationModel,
path));
}
this.observation.stop();
this.observation = null;
}
}
/** {@inheritDoc} */
public void propertyChange(PropertyChangeEvent evt) {
assert ("target".equals(evt.getPropertyName()));
PresentationModel pModel = observation.getTarget();
if (pModel == null) {
listener.modelLost(new ModelProviderEvent(ModelProvider.this, ModelProvider.this.presentationModel,
path));
} else {
listener.modelGained(new ModelProviderEvent(ModelProvider.this, ModelProvider.this.presentationModel,
path));
}
}
}
}