/*******************************************************************************
* Copyright (c) 2012 Arapiki Solutions Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* psmith - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.eclipse.packages.patterns;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.graphiti.features.context.IUpdateContext;
import org.eclipse.graphiti.features.context.impl.AddConnectionContext;
import org.eclipse.graphiti.features.context.impl.AddContext;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.pattern.AbstractPattern;
import org.eclipse.graphiti.pattern.IPattern;
import com.buildml.eclipse.bobj.UIAction;
import com.buildml.eclipse.bobj.UIFileActionConnection;
import com.buildml.eclipse.bobj.UIFileGroup;
import com.buildml.eclipse.bobj.UIInteger;
import com.buildml.eclipse.bobj.UIMergeFileGroupConnection;
import com.buildml.eclipse.bobj.UISubPackage;
import com.buildml.eclipse.packages.PackageDiagramEditor;
import com.buildml.eclipse.utils.GraphitiUtils;
import com.buildml.eclipse.utils.errors.FatalError;
import com.buildml.model.IActionMgr;
import com.buildml.model.IActionTypeMgr;
import com.buildml.model.IBuildStore;
import com.buildml.model.IFileGroupMgr;
import com.buildml.model.IPackageMemberMgr;
import com.buildml.model.IPackageMemberMgr.MemberDesc;
import com.buildml.model.ISlotTypes;
import com.buildml.model.ISlotTypes.SlotDetails;
/**
* A Graphiti pattern for managing the top-level "Diagram" graphical element in a
* BuildML diagram.
*
* @author Peter Smith <psmith@arapiki.com>
*/
public class DiagramPattern extends AbstractPattern implements IPattern {
/*=====================================================================================*
* FIELDS/TYPES
*=====================================================================================*/
/** The PackageDiagramEditor we're part of */
private PackageDiagramEditor editor;
/** The IBuildStore that this diagram represents */
private IBuildStore buildStore;
/** The IPackageMemberMgr in this BuildStore */
private IPackageMemberMgr pkgMemberMgr;
/** The IActionMgr in this BuildStore */
private IActionMgr actionMgr;
/** The IActionTypeMgr in this BuildStore */
private IActionTypeMgr actionTypeMgr;
/** The IFileGroupMgr in this BuildStore */
private IFileGroupMgr fileGroupMgr;
/** The pkgMgr ID of the package we're displaying */
private int pkgId;
/*=====================================================================================*
* CONSTRUCTORS
*=====================================================================================*/
/**
* Create a new {@link DiagramPattern} object.
*/
public DiagramPattern() {
super(null);
}
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/**
* It should not be possible to add a "Diagram" from the palette.
*/
@Override
public String getCreateName() {
return null;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see org.eclipse.graphiti.pattern.AbstractPattern#isMainBusinessObjectApplicable(java.lang.Object)
*/
@Override
public boolean isMainBusinessObjectApplicable(Object mainBusinessObject) {
return mainBusinessObject instanceof Diagram;
}
/*-------------------------------------------------------------------------------------*/
/*
* (non-Javadoc)
* @see org.eclipse.graphiti.pattern.AbstractPattern#isPatternControlled(
* org.eclipse.graphiti.mm.pictograms.PictogramElement)
*/
@Override
protected boolean isPatternControlled(PictogramElement pictogramElement) {
Object domainObject = getBusinessObjectForPictogramElement(pictogramElement);
return isMainBusinessObjectApplicable(domainObject);
}
/*-------------------------------------------------------------------------------------*/
/*
* (non-Javadoc)
* @see org.eclipse.graphiti.pattern.AbstractPattern#isPatternRoot(
* org.eclipse.graphiti.mm.pictograms.PictogramElement)
*/
@Override
protected boolean isPatternRoot(PictogramElement pictogramElement) {
Object domainObject = getBusinessObjectForPictogramElement(pictogramElement);
return isMainBusinessObjectApplicable(domainObject);
}
/*-------------------------------------------------------------------------------------*/
/**
* We can only update Diagram objects.
*/
@Override
public boolean canUpdate(IUpdateContext context) {
return (context.getPictogramElement() instanceof Diagram);
}
/*-------------------------------------------------------------------------------------*/
/**
* Update the Diagram from the model. This will create all of the FileGroups and Actions
* that should appear on the canvas. This method will only be called when the
* PackageEditorUpdateBehavior.isResourceChanged() method returns true.
*/
@Override
public boolean update(final IUpdateContext context) {
/* determine our editor and BuildStore */
editor = (PackageDiagramEditor)(getDiagramBehavior().getDiagramContainer());
buildStore = editor.getBuildStore();
pkgMemberMgr = buildStore.getPackageMemberMgr();
actionMgr = buildStore.getActionMgr();
actionTypeMgr = buildStore.getActionTypeMgr();
fileGroupMgr = buildStore.getFileGroupMgr();
pkgId = editor.getPackageId();
final ContainerShape container = (ContainerShape)context.getPictogramElement();
TransactionalEditingDomain editingDomain = editor.getEditingDomain();
/*
* Add each of the Diagram elements. This must be done as part of a transaction.
*/
CommandStack commandStack = editingDomain.getCommandStack();
commandStack.execute(new RecordingCommand(editingDomain) {
@Override
protected void doExecute() {
/* fetch the complete list of members in the package */
MemberDesc members[] = pkgMemberMgr.getMembersInPackage(
pkgId, IPackageMemberMgr.SCOPE_NONE, IPackageMemberMgr.TYPE_ANY);
int row = 0, column = 0;
for (MemberDesc member : members) {
/* create a business object to represent this member */
UIInteger memberBo = null;
switch (member.memberType) {
case IPackageMemberMgr.TYPE_ACTION:
memberBo = new UIAction(member.memberId);
break;
case IPackageMemberMgr.TYPE_FILE_GROUP:
int fileGroupType = fileGroupMgr.getGroupType(member.memberId);
if (fileGroupType != IFileGroupMgr.FILTER_GROUP) {
memberBo = new UIFileGroup(member.memberId);
}
break;
case IPackageMemberMgr.TYPE_SUB_PACKAGE:
memberBo = new UISubPackage(member.memberId);
break;
case IPackageMemberMgr.TYPE_FILE:
/* ignore */
break;
default:
throw new FatalError("Unrecognized package member type: " + member.memberType);
}
/*
* Add the business object (UIFileGroup, UIAction, etc) to the diagram.
*/
if (memberBo != null) {
/*
* Determine the location for this pictogram. If the database
* has a valid location, use it. If not, perform a very simple
* placement algorithm.
*/
if (member.x == -1) {
member.x = column * 150;
member.y = row * 50;
column++;
if (column == 5) {
column = 0;
row++;
}
}
container.eResource().getContents().add(memberBo);
AddContext context2 = new AddContext();
context2.setNewObject(memberBo);
context2.setLocation(member.x, member.y);
context2.setTargetContainer(container);
getFeatureProvider().addIfPossible(context2);
}
}
/*
* Now draw connections between various file groups, and actions.
*/
for (MemberDesc member : members) {
/* for package members that are actions... */
if (member.memberType == IPackageMemberMgr.TYPE_ACTION) {
addFileActionConnections(member);
}
/* for merge file group, draw the input arrows */
else if (member.memberType == IPackageMemberMgr.TYPE_FILE_GROUP) {
int fileGroupType = fileGroupMgr.getGroupType(member.memberId);
if (fileGroupType == IFileGroupMgr.MERGE_GROUP) {
addMergeFileGroupConnections(member);
}
}
}
}
});
return true;
}
/*-------------------------------------------------------------------------------------*/
/**
* For each of an action's slots that have a file group connected to them, draw a connection
* line, and possibly a filter. At this point we're not actually drawing them, but we're
* adding their business objects (UIAction, UIFileActionConnection, etc) to the diagram
* to be drawn later by Graphiti.
*
* @param member The UIAction's details.
*/
private void addFileActionConnections(MemberDesc member) {
/* get the complete list of input slots for this action - whether set, or not set */
int actionId = member.memberId;
int actionTypeId = actionMgr.getActionType(actionId);
if (actionTypeId < 0) {
return;
}
SlotDetails slots[] = actionTypeMgr.getSlots(actionTypeId, ISlotTypes.SLOT_POS_ANY);
if (slots == null) {
return;
}
/*
* For each slot within the action, see if there's a FileGroup in the slot.
*/
for (int i = 0; i < slots.length; i++) {
int slotId = slots[i].slotId;
int slotPos = slots[i].slotPos;
/* connections can only come from input or output slots... */
if ((slotPos == ISlotTypes.SLOT_POS_INPUT) || (slotPos == ISlotTypes.SLOT_POS_OUTPUT)){
Object slotValue = actionMgr.getSlotValue(actionId, slotId);
/* yes, this slot has a file group in it */
if ((slotValue != null) && (slotValue instanceof Integer)) {
Integer fileGroupId = (Integer)slotValue;
int connectionDirection = (slotPos == ISlotTypes.SLOT_POS_INPUT) ?
UIFileActionConnection.INPUT_TO_ACTION :
UIFileActionConnection.OUTPUT_FROM_ACTION;
/*
* if this is INPUT_TO_ACTION, and fileGroupId is a filter, register the
* filter in the connection object, and instead draw the line to the
* filter's input.
*/
int filterGroupId = -1;
if (connectionDirection == UIFileActionConnection.INPUT_TO_ACTION) {
int fileGroupType = fileGroupMgr.getGroupType(fileGroupId);
if (fileGroupType == IFileGroupMgr.FILTER_GROUP) {
filterGroupId = fileGroupId;
fileGroupId = fileGroupMgr.getPredId(fileGroupId);
}
}
/* create the new business object */
UIFileActionConnection newConnection = new UIFileActionConnection(
fileGroupId, actionId, slotId, connectionDirection);
if (filterGroupId != -1) {
newConnection.setFilterGroupId(filterGroupId);
}
getDiagram().eResource().getContents().add(newConnection);
/*
* Now we need to figure out which pictograms this connection goes between. We
* know the fileGroupId and actionId, but need to search for the pictogram.
* Search for the UIFileGroup and UIAction end points, and retrieve their anchors
*/
Anchor fileGroupAnchor = getAnchorFor(IPackageMemberMgr.TYPE_FILE_GROUP, fileGroupId, connectionDirection);
Anchor actionAnchor = getAnchorFor(IPackageMemberMgr.TYPE_ACTION, actionId, connectionDirection);
/* We found both anchors, so now draw the connection between them */
if ((fileGroupAnchor != null) && (actionAnchor != null)) {
AddConnectionContext addContext = new AddConnectionContext(fileGroupAnchor, actionAnchor);
addContext.setNewObject(newConnection);
getFeatureProvider().addIfPossible(addContext);
}
}
}
}
}
/*-------------------------------------------------------------------------------------*/
/**
* For each member of the merge file group, draw an arrow from the right side of the
* sub file group into the left side of the merge.
* @param member Details of the merge file group.
*/
protected void addMergeFileGroupConnections(MemberDesc member) {
/* the merge group's anchor point */
Anchor targetAnchor = getAnchorFor(IPackageMemberMgr.TYPE_FILE_GROUP, member.memberId,
UIMergeFileGroupConnection.INPUT_TO_MERGE_GROUP);
if (targetAnchor == null) {
return;
}
/* for each of the input sub groups... */
int groupSize = fileGroupMgr.getGroupSize(member.memberId);
for (int i = 0; i != groupSize; i++) {
int subGroupId = fileGroupMgr.getSubGroup(member.memberId, i);
/*
* If the subGroupId is a filter group, record the filter in the
* connection, but actually draw the line to the filter's predecessor.
*/
int filterGroupId = -1;
int subGroupType = fileGroupMgr.getGroupType(subGroupId);
if (subGroupType == IFileGroupMgr.FILTER_GROUP) {
filterGroupId = subGroupId;
subGroupId = fileGroupMgr.getPredId(subGroupId);
}
/* find the sub group's anchor */
Anchor sourceAnchor = getAnchorFor(IPackageMemberMgr.TYPE_FILE_GROUP, subGroupId,
UIMergeFileGroupConnection.OUTPUT_FROM_SUB_GROUP);
/* create a new business object (to be displayed) */
UIMergeFileGroupConnection newConnection = new UIMergeFileGroupConnection(subGroupId, member.memberId, i);
if (filterGroupId != -1) {
newConnection.setFilterGroupId(filterGroupId);
}
getDiagram().eResource().getContents().add(newConnection);
/* We found the sub group's anchor, so now draw the connection */
if (sourceAnchor != null) {
AddConnectionContext addContext = new AddConnectionContext(sourceAnchor, targetAnchor);
addContext.setNewObject(newConnection);
getFeatureProvider().addIfPossible(addContext);
}
}
}
/*-------------------------------------------------------------------------------------*/
/**
* For a particular package member, return the relevant Graphiti anchor.
*
* @param memberType The type of the member (TYPE_ACTION, TYPE_FILE_GROUP, etc).
* @param memberId The ID of the member (fileGroupId, or actionId, etc).
* @param connectionId The ID of the anchor (0, 1, etc.).
* @return The Graphiti anchor associated with this package member, or null if the member can't
* be found.
*/
private Anchor getAnchorFor(int memberType, Integer memberId, int connectionId) {
/* visit each shape in the current diagram, looking for the package member */
EList<Shape> shapes = getDiagram().getChildren();
for (Shape shape : shapes) {
Object bo = GraphitiUtils.getBusinessObject(shape);
if ((memberType == IPackageMemberMgr.TYPE_FILE_GROUP) && (bo instanceof UIFileGroup)) {
if (((UIFileGroup)bo).getId() == memberId) {
return shape.getAnchors().get(connectionId);
}
}
else if ((memberType == IPackageMemberMgr.TYPE_ACTION) && (bo instanceof UIAction)) {
if (((UIAction)bo).getId() == memberId) {
return shape.getAnchors().get(connectionId);
}
}
}
return null;
}
/*-------------------------------------------------------------------------------------*/
}