org.jgraph.graph.DefaultGraphModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ingeniasjgraphmod Show documentation
Show all versions of ingeniasjgraphmod Show documentation
A modified version of some JGraph files
The newest version!
/*
* $Id: DefaultGraphModel.java,v 1.25 2009/06/12 13:58:33 david Exp $
*
* Copyright (c) 2001-2009 Gaudenz Alder
*
*/
package org.jgraph.graph;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.swing.event.EventListenerList;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;
import org.jgraph.event.GraphModelEvent;
import org.jgraph.event.GraphModelListener;
/**
* The default implementation of a graph model.
*
* @version 1.0 1/1/02
* @author Gaudenz Alder
*/
public class DefaultGraphModel extends UndoableEditSupport implements
Serializable, GraphModel {
/**
* The list of listeners that listen to the model.
*/
protected transient EventListenerList listenerList = new EventListenerList();
/**
* Default instance of an empty iterator.
*/
protected transient Iterator emptyIterator = new EmptyIterator();
/**
* Set that contains all root cells of this model.
*/
protected List roots = null;
/**
* Indicates whether isLeaf is based on a node's allowsChildren value.
*/
protected boolean asksAllowsChildren = false;
/**
* Whether or not to remove group cells from the model when all of their
* children are removed
*/
protected boolean removeEmptyGroups = true;
/**
* The model's own attributes as a map. Defaults to an empty Hashtable.
*/
protected AttributeMap attributes = null;
/**
* Counter for the depth of nested transactions. Each call to beginUpdate
* increments this counter and each call to endUpdate decrements it. When
* the counter reaches 0, the transaction is closed and applied to the
* model.
*/
protected transient int updateLevel = 0;
/**
* Stores nested transaction added cells
*/
protected transient Set transAddedCells = null;
/**
* Stores nested transaction removed cells
*/
protected transient Set transRemovedCells = null;
/**
* Stores nested transaction transport attribute maps
*/
protected transient Map transEditAttrs = null;
/**
* Stores nested transaction connection sets
*/
protected transient ConnectionSet transEditCS = null;
/**
* Stores nested transaction parent maps
*/
protected transient ParentMap transEditPM = null;
/**
* Constructs a model that is not an attribute store.
*/
public DefaultGraphModel() {
this(null, null);
}
/**
* Constructs a model that is not an attribute store.
*/
public DefaultGraphModel(List roots, AttributeMap attributes) {
if (roots != null)
this.roots = roots;
else
this.roots = new ArrayList();
if (attributes != null)
this.attributes = attributes;
else
this.attributes = new AttributeMap();
}
/**
* Constructs a model using the specified information to construct the
* cells, attributes and connection data.
*/
public DefaultGraphModel(List roots, AttributeMap attributes,
ConnectionSet cs) {
this(roots, attributes);
handleConnectionSet(cs);
}
public List getRoots() {
return roots;
}
//
// Graph Model
//
/**
* Returns the number of roots in the model. Returns 0 if the model is
* empty.
*
* @return the number of roots in the model
*/
public int getRootCount() {
return roots.size();
}
/**
* Returns the root at index index in the model. This should not
* return null if index is a valid index for the model (that is
* index >= 0 && index < getRootCount()).
*
* @return the root of at index index
*/
public Object getRootAt(int index) {
return roots.get(index);
}
/**
* Returns the index of root
in the model. If root is
* null
, returns -1.
*
* @param root
* a root in the model, obtained from this data source
* @return the index of the root in the model, or -1 if the parent is
* null
*/
public int getIndexOfRoot(Object root) {
return roots.indexOf(root);
}
/**
* Returns true
if node
or one of its
* ancestors is in the model.
*
* @return true
if node
is in the model
*/
public boolean contains(Object node) {
Object parentNode = null;
while ((parentNode = getParent(node)) != null)
node = parentNode;
return roots.contains(node);
}
/**
* Returns a Map
that represents the attributes for the
* specified cell. This attributes have precedence over each view's
* attributes, regardless of isAttributeStore.
*
* @return attributes of node
as a Map
*/
public AttributeMap getAttributes(Object node) {
if (node instanceof GraphCell)
return ((GraphCell) node).getAttributes();
else if (node == null)
return attributes;
return null;
}
/**
* @return Returns the user object of the given cell. This implementation
* checks if the cell is a default mutable tree node and returns
* it's user object.
*/
public Object getValue(Object cell) {
if (cell instanceof DefaultMutableTreeNode)
return ((DefaultMutableTreeNode) cell).getUserObject();
return null;
}
/**
* Returns the graph model's attribute. Shortcut to
* getAttributes(null)
.
*
* @return attributes of node
as a Map
*/
public Map getAttributes() {
return getAttributes(null);
}
//
// Graph Structure
//
/**
* Returns the source of edge
. edge must be an
* object previously obtained from this data source.
*
* @return Object
that represents the source of edge
*/
public Object getSource(Object edge) {
if (edge instanceof Edge)
return ((Edge) edge).getSource();
return null;
}
/**
* Returns the target of edge
. edge must be an
* object previously obtained from this data source.
*
* @return Object
that represents the target of edge
*/
public Object getTarget(Object edge) {
if (edge instanceof Edge)
return ((Edge) edge).getTarget();
return null;
}
/**
* Returns true
if port
is a valid source for
* edge
. edge and port must be objects
* previously obtained from this data source.
*
* @return true
if port
is a valid source for
* edge
.
*/
public boolean acceptsSource(Object edge, Object port) {
return true;
}
/**
* Returns true
if port
is a valid target for
* edge
. edge and port must be objects
* previously obtained from this data source.
*
* @return true
if port
is a valid target for
* edge
.
*/
public boolean acceptsTarget(Object edge, Object port) {
return true;
}
/**
* Returns an iterator of the edges connected to port
.
* port must be a object previously obtained from this data source.
* This method never returns null.
*
* @param port
* a port in the graph, obtained from this data source
* @return Iterator
that represents the connected edges
*/
public Iterator edges(Object port) {
if (port instanceof Port)
return ((Port) port).edges();
return emptyIterator;
}
/**
* Returns true
if edge
is a valid edge.
*
* @return true
if edge
is a valid edge.
*/
public boolean isEdge(Object edge) {
return edge instanceof Edge;
}
/**
* Returns true
if port
is a valid port,
* possibly supporting edge connection.
*
* @return true
if port
is a valid port.
*/
public boolean isPort(Object port) {
return port instanceof Port;
}
/**
* A shortcut method to create a connection set that represents the
* connections in this model. Useful for encoding to avoid writing redundant
* connection data stored in the cells.
*/
public ConnectionSet getConnectionSet() {
return ConnectionSet
.create(this, DefaultGraphModel.getAll(this), false);
}
//
// Group Structure
//
/**
* Returns a map of (cell, clone)-pairs for all cells
. In
* the new array, all references are replaced with references to the cloned
* cells (ie parent or anchor). This method does only include children which
* are in cells
. Use JGraph.getDescendants to get a complete
* list of all children.
*/
public Map cloneCells(Object[] cells) {
Map map = new Hashtable();
// Add Cells to Queue
for (int i = 0; i < cells.length; i++)
map.put(cells[i], cloneCell(cells[i]));
// Replace Parent and Anchors
Iterator it = map.entrySet().iterator();
Object obj, cell, parent;
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
obj = entry.getValue();
cell = entry.getKey();
// Replaces the cloned cell's parent with the parent's clone
parent = getParent(cell);
if (parent != null)
parent = map.get(parent);
if (parent != null)
((DefaultMutableTreeNode) parent)
.add((DefaultMutableTreeNode) obj);
// Replaces the anchors for ports
if (obj instanceof Port) {
Object anchor = ((Port) obj).getAnchor();
if (anchor != null)
((Port) obj).setAnchor((Port) map.get(anchor));
}
}
return map;
}
/**
* Sets the parent of the specified cell.
*/
protected void setParent(Object child, Object parent) {
if (child instanceof DefaultMutableTreeNode
&& parent instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parent;
parentNode.add((DefaultMutableTreeNode) child);
}
}
/**
* Creates a shallow copy of the cell including a copy of the user object.
* Subclassers can override the cloneUserObject to provide a custom user
* object cloning mechanism.
*/
protected Object cloneCell(Object cellObj) {
if (cellObj instanceof DefaultGraphCell) {
// Clones the cell
DefaultGraphCell cell = (DefaultGraphCell) cellObj;
DefaultGraphCell clone = (DefaultGraphCell) cell.clone();
// Clones the user object
clone.setUserObject(cloneUserObject(cell.getUserObject()));
return clone;
}
return cellObj;
}
/**
* Clones the user object. Helper method that is invoked from cloneCells.
* You must use cloneCells (or cloneCell for single cells) to get a deep
* copy of a clone. Subclassers must override this and valueForCellChanged
* to implement custom user objects. This implementation returns
* object
.
*/
protected Object cloneUserObject(Object userObject) {
return userObject;
}
/**
* Returns the parent of child in the model. child must be a
* node previously obtained from this data source. This returns null if
* child is a root in the model.
*
* @param child
* a node in the graph, obtained from this data source
* @return the parent of child
*/
public Object getParent(Object child) {
if (child != null && child instanceof TreeNode)
return ((TreeNode) child).getParent();
return null;
}
/**
* Returns the index of child in parent. If either the parent or child is
* null
, returns -1.
*
* @param parent
* a note in the tree, obtained from this data source
* @param child
* the node we are interested in
* @return the index of the child in the parent, or -1 if either the parent
* or the child is null
*/
public int getIndexOfChild(Object parent, Object child) {
if (parent == null || child == null)
return -1;
return ((TreeNode) parent).getIndex((TreeNode) child);
}
/**
* Returns the child of parent at index index in the
* parent's child array. parent must be a node previously obtained
* from this data source. This should not return null if index is a
* valid index for parent (that is index >= 0 && index
* < getChildCount( parent )).
*
* @param parent
* a node in the tree, obtained from this data source
* @return the child of parent at index index
*/
public Object getChild(Object parent, int index) {
if (parent instanceof TreeNode)
return ((TreeNode) parent).getChildAt(index);
return null;
}
/**
* Returns the number of children of parent . Returns 0 if the node
* is a leaf or if it has no children. parent must be a node
* previously obtained from this data source.
*
* @param parent
* a node in the tree, obtained from this data source
* @return the number of children of the node parent
*/
public int getChildCount(Object parent) {
if (parent instanceof TreeNode)
return ((TreeNode) parent).getChildCount();
return 0;
}
/**
* Returns whether the specified node is a leaf node. The way the test is
* performed depends on the.
*
* @param node
* the node to check
* @return true if the node is a leaf node
*/
public boolean isLeaf(Object node) {
if (asksAllowsChildren && node instanceof TreeNode)
return !((TreeNode) node).getAllowsChildren();
return ((TreeNode) node).isLeaf();
}
//
// Change Support
//
/**
* Inserts the roots
and connections into the model. Notifies
* the model- and undo listeners of the change. The passed-in edits are
* executed if they implement the
* GraphModelEvent.ExecutableGraphChange
interface in
* ascending array-order, after execution of the model change. Note: The
* passed-in propertyMap may contain PortView
s which must be
* turned into Point
s when stored in the model.
*/
public void insert(Object[] roots, Map attributes, ConnectionSet cs,
ParentMap pm, UndoableEdit[] edits) {
if (updateLevel > 0) {
// Store the insert in the current transaction
updateTransaction(roots, null, attributes, cs, pm);
} else {
// Implement the insert immediately
GraphModelEdit edit = createEdit(roots, null, attributes, cs, pm, edits);
if (edit != null) {
edit.execute(); // fires graphChangeEvent
if (edits != null) {
for (int i = 0; i < edits.length; i++)
if (edits[i] instanceof GraphLayoutCache.GraphLayoutCacheEdit)
((GraphLayoutCache.GraphLayoutCacheEdit) edits[i])
.execute();
}
postEdit(edit); // fires undoableedithappened
}
}
}
/**
* Removes cells
from the model. Notifies the model- and undo
* listeners of the change.
*/
public void remove(Object[] roots) {
if (updateLevel > 0) {
// Store the insert in the current transaction
updateTransaction(null, roots, null, null, null);
} else {
GraphModelEdit edit = createRemoveEdit(roots);
if (edit != null) {
edit.execute();
postEdit(edit);
}
}
}
/**
* Shortcut to the new edit method which allows inserts and removes to go
* along with an edit.
*/
public void edit(Map attributes, ConnectionSet cs, ParentMap pm,
UndoableEdit[] edits) {
edit(null, null, attributes, cs, pm, edits);
}
/**
* Applies attributes
and the connection changes to the
* model. The initial edits
that triggered the call are
* considered to be part of this transaction. The passed-in edits are
* executed if they implement the
* GraphModelEvent.ExecutableGraphChange
interface in
* ascending array-order, after execution of the model change. Notifies the
* model- and undo listeners of the change. Note: If only
* edits
is non-null, the edits are directly passed to the
* UndoableEditListeners. Note: The passed-in propertyMap may contains
* PortViews which must be turned into Points when stored in the model.
*/
public void edit(Object[] inserted, Object[] removed, Map attributes,
ConnectionSet cs, ParentMap pm, UndoableEdit[] edits) {
if (updateLevel > 0) {
// Store the insert in the current transaction
updateTransaction(inserted, removed, attributes, cs, pm);
} else {
if ((inserted == null || inserted.length == 0)
&& (removed == null || removed.length == 0)
&& (attributes == null || attributes.isEmpty())
&& (cs == null || cs.isEmpty()) && pm == null && edits != null
&& edits.length == 1) {
if (edits[0] instanceof GraphLayoutCache.GraphLayoutCacheEdit)
((GraphLayoutCache.GraphLayoutCacheEdit) edits[0]).execute();
postEdit(edits[0]); // UndoableEdit Relay
} else {
GraphModelEdit edit = createEdit(inserted, removed, attributes, cs,
pm, edits);
if (edit != null) {
edit.execute();
if (edits != null) {
for (int i = 0; i < edits.length; i++)
if (edits[i] instanceof GraphLayoutCache.GraphLayoutCacheEdit)
((GraphLayoutCache.GraphLayoutCacheEdit) edits[i])
.execute();
}
postEdit(edit);
}
}
}
}
/*
* Unused, placeholder for JGraph 6 API
*/
public synchronized void execute(ExecutableChange change) {
}
/*
* Read section entitled "Complex Transactions" in the user manual
* chapter 2 for how to use the update level
*/
public int getUpdateLevel() {
return updateLevel;
}
/*
* Read section entitled "Complex Transactions" in the user manual
* chapter 2 for how to use the update level
*/
public void beginUpdate() {
updateLevel++;
if (updateLevel == 1) {
transEditAttrs = new Hashtable();
transEditCS = new ConnectionSet();
transEditPM = new ParentMap();
transAddedCells = new HashSet();
transRemovedCells = new HashSet();
}
}
/*
* Read section entitled "Complex Transactions" in the user manual
* chapter 2 for how to use the update level
*/
public void endUpdate()
{
updateLevel--;
if (updateLevel == 0)
{
// Dispatch the built up transaction
GraphModelEdit edit = createEdit(transAddedCells.toArray(),
transRemovedCells.toArray(), transEditAttrs, transEditCS,
transEditPM, null);
if (edit != null)
{
edit.execute(); // fires graphChangeEvent
postEdit(edit); // fires undoableedithappened
}
}
}
/**
* Updates the current state of the various transaction data
* @param inserted inserted cell to be added to the transaction
* @param removed removed cells to be removed from the transaction
* @param attributes nested attribute maps to apply to the transaction
* @param cs connection sets to add to the transaction
* @param pm parent maps to add to the transaction
*/
protected void updateTransaction(Object[] inserted, Object[] removed, Map attributes,
ConnectionSet cs, ParentMap pm)
{
// Inserts
if (inserted != null && inserted.length > 0) {
for (int i = 0; i < inserted.length; i++) {
if (transRemovedCells.contains(inserted[i])) {
// Does not make sense to remove then insert a cell
// in same transaction, operations cancel out
transRemovedCells.remove(inserted[i]);
} else {
transAddedCells.add(inserted[i]);
}
}
}
// Removes
if (removed != null && removed.length > 0) {
for (int i = 0; i < removed.length; i++) {
if (transAddedCells.contains(removed[i])) {
// Does not make sense to insert then remove a cell
// in same transaction, operations cancel out
transAddedCells.remove(removed[i]);
} else {
transRemovedCells.add(removed[i]);
}
}
}
// Attributes
if (attributes != null) {
GraphConstants.merge(attributes, transEditAttrs);
}
// Connection sets
if (cs != null) {
Set connections = transEditCS.getConnections();
connections.addAll(cs.getConnections());
transEditCS.setConnections(connections);
Set edges = transEditCS.getEdges();
edges.addAll(cs.getEdges());
transEditCS.setEdges(edges);
}
// Parent maps
if (pm != null) {
Iterator entries = pm.entries();
while (entries.hasNext()) {
ParentMap.Entry entry = (ParentMap.Entry)entries.next();
transEditPM.addEntry(entry.getChild(), entry.getParent());
}
}
}
/**
* Sends cells
to back.
*/
public void toBack(Object[] cells) {
GraphModelLayerEdit edit = createLayerEdit(cells,
GraphModelLayerEdit.BACK);
if (edit != null) {
edit.execute();
postEdit(edit);
}
}
/**
* Brings cells
to front.
*/
public void toFront(Object[] cells) {
GraphModelLayerEdit edit = createLayerEdit(cells,
GraphModelLayerEdit.FRONT);
if (edit != null) {
edit.execute();
postEdit(edit);
}
}
protected GraphModelLayerEdit createLayerEdit(Object[] cells, int layer) {
return new GraphModelLayerEdit(cells, layer);
}
//
// Edit Creation
//
/**
* Returns an edit that represents an insert.
*/
// protected GraphModelEdit createInsertEdit(Object[] cells, Map
// attributeMap,
// ConnectionSet cs, ParentMap pm, UndoableEdit[] edits) {
// return createEdit(cells, null, attributeMap, cs, pm, edits);
// }
/**
* Returns an edit that represents a remove.
*/
protected GraphModelEdit createRemoveEdit(Object[] cells) {
// Remove from GraphStructure
ConnectionSet cs = ConnectionSet.create(this, cells, true);
// Remove from Group Structure
ParentMap pm = ParentMap.create(this, cells, true, false);
// Construct Edit
GraphModelEdit edit = createEdit(null, cells, null, cs, pm, null);
if (edit != null)
edit.end();
return edit;
}
protected GraphModelEdit createEdit(Object[] inserted, Object[] removed,
Map attributes, ConnectionSet cs, ParentMap pm, UndoableEdit[] edits) {
GraphModelEdit edit = new GraphModelEdit(inserted, removed, attributes,
cs, pm);
if (edit != null) {
if (edits != null)
for (int i = 0; i < edits.length; i++)
edit.addEdit(edits[i]);
edit.end();
}
return edit;
}
//
// Change Handling
//
/**
* Inserts cells
into the model. Returns the cells that were
* inserted (including descendants).
*/
protected Object[] handleInsert(Object[] cells) {
Object[] inserted = null;
if (cells != null) {
for (int i = 0; i < cells.length; i++)
// Add to Roots if no parent
if (getParent(cells[i]) == null)
roots.add(cells[i]);
// Return *all* inserted cells
inserted = getDescendants(this, cells).toArray();
}
return inserted;
}
/**
* Removes cells
from the model. Returns the cells that were
* removed as roots.
*/
protected Object[] handleRemove(Object[] cells) {
Set removedRoots = new HashSet();
if (cells != null && cells.length > 0) {
Set rootsSet = new HashSet(roots);
for (int i = 0; i < cells.length; i++){
if (getParent(cells[i]) == null && rootsSet.contains(cells[i])) {
removedRoots.add(cells[i]);
}
}
if (removedRoots.size() > 0) {
// If any roots have been removed, reform the roots
// lists appropriately, keeping the order the same
int newRootsSize = roots.size() - removedRoots.size();
if (newRootsSize < 8) {
newRootsSize = 8;
}
List newRoots = new ArrayList(newRootsSize);
Iterator iter = roots.iterator();
while (iter.hasNext()) {
Object cell = iter.next();
if (!removedRoots.contains(cell)) {
newRoots.add(cell);
}
}
roots = newRoots;
}
}
return removedRoots.toArray();
}
/**
* Applies cells
to the model. Returns a parent map that may
* be used to undo this change.
*/
protected ParentMap handleParentMap(ParentMap parentMap) {
if (parentMap != null) {
ParentMap undo = new ParentMap();
HashSet rootsSet = null;
HashSet rootsToBeRemoved = null;
Iterator it = parentMap.entries();
while (it.hasNext()) {
ParentMap.Entry entry = (ParentMap.Entry) it.next();
Object child = entry.getChild();
Object parent = entry.getParent();
undo.addEntry(child, getParent(child));
if (parent == null) {
if (child instanceof MutableTreeNode) {
((MutableTreeNode) child).removeFromParent();
}
} else {
if (parent instanceof DefaultMutableTreeNode
&& child instanceof MutableTreeNode) {
((DefaultMutableTreeNode) parent)
.add((MutableTreeNode) child);
}
}
if (rootsSet == null) {
rootsSet = new HashSet(roots);
}
boolean isRoot = rootsSet.contains(child);
if (parent == null && !isRoot) {
rootsSet.add(child);
roots.add(child);
}
else if (parent != null && isRoot) {
if (rootsToBeRemoved == null) {
rootsToBeRemoved = new HashSet();
}
rootsSet.remove(child);
rootsToBeRemoved.add(child);
}
}
if (rootsToBeRemoved != null && rootsToBeRemoved.size() > 0) {
// If any roots have been removed, reform the roots
// lists appropriately, keeping the order the same
int newRootsSize = roots.size() - rootsToBeRemoved.size();
if (newRootsSize < 8) {
newRootsSize = 8;
}
List newRoots = new ArrayList(newRootsSize);
Iterator iter = roots.iterator();
while (iter.hasNext()) {
Object cell = iter.next();
if (!rootsToBeRemoved.contains(cell)) {
newRoots.add(cell);
}
}
roots = newRoots;
}
return undo;
}
return null;
}
/**
* Applies attributes
to the cells specified as keys. Returns
* the attributes
to undo the change.
*/
protected Map handleAttributes(Map attributes) {
if (attributes != null) {
Hashtable undo = new Hashtable(attributes.size());
Iterator it = attributes.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Object cell = entry.getKey();
Map deltaNew = (Map) entry.getValue();
// System.out.println("deltaNew="+deltaNew);
// System.out.println("stateOld="+getAttributes(cell));
// Handle New Values
Map deltaOld = null;
AttributeMap attr = getAttributes(cell);
if (attr != null) {
deltaOld = attr.applyMap(deltaNew);
// System.out.println("stateNew="+getAttributes(cell));
// System.out.println("deltaOld="+deltaOld);
undo.put(cell, deltaOld);
} else {
// Make room for the value
deltaOld = new Hashtable(2);
}
// Handle new values
Object newValue = deltaNew.get(GraphConstants.VALUE);
if (newValue != null) {
Object oldValue = valueForCellChanged(cell, newValue);
if (oldValue != null)
GraphConstants.setValue(deltaOld, oldValue);
// TODO: Userobject of null is probably invalid
else
GraphConstants.setRemoveAttributes(deltaOld,
new Object[] { GraphConstants.VALUE });
} else {
// Special case to handle removal of value attribute
Object[] remove = GraphConstants
.getRemoveAttributes(deltaNew);
if (remove != null && remove.length > 0) {
for (int i = 0; i < remove.length; i++) {
if (remove[i] == GraphConstants.VALUE) {
Object oldValue = valueForCellChanged(cell,
null);
if (oldValue != null) {
GraphConstants.setValue(deltaOld, oldValue);
}
}
}
}
}
}
return undo;
}
return null;
}
/**
* Applies the new value to the specified cell. Unfortunately for cloning
* the user object you must still override the attribute map and provide a
* custom cloneUserObject method. This is because the cloning of a cell is
* local to the cell, which in turn has a reference to its attribute map.
*
* @param cell
* @param newValue
* @return the old value for the cell, if any
*/
public Object valueForCellChanged(Object cell, Object newValue) {
if (cell instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) cell;
Object oldValue = node.getUserObject();
node.setUserObject(newValue);
return oldValue;
}
return null;
}
//
// Connection Set Handling
//
/**
* Applies connectionSet
to the model. Returns a connection
* set that may be used to undo this change.
*/
protected ConnectionSet handleConnectionSet(ConnectionSet cs) {
if (cs != null) {
ConnectionSet csundo = new ConnectionSet();
Iterator it = cs.connections();
while (it.hasNext()) {
ConnectionSet.Connection c = (ConnectionSet.Connection) it
.next();
Object edge = c.getEdge();
if (c.isSource())
csundo.connect(edge, getSource(edge), true);
else
csundo.connect(edge, getTarget(edge), false);
handleConnection(c, false);
}
// When removing edges it is possible that an edge is
// removed in a later step which has been added in a
// previous connection establishment (set semantic).
// Therefore, we first need to remove all old connections
// and then add all new connections in two steps.
it = cs.connections();
while (it.hasNext())
handleConnection((ConnectionSet.Connection) it.next(), true);
return csundo;
}
return null;
}
/**
* Inserts the specified connection into the model.
*/
protected void handleConnection(ConnectionSet.Connection c,
boolean establish) {
Object edge = c.getEdge();
Object port = (establish) ? c.getPort()
: (c.isSource()) ? getSource(edge) : getTarget(edge);
connect(edge, port, c.isSource(), establish);
}
/**
* Connects or disconnects the edge and port in this model based on
* remove
. Subclassers should override this to update
* connectivity datastructures.
*/
protected void connect(Object edge, Object port, boolean isSource,
boolean insert) {
if (port instanceof Port)
if (insert)
((Port) port).addEdge(edge);
// Only removes if opposite is not
// connected to same port
else if ((isSource) ? getTarget(edge) != port
: getSource(edge) != port)
((Port) port).removeEdge(edge);
if (!insert)
port = null;
if (edge instanceof Edge) {
if (isSource)
((Edge) edge).setSource(port);
else
((Edge) edge).setTarget(port);
}
}
//
// GraphModelListeners
//
/**
* Adds a listener for the GraphModelEvent posted after the graph changes.
*
* @see #removeGraphModelListener
* @param l
* the listener to add
*/
public void addGraphModelListener(GraphModelListener l) {
listenerList.add(GraphModelListener.class, l);
}
/**
* Removes a listener previously added with addGraphModelListener() .
*
* @see #addGraphModelListener
* @param l
* the listener to remove
*/
public void removeGraphModelListener(GraphModelListener l) {
listenerList.remove(GraphModelListener.class, l);
}
/**
* Invoke this method after you've changed how the cells are to be
* represented in the graph.
*/
public void cellsChanged(final Object[] cells) {
if (cells != null) {
fireGraphChanged(this, new GraphModelEvent.GraphModelChange() {
public Object[] getInserted() {
return null;
}
public Object[] getRemoved() {
return null;
}
public Map getPreviousAttributes() {
return null;
}
public ConnectionSet getConnectionSet() {
return null;
}
public ConnectionSet getPreviousConnectionSet() {
return null;
}
public ParentMap getParentMap() {
return null;
}
public ParentMap getPreviousParentMap() {
return null;
}
public void putViews(GraphLayoutCache view, CellView[] cellViews) {
}
public CellView[] getViews(GraphLayoutCache view) {
return null;
}
public Object getSource() {
return this;
}
public Object[] getChanged() {
return cells;
}
public Map getAttributes() {
return null;
}
public Object[] getContext() {
return null;
}
public Rectangle2D getDirtyRegion() {
return null;
}
public void setDirtyRegion(Rectangle2D dirty) {
}
});
}
}
/*
* Notify all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the
* parameters passed into the fire method.
*
* @see EventListenerList
*/
protected void fireGraphChanged(Object source,
GraphModelEvent.GraphModelChange edit) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
GraphModelEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == GraphModelListener.class) {
// Lazily create the event:
if (e == null)
e = new GraphModelEvent(source, edit);
((GraphModelListener) listeners[i + 1]).graphChanged(e);
}
}
}
/**
* Return an array of all GraphModelListeners that were added to this model.
*/
public GraphModelListener[] getGraphModelListeners() {
return (GraphModelListener[]) listenerList
.getListeners(GraphModelListener.class);
}
//
// GraphModelEdit
//
/**
* An implementation of GraphModelChange that can be added to the model
* event.
*/
public class GraphModelEdit extends CompoundEdit implements
GraphModelEvent.GraphModelChange {
/* Cells that were inserted/removed/changed during the last execution. */
protected Object[] insert, changed, remove, context;
/* Cells that were inserted/removed/changed during the last execution. */
protected Object[] inserted, removed;
/*
* Property map for the next execution. Attribute Map is passed to the
* views on inserts.
*/
protected Map attributes, previousAttributes;
/* Parent map for the next execution. */
protected ParentMap parentMap, previousParentMap;
/** The dirty region of the change prior to it happening */
protected Rectangle2D dirtyRegion = null;
/* ConnectionSet for the next execution. */
protected ConnectionSet connectionSet, previousConnectionSet;
/* Piggybacked undo from the views. */
protected Map cellViews = new Hashtable();
/**
* Constructs an edit record.
*
* @param inserted
* a set of roots that were inserted
* @param removed
* a set of elements that were removed
* @param attributes
* the attribute changes made by the edit
* @param connectionSet
* the set of changed connections
* @param parentMap
* the map of changed parents
*/
public GraphModelEdit(Object[] inserted, Object[] removed,
Map attributes, ConnectionSet connectionSet, ParentMap parentMap) {
super();
this.insert = inserted;
this.remove = removed;
this.connectionSet = connectionSet;
this.attributes = attributes;
this.parentMap = parentMap;
previousAttributes = null;
previousConnectionSet = connectionSet;
previousParentMap = parentMap;
// Remove Empty Parents
if (parentMap != null) {
// Compute Empty Group
Map childCount = new Hashtable();
Iterator it = parentMap.entries();
while (it.hasNext()) {
ParentMap.Entry entry = (ParentMap.Entry) it.next();
Object child = entry.getChild();
if (!isPort(child)) {
Object oldParent = getParent(child);
Object newParent = entry.getParent();
if (oldParent != newParent) {
changeChildCount(childCount, oldParent, -1);
changeChildCount(childCount, newParent, 1);
}
}
}
handleEmptyGroups(filterParents(childCount, 0));
}
}
public Object[] filterParents(Map childCount, int children) {
ArrayList list = new ArrayList();
Iterator it = childCount.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
if (entry.getValue() instanceof Integer) {
if (((Integer) entry.getValue()).intValue() == children)
list.add(entry.getKey());
}
}
return list.toArray();
}
protected void changeChildCount(Map childCount, Object parent,
int change) {
if (parent != null) {
Integer count = (Integer) childCount.get(parent);
if (count == null) {
count = new Integer(getChildCount(parent));
}
int newValue = count.intValue() + change;
childCount.put(parent, new Integer(newValue));
}
}
/**
* Adds the groups that become empty to the cells that will be removed.
* (Auto remove empty cells.) Removed cells will be re-inserted on undo,
* and the parent- child relations will be restored.
*/
protected void handleEmptyGroups(Object[] groups) {
if (removeEmptyGroups) {
if (groups != null && groups.length > 0) {
if (remove == null)
remove = new Object[] {};
Object[] tmp = new Object[remove.length + groups.length];
System.arraycopy(remove, 0, tmp, 0, remove.length);
System.arraycopy(groups, 0, tmp, remove.length,
groups.length);
remove = tmp;
}
}
}
public boolean isSignificant() {
return true;
}
/**
* Returns the source of this change. This can either be a view or a
* model, if this change is a GraphModelChange.
*/
public Object getSource() {
return DefaultGraphModel.this;
}
/**
* Returns the cells that have changed. This includes the cells that
* have been changed through a call to getAttributes and the edges that
* have been changed with the ConnectionSet.
*/
public Object[] getChanged() {
return changed;
}
/**
* Returns the objects that have not changed explicitly, but implicitly
* because one of their dependent cells has changed.
*/
public Object[] getContext() {
return context;
}
/**
* Returns the cells that were inserted.
*/
public Object[] getInserted() {
return inserted;
}
/**
* Returns the cells that were inserted.
*/
public Object[] getRemoved() {
return removed;
}
/**
* Returns a map that contains (object, map) pairs of the attributes
* that have been stored in the model.
*/
public Map getPreviousAttributes() {
return previousAttributes;
}
/**
* Returns a map of (object, view attributes). The objects are model
* objects which need to be mapped to views.
*/
public Map getAttributes() {
return attributes;
}
/**
* Returns the connectionSet.
*
* @return ConnectionSet
*/
public ConnectionSet getConnectionSet() {
return connectionSet;
}
public ConnectionSet getPreviousConnectionSet() {
return previousConnectionSet;
}
/**
* Returns the parentMap.
*
* @return ParentMap
*/
public ParentMap getParentMap() {
return parentMap;
}
public ParentMap getPreviousParentMap() {
return previousParentMap;
}
public Rectangle2D getDirtyRegion() {
return dirtyRegion;
}
public void setDirtyRegion(Rectangle2D dirty) {
this.dirtyRegion = dirty;
}
/**
* Redoes a change.
*
* @exception CannotRedoException
* if the change cannot be redone
*/
public void redo() throws CannotRedoException {
super.redo();
execute();
}
/**
* Undoes a change.
*
* @exception CannotUndoException
* if the change cannot be undone
*/
public void undo() throws CannotUndoException {
super.undo();
execute();
}
/**
* Execute this edit such that the next invocation to this method will
* invert the last execution.
*/
public void execute() {
// dirtyRegion = null;
// Compute Changed Cells
Set tmp = new HashSet();
if (attributes != null)
tmp.addAll(attributes.keySet());
if (parentMap != null)
tmp.addAll(parentMap.getChangedNodes());
// Note: One must also include the previous parents!
if (connectionSet != null)
tmp.addAll(connectionSet.getChangedEdges());
if (remove != null) {
for (int i = 0; i < remove.length; i++)
tmp.remove(remove[i]);
}
changed = tmp.toArray();
// Context cells
Set ctx = getEdges(DefaultGraphModel.this, changed);
context = ctx.toArray();
// Do Execute
inserted = insert;
removed = remove;
remove = handleInsert(inserted);
previousParentMap = parentMap;
parentMap = handleParentMap(parentMap);
// Adds previous parents
if (parentMap != null)
tmp.addAll(parentMap.getChangedNodes());
previousConnectionSet = connectionSet;
connectionSet = handleConnectionSet(connectionSet);
insert = handleRemove(removed);
previousAttributes = attributes;
attributes = handleAttributes(attributes);
changed = tmp.toArray();
// Fire Event
fireGraphChanged(DefaultGraphModel.this, this);
}
public void putViews(GraphLayoutCache view, CellView[] views) {
if (view != null && views != null)
cellViews.put(view, views);
}
public CellView[] getViews(GraphLayoutCache view) {
return (CellView[]) cellViews.get(view);
}
public String toString() {
String s = new String();
if (inserted != null) {
s += "Inserted:\n";
for (int i = 0; i < inserted.length; i++)
s += " " + inserted[i] + "\n";
} else
s += "None inserted\n";
if (removed != null) {
s += "Removed:\n";
for (int i = 0; i < removed.length; i++)
s += " " + removed[i] + "\n";
} else
s += "None removed\n";
if (changed != null && changed.length > 0) {
s += "Changed:\n";
for (int i = 0; i < changed.length; i++)
s += " " + changed[i] + "\n";
} else
s += "None changed\n";
if (parentMap != null)
s += parentMap.toString();
else
s += "No parent map\n";
return s;
}
}
/**
* An implementation of GraphViewChange.
*/
public class GraphModelLayerEdit extends AbstractUndoableEdit implements
GraphModelEvent.GraphModelChange {
public static final int FRONT = -1, BACK = -2;
private Object changeSource;
private transient Object[] cells;
private transient int[] next, prev;
private int layer;
// The cell that change are the parents, because they need to
// reload their childs for reordering!
private Object[] changed;
/**
* Constructs a GraphModelEdit. This modifies the order of the cells in
* the model.
*/
public GraphModelLayerEdit(Object[] cells, int layer) {
// A copy is made to avoid changes in the original array affect this listener
this.cells = new Object[cells.length];
System.arraycopy(cells, 0, this.cells, 0, cells.length);
this.layer = layer;
next = new int[cells.length];
prev = new int[cells.length];
updateNext();
// Compute array of changed cells (roots or parents of cells)
Set par = new HashSet();
for (int i = 0; i < cells.length; i++) {
Object cell = DefaultGraphModel.this.getParent(cells[i]);
if (cell == null)
cell = cells[i];
par.add(cell);
}
changed = par.toArray();
}
protected void updateNext() {
for (int i = 0; i < next.length; i++)
next[i] = layer;
}
/**
* Returns the source of this change. This can either be a view or a
* model, if this change is a GraphModelChange.
*/
public Object getSource() {
return DefaultGraphModel.this;
}
/**
* Returns the cells that have changed.
*/
public Object[] getChanged() {
return changed;
}
/**
* Returns the cells that have changed.
*/
public Object[] getInserted() {
return null;
}
/**
* Returns the cells that have changed.
*/
public Object[] getRemoved() {
return null;
}
/**
* Returns null.
*/
public Object[] getContext() {
return null;
}
/**
* Returns null.
*/
public Map getAttributes() {
return null;
}
/**
* Returns null.
*/
public Map getPreviousAttributes() {
return null;
}
public ConnectionSet getConnectionSet() {
return null;
}
public ConnectionSet getPreviousConnectionSet() {
return null;
}
/**
* Returns null.
*/
public ParentMap getParentMap() {
return null;
}
public ParentMap getPreviousParentMap() {
return null;
}
public Rectangle2D getDirtyRegion() {
return null;
}
public void setDirtyRegion(Rectangle2D dirty) {
}
/**
* Allows a GraphLayoutCache
to add and execute and
* UndoableEdit in this change. This does also work if the parent edit
* has already been executed, in which case the to be added edit will be
* executed immediately, after addition. This is used to handle changes
* to the view that are triggered by certain changes of the model. Such
* implicit edits may be associated with the view so that they may be
* undone and redone correctly, and are stored in the model's global
* history together with the parent event as one unit.
*/
public void addImplicitEdit(UndoableEdit edit) {
// ignore
}
/**
* Returns the views that have not changed explicitly, but implicitly
* because one of their dependent cells has changed.
*/
public CellView[] getViews(GraphLayoutCache view) {
return null;
}
/**
* Returns the views that have not changed explicitly, but implicitly
* because one of their dependent cells has changed.
*/
public void putViews(GraphLayoutCache view, CellView[] cellViews) {
// ignore
}
/**
* Redoes a change.
*
* @exception CannotRedoException
* if the change cannot be redone
*/
public void redo() throws CannotRedoException {
super.redo();
updateNext();
execute();
}
/**
* Undoes a change.
*
* @exception CannotUndoException
* if the change cannot be undone
*/
public void undo() throws CannotUndoException {
super.undo();
execute();
}
/**
* Execute this edit such that the next invocation to this method will
* invert the last execution.
*/
public void execute() {
for (int i = 0; i < cells.length; i++) {
List list = getParentList(cells[i]);
if (list != null) {
prev[i] = list.indexOf(cells[i]);
if (prev[i] >= 0) {
list.remove(prev[i]);
int n = next[i];
if (n == FRONT)
n = list.size();
else if (n == BACK)
n = 0;
list.add(n, cells[i]);
next[i] = prev[i];
}
}
}
updateListeners();
}
protected void updateListeners() {
fireGraphChanged(DefaultGraphModel.this, this);
}
/**
* Returns the list that exclusively contains view
.
*/
protected List getParentList(Object cell) {
List list = null;
if (cell instanceof DefaultMutableTreeNode) {
Object parent = ((DefaultMutableTreeNode) cell).getParent();
if (parent instanceof DefaultGraphCell)
list = ((DefaultGraphCell) parent).getChildren();
else
list = roots;
}
return list;
}
}
//
// Static Methods
//
/**
* Returns a deep clone of the specified cell, including all children.
*/
public static Object cloneCell(GraphModel model, Object cell) {
Map clones = model.cloneCells(getDescendants(model,
new Object[] { cell }).toArray());
return clones.get(cell);
}
/**
* Returns a deep clone of the specified cells, including all children.
*/
public static Object[] cloneCell(GraphModel model, Object[] cells) {
Map clones = model.cloneCells(getDescendants(model, cells).toArray());
for (int i = 0; i < cells.length; i++) {
cells[i] = clones.get(cells[i]);
}
return cells;
}
/**
* Helper methods that connects the source of edge
to
* port
in model.
*/
public static void setSourcePort(GraphModel model, Object edge, Object port) {
model.edit(null, new ConnectionSet(edge, port, true), null, null);
}
/**
* Helper methods that connects the source of edge
to
* port
in model.
*/
public static void setTargetPort(GraphModel model, Object edge, Object port) {
model.edit(null, new ConnectionSet(edge, port, false), null, null);
}
/**
* Returns the source vertex of the edge by calling getParent on getSource
* on the specified model.
*/
public static Object getSourceVertex(GraphModel model, Object edge) {
if (model != null)
return model.getParent(model.getSource(edge));
return null;
}
/**
* Returns the target vertex of the edge by calling getParent on getTarget
* on the specified model.
*/
public static Object getTargetVertex(GraphModel model, Object edge) {
if (model != null)
return model.getParent(model.getTarget(edge));
return null;
}
/**
* @return Returns the user object of the given cell. This implementation
* checks if the cell is a default mutable tree node and returns
* it's user object.
*
* @deprecated Use {@link GraphModel#getValue(Object)} instead.
*/
public static Object getUserObject(Object cell) {
if (cell instanceof DefaultMutableTreeNode)
return ((DefaultMutableTreeNode) cell).getUserObject();
return null;
}
/**
* Checks whether the cell has at least one child which is not a port. This
* implementation operates on the model, not taking into account visibility
* of cells. It returns true for groups regardless of their folded state.
*
* @param cell
* the cell to check for being a group
* @return Returns true if the cell contains at least one cell which is not
* a port
*/
public static boolean isGroup(GraphModel model, Object cell) {
for (int i = 0; i < model.getChildCount(cell); i++) {
if (!model.isPort(model.getChild(cell, i)))
return true;
}
return false;
}
/**
* Returns all cells of the model in an array.
*
* @see #getDescendants(GraphModel, Object[])
*
* @return Returns all cells in the model including all descandants.
*/
public static Object[] getAll(GraphModel model) {
return getDescendants(model, getRoots(model)).toArray();
}
/**
* Returns the roots of the specified model as an array. This implementation
* uses the GraphModel interface in the general case, but if the model is a
* DefaultGraphModel
the performance can be improved to
* linear time.
*/
public static Object[] getRoots(GraphModel model) {
Object[] cells = null;
if (model != null) {
// If model is DefaultGraphModel, we can do a linear time getRoots
if (model instanceof DefaultGraphModel) {
cells = ((DefaultGraphModel) model).getRoots().toArray();
} else {
cells = new Object[model.getRootCount()];
for (int i = 0; i < cells.length; i++) {
cells[i] = model.getRootAt(i);
}
}
}
return cells;
}
/**
* Returns the roots of the specified model as a collection. This implementation
* uses the GraphModel interface in the general case, but if the model is a
* DefaultGraphModel
the performance can be improved to
* linear time.
*/
public static Collection getRootsAsCollection(GraphModel model) {
Collection cells = null;
if (model != null) {
// If model is DefaultGraphModel, we can do a linear time getRoots
if (model instanceof DefaultGraphModel) {
cells = ((DefaultGraphModel) model).getRoots();
} else {
cells = new LinkedHashSet(model.getRootCount());
for (int i = 0; i < cells.size(); i++) {
cells.add(model.getRootAt(i));
}
}
}
return cells;
}
/**
* Returns the roots in cells
by checking if their parent is
* null
. This implementation only uses the GraphModel
* interface. This method never returns null.
*/
public static Object[] getRoots(GraphModel model, Object[] cells) {
List roots = new ArrayList();
if (cells != null) {
for (int i = 0; i < cells.length; i++) {
if (model.getParent(cells[i]) == null) {
roots.add(cells[i]);
}
}
}
return roots.toArray();
}
/**
* @return Returns the roots of cells, eg. an array that contains no cell
* having an ancestor in cells.
*/
public static Object[] getTopmostCells(GraphModel model, Object[] cells) {
Set cellSet = new HashSet();
for (int i = 0; i < cells.length; i++)
cellSet.add(cells[i]);
List parents = new ArrayList();
for (int i = 0; i < cells.length; i++) {
if (!hasAncestorIn(model, cellSet, cells[i]))
parents.add(cells[i]);
}
return parents.toArray();
}
/**
* Returns true if the specified child has an ancestor in parents.
*/
public static boolean hasAncestorIn(GraphModel model, Set parents,
Object child) {
Object parent = model.getParent(child);
while (parent != null) {
if (parents.contains(parent))
return true;
parent = model.getParent(parent);
}
return false;
}
/**
* Flattens the given array of root cells by adding the roots and their
* descandants. The resulting set contains all cells, which means it
* contains branches and leafs. Note: This is an iterative
* implementation. No recursion used.
* Note: This returns a linked list, for frequent read operations you should
* turn this into an array, or at least an array list.
*/
public static List getDescendants(GraphModel model, Object[] cells) {
if (cells != null) {
Stack stack = new Stack();
for (int i = cells.length - 1; i >= 0; i--)
stack.add(cells[i]);
LinkedList result = new LinkedList();
while (!stack.isEmpty()) {
Object tmp = stack.pop();
for (int i = model.getChildCount(tmp) - 1; i >= 0; i--)
stack.add(model.getChild(tmp, i));
if (tmp != null)
result.add(tmp);
}
return result;
}
return null;
}
/**
* Orders cells so that they reflect the model order.
*/
public static Object[] order(GraphModel model, Object[] cells) {
if (cells != null) {
Set cellSet = new HashSet();
for (int i = 0; i < cells.length; i++)
cellSet.add(cells[i]);
Stack stack = new Stack();
for (int i = model.getRootCount() - 1; i >= 0; i--)
stack.add(model.getRootAt(i));
LinkedList result = new LinkedList();
while (!stack.isEmpty()) {
Object tmp = stack.pop();
for (int i = model.getChildCount(tmp) - 1; i >= 0; i--)
stack.add(model.getChild(tmp, i));
if (cellSet.remove(tmp))
result.add(tmp);
}
return result.toArray();
}
return null;
}
/**
* Returns the set of all connected edges to cells
or their
* descendants. The passed-in cells are never returned as part of the result
* set. This can be used on vertices, edges and ports.
*/
public static Set getEdges(GraphModel model, Object[] cells) {
Set result = new LinkedHashSet();
if (cells != null) {
// We know the minimum initial capacity of this set is cells.length
// We assume the cell has one port at a minimum
int setSize = ((int)(cells.length * 1.33) + 1);
Set allCells = new HashSet(setSize, 0.75f);
for (int i = 0; i < cells.length; i++) {
allCells.add(cells[i]);
}
// Include descendants
List descendants = getDescendants(model, cells);
// Iterate through the list rather than adding all to preserve order
Iterator desIter = descendants.iterator();
while (desIter.hasNext()) {
allCells.add(desIter.next());
}
if (allCells != null) {
Iterator it = allCells.iterator();
while (it.hasNext()) {
Iterator edges = model.edges(it.next());
while (edges.hasNext())
result.add(edges.next());
}
for (int i = 0; i < cells.length; i++)
result.remove(cells[i]);
}
}
return result;
}
/**
* @return Returns the opposite port or vertex in edge
.
*/
public static Object getOpposite(GraphModel model, Object edge, Object cell) {
boolean isPort = model.isPort(cell);
Object source = (isPort) ? model.getSource(edge) : getSourceVertex(
model, edge);
if (cell == source)
return (isPort) ? model.getTarget(edge) : getTargetVertex(model,
edge);
else
return source;
}
/**
* Returns true if the given vertices are conntected by a single edge in
* this document.
*/
public static boolean containsEdgeBetween(GraphModel model, Object v1,
Object v2) {
Object[] edges = getEdgesBetween(model, v1, v2, false);
return (edges != null && edges.length > 0);
}
/**
* Returns the edges between two specified ports or two specified vertices.
* If directed is true then cell1
must be the source of the
* returned edges. This method never returns null. If there are no edges
* between the specified cells, then an array of length 0 is returned.
*/
public static Object[] getEdgesBetween(GraphModel model, Object cell1,
Object cell2, boolean directed) {
boolean isPort1 = model.isPort(cell1);
boolean isPort2 = model.isPort(cell2);
ArrayList result = new ArrayList();
Set edges = DefaultGraphModel.getEdges(model, new Object[] { cell1 });
Iterator it = edges.iterator();
while (it.hasNext()) {
Object edge = it.next();
// TODO: Handle edge groups
Object source = (isPort1) ? model.getSource(edge)
: getSourceVertex(model, edge);
Object target = (isPort2) ? model.getTarget(edge)
: getTargetVertex(model, edge);
if ((source == cell1 && target == cell2)
|| (!directed && source == cell2 && target == cell1))
result.add(edge);
}
return result.toArray();
}
/**
* Returns the outgoing edges for cell. Cell should be a port or a vertex.
*/
public static Object[] getOutgoingEdges(GraphModel model, Object cell) {
return getEdges(model, cell, false);
}
/**
* Returns the incoming edges for cell. Cell should be a port or a vertex.
*/
public static Object[] getIncomingEdges(GraphModel model, Object cell) {
return getEdges(model, cell, true);
}
/**
* Returns the incoming or outgoing edges for cell. Cell should be a port or
* a vertex.
*/
public static Object[] getEdges(GraphModel model, Object cell,
boolean incoming) {
Set edges = DefaultGraphModel.getEdges(model, new Object[] { cell });
// Base initial capacity on size of set, it can't be any larger
ArrayList result = new ArrayList(edges.size());
Iterator it = edges.iterator();
while (it.hasNext()) {
Object edge = it.next();
// TODO: Handle edge groups
Object port = (incoming) ? model.getTarget(edge) : model
.getSource(edge);
Object parent = model.getParent(port);
if (port == cell || parent == cell)
result.add(edge);
}
return result.toArray();
}
/**
* Returns true
if vertex
is a valid vertex.
*
* @return true
if vertex
is a valid vertex.
*/
public static boolean isVertex(GraphModel model, Object vertex) {
return (vertex != null && !model.isEdge(vertex) && !model.isPort(vertex));
}
// Serialization support
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
s.defaultReadObject();
listenerList = new EventListenerList();
emptyIterator = new EmptyIterator();
}
public static class EmptyIterator implements Iterator, Serializable {
public boolean hasNext() {
return false;
}
public Object next() {
return null;
}
public void remove() {
// nop
}
}
/**
* @return the removeEmptyGroups
*/
public boolean isRemoveEmptyGroups() {
return removeEmptyGroups;
}
/**
* @param removeEmptyGroups the removeEmptyGroups to set
*/
public void setRemoveEmptyGroups(boolean removeEmptyGroups) {
this.removeEmptyGroups = removeEmptyGroups;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy