/* This file is part of Green.
*
* Copyright (C) 2005 The Research Foundation of State University of New York
* All Rights Under Copyright Reserved, The Research Foundation of S.U.N.Y.
*
* Green is free software, licensed under the terms of the Eclipse
* Public License, version 1.0. The license is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package edu.buffalo.cse.green.editor.model;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.Children;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.Location;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.Refresh;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.Size;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.Visibility;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.jdt.core.IJavaElement;
import edu.buffalo.cse.green.GreenException;
import edu.buffalo.cse.green.editor.DiagramEditor;
import edu.buffalo.cse.green.editor.action.ContextAction;
import edu.buffalo.cse.green.editor.controller.PropertyChange;
import edu.buffalo.cse.green.editor.controller.PropertyListener;
import edu.buffalo.cse.green.editor.model.commands.DeleteCommand;
import edu.buffalo.cse.green.xml.XMLConverter;
/**
* Model upon which all other models are based. This class provides the basic
* functionality that all models in the editor share.
*
* @author bcmartin
*
* C - Child
* P - Parent
* E - Element
*/
public abstract class AbstractModel<C extends AbstractModel, P extends AbstractModel, E extends IJavaElement> {
/**
* Holds a mapping of listeners. The appropriate listener will receive
* property change event information fired by this model.
*/
private Map<PropertyChange, PropertyListener> _listeners;
/**
* Holds a list of all the children of this model.
*/
private List<C> _children;
/**
* Holds value that indicates whether or not the model is visible.
*/
private boolean _isVisible = true;
/**
* Holds this model's location.
*/
private Point _location = new Point(1, 1);
/**
* Holds a reference to this model's parent.
*/
private P _parent;
/**
* Holds this model's size.
*/
private Dimension _size = new Dimension(1, 1);
private Dimension _drawnSize;
protected AbstractModel() {
_children = new ArrayList<C>();
_listeners = new HashMap<PropertyChange, PropertyListener>();
}
/**
* @return The <code>Class</code> representing the <code>AbstractPart</code>
* that corresponds to this <code>AbstractModel</code> instance.
*/
public abstract Class getPartClass();
/**
* Sets a global property held in the <code>UMLRootModel</code>.
*
* @param property - The property to set.
* @param value - The value to assign to the given property.
*/
public void setProperty(String property, Object value) {
getRootModel().setProperty(property, value);
}
/**
* Retrieves the value assigned to the given property.
*
* @param property - The property.
* @return The value held by the given property.
*/
public Object getProperty(String property) {
return getRootModel().getProperty(property);
}
/**
* Toggles the value of a property between true and false.
*
* @param property - The property.
*/
public void toggleProperty(String property) {
getRootModel().toggleProperty(property);
}
/**
* Gets the root model.
*/
public RootModel getRootModel() {
return _parent.getRootModel();
}
/**
* Sets the location of the model.
*/
public void setLocation(Point iLocation) {
_location = iLocation;
firePropertyChange(Location, null, iLocation);
}
/**
* Sets the location of the model.
*
* @param x - The x location of the model.
* @param y - The y location of the model.
*/
public void setLocation(int x, int y) {
setLocation(new Point(x, y));
}
/**
* @return The location of the model.
*/
public Point getLocation() {
return _location;
}
/**
* Sets the size of the model.
*
* @param iSize - The size.
*/
public void setSize(Dimension iSize) {
if (iSize == null) return;
Dimension oldSize = _size;
_size = iSize;
firePropertyChange(Size, oldSize, iSize);
}
/**
* Sets the size of the model.
*
* @param width - The width of the model.
* @param height - The height of the model.
*/
public void setSize(int width, int height) {
setSize(new Dimension(width, height));
}
/**
* Gets the size of the model.
*/
public Dimension getSize() {
return _size;
}
/**
* Adds a child to this model.
*
* @param model - The model to add.
* @param setAsParent - If true, sets the child's parent value to this
* model.
* @param element - The element to map to the child model, or null if no
* element should be mapped.
*/
@SuppressWarnings("unchecked")
protected final void addChild(C model, IJavaElement element) {
if (!equals(model.getParent())) {
// don't allow a model to have more than one parent
if (model.getParent() != null) {
GreenException.illegalOperation(
"Model already has a parent");
}
model.setParent(this);
}
if (element != null) {
getRootModel().mapElementToModel(element, model);
}
// add the child
_children.add(model);
firePropertyChange(Children, null, model);
}
/**
* Removes the specified child model.
*
* @param model - The child model.
* @param element - The element that should be unmapped, or null if no
* element should be unmapped.
* @return The removed child model.
*/
protected final boolean removeChild(AbstractModel model) {
boolean removed = _children.remove(model);
firePropertyChange(Children, model, null);
dispose();
if (getJavaElement() != null) {
getRootModel().unmapElement(getJavaElement());
}
return removed;
}
/**
* Gets all child models.
*/
public List<C> getChildren() {
return _children;
}
/**
* Adds a <code>PropertyChangeListener</code> to this model.
*
* @param listener - The listener to add.
*/
public void addListener(PropertyChange type, PropertyListener listener) {
boolean exists = _listeners.containsKey(type);
if (exists) {
// combine the listeners
_listeners.put(type, new CombinedListener(
_listeners.get(type), listener));
} else {
// simply add the listeners
_listeners.put(type, listener);
}
}
/**
* Fires a property change. Used to communicate with the controller.
*
* @param prop - The property that changed.
* @param old - The old value, or null if it is insignificant.
* @param newValue - The new value, or null if it is insignificant.
*/
public void firePropertyChange(PropertyChange type, Object oValue, Object nValue) {
PropertyListener listener = _listeners.get(type);
if (listener != null) {
listener.notify(oValue, nValue);
}
}
protected final void firePropertyChange(PropertyChange type) {
firePropertyChange(type, false, true);
}
/**
* Gets the GUI parent of this model (in the EditPart tree).
*
* @return The parent of this model, or <code>null</code> if none.
*/
public P getParent() {
return _parent;
}
/**
* Sets the GUI parent of this model (in the EditPart tree).
*
* @param parent - The parent of this model in the GUI tree.
*/
public void setParent(P parent) {
_parent = parent;
}
/**
* Recursively calls this method on all children. Useful for writing XML to
* the converter. The converter processes the XML; after this method is
* called from the <code>DiagramEditor</code>, it is stored in a file for
* loading at a later point.
*
* @param converter - The converter to use to translate the contents of the
* <code>AbstractModel</code>s contained in the editor into XML.
*/
public void toXML(XMLConverter converter) {
for (C model : getChildren()) {
model.toXML(converter);
}
}
/**
* Shows/hides the model.
*
* @param show - If true, the visibility is set to true; if false, the
* visibility is set to false.
*/
public void setVisible(boolean show) {
_isVisible = show;
if (show) {
firePropertyChange(Visibility, null, true);
} else {
firePropertyChange(Visibility, null, false);
}
}
/**
* @return True if the model is visible, false otherwise.
*/
public boolean isVisible() {
return _isVisible;
}
/**
* @return The value indicating what kind of context menu should be shown
* if this model is selected.
*/
public int getContextMenuFlag() {
return ContextAction.CM_EDITOR;
}
/**
* @param klass - The <code>Class</code>
* @return A list of all children of this model that are of the instance
* specified by klass.
*/
public List<C> getChildren(Class klass) {
List<C> children = new ArrayList<C>();
for (C model : _children) {
if (klass.isInstance(model)) {
children.add(model);
}
}
return children;
}
/**
* Refreshes this model and all of its children.
*/
protected void refresh() {
for (C model : getChildren()) {
model.refresh();
}
firePropertyChange(Refresh);
}
/**
* Forces the refresh() method to be called.
*/
public void forceRefesh() {
firePropertyChange(Refresh);
}
/**
* @param editor - The editor.
* @return A delete command for this model.
*/
public abstract DeleteCommand getDeleteCommand(DiagramEditor editor);
/**
* @return The bounds of this model.
*/
public Rectangle getBounds() {
return new Rectangle(getLocation(), getSize());
}
/**
* @return The <code>IJavaElement</code> that corresponds to this model.
*/
public abstract E getJavaElement();
/**
* Removes this model from its parent.
*/
public abstract void removeFromParent();
/**
* @see java.lang.Object#toString()
*/
public abstract String toString();
/**
* Removes all of this model's children from it.
*/
public final void removeChildren() {
List<C> children = new ArrayList<C>();
children.addAll(getChildren());
for (C model : children) {
model.removeFromParent();
}
firePropertyChange(Children);
}
/**
* Called when a model is destroyed.
*/
public final void dispose() {
handleDispose();
for (C model : getChildren()) {
model.dispose();
}
//_listeners = new HashMap<PropertyChange, PropertyListener>();
}
/**
* Called recursively by dispose to perform disposal-time events.
*/
public abstract void handleDispose();
/**
* @return Retrieves the <code>TypeModel</code> that contains this model, or
* this if this is a <code>TypeModel</code>.
*/
public TypeModel getTypeModel() {
GreenException.illegalOperation("Invalid operation");
return null;
}
/**
* Display a creation dialog to the user.
*
* @return true if the invocation should occur, false otherwise.
*/
public int invokeCreationDialog(ToolEntry tool) {
GreenException.illegalOperation("This operation is not supported");
return 0;
}
/**
* Creates a clone of the given model.
*
* @param model - The given model.
*/
public void createNewInstance(AbstractModel model) {
GreenException.illegalOperation("This operation is not supported");
}
/**
* Called during creation to ensure this model is valid.
*/
public void assertValid() {
// do nothing by default
}
/**
* @return The size the figure corresponding to this model is drawn as.
*/
public Dimension getDrawnSize() {
return _drawnSize;
}
/**
* Sets the drawn size of the model. Used for laying out the diagram.
*
* @param size - The actual size of the figure.
*/
public void setDrawnSize(Dimension size) {
_drawnSize = size;
}
}
class CombinedListener implements PropertyListener {
private PropertyListener _oldListener;
private PropertyListener _newListener;
public CombinedListener(PropertyListener ol, PropertyListener nl) {
_oldListener = ol;
_newListener = nl;
}
/**
* @see edu.buffalo.cse.green.editor.controller.PropertyListener#notify(java.lang.Object, java.lang.Object)
*/
public void notify(Object oValue, Object nValue) {
// call the newer notification earlier
_newListener.notify(oValue, nValue);
_oldListener.notify(oValue, nValue);
}
}