/* * 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 org.beanfabrics.model.PresentationModel; /** * The {@link PathObservation} observes all presentation models that are nodes * along a specified {@link Path} (using a specified presentation model as root * node) and notifies any {@link PropertyChangeListener} whenever the target * node reference changes. * * @author Michael Karneim */ public class PathObservation extends AbstractBean { private PresentationModel rootNode; private Path path; private PathEvaluation evaluation; private PresentationModel target; private PropertyChangeListener pcl; private boolean started; /** * Creates a new {@link PathObservation} along the given {@link Path} * starting at the given root node. * * @param rootNode * @param path */ public PathObservation(PresentationModel rootNode, Path path) { if (rootNode == null) { throw new IllegalArgumentException("rootNode==null"); } if (path == null) { throw new IllegalArgumentException("path==null"); } this.rootNode = rootNode; this.path = path; start(); } /** * Returns the path of this observation. * * @return the path of this observation */ public Path getPath() { return path; } /** * Returns the root node. * * @return the root node */ public PresentationModel getRootNode() { return rootNode; } /** * Returns <code>true</code> if this observation has been started. * * @return <code>true</code> if this observation has been started */ public boolean isStarted() { return started; } /** * Stops this observation. */ public void stop() { if (!started) { return; } this.removeListener(); started = false; } /** * Start this observation (again). */ public void start() { if (started) { return; } evaluate(); addListener(); started = true; } /** * Returns <code>true</code> if the target of this observation's path is not * <code>null</code>. * * @return <code>true</code> if the target of this observation's path is not * <code>null</code> */ public boolean hasTarget() { return this.target != null; } /** * Returns the target node specified by this observation's path relative to * the root node. * * @return the target node specified by this observation's path relative to * the root node */ public PresentationModel getTarget() { return this.target; } private void evaluate() { this.evaluation = new PathEvaluation(rootNode, path); if (evaluation.isCompletelyResolved()) { this.setTarget(evaluation.getResult().getValue()); } else { this.setTarget(null); } } protected void setTarget(PresentationModel pModel) { PresentationModel old = this.target; if (pModel == old) { return; } this.target = pModel; this.getPropertyChangeSupport().firePropertyChange("target", old, pModel); } private void addListener() { for (PathEvaluation.Entry entry : this.evaluation) { if (entry.getOwner() != null && entry.getName() != null) { PresentationModel owner = entry.getOwner(); String propertyName = entry.getName(); owner.addPropertyChangeListener(propertyName, this.getPropertyChangeListener()); } } } private void removeListener() { for (PathEvaluation.Entry entry : this.evaluation) { if (entry.getOwner() != null && entry.getName() != null) { PresentationModel owner = entry.getOwner(); String propertyName = entry.getName(); owner.removePropertyChangeListener(propertyName, this.getPropertyChangeListener()); } } } private PropertyChangeListener getPropertyChangeListener() { if (this.pcl == null) { this.pcl = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { Object oldValue = evt.getOldValue(); Object newValue = evt.getNewValue(); if (oldValue != newValue) { stop(); start(); } } }; } return this.pcl; } }