All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yaoqiang.graph.view.Graph Maven / Gradle / Ivy

There is a newer version: 2.2.18
Show newest version
package org.yaoqiang.graph.view;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.JOptionPane;

import org.w3c.dom.Element;
import org.yaoqiang.bpmn.model.BPMNModelUtils;
import org.yaoqiang.bpmn.model.elements.XMLElement;
import org.yaoqiang.bpmn.model.elements.activities.SubProcess;
import org.yaoqiang.bpmn.model.elements.artifacts.Association;
import org.yaoqiang.bpmn.model.elements.artifacts.Group;
import org.yaoqiang.bpmn.model.elements.artifacts.TextAnnotation;
import org.yaoqiang.bpmn.model.elements.collaboration.Participant;
import org.yaoqiang.bpmn.model.elements.core.common.FlowNode;
import org.yaoqiang.bpmn.model.elements.core.foundation.BaseElement;
import org.yaoqiang.bpmn.model.elements.core.infrastructure.Definitions;
import org.yaoqiang.bpmn.model.elements.data.DataAssociation;
import org.yaoqiang.bpmn.model.elements.data.DataInput;
import org.yaoqiang.bpmn.model.elements.data.DataObject;
import org.yaoqiang.bpmn.model.elements.data.DataObjectReference;
import org.yaoqiang.bpmn.model.elements.data.DataOutput;
import org.yaoqiang.bpmn.model.elements.data.DataStoreReference;
import org.yaoqiang.bpmn.model.elements.data.ItemAwareElement;
import org.yaoqiang.graph.model.GraphModel;
import org.yaoqiang.graph.util.GraphUtils;
import org.yaoqiang.graph.util.TooltipBuilder;
import org.yaoqiang.util.Constants;
import org.yaoqiang.util.Resources;

import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxICell;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;

/**
 * Graph
 * 
 * @author Shi Yaoqiang([email protected])
 */
public class Graph extends mxGraph {

	protected Map bpmnElementMap = new HashMap();

	protected Map externalDefinitions = new HashMap();

	protected TooltipBuilder tooltipBuilder;

	public Graph(mxGraphModel model) {
		super(model, null);

		setVertexLabelsMovable(true);
		setResetViewOnRootChange(false);
		setDisconnectOnMove(false);
		setAllowDanglingEdges(false);
		setResetEdgesOnConnect(false);
		setMultigraph(false);
		setConnectableEdges(Constants.SETTINGS.getProperty("connectableEdge", "0").equals("1"));
		setCellsDisconnectable(false);
		setKeepEdgesInForeground(true);
		setGridSize(Integer.parseInt(Constants.SETTINGS.getProperty("gridSize", "5")));

		this.tooltipBuilder = new TooltipBuilder();
	}

	public final Definitions getBpmnModel() {
		return getModel().getBpmnModel();
	}

	public Map getBpmnElementMap() {
		return bpmnElementMap;
	}

	public void clearBpmnModel() {
		getBpmnModel().clear();
		externalDefinitions.clear();
		bpmnElementMap.clear();
		getBpmnModel().setName("");
	}

	public final void setBpmnElementMap(Map bpmnElementMap) {
		this.bpmnElementMap = bpmnElementMap;
	}

	public Map getExternalDefinitions() {
		return externalDefinitions;
	}

	public GraphModel getModel() {
		return (GraphModel) model;
	}

	public GraphView getView() {
		return (GraphView) view;
	}

	protected GraphView createGraphView() {
		return new GraphView(this);
	}

	public String getToolTipForCell(Object cell) {
		return tooltipBuilder.getTooltip(this, (mxCell) cell);
	}

	public String convertValueToString(Object cell) {
		if (cell instanceof mxCell) {
			Object value = ((mxCell) cell).getValue();
			String name = null;
			if (value instanceof Element) {
				Element elt = (Element) value;
				if (isOrganizationElement(cell)) {
					name = elt.getAttribute("cn");
					if (isOrganization(cell) || isOrganizationRoot(cell)) {
						name = elt.getAttribute("o");
					} else if (isOrganizationalUnit(cell)) {
						name = elt.getAttribute("ou");
					}
				} else {
					name = elt.getAttribute("value");
				}
				return name;
			} else if (value instanceof BaseElement) {
				if (value instanceof TextAnnotation) {
					return ((BaseElement) value).get("text").toValue();
				} else if (value instanceof Group) {
					return BPMNModelUtils.getCategoryValueString(getBpmnModel(), ((Group) value).getCategoryValueRef());
				} else if (value instanceof Association || value instanceof DataAssociation) {
					return "";
				} else if (value instanceof DataStoreReference) {
					String dsRef = ((DataStoreReference) value).getDataStoreRef();
					if (dsRef.length() == 0) {
						return "";
					} else {
						return getBpmnModel().getDataStore(dsRef).getName();
					}
				} else if (value instanceof ItemAwareElement) {
					String dataState = null;
					if (value instanceof DataObjectReference) {
						DataObject obj = ((DataObjectReference) value).getRefDataObject();
						if (obj == null) {
							return "";
						} else {
							name = obj.getName();
							dataState = obj.getDataState();
						}
					} else if (value instanceof DataInput || value instanceof DataOutput) {
						name = ((BaseElement) value).get("name").toValue();
						dataState = ((ItemAwareElement) value).getDataState();
					}
					if (dataState == null || dataState.length() == 0) {
						return name;
					} else {
						return name + "\n[" + dataState + "]";
					}
				}
				return ((BaseElement) value).get("name").toValue();
			}
		}
		return super.convertValueToString(cell);
	}

