blob: 6c88c83ef7eb80a051bf2afc655eb2bd146833f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2012, 2013 Red Hat, Inc.
* All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.features;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;
import org.eclipse.bpmn2.modeler.core.features.RoutingLane.Adjacence;
import org.eclipse.bpmn2.modeler.core.utils.FeatureSupport;
import org.eclipse.bpmn2.modeler.core.utils.GraphicsUtil;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.graphiti.datatypes.IDimension;
import org.eclipse.graphiti.datatypes.ILocation;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.context.IAddConnectionContext;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.impl.AddConnectionContext;
import org.eclipse.graphiti.features.context.impl.AddContext;
import org.eclipse.graphiti.features.context.impl.DeleteContext;
import org.eclipse.graphiti.features.impl.AbstractAddShapeFeature;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.algorithms.RoundedRectangle;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.FixPointAnchor;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeService;
import org.eclipse.graphiti.ui.features.DefaultDeleteFeature;
import org.eclipse.graphiti.util.ColorConstant;
import org.eclipse.graphiti.util.IColorConstant;
// TODO: Auto-generated Javadoc
/**
* This class manages a network of RoutingLane nodes. The nodes are linked if their
* physical rectangles share an edge. Depending on the orientation in which the net was created,
* only the left/right (for a "vertical" or non-rotated net) or top/bottom (a "horizontal"
* or rotated net) edges are tested for adjacency.
*/
public class RoutingNet extends ArrayList<RoutingLane> {
private static final long serialVersionUID = -3041403111796385182L;
/** The Constant gaService. */
protected static final IGaService gaService = Graphiti.getGaService();
/** The Constant peService. */
protected static final IPeService peService = Graphiti.getPeService();
/** The Constant CONNECTION. */
public static final String CONNECTION = "ROUTING_NET_CONNECTION"; //$NON-NLS-1$
/** The Constant LANE. */
public static final String LANE = "ROUTING_NET_LANE"; //$NON-NLS-1$
/** The is rotated. */
boolean isRotated = false;
/** The source. */
Shape source;
/** The target. */
Shape target;
/** The source adjacent lanes. */
List<RoutingLane> sourceAdjacentLanes = new ArrayList<RoutingLane>();
/** The target adjacent lanes. */
List<RoutingLane> targetAdjacentLanes = new ArrayList<RoutingLane>();
/** The solution stack. */
Stack<RoutingLane> solutionStack;
/** The all solutions. */
List< List<RoutingLane> > allSolutions;
/** The min dist. */
int minDist = Integer.MAX_VALUE;
/** The fp. */
IFeatureProvider fp;
/**
* Instantiates a new routing net.
*
* @param fp the fp
*/
public RoutingNet(IFeatureProvider fp) {
this.fp = fp;
}
/* (non-Javadoc)
* @see java.util.ArrayList#add(java.lang.Object)
*/
@Override
public boolean add(RoutingLane a) {
if (!contains(a) && a.getWidth()>0 && a.getHeight()>0)
return super.add(a);
return false;
}
/**
* Adds the.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @return true, if successful
*/
public boolean add(int x, int y, int width, int height) {
RoutingLane a = new RoutingLane(x,y,width,height);
return this.add(a);
}
/**
* Sets the feature provider.
*
* @param fp the new feature provider
*/
public void setFeatureProvider(IFeatureProvider fp) {
this.fp = fp;
}
/**
* Link.
*/
public void link() {
for (RoutingLane a1 : this) {
for (RoutingLane a2 : this) {
if (a1!=a2) {
switch (a1.adjacent(a2)) {
case LEFT:
case TOP:
a1.addLeft(a2);
break;
case RIGHT:
case BOTTOM:
a1.addRight(a2);
break;
case NONE:
break;
}
}
}
}
}
/* (non-Javadoc)
* @see java.util.ArrayList#clear()
*/
public void clear() {
super.clear();
sourceAdjacentLanes.clear();
targetAdjacentLanes.clear();
if (solutionStack!=null)
solutionStack.clear();
if (allSolutions!=null)
allSolutions.clear();
}
/**
* Find solutions.
*
* @param source2 the source2
* @param target2 the target2
* @return the list
*/
public List< List<RoutingLane> > findSolutions(Shape source2, Shape target2) {
allSolutions = new ArrayList< List<RoutingLane> >();
this.source = source2;
this.target = target2;
if (source2==null || target2==null) {
return allSolutions;
}
Rectangle sourceBounds = getBounds(false, source2);
Rectangle targetBounds = getBounds(false, target2);
sourceAdjacentLanes.clear();
targetAdjacentLanes.clear();
for (RoutingLane a1 : this) {
if (a1.adjacent(sourceBounds) != RoutingLane.Adjacence.NONE) {
sourceAdjacentLanes.add(a1);
}
if (a1.adjacent(targetBounds) != RoutingLane.Adjacence.NONE) {
targetAdjacentLanes.add(a1);
}
}
solutionStack = new Stack<RoutingLane>();
minDist = Integer.MAX_VALUE;
for (RoutingLane sa : sourceAdjacentLanes) {
for (RoutingLane ta : targetAdjacentLanes) {
sa.navigateTo(ta, this);
}
}
Collections.sort(allSolutions, new Comparator<List<RoutingLane>>() {
@Override
public int compare(List<RoutingLane> arg0, List<RoutingLane> arg1) {
int i;
double m0 = merit(arg0);
double m1 = merit(arg1);
if (m0<m1)
i = 1;
else if (m0>m1)
i = -1;
else
i = 0;
if (true)
return i;
i = getManhattanDistance(arg0) - getManhattanDistance(arg1);
if (i==0) {
// find the lane with the easiest passage
int arg0min = Integer.MAX_VALUE;
int arg1min = Integer.MAX_VALUE;
if (isRotated) {
for (RoutingLane a : arg0) {
int d = a.getHeight();
if (d<arg0min)
arg0min = d;
}
for (RoutingLane a : arg1) {
int d = a.getHeight();
if (d<arg1min)
arg1min = d;
}
}
else {
for (RoutingLane a : arg0) {
int d = a.getWidth();
if (d<arg0min)
arg0min = d;
}
for (RoutingLane a : arg1) {
int d = a.getWidth();
if (d<arg1min)
arg1min = d;
}
}
i = arg1min - arg0min;
}
return i;
}
});
return allSolutions;
}
/**
* Merit.
*
* @param list the list
* @return the double
*/
public double merit(List<RoutingLane> list) {
ILocation sourceLoc = Graphiti.getPeService().getLocationRelativeToDiagram(source);
IDimension sourceSize = GraphicsUtil.calculateSize(source);
ILocation targetLoc = Graphiti.getPeService().getLocationRelativeToDiagram(target);
IDimension targetSize = GraphicsUtil.calculateSize(target);
Point p1 = GraphicsUtil.getShapeCenter(source);
Point p2 = GraphicsUtil.getShapeCenter(target);
if (isRotated) {
if (sourceLoc.getY() + sourceSize.getHeight() < targetLoc.getY()) {
p1.setY(sourceLoc.getY() + sourceSize.getHeight());
p2.setY(targetLoc.getY() );
}
else if (targetLoc.getY() + targetSize.getHeight() < sourceLoc.getY()) {
p1.setY(sourceLoc.getY());
p2.setY(targetLoc.getY() + targetSize.getHeight());
}
}
else {
if (sourceLoc.getX() + sourceSize.getWidth() < targetLoc.getX()) {
p1.setX(sourceLoc.getX() + sourceSize.getWidth());
p2.setX(targetLoc.getX() );
}
else if (targetLoc.getX() + targetSize.getWidth() < sourceLoc.getX()) {
p1.setX(sourceLoc.getX());
p2.setX(targetLoc.getX() + targetSize.getWidth());
}
}
int i = 0;
double length = GraphicsUtil.getLength(p1, p2);
for (RoutingLane rl : list) {
if ( GraphicsUtil.RectangleIntersectsLine.intersectsLine(
p1.getX(), p1.getY(), p2.getX(), p2.getY(),
rl.rect.x, rl.rect.y, rl.rect.width, rl.rect.height)) {
i += length;
}
else
{
Point c = GraphicsUtil.createPoint(rl.rect.getCenter().x, rl.rect.getCenter().y);
double d = pointToLineDistance(p1, p2, c);
i += length / d;
}
}
return (double) i / (double)(length * list.size());
}
/**
* Point to line distance.
*
* @param p1 the p1
* @param p2 the p2
* @param p the p
* @return the double
*/
public double pointToLineDistance(Point p1, Point p2, Point p) {
double normalLength = Math.sqrt((p2.getX() - p1.getX()) * (p2.getX() - p1.getX()) + (p2.getY() - p1.getY()) * (p2.getY() - p1.getY()));
return Math.abs((p.getX() - p1.getX()) * (p2.getY() - p1.getY()) - (p.getY() - p1.getY()) * (p2.getX() - p1.getX())) / normalLength;
}
/**
* Pop.
*/
public void pop() {
solutionStack.pop();
}
/**
* Visited.
*
* @param lane the lane
* @return true, if successful
*/
public boolean visited(RoutingLane lane) {
return solutionStack.contains(lane);
}
/**
* Push.
*
* @param lane the lane
*/
public void push(RoutingLane lane) {
solutionStack.push(lane);
}
/**
* Gets the manhattan distance.
*
* @param lanes the lanes
* @return the manhattan distance
*/
public int getManhattanDistance(List<RoutingLane> lanes) {
int dist = 0;
Rectangle r0 = getBounds(false, source);
int x0 = r0.x + r0.width/2;
int y0 = r0.y + r0.height/2;
Rectangle r1 = getBounds(false, target);
int x1 = r1.x + r1.width/2;
int y1 = r1.y + r1.height/2;
int dx = Math.abs(x0 - x1);
int dy = Math.abs(y0 - y1);
RoutingLane a0 = lanes.get(0);
RoutingLane a1;
RoutingLane a2 = null;
if (isRotated) {
if (dy>a0.getHeight())
dist += a0.getHeight();
dist += dx;
for (int i=1; i<lanes.size(); ++i) {
a1 = lanes.get(i);
int d = 0;
if (dy>a1.getHeight())
d = a1.getHeight();
else
d = dy;
if (i+1<lanes.size()) {
a2 = lanes.get(i+1);
if (a0.getRight().contains(a1) && a1.getRight().contains(a2) ||
a0.getLeft().contains(a1) && a1.getLeft().contains(a2)) {
dist += d;
}
}
else {
dist += d;
}
d = 0;
int right = a1.getX() + a1.getWidth();
int left = a1.getX();
if (right < x0) {
d = x0 - right;
x0 = right;
}
else if (left > x0) {
d = left - x0;
x0 = left;
}
dist += d;
a0 = a1;
}
}
else {
if (dx>a0.getWidth())
dist += a0.getWidth();
double dd = (double) a0.getWidth() / (double) a0.getHeight();
dist += dy;
for (int i=1; i<lanes.size(); ++i) {
a1 = lanes.get(i);
int d = 0;
if (dx>a1.getWidth())
d = a1.getWidth();
else
d = dx;
if (i+1<lanes.size()) {
a2 = lanes.get(i+1);
if (a0.getRight().contains(a1) && a1.getRight().contains(a2) ||
a0.getLeft().contains(a1) && a1.getLeft().contains(a2)) {
dist += d;
}
}
else {
dist += d;
}
d = 0;
int bottom = a1.getY() + a1.getHeight();
int top = a1.getY();
if (bottom < y0) {
d = y0 - bottom;
y0 = bottom;
}
else if (top > y0) {
d = top - y0;
y0 = top;
}
dist += d;
a0 = a1;
}
}
return dist;
}
/**
* Solution found.
*
* @return true, if successful
*/
public boolean solutionFound() {
if (!allSolutions.contains(solutionStack)) {
// int d = getManhattanDistance(solutionStack);
// if (d - 0.5 * minDist < minDist) {
// if (d < minDist)
// minDist = d;
List<RoutingLane> solution = new ArrayList<RoutingLane>(solutionStack);
allSolutions.add(solution);
// }
}
return true;
}
/**
* Rotate.
*
* @param b the b
*/
public void rotate(boolean b) {
if (isRotated!=b) {
for (RoutingLane node : this) {
node.rotate(b);
}
isRotated = b;
}
}
/**
* Gets the lanes adjacent to.
*
* @param shape the shape
* @param adjacence the adjacence
* @return the lanes adjacent to
*/
public List<RoutingLane> getLanesAdjacentTo(ContainerShape shape, Adjacence adjacence) {
List<RoutingLane> adjacentLanes;
List<RoutingLane> list = new ArrayList<RoutingLane>();
if (shape==source) {
adjacentLanes = sourceAdjacentLanes;
}
else if (shape==target) {
adjacentLanes = targetAdjacentLanes;
}
else
return list;
Rectangle bounds = getBounds(isRotated, shape);
for (RoutingLane a : adjacentLanes) {
if (a.adjacent(bounds) == adjacence)
list.add(a);
}
return list;
}
private Rectangle getBounds(Shape target2) {
return getBounds(isRotated,target2);
}
/**
* Gets the bounds.
*
* @param rotate the rotate
* @param source2 the source2
* @return the bounds
*/
protected static Rectangle getBounds(boolean rotate, Shape source2) {
ILocation loc = peService.getLocationRelativeToDiagram(source2);
IDimension size = GraphicsUtil.calculateSize(source2);
if (rotate) {
return rotateRectangle(loc.getX(), loc.getY(), size.getWidth(), size.getHeight());
}
return new Rectangle(loc.getX(), loc.getY(), size.getWidth(), size.getHeight());
}
/**
* Rotate rectangle.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @return the rectangle
*/
protected static Rectangle rotateRectangle(int x, int y, int width, int height) {
return rotateRectangle(new Rectangle(x,y,width,height));
}
/**
* Rotate rectangle.
*
* @param r the r
* @return the rectangle
*/
public static Rectangle rotateRectangle(Rectangle r) {
int y = r.x;
int x = r.y;
int w = r.height;
int h = r.width;
r.x = x;
r.y = y;
r.width = w;
r.height = h;
return r;
}
/**
* **************************************************************************************
*
* Debug stuff
*
* **************************************************************************************.
*/
protected class AddRoutingLaneFeature extends AbstractAddShapeFeature {
/**
* Instantiates a new adds the routing lane feature.
*
* @param fp the fp
*/
public AddRoutingLaneFeature(IFeatureProvider fp) {
super(fp);
}
/* (non-Javadoc)
* @see org.eclipse.graphiti.func.IAdd#canAdd(org.eclipse.graphiti.features.context.IAddContext)
*/
@Override
public boolean canAdd(IAddContext context) {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.graphiti.func.IAdd#add(org.eclipse.graphiti.features.context.IAddContext)
*/
@Override
public PictogramElement add(IAddContext context) {
int x = context.getX();
int y = context.getY();
int width = context.getWidth();
int height = context.getHeight();
IColorConstant foreground = new ColorConstant(0,0,255);
RoutingLane lane = (RoutingLane)context.getNewObject();
IColorConstant background = new ColorConstant(128,128,128);
double transparency = 0.75;
Rectangle bounds = getBounds(source);
if (lane==null) {
Object bg = context.getProperty("background"); //$NON-NLS-1$
if (bg instanceof ColorConstant)
background = (ColorConstant)bg;
else
background = new ColorConstant(0,255,255);
transparency = .50;
}
else {
boolean sourceAdjacent = false;
if (lane.adjacent(bounds) != RoutingLane.Adjacence.NONE) {
background = new ColorConstant(0,255,0);
transparency = 0.25;
sourceAdjacent = true;
}
bounds = getBounds(target);
if (lane.adjacent(bounds) != RoutingLane.Adjacence.NONE) {
if (sourceAdjacent) {
background = new ColorConstant(255,255,0);
}
else {
background = new ColorConstant(255,0,0);
}
transparency = 0.25;
}
}
Diagram diagram = getDiagram();
ContainerShape containerShape = peService.createContainerShape(context.getTargetContainer(), true);
org.eclipse.graphiti.mm.algorithms.Rectangle invisibleRect = gaService.createInvisibleRectangle(containerShape);
gaService.setLocationAndSize(invisibleRect, x, y, width, height);
Shape rectShape = peService.createShape(containerShape, false);
RoundedRectangle roundedRect = gaService.createRoundedRectangle(rectShape, 1, 1);
roundedRect.setForeground(gaService.manageColor(diagram, foreground));
roundedRect.setBackground(gaService.manageColor(diagram, background));
roundedRect.setFilled(true);
roundedRect.setTransparency(transparency);
roundedRect.setLineWidth(2);
// link(rectShape, context.getNewObject());
FeatureSupport.setPropertyValue(containerShape, LANE, "true"); //$NON-NLS-1$
gaService.setLocationAndSize(roundedRect, 0, 0, width, height);
peService.sendToFront(containerShape);
return containerShape;
}
}
private class DeleteRoutingLaneFeature extends DefaultDeleteFeature {
public DeleteRoutingLaneFeature(IFeatureProvider fp) {
super(fp);
}
}
private class AddRoutingLaneConnectionFeature extends AbstractAddShapeFeature {
public AddRoutingLaneConnectionFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canAdd(IAddContext ac) {
return true;
}
@Override
public PictogramElement add(IAddContext ac) {
IAddConnectionContext context = (IAddConnectionContext) ac;
Anchor sourceAnchor = context.getSourceAnchor();
Anchor targetAnchor = context.getTargetAnchor();
ContainerShape sourceShape = (ContainerShape) sourceAnchor.getParent();
ContainerShape targetShape = (ContainerShape) targetAnchor.getParent();
Object[] newObject = (Object[]) context.getNewObject();
RoutingLane sourceNode = (RoutingLane)newObject[0];
RoutingLane targetNode = (RoutingLane)newObject[1];
Diagram diagram = getDiagram();
Connection connection = peService.createFreeFormConnection(diagram);
connection.setStart(sourceAnchor);
connection.setEnd(targetAnchor);
FeatureSupport.setPropertyValue(connection, CONNECTION, "true"); //$NON-NLS-1$
Polyline connectionLine = Graphiti.getGaService().createPolyline(connection);
connectionLine.setLineWidth(1);
IColorConstant foreground = new ColorConstant(0,0,255);
int w = 3;
int l = 15;
ConnectionDecorator decorator = peService.createConnectionDecorator(connection, false, 1.0, true);
Polyline arrowhead = gaService.createPolygon(decorator, new int[] { -l, w, 0, 0, -l, -w, -l, w });
arrowhead.setForeground(gaService.manageColor(diagram, foreground));
connectionLine.setForeground(gaService.manageColor(diagram, foreground));
return connection;
}
}
/**
* Draw lanes.
*/
public void drawLanes() {
if (fp!=null) {
Diagram diagram = fp.getDiagramTypeProvider().getDiagram();
for (RoutingLane a : this) {
AddContext context = new AddContext();
context.setTargetContainer(diagram);
context.setNewObject(a);
context.setX(a.getX());
context.setY(a.getY());
context.setSize(a.getWidth(), a.getHeight());
AddRoutingLaneFeature feature = new AddRoutingLaneFeature(fp);
a.setShape(feature.add(context));
}
}
}
/**
* Draw connections.
*/
public void drawConnections() {
if (fp!=null) {
Diagram diagram = fp.getDiagramTypeProvider().getDiagram();
ContainerShape sourceShape;
Anchor sourceAnchor;
ContainerShape targetShape;
Anchor targetAnchor;
for (RoutingLane n1 : this) {
for (RoutingLane n2 : n1.getRight()) {
if (n1!=n2) {
sourceShape = n1.getShape();
targetShape = n2.getShape();
if (sourceShape!=null && targetShape!=null) {
if (sourceShape.getAnchors().size()>0)
sourceAnchor = sourceShape.getAnchors().get(0);
else {
FixPointAnchor a = peService.createFixPointAnchor(sourceShape);
Rectangle r = getBounds(false,sourceShape);
a.setLocation(GraphicsUtil.createPoint(r.width/2, r.height/2));
gaService.createInvisibleRectangle(a);
sourceAnchor = a;
}
if (targetShape.getAnchors().size()>0)
targetAnchor = targetShape.getAnchors().get(0);
else {
FixPointAnchor a = peService.createFixPointAnchor(targetShape);
Rectangle r = getBounds(false,targetShape);
a.setLocation(GraphicsUtil.createPoint(r.width/2, r.height/2));
gaService.createInvisibleRectangle(a);
targetAnchor = a;
}
AddConnectionContext context = new AddConnectionContext(sourceAnchor, targetAnchor);
context.setTargetContainer(diagram);
context.setNewObject(new Object[] {n1, n2});
AddRoutingLaneConnectionFeature feature = new AddRoutingLaneConnectionFeature(fp);
feature.add(context);
}
}
}
}
}
}
/**
* Draw solution.
*
* @param net the net
* @param i the i
*/
public void drawSolution(List<RoutingLane> net, int i) {
if (fp!=null) {
Diagram diagram = fp.getDiagramTypeProvider().getDiagram();
for (RoutingLane a : net) {
AddContext context = new AddContext();
context.setTargetContainer(diagram);
context.setNewObject( null );
context.setLocation(a.getX(), a.getY());
context.setSize(a.getWidth(), a.getHeight());
// context.putProperty("background", new ColorConstant(32+i*(i%3),32+i*(i%6),32+i*(i%9)));
AddRoutingLaneFeature feature = new AddRoutingLaneFeature(fp);
a.setShape(feature.add(context));
}
}
}
/**
* Erase lanes.
*/
public void eraseLanes() {
if (fp!=null) {
for (RoutingLane a : this) {
Diagram diagram = fp.getDiagramTypeProvider().getDiagram();
List<ContainerShape> deleted = new ArrayList<ContainerShape>();
TreeIterator iter = diagram.eAllContents();
while (iter.hasNext()) {
Object o = iter.next();
if (o instanceof ContainerShape) {
ContainerShape s = (ContainerShape)o;
if (FeatureSupport.getPropertyValue(s, LANE)!=null) {
deleted.add(s);
}
}
}
for (ContainerShape s : deleted) {
DeleteContext context = new DeleteContext(s);
DeleteRoutingLaneFeature feature = new DeleteRoutingLaneFeature(fp);
feature.delete(context);
}
}
}
}
}