	public void cellLabelChanged(Object cell, Object newValue, boolean autoSize) {
		if (cell instanceof mxCell && newValue != null) {
			Object value = ((mxCell) cell).getValue();

			if (value instanceof Element) {
				String label = newValue.toString();
				Element elt = (Element) value;

				// Clones the value for correct undo/redo
				elt = (Element) elt.cloneNode(true);
				String attrName = "value";
				if (isOrganizationElement(cell)) {
					attrName = "cn";
					if (isOrganization(cell) || isOrganizationRoot(cell)) {
						attrName = "o";
					} else if (isOrganizationalUnit(cell)) {
						attrName = "ou";
					}
				}
				elt.setAttribute(attrName, label);
				newValue = elt;
			} else if (value instanceof BaseElement && newValue instanceof String) {
				String label = newValue.toString();
				BaseElement baseElement = (BaseElement) ((BaseElement) value).clone();
				if (value instanceof TextAnnotation) {
					baseElement.set("text", label);
				} else if (value instanceof ItemAwareElement) {
					int index = label.lastIndexOf("\n[");
					if (index > 0) {
						String name = label.substring(0, index);
						String state = label.substring(index + 2, label.length() - 1);
						baseElement.set("name", name);
						((ItemAwareElement) baseElement).setDataState(state);
					} else {
						baseElement.set("name", label);
						((ItemAwareElement) baseElement).setDataState("");
					}

				} else {
					baseElement.set("name", label);
				}
				newValue = baseElement;
			}
			if (newValue.toString().length() == 0 && getModel().isLinkEvent(cell)) {
				JOptionPane.showMessageDialog(null, Resources.get("WarningLinkEventMustHaveAName"), "Validation Error!", JOptionPane.WARNING_MESSAGE);
				return;
			}
			if (!(value instanceof BaseElement) && value.equals(newValue)) {
				return;
			}
			model.beginUpdate();

			if (getModel().isChoreographyParticipant(cell)) {
				int index = ((mxCell) cell).getId().indexOf("_part_");
				String partId = ((mxCell) cell).getId().substring(index + 6);
				String actId = ((mxCell) cell).getId().substring(0, index);

				int option = JOptionPane.YES_OPTION;
				Map participants = getAllParticipants();
				if (participants.get(newValue) == null && getModel().hasParticipantRef((mxCell) cell, partId)) {
					option = JOptionPane.showConfirmDialog(null, Resources.get("InfoYesToRenameSelectedParticipantOrNoToCreateNewParticipant"),
							Resources.get("InfoRenameOrCreate"), JOptionPane.YES_NO_OPTION);
					if (option == JOptionPane.NO_OPTION) {
						String newPartId = getModel().createId(cell);
						mxCell newCell = (mxCell) model.cloneCells(new Object[] { cell }, false)[0];
						newCell.setValue(new Participant(newValue.toString()));
						newCell.setId(actId + "_part_" + newPartId);
						newCell.setParent(null);
						Object parent = ((mxCell) cell).getParent();
						removeCells(new Object[] { cell });
						addCell(newCell, parent);
						if (this.isAdditionalChoreographyParticipant(newCell)) {
							orderCells(false, new Object[] { newCell });
						} else {
							orderCells(true, new Object[] { newCell });
						}
						cell = newCell;
					} else {
						Map participantCells = getModel().getAllChoreographyParticipants();
						for (Entry part : participantCells.entrySet()) {
							if (part.getKey().endsWith(partId)) {
								model.setValue(part.getValue(), newValue);
							}
						}
					}
				} else if (participants.get(newValue) != null) {
					String newPartId = participants.get(newValue);
					mxCell newCell = (mxCell) model.cloneCells(new Object[] { cell }, false)[0];
					newCell.setId(actId + "_part_" + newPartId);
					if (getModel().getCell(newCell.getId()) != null) {
						JOptionPane.showMessageDialog(null, Resources.get("WarningSubChoreographyMustNotInvolveDuplicatedParticipants"), "Validation Error!",
								JOptionPane.WARNING_MESSAGE);
						return;
					}
					newCell.setParent(null);
					newCell.setValue(newValue);
					removeCells(new Object[] { cell });
					addCell(newCell, ((mxCell) cell).getParent());
					if (this.isAdditionalChoreographyParticipant(newCell)) {
						orderCells(false, new Object[] { newCell });
					} else {
						orderCells(true, new Object[] { newCell });
					}
					cell = newCell;
				} else if (!getModel().hasParticipantRef((mxCell) cell, partId)) {
					model.setValue(cell, newValue);
				}
			} else {
				model.setValue(cell, newValue);
			}

			model.endUpdate();
		}

		if (newValue != null) {
			fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED, "cells", new Object[] { cell }));
		}
	}

	public boolean isLaneMovable(Object cell) {
		if (getModel().isLane(cell) && model.getChildCount(model.getParent(cell)) > 1) {
			return true;
		}
		return false;
	}

	public boolean isLabelMovable(Object cell) {
		return !isCellLocked(cell)
				&& ((model.isEdge(cell) && isEdgeLabelsMovable()) || ((getModel().isGateway(cell) || getModel().isEvent(cell) || getModel().isDataObject(cell)
						|| getModel().isDataInput(cell) || getModel().isDataOutput(cell) || getModel().isDataStore(cell) || getModel().isConversationNode(cell)
						|| getModel().isMessage(cell) || isImageArtifact(cell)) && isVertexLabelsMovable()));
	}

	public boolean isCellSelectable(Object cell) {
		return isCellsSelectable() && !getModel().isChoreographyTask(cell) && !getModel().isChoreographySubprocess(cell)
				&& !getModel().isChoreographyParticipant(cell) && !isOrganizationName(cell);
	}

	public boolean isCellCollapsed(Object cell) {
		return isCollapsedSwimlane(cell) || model.isCollapsed(cell);
	}

	public boolean isCellEditable(Object cell) {
		if (getModel().isAssociation(cell) || getModel().isDataAssociation(cell) || isOrganizationName(cell)) {
			return false;
		}
		return true;
	}

	public boolean isCellResizable(Object cell) {
		if (((mxCell) cell).getStyle() != null
				&& (getModel().isLane(cell) && hasChildLane(cell) || isAutoPool(cell) && hasChildLane(cell) || getModel().isDataObject(cell)
						|| getModel().isDataInput(cell) || getModel().isDataOutput(cell) || getModel().isDataStore(cell) || getModel().isMessage(cell)
						|| getModel().isChoreographyTask(cell) || getModel().isChoreographySubprocess(cell) || getModel().isChoreographyParticipant(cell)
						|| getModel().isConversationNode(cell) || getModel().isEvent(cell) || getModel().isGateway(cell) || isOrganizationName(cell) || isOrganizationElement(cell))) {
			return false;
		}
		return super.isCellResizable(cell);
	}

	public boolean isCellFoldable(Object cell, boolean collapse) {
		return (!getModel().isCallActivity(cell) && !isSubChoreography(cell) && !isChoreography(cell) && !getModel().isTask(cell))
				&& !getModel().isMessageFlow(cell) && model.getChildCount(cell) > 0 || getModel().isSubProcess(cell)
				|| getModel().isChoreographySubprocess(cell) || isSwimlane(cell);
	}

	public boolean isCellMovable(Object cell) {
		if (getModel().isLane(cell) && model.getChildCount(cell) > 1 || getModel().isChoreographyTask(cell) || getModel().isChoreographySubprocess(cell)
				|| getModel().isChoreographyParticipant(cell) || isOrganizationName(cell)) {
			return false;
		}
		return super.isCellMovable(cell) || getModel().isBoundaryEvent(cell) || getModel().isMessage(cell);
	}

	public boolean isValidDropTarget(Object cell, Object[] cells) {
		if (cells == null) {
			cells = getSelectionCells();
		}
		if (hasChildNonLane(cell) && isSwimlane(cells[0]) && model.getParent(cells[0]) != cell) {
			return false;
		}
		if (hasChildLane(cell) && !isSwimlane(cells[0])) {
			return false;
		}
		if (getModel().isSubProcess(cell) && isSwimlane(cells[0])) {
			return false;
		}
		if (model.isCollapsed(cell) && isSwimlane(cell)) {
			return false;
		}
		if (isSwimlane(cell) && getModel().isPool(cells[0])) {
			return false;
		}
		if (getModel().isChoreographyTask(cell) || getModel().isCollapsedSubProcess(cell) && getModel().isChoreographySubprocess(cell)) {
			return false;
		}
		if ((getModel().isTask(cell) || getModel().isCallActivity(cell) || getModel().isCollapsedSubProcess(cell))
				&& (getConnections(cells[0]).length > 0 || getModel().isLinkEvent(cells[0]) || getModel().isThrowEvent(cells[0]))) {
			return false;
		}
		if ((getModel().isTask(cell) || getModel().isCallActivity(cell))
				&& (model.getChildCount(cell) > 3 || !getModel().isIntermediateEvent(cells[0]) || cells.length > 1)) {
			return false;
		}
		if (getModel().isCollapsedSubProcess(cell)
				&& (!getModel().isEvent(cells[0]) || getModel().isStartEvent(cells[0]) && !getModel().isNoneEvent(cells[0]) || getModel()
						.isThrowEvent(cells[0]) && !getModel().isEndEvent(cells[0]) && !getModel().isNoneEvent(cells[0]))) {
			return false;
		}
		if (getModel().isReceiveTask(cell) && getModel().isIntermediateEvent(cells[0])) {
			Object[] edges = getIncomingEdges(cell);
			if (edges.length > 0 && getModel().isEventGateway(model.getTerminal(edges[0], true))) {
				return false;
			}
		}

		if (getModel().isBoundaryEvent(cells[0]) && model.getParent(cells[0]) != cell) {
			return false;
		}
		if (getModel().isGroupArtifact(cells[0]) && !getModel().isSubProcess(cell) && !isSubChoreography(cell)) {
			return false;
		}
		if (getModel().isAnnotation(cells[0]) || (isChoreography(cells[0]) || isSubChoreography(cells[0]))
				&& (!getModel().isChoreographySubprocess(cell) || getModel().isChoreographySubprocess(cell) && model.isAncestor(cells[0], cell))) {
			return false;
		}
		if (!getModel().isChoreographyParticipant(cells[0]) && !getModel().isChoreographySubprocess(cells[0]) && !getModel().isChoreographyTask(cells[0])
				&& (isChoreography(cell) || isSubChoreography(cell))) {
			return false;
		}
		if (!getModel().isGroupArtifact(cells[0]) && !getModel().isEvent(cells[0]) && !getModel().isGateway(cells[0]) && !getModel().isMessage(cells[0])
				&& !isChoreography(cells[0]) && !isSubChoreography(cells[0]) && getModel().isChoreographySubprocess(cell)) {
			return false;
		}
		if (cells != null) {
			for (int i = 0; i < cells.length; i++) {
				if (isSubChoreography(cells[i]) && getModel().isChoreographySubprocess(cell) && model.isAncestor(cells[i], cell))
					return false;
				if (getModel().isSubProcess(cells[i]) && getModel().isSubProcess(cell) && model.isAncestor(cells[i], cell))
					return false;
				if (isSwimlane(cells[i]) && isSwimlane(cell) && model.isAncestor(cells[i], cell))
					return false;
			}
		}

		if (!isPlane(cell) && getModel().isConversationNode(cells[0])) {
			return false;
		}
		if (!getModel().isMessageFlow(cell) && !isPlane(cell) && !getModel().isChoreographySubprocess(cell) && getModel().isMessage(cells[0])) {
			return false;
		}
		if (getModel().isMessageFlow(cell) && model.getChildCount(cell) > 0) {
			return false;
		}
		return cell != null
				&& cells != null
				&& cell != cells[0]
				&& ((isSplitEnabled() && isSplitTarget(cell, cells)) || getModel().isMessageFlow(cell) && getModel().isMessage(cells[0]) || (!model
						.isEdge(cell) && (isSwimlane(cell) || getModel().isTask(cell) && getModel().isIntermediateEvent(cells[0])
						|| getModel().isCallActivity(cell) && getModel().isIntermediateEvent(cells[0]) || getModel().isSubProcess(cell)
						|| getModel().isChoreographySubprocess(cell) || (model.getChildCount(cell) > 0 && !isCellCollapsed(cell)))));
	}

	public boolean isSplitTarget(Object target, Object[] cells) {
		if (getModel().isMessageFlow(target) || isCompensationAssociation(target) || getModel().isDataAssociation(target) || getModel().isAssociation(target)) {
			return false;
		}
		if (cells == null || cells.length != 1 || getModel().isArtifact(cells[0]) || isSwimlane(cells[0]) || getModel().isStartEvent(cells[0])
				|| getModel().isEndEvent(cells[0]) || getModel().isDataObject(cells[0]) || getModel().isDataInput(cells[0])
				|| getModel().isDataOutput(cells[0]) || getModel().isDataStore(cells[0])) {
			return false;
		}
		return super.isSplitTarget(target, cells);

	}

	public Object splitEdge(Object edge, Object[] cells, double dx, double dy) {
		if (getModel().isMessage(cells[0])) {
			return null;
		}
		if (getModel().isSubProcess(cells[0])) {
			mxGeometry geo = ((mxICell) cells[0]).getGeometry();
			geo.setAlternateBounds(new mxRectangle(0, 0, Constants.ACTIVITY_WIDTH, Constants.ACTIVITY_HEIGHT));
			model.setGeometry(cells[0], geo);
			foldCells(true, false, new Object[] { cells[0] });
		}

		Object parent = model.getParent(edge);
		Object source = model.getTerminal(edge, true);
		Object target = model.getTerminal(edge, false);

		model.beginUpdate();
		try {
			cellsMoved(cells, dx, dy, false, false);
			cellsAdded(cells, parent, model.getChildCount(parent), null, null, true);
			Object leftEdge = cloneCells(new Object[] { edge })[0];
			Object rightEdge = cloneCells(new Object[] { edge })[0];
			cellsAdded(new Object[] { leftEdge }, parent, model.getChildCount(parent), source, cells[0], false);
			cellsAdded(new Object[] { rightEdge }, parent, model.getChildCount(parent), cells[0], target, false);
			removeCells(new Object[] { edge }, true);
			fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, "edge", edge, "cells", cells, "dx", dx, "dy", dy));
		} finally {
			model.endUpdate();
		}

		return edge;
	}

	public mxRectangle getBoundsForGroup(Object group, Object[] children, double border) {
		mxRectangle result = getBoundingBoxFromGeometry(children);

		if (result != null) {
			if (isSwimlane(group)) {
				mxRectangle size = getStartSize(group);

				result.setX(result.getX() - size.getWidth());
				result.setY(result.getY() - size.getHeight());
				result.setWidth(result.getWidth() + size.getWidth());
				result.setHeight(result.getHeight() + size.getHeight());
			} else if (getModel().isSubProcess(group)) {
				if (result.getWidth() <= Constants.FOLDED_SUBPROCESS_WIDTH) {
					result.setWidth(Constants.FOLDED_SUBPROCESS_WIDTH);
				}
				if (result.getHeight() <= Constants.FOLDED_SUBPROCESS_HEIGHT) {
					result.setHeight(Constants.FOLDED_SUBPROCESS_HEIGHT);
				}
			}

			// Adds the border
			result.setX(result.getX() - border);
			result.setY(result.getY() - border);
			result.setWidth(result.getWidth() + 2 * border);
			result.setHeight(result.getHeight() + 2 * border);
		}

		return result;
	}

	public Object groupCells(Object group, double border, Object[] cells) {
		if (cells == null) {
			cells = mxUtils.sortCells(getSelectionCells(), true);
		}

		cells = getCellsForGroup(cells);

		if (group == null) {
			group = createGroupCell(cells);
		}

		mxRectangle bounds = getBoundsForGroup(group, cells, border);

		if (cells.length > 0 && bounds != null) {
			// Uses parent of group or previous parent of first child
			Object parent = model.getParent(group);

			if (parent == null) {
				parent = model.getParent(cells[0]);
			}

			model.beginUpdate();
			try {
				// Checks if the group has a geometry and
				// creates one if one does not exist
				if (getCellGeometry(group) == null) {
					model.setGeometry(group, new mxGeometry());
				}

				// Adds the children into the group and moves
				int index = model.getChildCount(group);
				cellsAdded(cells, group, index, null, null, false);
				cellsMoved(cells, -bounds.getX(), -bounds.getY(), false, true);

				// Adds the group into the parent and resizes
				index = model.getChildCount(parent);
				cellsAdded(new Object[] { group }, parent, index, null, null, false, false);
				cellsResized(new Object[] { group }, new mxRectangle[] { bounds });

				// ==============start==============
				for (Object cell : cells) {
					if (model.isVertex(cell)) {
						List edges = Arrays.asList(getConnections(cell));
						for (Object edge : edges) {
							String id = ((mxCell) edge).getId();
							Object source = model.getTerminal(edge, true);
							Object target = model.getTerminal(edge, false);
							if (!Arrays.asList(cells).contains(source)) {
								model.setTerminal(edge, group, false);
								Object value = model.getValue(target);
								if (value instanceof FlowNode) {
									((FlowNode) value).removeIncoming(id);
								}
							} else if (!Arrays.asList(cells).contains(target)) {
								model.setTerminal(edge, group, true);
								Object value = model.getValue(source);
								if (value instanceof FlowNode) {
									((FlowNode) value).removeOutgoing(id);
								}
							}
						}
					}
				}
				// ==============end================

				fireEvent(new mxEventObject(mxEvent.GROUP_CELLS, "group", group, "cells", cells, "border", border));
			} finally {
				model.endUpdate();
			}
		}

		return group;
	}

	public Object[] getCellsForGroup(Object[] cells) {
		List result = new ArrayList(cells.length);

		if (cells.length > 0) {
			Object parent = model.getParent(cells[0]);
			result.add(cells[0]);

			// Filters selection cells with the same parent
			for (int i = 1; i < cells.length; i++) {
				if (model.getParent(cells[i]) == parent) {
					result.add(cells[i]);
				}
			}
		}

		Set tmp = new HashSet();
		for (Object cell : result) {
			if (model.isEdge(cell)) {
				if (!result.contains(model.getTerminal(cell, true)) || !result.contains(model.getTerminal(cell, false))) {
					tmp.add(cell);
				}
			}
		}
		result.removeAll(tmp);

		Set addEdges = new HashSet();
		for (Object cell : result) {
			if (model.isVertex(cell)) {
				List edges = Arrays.asList(getConnections(cell));
				for (Object edge : edges) {
					Object source = model.getTerminal(edge, true);
					Object target = model.getTerminal(edge, false);
					if (result.contains(source) && result.contains(target) && !result.contains(edge)) {
						addEdges.add(edge);
					}
				}
			}
		}

		result.addAll(addEdges);

		return result.toArray();
	}

	public Object createGroupCell(Object[] cells) {
		mxGeometry geo = new mxGeometry();
		geo.setAlternateBounds(new mxRectangle(0, 0, 85, 55));
		mxCell group = new mxCell(new SubProcess(Resources.get("subprocess")), geo, "subprocess");
		group.setVertex(true);
		return group;
	}

	public Object[] foldCells(boolean collapse, boolean recurse, Object[] cells) {
		if (cells == null) {
			cells = getFoldableCells(getSelectionCells(), collapse);
			if (cells == null || cells.length < 1)
				return null;
		}

		model.beginUpdate();
		try {
			if (getModel().isSubProcess(cells[0]) || getModel().isChoreographySubprocess(cells[0])) {
				Object[] selectedCells = cells;
				if (getModel().isCollapsedSubProcess(cells[0])) {
					if (isSubChoreography(cells[0])) {
						selectedCells = new Object[] { GraphUtils.getChoreographyActivity(this, cells[0]) };
					}
					setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_TOP, selectedCells);
					if (getAttechedEventCount(cells[0]) > 0) {
						mxCell subflow = (mxCell) cells[0];
						for (Object eventCell : getAttechedEvents(subflow)) {
							mxGeometry eventGeo = (mxGeometry) model.getGeometry(eventCell).clone();
							mxRectangle alterBounds = eventGeo.getAlternateBounds();
							if (alterBounds != null) {
								alterBounds.setX(eventGeo.getOffset().getX());
								alterBounds.setY(eventGeo.getOffset().getY());
								mxPoint offset = new mxPoint(alterBounds.getHeight(), alterBounds.getWidth());
								eventGeo.setOffset(offset);
								model.setGeometry(eventCell, eventGeo);
							}
						}
					}
				} else {
					if (isSubChoreography(cells[0])) {
						selectedCells = new Object[] { GraphUtils.getChoreographyActivity(this, cells[0]) };
					}
					setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE, selectedCells);

					if (getAttechedEventCount(cells[0]) > 0) {
						mxCell subflow = (mxCell) cells[0];
						mxGeometry geo = subflow.getGeometry();
						mxRectangle rect = geo.getAlternateBounds();
						if (rect != null && (rect.getHeight() <= 65 || rect.getWidth() <= 95)) {
							rect.setHeight(65);
							rect.setWidth(95);
							geo.setAlternateBounds(rect);
							model.setGeometry(subflow, geo);
						}
						for (Object eventCell : getAttechedEvents(subflow)) {
							mxGeometry eventGeo = (mxGeometry) model.getGeometry(eventCell).clone();
							Point offset = null;
							mxRectangle alterBounds = eventGeo.getAlternateBounds();
							if (alterBounds == null) {
								alterBounds = new mxRectangle();
								alterBounds.setHeight(eventGeo.getOffset().getX());
								alterBounds.setWidth(eventGeo.getOffset().getY());
								if (eventGeo.getX() == 1 || eventGeo.getY() == 0 && eventGeo.getOffset().getX() < 0) {
									alterBounds.setX(eventGeo.getOffset().getX());
									alterBounds.setY(10);
								} else if (eventGeo.getY() == 1 || eventGeo.getX() == 0 && eventGeo.getOffset().getY() < 0) {
									alterBounds.setX(50);
									alterBounds.setY(eventGeo.getOffset().getY());
								}
								offset = alterBounds.getPoint();
								eventGeo.setAlternateBounds(alterBounds);
							} else {
								alterBounds.setWidth(eventGeo.getOffset().getY());
								alterBounds.setHeight(eventGeo.getOffset().getX());
								offset = eventGeo.getAlternateBounds().getPoint();
							}
							eventGeo.setOffset(new mxPoint(offset));
							model.setGeometry(eventCell, eventGeo);
						}
					}
				}
			} else if (isSwimlane(cells[0])) {
				boolean horizontal = true;
				toggleCellStyles(mxConstants.STYLE_HORIZONTAL, horizontal, cells);

				mxGeometry geo = model.getGeometry(cells[0]);
				mxRectangle rect = geo.getAlternateBounds();
				if (rect == null) {
					if (isVerticalSwimlane(cells[0])) {
						rect = new mxRectangle(geo.getX(), geo.getY(), collapse ? Constants.SWIMLANE_SIZE / 3 : getMinSwimlaneSize(cells[0]), geo.getHeight());
						geo.setAlternateBounds(rect);
						model.setGeometry(cells[0], geo);
					} else {
						rect = new mxRectangle(geo.getX(), geo.getY(), geo.getWidth(), collapse ? Constants.SWIMLANE_SIZE / 3 : getMinSwimlaneSize(cells[0]));
						geo.setAlternateBounds(rect);
						model.setGeometry(cells[0], geo);
					}
				}
			}
			cellsFolded(cells, collapse, recurse);
			fireEvent(new mxEventObject(mxEvent.FOLD_CELLS, "cells", cells, "collapse", collapse, "recurse", recurse));
		} finally {
			model.endUpdate();
		}

		return cells;
	}

	public void cellsFolded(Object[] cells, boolean collapse, boolean recurse) {
		if (cells != null && cells.length > 0) {
			model.beginUpdate();
			try {
				for (int i = 0; i < cells.length; i++) {
					if (collapse != isCellCollapsed(cells[i])) {
						// ==============start==============
						if (!getModel().isSubProcess(cells[i]) && !getModel().isChoreographySubprocess(cells[i])) {
							model.setCollapsed(cells[i], collapse);
						}
						// ==============end================
						swapBounds(cells[i], collapse);

						if (isExtendParent(cells[i])) {
							extendParent(cells[i]);
						}

						// ==============start==============
						if (getModel().isExpandedSubProcess(cells[i])) {
							for (Object cell : this.getChildVertices(cells[i])) {
								if (isExtendParent(cell)) {
									extendParent(cell);
									constrainChild(cell);
								}
							}
						}
						// ==============end================

						if (recurse) {
							Object[] children = mxGraphModel.getChildren(model, cells[i]);
							cellsFolded(children, collapse, recurse);
						}
					}
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED, "cells", cells, "collapse", collapse, "recurse", recurse));
			} finally {
				model.endUpdate();
			}
		}
	}

	public void cellsRemoved(Object[] cells) {
		if (cells != null && cells.length > 0) {
			model.beginUpdate();
			try {
				for (int i = 0; i < cells.length; i++) {

					Collection cellSet = new HashSet();
					cellSet.addAll(Arrays.asList(cells));
					Object[] edges = getConnections(cells[i]);

					// removes edges which are not in cells
					for (int j = 0; j < edges.length; j++) {
						if (!cellSet.contains(edges[j])) {
							removeCells(new Object[] { edges[j] }, true);
						}
					}

					// merge edges
					if (edges.length == 2 && getModel().isSequenceFlow(edges[0]) && getModel().isSequenceFlow(edges[1])) {
						Object source = null;
						Object target = null;
						Object edge = null;
						Object parent = null;
						if (model.getTerminal(edges[0], false) == cells[i] && model.getTerminal(edges[1], true) == cells[i]) {
							source = model.getTerminal(edges[0], true);
							target = model.getTerminal(edges[1], false);
							parent = model.getParent(edges[0]);
							edge = cloneCells(new Object[] { edges[0] })[0];
						} else if (model.getTerminal(edges[0], true) == cells[i] && model.getTerminal(edges[1], false) == cells[i]) {
							source = model.getTerminal(edges[1], true);
							target = model.getTerminal(edges[0], false);
							parent = model.getParent(edges[1]);
							edge = cloneCells(new Object[] { edges[1] })[0];
						}
						if (edge != null && cells.length == 3) {
							cellsAdded(new Object[] { edge }, parent, model.getChildCount(parent), source, target, false);
						}
					}

					model.remove(cells[i]);
				}

				fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, "cells", cells));
			} finally {
				model.endUpdate();
			}
		}
	}

	public Object[] moveCells(Object[] cells, double dx, double dy, boolean clone, Object target, Point location) {
		// ==============start==============
		Object previous_parent = null;
		if (clone && cells.length == 1 && (getModel().isBoundaryEvent(cells[0]) || getModel().isAttachedMessage(cells[0]))) {
			return null;
		}
		for (int i = 0; i < cells.length; i++) {
			if (clone && model.getChildCount(cells[i]) > 0 && (isChoreography(cells[i]) || isSubChoreography(cells[i]))) {
				JOptionPane.showMessageDialog(null, Resources.get("WarningCopyPasteNotApplicableForChoreography"), "Not Applicable",
						JOptionPane.WARNING_MESSAGE);
				return null;
			}

			Object[] edges = getConnections(cells[i]);
			Set movedEdges = new HashSet(Arrays.asList(edges));
			Set movedCells = new HashSet(Arrays.asList(cells));
			for (Object movedCell : cells) {
				if (getAttechedEventCount(movedCell) > 0) {
					for (Object event : getAttechedEvents(movedCell)) {
						movedEdges.addAll(new HashSet(Arrays.asList(getConnections(event))));
						movedCells.add(event);
					}
				}
			}
			if (!getModel().isBoundaryEvent(cells[i]) && !movedEdges.isEmpty()) {
				Object moveTarget = target == null ? getDefaultParent() : target;
				while (getModel().isLane(moveTarget)) {
					moveTarget = getParentCell((mxCell) moveTarget);
				}
				for (Object edge : movedEdges) {
					Object src = model.getTerminal(edge, true);
					Object srcParent = getParentCell((mxCell) src);
					while (getModel().isLane(srcParent)) {
						srcParent = getParentCell((mxCell) srcParent);
					}
					Object tgt = model.getTerminal(edge, false);
					Object tgtParent = getParentCell((mxCell) tgt);
					while (getModel().isLane(tgtParent)) {
						tgtParent = getParentCell((mxCell) tgtParent);
					}
					if (getModel().isSequenceFlow(edge)) {
						if ((!movedCells.contains(src) || !movedCells.contains(tgt)) && (srcParent != moveTarget || tgtParent != moveTarget)) {
							JOptionPane.showMessageDialog(null, Resources.get("WarningInvalidOperation"), "Validation Error!", JOptionPane.WARNING_MESSAGE);
							return null;
						}
					} else if (getModel().isMessageFlow(edge)) {
						if (movedCells.contains(src) && (srcParent != moveTarget) || movedCells.contains(tgt) && (tgtParent != moveTarget)) {
							return null;
						}
					}
				}
			}

			Object parent = model.getParent(cells[i]);
			if (previous_parent == null) {
				previous_parent = parent;
			} else if (previous_parent != parent) {
				return null;
			}
			if (getModel().isEventSubProcess(target)) {
				if (getModel().isStartEvent(cells[i])) {
					if (hasStartEvent(target) && model.getParent(cells[i]) != target) {
						JOptionPane.showMessageDialog(null, Resources.get("WarningEventSubProcessMustHaveOneAndOnlyOneStartEvent"), "Validation Error!",
								JOptionPane.WARNING_MESSAGE);
						return null;
					}
				}
			}
			if (getModel().isStartEvent(cells[i]) && getModel().isEventSubProcess(model.getParent(cells[i])) && model.getParent(cells[i]) != target) {
				JOptionPane.showMessageDialog(null, Resources.get("WarningEventSubProcessMustHaveOneAndOnlyOneStartEvent"), "Validation Error!",
						JOptionPane.WARNING_MESSAGE);
				return null;
			}
			if (hasElementNotAllowedInChoreography()) {
				if (isChoreography(cells[i]) || isSubChoreography(cells[i])) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningOnlyEventGatewayArtifactAndPoolAreAllowedInChoreography"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
			}

			if (hasElementNotAllowedInConversation()) {
				if (getModel().isConversationNode(cells[i])) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningOnlyParticipantsAreAllowedInConversation"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
			}

			if (hasChoreography()) {
				if (isElementNotAllowedInChoreography(cells[i]) && !isSwimlane(target) && !getModel().isSubProcess(target) || isSwimlane(cells[i])
						&& isSwimlane(target)) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningOnlyEventGatewayArtifactAndPoolAreAllowedInChoreography"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
			}

			if (hasConversation()) {
				if (isElementNotAllowedInConversation(cells[i]) && !isSwimlane(target) && !getModel().isSubProcess(target) || isSwimlane(cells[i])
						&& isSwimlane(target)) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningOnlyParticipantsAreAllowedInConversation"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
			}

			if (getModel().isCancelEndEvent(cells[i]) && !getModel().isTransactionSubProcess(target)) {
				JOptionPane.showMessageDialog(null, Resources.get("WarningCancelEndEventCanOnlyBeUsedWithinTranSubProcess"), "Validation Error!",
						JOptionPane.WARNING_MESSAGE);
				return null;
			}

			if (getModel().isAdhocSubProcess(target)) {
				if (getModel().isStartEvent(cells[i])) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningStartEventMustNotBeUsedInAdHocSubProcess"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
				if (getModel().isEndEvent(cells[i])) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningEndEventMustNotBeUsedInAdHocSubProcess"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
			}

			if (getModel().isSubProcess(target)) {
				if (getModel().isDataInput(cells[i]) || getModel().isDataOutput(cells[i])) {
					JOptionPane.showMessageDialog(null, Resources.get("WarningSubProcessMustNotDefineDataInputsOutputsDirectly"), "Validation Error!",
							JOptionPane.WARNING_MESSAGE);
					return null;
				}
			}
			previous_parent = model.getParent(cells[i]);
			if (isSwimlane(cells[i])) {
				if (target == null || isPlane(target)) {
					if (getModel().isLane(cells[i])) {
						return null;
					}
					((mxCell) cells[i]).setConnectable(true);
				} else if (isSwimlane(target)) {
					if ((isVerticalSwimlane(cells[i]) && !isVerticalSwimlane(target)) || (!isVerticalSwimlane(cells[i]) && isVerticalSwimlane(target))) {
						return null;
					}
					((mxCell) cells[i]).setConnectable(false);
				}
			} else {

				if (clone && model.isEdge(cells[i])) {
					mxCell flow = (mxCell) cells[i];
					List cellList = Arrays.asList(cells);
					if (!cellList.contains(flow.getSource()) || !cellList.contains(flow.getTarget())) {
						return null;
					}
				}

				if (getModel().isBoundaryEvent(cells[i]) && target != model.getParent(cells[i])) {
					target = model.getParent(cells[i]);
				}

				if ((getModel().isTask(target) || getModel().isCallActivity(target) && target != getCurrentRoot() || getModel().isCollapsedSubProcess(target))
						&& getModel().isEvent(cells[i]) && !getModel().isBoundaryEvent(cells[i])) {
					if (getAttechedEventCount(target) < 4) {
						mxCell subflow = (mxCell) target;
						mxGeometry geo = subflow.getGeometry();
						if (geo.getWidth() <= Constants.ACTIVITY_WIDTH) {
							geo.grow(5);
							getModel().setGeometry(target, geo);
						}
					}
				}
			}
		}
		// ==============end================
		if (cells != null && (dx != 0 || dy != 0 || clone || target != null)) {
			model.beginUpdate();
			try {
				if (clone) {
					cells = cloneCells(cells, isCloneInvalidEdges());

					if (target == null) {
						target = getDefaultParent();
					}
				}

				cellsMoved(cells, dx, dy, !clone && isDisconnectOnMove() && isAllowDanglingEdges(), target == null);

				if (target != null) {
					// ==============start==============
					boolean changeParent = false;
					Set parents = new HashSet(Arrays.asList(cells));
					parents.add(target);
					for (Object cell : cells) {
						if (!parents.contains(model.getParent(cell))) {
							changeParent = true;
							break;
						}
					}
					if (changeParent) {
						Integer index = model.getChildCount(target);
						cellsAdded(cells, target, index, null, null, true);
					} else {
						for (Object cell : cells) {
							if (isExtendParentsOnAdd() && isExtendParent(cell)) {
								extendParent(cell);
							}
							constrainChild(cell);
						}
					}
					// ==============end================
				}

				fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, "cells", cells, "dx", dx, "dy", dy, "clone", clone, "target", target, "location", location));
			} finally {
				model.endUpdate();
			}
		}

		return cells;
	}

	public Object[] alignCells(String align, Object[] cells, Object param) {
		if (cells == null) {
			cells = getSelectionCells();
		}

		if (cells != null && cells.length > 1) {
			Object parent = model.getParent(cells[0]);
			// Finds the required coordinate for the alignment
			if (param == null) {
				for (int i = 0; i < cells.length; i++) {
					mxGeometry geo = getCellGeometry(cells[i]);

					if (geo != null && !model.isEdge(cells[i]) && model.getParent(cells[i]) == parent && !getModel().isBoundaryEvent(cells[i])
							&& !getModel().isAttachedMessage(cells[i]) && !getModel().isChoreographyTask(cells[i])
							&& !getModel().isChoreographySubprocess(cells[i]) && !getModel().isChoreographyParticipant(cells[i])) {
						if (param == null) {
							if (align == null || align.equals(mxConstants.ALIGN_LEFT)) {
								param = geo.getX();
							} else if (align.equals(mxConstants.ALIGN_CENTER)) {
								param = geo.getX() + geo.getWidth() / 2;
								break;
							} else if (align.equals(mxConstants.ALIGN_RIGHT)) {
								param = geo.getX() + geo.getWidth();
							} else if (align.equals(mxConstants.ALIGN_TOP)) {
								param = geo.getY();
							} else if (align.equals(mxConstants.ALIGN_MIDDLE)) {
								param = geo.getY() + geo.getHeight() / 2;
								break;
							} else if (align.equals(mxConstants.ALIGN_BOTTOM)) {
								param = geo.getY() + geo.getHeight();
							}
						} else {
							double tmp = Double.parseDouble(String.valueOf(param));

							if (align == null || align.equals(mxConstants.ALIGN_LEFT)) {
								param = Math.min(tmp, geo.getX());
							} else if (align.equals(mxConstants.ALIGN_RIGHT)) {
								param = Math.max(tmp, geo.getX() + geo.getWidth());
							} else if (align.equals(mxConstants.ALIGN_TOP)) {
								param = Math.min(tmp, geo.getY());
							} else if (align.equals(mxConstants.ALIGN_BOTTOM)) {
								param = Math.max(tmp, geo.getY() + geo.getHeight());
							}
						}
					}
				}
			}

			// Aligns the cells to the coordinate
			model.beginUpdate();
			try {
				double tmp = Double.parseDouble(String.valueOf(param));

				for (int i = 0; i < cells.length; i++) {
					mxGeometry geo = getCellGeometry(cells[i]);

					if (geo != null && !model.isEdge(cells[i]) && model.getParent(cells[i]) == parent && !getModel().isBoundaryEvent(cells[i])
							&& !getModel().isAttachedMessage(cells[i]) && !getModel().isChoreographyTask(cells[i])
							&& !getModel().isChoreographySubprocess(cells[i]) && !getModel().isChoreographyParticipant(cells[i])) {
						geo = (mxGeometry) geo.clone();

						if (align == null || align.equals(mxConstants.ALIGN_LEFT)) {
							geo.setX(tmp);
						} else if (align.equals(mxConstants.ALIGN_CENTER)) {
							geo.setX(tmp - geo.getWidth() / 2);
						} else if (align.equals(mxConstants.ALIGN_RIGHT)) {
							geo.setX(tmp - geo.getWidth());
						} else if (align.equals(mxConstants.ALIGN_TOP)) {
							geo.setY(tmp);
						} else if (align.equals(mxConstants.ALIGN_MIDDLE)) {
							geo.setY(tmp - geo.getHeight() / 2);
						} else if (align.equals(mxConstants.ALIGN_BOTTOM)) {
							geo.setY(tmp - geo.getHeight());
						}

						model.setGeometry(cells[i], geo);

						if (isResetEdgesOnMove()) {
							resetEdges(new Object[] { cells[i] });
						}
					}
				}

				fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "cells", cells, "align", align));
			} finally {
				model.endUpdate();
			}
		}

		return cells;
	}

	public List findTreeRoots(Object parent, boolean isolate, boolean invert) {
		List roots = new ArrayList();

		if (parent != null) {
			int childCount = model.getChildCount(parent);
			Object best = null;
			int maxDiff = 0;

			for (int i = 0; i < childCount; i++) {
				Object cell = model.getChildAt(parent, i);

				if (model.isVertex(cell) && isCellVisible(cell)) {
					Object[] conns = getConnections(cell, (isolate) ? parent : null);
					int fanOut = 0;
					int fanIn = 0;

					for (int j = 0; j < conns.length; j++) {
						Object src = view.getVisibleTerminal(conns[j], true);

						if (src == cell) {
							fanOut++;
						} else {
							fanIn++;
						}
					}

					if ((invert && fanOut == 0 && fanIn > 0) || (!invert && fanIn == 0 && fanOut > 0)) {
						roots.add(0, cell);
					}

					int diff = (invert) ? fanIn - fanOut : fanOut - fanIn;

					if (diff > maxDiff) {
						maxDiff = diff;
						best = cell;
					}
					roots.addAll(Arrays.asList(getAttechedEvents(cell)));
				}
			}

			if (roots.isEmpty() && best != null) {
				roots.add(best);
			}
		}

		return roots;
	}

	public Object[] moveCells(String dir, boolean slow) {
		double step = 5;
		if (slow) {
			step = 1;
		}
		Object[] cells = getSelectionCells();
		if (cells != null && cells.length > 0) {
			model.beginUpdate();
			try {
				for (Object cell : cells) {
					if (isCellMovable(cell) && !getModel().isBoundaryEvent(cell) && !isSwimlane(cell)) {
						mxGeometry geo = getCellGeometry(cell);
						if (geo != null) {
							geo = (mxGeometry) geo.clone();
							if (model.isVertex(cell)) {
								if ("up".equals(dir)) {
									geo.translate(0, -1 * step);
								} else if ("down".equals(dir)) {
									geo.translate(0, 1 * step);
								} else if ("right".equals(dir)) {
									geo.translate(1 * step, 0);
								} else if ("left".equals(dir)) {
									geo.translate(-1 * step, 0);
								}
							} else if (model.isEdge(cell)) {
								List points = geo.getPoints();
								if (points != null) {
									for (mxPoint p : points) {
										if ("up".equals(dir)) {
											p.setY(p.getY() - 1 * step);
										} else if ("down".equals(dir)) {
											p.setY(p.getY() + 1 * step);
										} else if ("right".equals(dir)) {
											p.setX(p.getX() + 1 * step);
										} else if ("left".equals(dir)) {
											p.setX(p.getX() - 1 * step);
										}
									}
								}
							}

							model.setGeometry(cell, geo);
						}
					}
				}
				fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "cells", cells, "align", dir));
			} finally {
				model.endUpdate();
			}
		}
		return cells;
	}

	public Object[] sameCells(String type) {
		Object[] cells = getSelectionCells();
		if (cells != null && cells.length > 1) {
			mxGeometry geo = getCellGeometry(cells[0]);
			if (!getModel().isTask(cells[0]) && !getModel().isCallActivity(cells[0]) && !getModel().isCollapsedSubProcess(cells[0]) && !isSwimlane(cells[0])
					&& !isOrganizationElement(cells[0])) {
				geo = getCellGeometry(cells[1]);
			}
			double width = geo.getWidth();
			double height = geo.getHeight();
			boolean isSwimlane = isSwimlane(cells[0]) && (hasChildNonLane(cells[0]) || isEmptySwimlane(cells[0]));
			boolean isVerticalSwimlane = isVerticalSwimlane(cells[0]);
			model.beginUpdate();
			try {
				for (Object cell : cells) {
					geo = getCellGeometry(cell);
					if (geo != null) {
						geo = (mxGeometry) geo.clone();
						if (isSwimlane && isSwimlane(cell) && (hasChildNonLane(cell) || isEmptySwimlane(cell))
								&& isVerticalSwimlane == isVerticalSwimlane(cell)) {
							if (isVerticalSwimlane) {
								geo.setWidth(width);
							} else {
								geo.setHeight(height);
							}
						} else if (getModel().isTask(cell) || getModel().isCallActivity(cell) || getModel().isSubProcess(cell) || isSwimlane(cell)
								|| isOrganizationElement(cell)) {
							if (type == null || type.length() == 0) {
								geo.setWidth(width);
								geo.setHeight(height);
							} else if (type.equals(Constants.HEIGHT)) {
								geo.setHeight(height);
							} else if (type.equals(Constants.WIDTH)) {
								geo.setWidth(width);
							}
						}

						model.setGeometry(cell, geo);
					}
				}
				fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "cells", cells, "align", type));
			} finally {
				model.endUpdate();
			}
		}
		return cells;
	}

	public Object[] distributeCells(String type) {
		Object[] cells = getSelectionCells();
		if (cells != null && cells.length > 2) {
			Object parent = null;
			mxGeometry geo = null;
			List cells4align = new ArrayList();
			for (Object cell : cells) {
				if (!model.isEdge(cell) && (parent == null || model.getParent(cell) == parent) && !getModel().isBoundaryEvent(cell)
						&& !getModel().isAttachedMessage(cell) && !getModel().isChoreographyTask(cell) && !getModel().isChoreographySubprocess(cell)
						&& !getModel().isChoreographyParticipant(cell)) {
					if (parent == null) {
						parent = model.getParent(cell);
					}
					cells4align.add(cell);
				}
			}

			if (type.equals(mxConstants.ALIGN_CENTER)) {
				Collections.sort(cells4align, new Comparator() {
					public int compare(Object o1, Object o2) {
						mxGeometry geo1 = ((mxCell) o1).getGeometry();
						mxGeometry geo2 = ((mxCell) o2).getGeometry();
						return (int) (geo1.getCenterX() - geo2.getCenterX());
					}
				});
				double offset = ((mxCell) cells4align.get(0)).getGeometry().getCenterX();
				double space = (((mxCell) cells4align.get(cells4align.size() - 1)).getGeometry().getCenterX() - offset) / (cells4align.size() - 1);

				model.beginUpdate();
				try {
					for (Object cell : cells4align) {
						geo = (mxGeometry) getCellGeometry(cell).clone();
						geo.setX(offset - geo.getWidth() / 2);
						model.setGeometry(cell, geo);
						offset += space;
					}
					fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "cells", cells, "distribute", type));
				} finally {
					model.endUpdate();
				}
			} else {
				Collections.sort(cells4align, new Comparator() {
					public int compare(Object o1, Object o2) {
						mxGeometry geo1 = ((mxCell) o1).getGeometry();
						mxGeometry geo2 = ((mxCell) o2).getGeometry();
						return (int) (geo1.getCenterY() - geo2.getCenterY());
					}
				});
				double offset = ((mxCell) cells4align.get(0)).getGeometry().getCenterY();
				double space = (((mxCell) cells4align.get(cells4align.size() - 1)).getGeometry().getCenterY() - offset) / (cells4align.size() - 1);

				model.beginUpdate();
				try {
					for (Object cell : cells4align) {
						geo = (mxGeometry) getCellGeometry(cell).clone();
						geo.setY(offset - geo.getHeight() / 2);
						model.setGeometry(cell, geo);
						offset += space;
					}
					fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, "cells", cells, "distribute", type));
				} finally {
					model.endUpdate();
				}
			}
		}
		return cells;
	}

	public void resetSelection() {
		Object cell = selectionModel.getCell();
		selectionModel.clear();
		selectionModel.setCell(cell);
	}

	public String getCellValue(String id) {
		Object value = model.getValue(getModel().getCell(id));
		return value == null ? "" : value.toString();
	}

	public String adjustEdgeStyle(Object edge) {
		Object source = model.getTerminal(edge, true);
		Object target = model.getTerminal(edge, false);
		return adjustEdgeStyle(source, target, edge);
	}

	public String adjustEdgeStyle(Object source, Object target, Object edge) {
		String style = null;
		if (!getModel().hasSameParentPool(source, target) && !getModel().isConversationNode(source) && !getModel().isConversationNode(target)
				|| getModel().isPool(source) || getModel().isPool(target)) {
			if (getModel().isChoreographyParticipant(source)) {
				style = "messageFlow;startArrow=none";
			} else if (getModel().isChoreographyParticipant(target)) {
				style = "messageFlow;endArrow=none";
			} else {
				if (isVerticalSwimlane(getModel().getParentPool(source)) || isVerticalSwimlane(getModel().getParentPool(target))) {
					style = "messageFlow;elbow=horizontal";
				} else {
					style = "messageFlow";
				}
			}
		}
		if (getModel().isItemAwareElement(source) || getModel().isItemAwareElement(target)) {
			if (model.isEdge(source) || model.isEdge(target)) {
				style = "association";
			} else {
				style = "dataAssociation";
			}
		}

		if (getModel().isBoundaryEvent(source) && getModel().isCompensationIntermediateEvent(source)) {
			style = "compensationAssociation";
		}
		if (getModel().isConversationNode(source) || getModel().isConversationNode(target)) {
			style = "conversationLink";
		}
		if (getModel().isAnnotation(source) || getModel().isAnnotation(target) || isImageArtifact(source) || isImageArtifact(target)
				|| getModel().isMessage(source) || getModel().isMessage(target)) {
			style = "association";
		}
		if (isOrganizationElement(source) || isOrganizationElement(target)) {
			style = "organizationFlow";
		}
		if (style == null) {
			style = "defaultEdge";
		}
		return style;
	}

	public double getMinSwimlaneSize(Object cell) {
		double size = Constants.SWIMLANE_SIZE;
		if (isVerticalSwimlane(cell)) {
			for (Object child : mxGraphModel.getChildVertices(model, cell)) {
				mxGeometry geo = model.getGeometry(child);
				if (size < geo.getX() + geo.getWidth()) {
					size = geo.getX() + geo.getWidth();
				}
			}
		} else {
			for (Object child : mxGraphModel.getChildVertices(model, cell)) {
				mxGeometry geo = model.getGeometry(child);
				if (size < geo.getY() + geo.getHeight()) {
					size = geo.getY() + geo.getHeight();
				}
			}
		}
		return size;
	}

	public String validateEdge(Object edge, Object source, Object target) {
		return GraphUtils.validateEdge(this, edge, source, target);
	}

	public String validateCell(Object cell, Hashtable context) {
		return GraphUtils.validateCell(this, cell);
	}

	// public void enterGroup(Object cell) {
	// if (cell == null) {
	// cell = getSelectionCell();
	// }
	//
	// if (cell != null && isValidRoot(cell)) {
	// view.setCurrentRoot(cell);
	// clearSelection();
	// }
	// }

	public boolean isValidRoot(Object cell) {
		return (cell != null && (getModel().isSubProcess(cell) || isPlane(cell)));
	}

	public boolean isPlane(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) == model.getRoot() || cell == view.getCurrentRoot() && !getModel().isSubProcess(cell)) {
				return true;
			}
		}
		return false;
	}

	public boolean isVerticalSwimlane(Object cell) {
		if (isSwimlane(cell)) {
			return model.getStyle(cell).startsWith("vertical");
		}
		return false;
	}

	public boolean isInCollapsedSwimlane(Object cell) {
		if (isPlane(cell)) {
			return false;
		}
		if (!isSwimlane(cell)) {
			cell = model.getParent(cell);
			while (!isSwimlane(cell) && !isPlane(cell)) {
				cell = model.getParent(cell);
			}
		}

		if (isSwimlane(cell)) {
			while (isSwimlane(cell)) {
				if (isCollapsedSwimlane(cell)) {
					return true;
				} else {
					cell = model.getParent(cell);
				}
			}
			return false;
		} else {
			return false;
		}
	}

	public boolean isCollapsedSwimlane(Object cell) {
		mxCellState state = view.getState(cell);
		Map style = (state != null) ? state.getStyle() : getCellStyle(cell);

		if (style != null && !model.isEdge(cell)) {
			if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, false)) {
				return !isVerticalSwimlane(cell);
			} else {
				return isVerticalSwimlane(cell);
			}
		}
		return false;
	}

	public boolean isAutoPool(Object cell) {
		if (getModel().isPool(cell)) {
			mxCellState state = view.getState(cell);
			Map style = (state != null) ? state.getStyle() : getCellStyle(cell);

			if (style != null && !model.isEdge(cell)) {
				return mxUtils.isTrue(style, Constants.STYLE_AUTO, true);
			}
		}
		return false;
	}

	public boolean isManualPool(Object cell) {
		if (getModel().isPool(cell)) {
			mxCellState state = view.getState(cell);
			Map style = (state != null) ? state.getStyle() : getCellStyle(cell);

			if (style != null && !model.isEdge(cell)) {
				return !mxUtils.isTrue(style, Constants.STYLE_AUTO, true);
			}
		}
		return false;
	}

	public boolean isLaneByStyle(Object cell) {
		if (isSwimlane(cell)) {
			return mxUtils.isTrue(view.getState(cell).getStyle(), Constants.SWIMLANE_STYLE_LANE, false);
		}
		return false;
	}

	public boolean isInitiatingMessage(Object cell) {
		if (getModel().isMessage(cell)) {
			if (mxUtils.isTrue(view.getState(cell).getStyle(), Constants.STYLE_INIT, false)) {
				return true;
			}
		}
		return false;
	}

	public boolean isImageArtifact(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				mxCellState state = view.getState(cell);
				Map style = (state != null) ? state.getStyle() : getCellStyle(cell);

				if (style != null && !model.isEdge(cell)) {
					String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, "");
					String type = mxUtils.getString(style, Constants.STYLE_TYPE, "");
					return shape.equals(mxConstants.SHAPE_IMAGE) && type.equals(mxConstants.SHAPE_IMAGE);
				}
			}
		}

		return false;
	}

	public boolean isOrganizationFlow(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && model.isEdge(cell)) {
					return style.startsWith(Constants.EDGE_TYPE_ORGANIZATION_FLOW);
				}
			}
		}

		return false;
	}

	public boolean isCompensationAssociation(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && model.isEdge(cell)) {
					return style.startsWith(Constants.EDGE_TYPE_COMPENSATION_ASSOCIATION);
				}
			}
		}

		return false;
	}

	public boolean isLinkEvent(Object cell, boolean source) {
		if (getModel().isIntermediateEvent(cell)) {
			if (source) {
				return source == mxUtils.isTrue(getCellStyle(cell), Constants.STYLE_SOURCE, false);
			} else {
				return source == mxUtils.isTrue(getCellStyle(cell), Constants.STYLE_SOURCE, true);
			}
		}
		return false;
	}

	public boolean isCallChoreography(Object cell) {
		if (isChoreography(cell) || isSubChoreography(cell)) {
			return mxUtils.isTrue(getCellStyle(cell), Constants.STYLE_CALL);
		}
		return false;
	}

	public boolean isChoreography(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				mxCellState state = view.getState(cell);
				Map style = (state != null) ? state.getStyle() : getCellStyle(cell);

				if (style != null && !model.isEdge(cell)) {
					String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, "");
					String type = mxUtils.getString(style, Constants.STYLE_TYPE, "");
					return shape.equals(Constants.SHAPE_ACTIVITY) && type.equals(Constants.ACTIVITY_STYLE_CHOREOGRAPHY);
				}
			}
		}

		return false;
	}

	public boolean isSubChoreography(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				mxCellState state = view.getState(cell);
				Map style = (state != null) ? state.getStyle() : getCellStyle(cell);

				if (style != null && !model.isEdge(cell)) {
					String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, "");
					String type = mxUtils.getString(style, Constants.STYLE_TYPE, "");
					return shape.equals(Constants.SHAPE_ACTIVITY) && type.equals(Constants.ACTIVITY_STYLE_SUBCHOREOGRAPHY);
				}
			}
		}

		return false;
	}

	public boolean isTopChoreographyParticipant(Object cell) {
		if (getModel().isChoreographyParticipant(cell)) {
			String position = mxUtils.getString(getCellStyle(cell), Constants.STYLE_POSITION, "top");
			if (position.equals(mxConstants.ALIGN_TOP)) {
				return true;
			}
		}
		return false;
	}

	public boolean isBottomChoreographyParticipant(Object cell) {
		if (getModel().isChoreographyParticipant(cell)) {
			String position = mxUtils.getString(getCellStyle(cell), Constants.STYLE_POSITION, "top");
			if (position.equals(mxConstants.ALIGN_BOTTOM)) {
				return true;
			}
		}
		return false;
	}

	public boolean isAdditionalChoreographyParticipant(Object cell) {
		if (getModel().isChoreographyParticipant(cell)) {
			if (!mxUtils.isTrue(getCellStyle(cell), Constants.STYLE_BORDER, true)) {
				return true;
			}
		}
		return false;
	}

	public boolean isEmptySwimlane(Object cell) {
		return isEmptyPool(cell) || isEmptyLane(cell);
	}

	public boolean isEmptyPool(Object cell) {
		if (getModel().isPool(cell)) {
			int count = model.getChildCount(cell);
			if (count == 0) {
				return true;
			}
			return false;
		}
		return false;
	}

	public boolean isEmptyLane(Object cell) {
		if (getModel().isLane(cell)) {
			int count = model.getChildCount(cell);
			if (count == 0) {
				return true;
			}
			return false;
		}
		return false;
	}

	public boolean hasChildLane(Object cell) {
		if (isSwimlane(cell)) {
			int count = model.getChildCount(cell);
			if (count == 0) {
				return false;
			} else {
				for (int i = 0; i < count; i++) {
					Object child = model.getChildAt(cell, i);
					if (isSwimlane(child)) {
						return true;
					}
				}
			}
			return false;
		}
		return false;
	}

	public boolean hasChildNonLane(Object cell) {
		if (isSwimlane(cell)) {
			int count = model.getChildCount(cell);
			if (count == 0) {
				return false;
			} else {
				for (int i = 0; i < count; i++) {
					Object child = model.getChildAt(cell, i);
					if (!isSwimlane(child) && !model.isEdge(child)) {
						return true;
					}
				}
			}
			return false;
		}
		return false;
	}

	public boolean isInCollapsedSubProcess(Object cell) {
		if (isPlane(cell)) {
			return false;
		}
		if (!getModel().isSubProcess(cell)) {
			cell = model.getParent(cell);
			while (!getModel().isSubProcess(cell) && !isPlane(cell)) {
				cell = model.getParent(cell);
			}
		}

		if (getModel().isSubProcess(cell)) {
			while (getModel().isSubProcess(cell)) {
				if (getModel().isCollapsedSubProcess(cell)) {
					return true;
				} else {
					cell = model.getParent(cell);
				}
			}
			return false;
		} else {
			return false;
		}
	}

	public boolean isElementNotAllowedInChoreography(Object cell) {
		if (!isSwimlane(cell) && !getModel().isMessage(cell) && !getModel().isEvent(cell) && !getModel().isGateway(cell) && !getModel().isArtifact(cell)
				&& !getModel().isSequenceFlow(cell) && !getModel().isMessageFlow(cell) && !isChoreography(cell) && !isSubChoreography(cell)
				&& !getModel().isChoreographySubprocess(cell)) {
			return true;
		}
		return false;
	}

	public boolean isElementNotAllowedInConversation(Object cell) {
		if (!isSwimlane(cell) && !getModel().isMessage(cell) && !getModel().isArtifact(cell) && !getModel().isMessageFlow(cell)
				&& !getModel().isConversationLink(cell) && !getModel().isConversationNode(cell)) {
			return true;
		}
		return false;
	}

	public boolean isFragments(Object cell) {
		if (cell != null) {
			return model.getStyle(cell).startsWith("pattern=") || model.getStyle(cell).startsWith("fragment=");
		}
		return false;
	}

	public boolean isOrganizationElement(Object cell) {
		if (isOrganizationRoot(cell) || isOrganization(cell) || isOrganizationalUnit(cell) || isOrganizationalGroup(cell) || isOrganizationalRole(cell)
				|| isOrganizationalPerson(cell)) {
			return true;
		}
		return false;
	}

	public boolean isOrganizationName(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.startsWith(Constants.STYLE_ORGANIZATION_NAME);
				}
			}
		}
		return false;
	}

	public boolean isOrganizationRoot(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.startsWith(Constants.STYLE_ORGANIZATIONAL_ROOT);
				}
			}
		}
		return false;
	}

	public boolean isOrganization(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.equals(Constants.STYLE_ORGANIZATION) || style.startsWith(Constants.STYLE_ORGANIZATION + ";");
				}
			}
		}
		return false;
	}

	public boolean isOrganizationalUnit(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.startsWith(Constants.STYLE_ORGANIZATIONAL_UNIT);
				}
			}
		}
		return false;
	}

	public boolean isOrganizationalGroup(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.startsWith(Constants.STYLE_ORGANIZATIONAL_GROUP);
				}
			}
		}
		return false;
	}

	public boolean isOrganizationalRole(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.startsWith(Constants.STYLE_ORGANIZATIONAL_ROLE);
				}
			}
		}
		return false;
	}

	public boolean isOrganizationalPerson(Object cell) {
		if (cell != null) {
			if (model.getParent(cell) != model.getRoot()) {
				String style = model.getStyle(cell);
				if (style != null && !model.isEdge(cell)) {
					return style.startsWith(Constants.STYLE_ORGANIZATIONAL_PERSON);
				}
			}
		}
		return false;
	}

	public boolean hasPool() {
		Object[] cells = mxGraphModel.getChildVertices(model, this.getDefaultParent());
		if (cells == null) {
			return false;
		}

		for (Object cell : cells) {
			if (getModel().isPool(cell)) {
				return true;
			}
		}
		return false;

	}

	public boolean hasChoreography() {
		Object[] cells = mxGraphModel.getChildVertices(model, this.getDefaultParent());
		if (cells == null) {
			return false;
		}

		for (Object cell : cells) {
			if (isChoreography(cell) || isSubChoreography(cell)) {
				return true;
			}
		}
		return false;
	}

	public boolean hasConversation() {
		Object[] cells = mxGraphModel.getChildVertices(model, this.getDefaultParent());
		if (cells == null) {
			return false;
		}

		for (Object cell : cells) {
			if (getModel().isConversationNode(cell)) {
				return true;
			}
		}
		return false;
	}

	public boolean hasElementNotAllowedInChoreography() {
		if (getCurrentRoot() != null && isElementNotAllowedInChoreography(getCurrentRoot())) {
			return true;
		}
		Object[] cells = mxGraphModel.getChildVertices(model, getDefaultParent());
		if (cells == null) {
			return false;
		}

		for (Object cell : cells) {
			if (isElementNotAllowedInChoreography(cell)) {
				return true;
			}
		}
		return false;

	}

	public boolean hasElementNotAllowedInConversation() {
		if (getCurrentRoot() != null && isElementNotAllowedInConversation(getCurrentRoot())) {
			return true;
		}
		Object[] cells = mxGraphModel.getChildVertices(model, getDefaultParent());
		if (cells == null) {
			return false;
		}

		for (Object cell : cells) {
			if (isElementNotAllowedInConversation(cell)) {
				return true;
			}
		}
		return false;

	}

	public boolean hasDataAssociationConnections(Object cell) {
		Object[] connections = getConnections(cell);
		for (Object conn : connections) {
			if (getModel().isDataAssociation(conn)) {
				return true;
			}
		}
		return false;
	}

	public boolean canBeAssembled() {
		List cells = new ArrayList(Arrays.asList(getSelectionCells()));
		Object cell = getSelectionCell();
		if (cells.isEmpty()) {
			return false;
		}
		if (cells.size() == 1 && (model.isEdge(cell) || !getModel().isTask(cell))) {
			return false;
		}

		Set delEdges = new HashSet();
		for (Object c : cells) {
			if (model.isEdge(c)) {
				if (!cells.contains(model.getTerminal(c, true)) || !cells.contains(model.getTerminal(c, false))) {
					delEdges.add(c);
				}
			}
		}
		cells.removeAll(delEdges);
		if (cells.isEmpty() || (cells.size() == 1 && !getModel().isTask(cell))) {
			return false;
		}

		for (Object c : cells) {
			if (isSwimlane(c) || getModel().isConversationNode(c) || getModel().isDataOutput(c) || getModel().isDataInput(c)) {
				return false;
			}
		}

		return true;
	}

	public List getArtifacts() {
		List artifacts = new ArrayList();
		Object[] cells = mxGraphModel.getChildren(model, this.getDefaultParent());
		if (cells == null) {
			return artifacts;
		}
		for (Object cell : cells) {
			if (getModel().isArtifact(cell)) {
				artifacts.add(cell);
			}
		}
		return artifacts;
	}

	public Object getParentSwimlane(Object cell) {
		Object parent = null;
		if (cell != null) {
			if (isSwimlane(cell)) {
				return cell;
			}
			if (model.getParent(cell) != model.getRoot()) {
				parent = model.getParent(cell);
				while (parent != null && !isSwimlane(parent)) {
					parent = model.getParent(cell);
					cell = parent;
				}
			}
		}

		return parent;
	}

	public mxCell getParentCell(mxCell cell) {
		mxCell parent = (mxCell) cell.getParent();
		if (getModel().isBoundaryEvent(cell) || getModel().isAttachedMessage(cell) || getModel().isChoreographyTask(cell)
				|| getModel().isChoreographySubprocess(cell) || getModel().isChoreographyParticipant(cell)) {
			parent = (mxCell) parent.getParent();
		}
		return parent;
	}

	public List getAllPools() {
		List pools = new ArrayList();
		Object[] cells = mxGraphModel.getChildVertices(model, this.getDefaultParent());
		if (cells == null) {
			return pools;
		}

		for (Object cell : cells) {
			if (getModel().isPool(cell)) {
				pools.add(cell);
			}
		}
		return pools;
	}

	public List getAllLanesAndSubprocesses() {
		List parents = new ArrayList();
		List vertices = new ArrayList();
		List callActivities = new ArrayList();
		Object parent = getCurrentRoot() == null ? getDefaultParent() : getCurrentRoot();
		vertices.add(parent);
		GraphUtils.getAllVerticesInOrder(this, parent, vertices, callActivities);

		for (Object cell : vertices) {
			if (hasChildNonLane(cell) || isPlane(cell) || getModel().isPool(cell)) {
				parents.add(cell);
			} else if (getModel().isExpandedSubProcess(cell)) {
				parents.add(0, cell);
			}
		}

		return parents;
	}

	public Map getAllParticipants() {
		Map participants = new HashMap();
		for (mxCell cell : getModel().getAllChoreographyParticipants().values()) {
			if (getModel().isChoreographyParticipant(cell)) {
				mxCell participant = cell;
				int index = participant.getId().indexOf("_part_");
				if (index != -1) {
					participants.put(cell.getValue().toString(), cell.getId().substring(index + 6));
				}
			}
		}
		return participants;
	}

	public Map getAllAdditionalChoreographyParticipants() {
		Map participants = new HashMap();
		Object[] cells = getModel().getCells().values().toArray();
		for (Object cell : cells) {
			if (this.isAdditionalChoreographyParticipant(cell)) {
				participants.put(((mxCell) cell).getId(), (mxCell) cell);
			}
		}
		return participants;
	}

	public Map getAllChoreographyParticipants(Object activity) {
		Map participants = new HashMap();
		Object[] cells = mxGraphModel.getChildVertices(model, activity);
		for (Object cell : cells) {
			if (getModel().isChoreographyParticipant(cell)) {
				participants.put(((mxCell) cell).getId(), (mxCell) cell);
			}
		}
		return participants;
	}

	public Object getAttechedMessage(Object cell) {
		if (cell == null || !getModel().isMessageFlow(cell) || model.getChildCount(cell) != 1) {
			return null;
		}
		return model.getChildAt(cell, 0);
	}

	public Object[] getAttechedEvents(Object cell) {
		if (cell == null) {
			return null;
		}
		int childCount = model.getChildCount(cell);
		List result = new ArrayList(childCount);
		if (getModel().isSubProcess(cell) || getModel().isCallActivity(cell) || getModel().isTask(cell)) {
			for (int i = 0; i < childCount; i++) {
				Object child = model.getChildAt(cell, i);
				if (getModel().isBoundaryEvent(child)) {
					result.add(child);
				}
			}
		}
		return result.toArray();
	}

	public int getAttechedEventCount(Object cell) {
		if (getModel().isSubProcess(cell)) {
			Object[] events = getAttechedEvents(cell);
			if (events == null) {
				return 0;
			} else {
				return events.length;
			}
		} else if (getModel().isCallActivity(cell) || getModel().isTask(cell)) {
			return model.getChildCount(cell);
		}
		return 0;
	}

	public boolean hasStartEvent(Object cell) {
		Object[] cells = mxGraphModel.getChildVertices(model, cell);
		for (int i = 0; i < cells.length; i++) {
			if (getModel().isStartEvent(cells[i])) {
				return true;
			}
		}
		return false;
	}

	public boolean hasEndEvent(Object cell) {
		Object[] cells = mxGraphModel.getChildVertices(model, cell);
		for (int i = 0; i < cells.length; i++) {
			if (getModel().isEndEvent(cells[i])) {
				return true;
			}
		}
		return false;
	}

	public Object[] getLinkEvent(Object cell, boolean source, String name) {
		Object[] cells = mxGraphModel.getChildVertices(model, cell);
		List links = new ArrayList();
		for (int i = 0; i < cells.length; i++) {
			if (isLinkEvent(cells[i], source)) {
				if (name == null || ((mxCell) cells[i]).getValue().equals(name)) {
					links.add(cells[i]);
				}
			}
		}

		return links.toArray();
	}

	public void selectProcess(String direction) {
		Object cell = getSelectionCell();
		Set selectedCells = new HashSet();
		Set outgoingCells = GraphUtils.getOutgoingCells(this, cell, model.getParent(cell));
		Set incomingCells = GraphUtils.getIncomingCells(this, cell, model.getParent(cell));
		if ("entrire".equals(direction)) {
			selectedCells.addAll(incomingCells);
			selectedCells.addAll(outgoingCells);
		} else {
			mxGeometry geo = model.getGeometry(cell);
			if ("right".equals(direction) || "bottom".equals(direction)) {
				selectedCells.addAll(outgoingCells);
				for (Object c : outgoingCells) {
					if (model.isVertex(c)) {
						mxGeometry g = model.getGeometry(c);
						if (g.getCenterX() < geo.getCenterX() && "right".equals(direction)) {
							selectedCells.remove(c);
							selectedCells.removeAll(Arrays.asList(getConnections(c, model.getParent(cell))));
						} else if (g.getCenterY() < geo.getCenterY() && "bottom".equals(direction)) {
							selectedCells.remove(c);
							selectedCells.removeAll(Arrays.asList(getConnections(c, model.getParent(cell))));
						}
					}
				}
			} else if ("left".equals(direction) || "top".equals(direction)) {
				selectedCells.addAll(incomingCells);
				for (Object c : incomingCells) {
					if (model.isVertex(c)) {
						mxGeometry g = model.getGeometry(c);
						if (g.getCenterX() > geo.getCenterX() && "left".equals(direction)) {
							selectedCells.remove(c);
							selectedCells.removeAll(Arrays.asList(getConnections(c, model.getParent(cell))));
						} else if (g.getCenterY() > geo.getCenterY() && "top".equals(direction)) {
							selectedCells.remove(c);
							selectedCells.removeAll(Arrays.asList(getConnections(c, model.getParent(cell))));
						}
					}
				}
			}
		}
		setSelectionCells(selectedCells);
	}

	public static void main(String[] args) {
		System.out.println("BPMN 2.0 Graph version \"" + VERSION + "\"");
	}

}