Bug 442157 improved rectilinear router

Change-Id: I69d1b6e9009669538d9a7978bf44b6399d9ad28d
Signed-off-by: mgolubev <golubev@montages.com>
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/HintedOrthogonalRouter.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/HintedOrthogonalRouter.java
new file mode 100644
index 0000000..2b09bb7
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/HintedOrthogonalRouter.java
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG and others.
+ * 
+ * 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:
+ *   Michael Golubev (Montages) - Initial API and implementation
+ *   
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.draw2d.Connection;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouter;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
+
+/**
+ * When connection anchor and first/last bendpoint are not aligned vertically / horizontally,
+ * the {@link OrthogonalRouter} has to decide whether the first / last segment of link will
+ * go:
+ * <ul>
+ * <li>from bendpoint to some point different to anchor</li>
+ * <li>from anchor to some point different to bendpoint</li>
+ * </ul>
+ * The default {@link RectilinearRouter} always prefers the first choice.
+ * <p>
+ * This router extends the {@link OrthogonalRouter} with ability to specify the 
+ * hint for this choice externally, on the per-connection basis.
+ * 
+ * @since 3.3
+ */
+public interface HintedOrthogonalRouter extends OrthogonalRouter {
+
+	/**
+	 * Specifies 2 possible strategy for the routing of the first / last link segment.
+	 * <ul>
+	 * <li> {@link EndRoutingHint#FixBendpointMoveAnchor} is default behavior. 
+	 * It will route the segment from bendpoint to some point different to anchor</li>
+	 * <li> {@link EndRoutingHint#FixAnchorMoveBendpoint} will route from anchor 
+	 * to some point different to bendpoint</li>
+	 * </ul>
+	 */
+	public static enum EndRoutingHint {
+		FixAnchorMoveBendpoint, //
+		FixBendpointMoveAnchor;
+	}
+	
+	/**
+	 * Returns default strategy for this router which will be used when hint is not specified.
+	 */
+	public EndRoutingHint getDefaultEndRoutingStrategy();
+	
+	/**
+	 * Asks router to use specified strategy for given connection.
+	 * @param hint desired hint or <code>null</code> to revert connection back to default strategy.
+	 */
+	public void setEndRoutingHint(Connection conn, EndRoutingHint hint);
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFConnectionLayer.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFConnectionLayer.java
new file mode 100644
index 0000000..dd699c1
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFConnectionLayer.java
@@ -0,0 +1,31 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG and others.
+ * 
+ * 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:
+ *   Michael Golubev (Montages) - Initial API and implementation
+ *   
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.draw2d.ConnectionRouter;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersConnectionLayer;
+
+/**
+ * Replaces rectilinear router with custom {@link LinkLFRectilinearRouter}
+ * implementation.
+ * 
+ * @since 3.3
+ */
+public class LinkLFConnectionLayer extends CustomRoutersConnectionLayer {
+
+	@Override
+	protected ConnectionRouter createRectilinearRouter() {
+		return new LinkLFRectilinearRouter();
+	}
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPart.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPart.java
new file mode 100644
index 0000000..c2718f4
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPart.java
@@ -0,0 +1,37 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG and others.
+ * 
+ * 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:
+ *   Michael Golubev (Montages) - Initial API and implementation
+ *   
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.gmf.runtime.notation.MeasurementUnit;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersConnectionLayer;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersDiagramRootEditPart;
+
+/**
+ * Implementation of the {@link CustomRoutersDiagramRootEditPart} which installs
+ * customized {@link LinkLFRectilinearRouter} as a rectilinear router for given
+ * diagram.
+ * 
+ * @since 3.3
+ */
+public class LinkLFDiagramRootEditPart extends CustomRoutersDiagramRootEditPart {
+
+	public LinkLFDiagramRootEditPart(MeasurementUnit mUnit) {
+		super(mUnit);
+	}
+
+	@Override
+	protected CustomRoutersConnectionLayer createConnectionLayer() {
+		return new LinkLFConnectionLayer();
+	}
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPartProvider.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPartProvider.java
new file mode 100644
index 0000000..d190c17
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPartProvider.java
@@ -0,0 +1,37 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG and others.
+ * 
+ * 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:
+ *   Michael Golubev (Montages) - Initial API and implementation
+ *   
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.gmf.runtime.notation.Diagram;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersDiagramRootEditPart;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersDiagramRootEditPartProvider;
+
+/**
+ * LinkLF specific implementation of the
+ * {@link CustomRoutersDiagramRootEditPart}.
+ * <p/>
+ * This provider will install LinkLF-specific {@link LinkLFConnectionLayer}
+ * which in turn will use the customized {@link LinkLFRectilinearRouter} for
+ * applicable diagram.
+ * 
+ * @since 3.3
+ */
+public class LinkLFDiagramRootEditPartProvider extends
+		CustomRoutersDiagramRootEditPartProvider {
+
+	@Override
+	public CustomRoutersDiagramRootEditPart createRootEditPart(Diagram diagram) {
+		return new LinkLFDiagramRootEditPart(diagram.getMeasurementUnit());
+	}
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFRectilinearRouter.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFRectilinearRouter.java
new file mode 100644
index 0000000..c8dace9
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFRectilinearRouter.java
@@ -0,0 +1,519 @@
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import org.eclipse.draw2d.Connection;
+import org.eclipse.draw2d.ConnectionAnchor;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.PositionConstants;
+import org.eclipse.draw2d.geometry.Dimension;
+import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.draw2d.geometry.PointList;
+import org.eclipse.draw2d.geometry.PrecisionPoint;
+import org.eclipse.draw2d.geometry.PrecisionRectangle;
+import org.eclipse.gef.EditPartViewer;
+import org.eclipse.gef.GraphicalEditPart;
+import org.eclipse.gef.SnapToGrid;
+import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
+import org.eclipse.gmf.runtime.draw2d.ui.figures.FigureUtilities;
+import org.eclipse.gmf.runtime.draw2d.ui.figures.OrthogonalConnectionAnchor;
+import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouterUtilities;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
+import org.eclipse.gmf.tooling.runtime.linklf.DiagramGridSpec;
+import org.eclipse.gmf.tooling.runtime.linklf.SlidableSnapToGridAnchor;
+import org.eclipse.gmf.tooling.runtime.providers.router.SnapToGridRouter;
+
+/**
+ * Extends the standard GMF Runtime rectilinear router with following
+ * improvements:
+ * <ul>
+ * <li>respects Snap to grid for the implicit bendpoints introduced when
+ * switching from oblique to recti- routing</li>
+ * <li>enforces that the last / first segment is always orthogonal to the node
+ * side, avoiding the visual anchor position different to the user-set anchor
+ * position. The latter happens with default RectilinearRouter when first or
+ * last segment is parallel to (and in fact hidden by) the node side</li>
+ * <li>routes the segments from/to affixed items orthogonal to the side of the
+ * parent node to which affixed node is attached</li>
+ * </ul>
+ * 
+ * @since 3.3
+ */
+public class LinkLFRectilinearRouter extends RectilinearRouter2 implements
+		HintedOrthogonalRouter, SnapToGridRouter {
+
+	private final Map<Connection, EndRoutingHint> myEndRoutingHints = new IdentityHashMap<Connection, EndRoutingHint>();
+
+	private DiagramGridSpec myGridSpec;
+
+	public LinkLFRectilinearRouter() {
+		super();
+	}
+
+	public EndRoutingHint getDefaultEndRoutingStrategy() {
+		return EndRoutingHint.FixAnchorMoveBendpoint;
+	}
+
+	@Override
+	public void setEditPartViewer(EditPartViewer viewer) {
+		if (myGridSpec != null && myGridSpec.getViewer() == viewer) {
+			return;
+		}
+		if (myGridSpec != null) {
+			myGridSpec.dispose();
+		}
+		myGridSpec = viewer == null ? null : new DiagramGridSpec(viewer);
+	}
+
+	@Override
+	public void setEndRoutingHint(Connection conn, EndRoutingHint hint) {
+		if (hint == getDefaultEndRoutingStrategy()) {
+			hint = null;
+		}
+		if (hint == null) {
+			myEndRoutingHints.remove(conn);
+		} else {
+			myEndRoutingHints.put(conn, hint);
+		}
+	}
+
+	/**
+	 * While {@link RectilinearRouter} claims to ignore the endpoints, it still
+	 * passes them into the
+	 * {@link #removePointsInViews(Connection, PointList, Point, Point)} method.
+	 * 
+	 * This leads to 2 problems: - the one described in the bug #451604 - even
+	 * if we fix the bug above by providing more hints, this added middle point
+	 * is not snapped when should be
+	 * <p/>
+	 * 
+	 * So this class ensures that the endpoints are reset to anchor positions
+	 * befor routing
+	 */
+	@Override
+	public void routeLine(Connection conn, int nestedRoutingDepth,
+			PointList newLine) {
+		if (nestedRoutingDepth == 0 && newLine.size() >= 2) {
+			Point sourceLoc = conn.getSourceAnchor().getReferencePoint();
+			Point targetLoc = conn.getTargetAnchor().getReferencePoint();
+
+			conn.translateToRelative(sourceLoc);
+			conn.translateToRelative(targetLoc);
+
+			newLine.setPoint(sourceLoc, 0);
+			newLine.setPoint(targetLoc, newLine.size() - 1);
+		}
+		super.routeLine(conn, nestedRoutingDepth, newLine);
+	}
+
+	/**
+	 * @see org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter#resetEndPointsToEdge(org.eclipse.draw2d.Connection,
+	 *      org.eclipse.draw2d.geometry.PointList)
+	 *
+	 * @param conn
+	 * @param line
+	 */
+	@Override
+	protected void resetEndPointsToEdge(Connection conn, PointList line) {
+		if (isReorienting(conn)) {
+			/*
+			 * If the connection doesn't have a shape as a source or target
+			 * we'll let the oblique router to do the work. The connection
+			 * doesn't need to be rectilinear at this point. There is no support
+			 * for making a rectilinear connection for which one of the ends is
+			 * not connected to anything.
+			 */
+			super.resetEndPointsToEdge(conn, line);
+			return;
+		}
+		PrecisionRectangle source = sourceBoundsRelativeToConnection2(conn);
+		PrecisionRectangle target = targetBoundsRelativeToConnection2(conn);
+		int offSourceDirection = PositionConstants.NONE;
+		int offTargetDirection = PositionConstants.NONE;
+		int sourceAnchorRelativeLocation = PositionConstants.NONE;
+		int targetAnchorRelativeLocation = PositionConstants.NONE;
+
+		SnapToGrid snapper = getSnapper();
+
+		if (line.size() == 0) {
+			/*
+			 * No valid bend points. We can't call super, so we will do the work
+			 * from RouterHelper ourselves
+			 */
+			PrecisionPoint sourceReference = new PrecisionPoint(conn
+					.getTargetAnchor().getReferencePoint());
+			PrecisionPoint sourceAnchorPoint = new PrecisionPoint(conn
+					.getSourceAnchor().getLocation(sourceReference));
+			PrecisionPoint targetAnchorPoint = new PrecisionPoint(conn
+					.getTargetAnchor().getLocation(sourceAnchorPoint));
+			conn.translateToRelative(sourceAnchorPoint);
+			conn.translateToRelative(targetAnchorPoint);
+
+			line.addPoint(sourceAnchorPoint);
+			line.addPoint(targetAnchorPoint);
+
+			sourceAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+					sourceAnchorPoint, source);
+			targetAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+					targetAnchorPoint, target);
+
+			insertPointsProducingNotAlignedRectilinearSegments(conn, line,
+					sourceAnchorRelativeLocation, targetAnchorRelativeLocation,
+					snapper);
+
+			offSourceDirection = getOffShapeDirection2(sourceAnchorRelativeLocation);
+			offTargetDirection = getOffShapeDirection2(targetAnchorRelativeLocation);
+		} else {
+			if (conn.getSourceAnchor() instanceof OrthogonalConnectionAnchor) {
+				addSegmentToSourceAnchor(line, conn,
+						(OrthogonalConnectionAnchor) conn.getSourceAnchor());
+			} else {
+				/*
+				 * If anchor is not supporting orthogonal connections we'll use
+				 * the oblique connection anchors and then convert it to
+				 * rectilinear.
+				 */
+				PrecisionPoint reference = new PrecisionPoint(
+						line.getFirstPoint());
+				conn.getSourceAnchor().getOwner()
+						.translateToAbsolute(reference);
+				PrecisionPoint anchorLocation = new PrecisionPoint(conn
+						.getSourceAnchor().getLocation(reference));
+				conn.translateToRelative(anchorLocation);
+				line.insertPoint(anchorLocation, 0);
+			}
+			if (conn.getTargetAnchor() instanceof OrthogonalConnectionAnchor) {
+				addSegmentToTargetAnchor(line, conn,
+						(OrthogonalConnectionAnchor) conn.getTargetAnchor());
+			} else {
+				/*
+				 * If anchor is not supporting orthogonal connections we'll use
+				 * the oblique connection anchors and then convert it to
+				 * rectilinear.
+				 */
+				PrecisionPoint reference = new PrecisionPoint(
+						line.getLastPoint());
+				conn.getSourceAnchor().getOwner()
+						.translateToAbsolute(reference);
+				PrecisionPoint anchorLocation = new PrecisionPoint(conn
+						.getTargetAnchor().getLocation(reference));
+				conn.translateToRelative(anchorLocation);
+				line.addPoint(anchorLocation);
+			}
+			sourceAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+					line.getFirstPoint(), source);
+			offSourceDirection = getOffShapeDirection2(sourceAnchorRelativeLocation);
+			targetAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+					line.getLastPoint(), target);
+			offTargetDirection = getOffShapeDirection2(targetAnchorRelativeLocation);
+		}
+
+		/*
+		 * Convert the polyline to rectilinear. If the connection is rectilinear
+		 * already then the connection will remain as it is.
+		 */
+		OrthogonalRouterUtilities.transformToOrthogonalPointList(line,
+				offSourceDirection, offTargetDirection);
+		removeRedundantPoints(line);
+	}
+
+	/**
+	 * We need to find two points offset from the source and target anchors
+	 * outside the shapes such that when the polyline is converted to
+	 * rectilinear from oblique we won't have rectilinear line segments alligned
+	 * with source or target shapes edges.
+	 * <p/>
+	 * Copy-pasted from {@link RectilinearRouter} lines 416.
+	 */
+	@Deprecated
+	public static void insertPointsProducingNotAlignedRectilinearSegments(
+			PointList line, int sourceAnchorRelativeLocation,
+			int targetAnchorRelativeLocation) {
+		Point offStart = line.getFirstPoint();
+		Point offEnd = line.getLastPoint();
+		Dimension offsetDim = offStart.getDifference(offEnd).scale(0.5);
+		offStart.translate(getTranslationValue2(sourceAnchorRelativeLocation,
+				Math.abs(offsetDim.width), Math.abs(offsetDim.height)));
+		offEnd.translate(getTranslationValue2(targetAnchorRelativeLocation,
+				Math.abs(offsetDim.width), Math.abs(offsetDim.height)));
+		line.insertPoint(offStart, 1);
+		line.insertPoint(offEnd, 2);
+	}
+
+	public static void insertPointsProducingNotAlignedRectilinearSegments(
+			Connection conn, PointList line, int sourceAnchorRelativeLocation,
+			int targetAnchorRelativeLocation, SnapToGrid snapper) {
+		insertPointsProducingNotAlignedRectilinearSegments(line,
+				sourceAnchorRelativeLocation, targetAnchorRelativeLocation);
+		if (asVerticalOrHorizontal(sourceAnchorRelativeLocation) != asVerticalOrHorizontal(targetAnchorRelativeLocation)) {
+			// for "|_"-like routing we don't need snapping, the added bendpoint
+			// is determined fully by anchors
+			return;
+		}
+		// so we are in "_|~"-like routing
+		if (snapper != null) {
+			PrecisionPoint addedForSource = new PrecisionPoint(line.getPoint(1));
+			PrecisionPoint addedForSourceAbs = makeAbsolute(conn,
+					addedForSource.getPreciseCopy());
+			PrecisionPoint snappedForSourceAbs = addedForSourceAbs
+					.getPreciseCopy();
+
+			PrecisionPoint addedForTarget = new PrecisionPoint(line.getPoint(2));
+			PrecisionPoint addedForTargetAbs = makeAbsolute(conn,
+					addedForTarget.getPreciseCopy());
+			PrecisionPoint snappedForTargetAbs = addedForTargetAbs
+					.getPreciseCopy();
+
+			snapper.snapPoint(null,
+					asVerticalOrHorizontal(sourceAnchorRelativeLocation),
+					addedForSourceAbs, snappedForSourceAbs);
+			snapper.snapPoint(null,
+					asVerticalOrHorizontal(targetAnchorRelativeLocation),
+					addedForTargetAbs, snappedForTargetAbs);
+
+			PrecisionPoint snappedForSource = makeRelative(conn,
+					snappedForSourceAbs.getPreciseCopy());
+			PrecisionPoint snappedForTarget = makeRelative(conn,
+					snappedForTargetAbs.getPreciseCopy());
+
+			if (snappedForSource.getDistance(addedForSource) <= snappedForTarget
+					.getDistance(addedForTarget)) {
+				Dimension delta = snappedForSource
+						.getDifference(addedForSource);
+				line.setPoint(snappedForSource, 1);
+				line.setPoint(addedForTarget.getTranslated(delta), 2);
+			} else {
+				Dimension delta = snappedForTarget
+						.getDifference(addedForTarget);
+				line.setPoint(addedForSource.getTranslated(delta), 1);
+				line.setPoint(snappedForTarget, 2);
+			}
+		}
+	}
+
+	public static int asVerticalOrHorizontal(int direction) {
+		return getOffShapeDirection2(direction);
+	}
+
+	protected SnapToGrid getSnapper() {
+		if (myGridSpec == null || myGridSpec.getAbsoluteGridSpec() == null) {
+			return null;
+		}
+		return new SnapToGrid((GraphicalEditPart) myGridSpec.getViewer()
+				.getContents());
+	}
+
+	protected void addSegmentToSourceAnchor(PointList line, Connection conn,
+			OrthogonalConnectionAnchor anchor) {
+		Point firstBend = line.getFirstPoint();
+		int prevSegmentOrientation = line.size() < 2 ? PositionConstants.NONE
+				: getSegmentOrientation(firstBend, line.getPoint(1));
+		Point[] prependSegment = computeFirstOrLastSegment(conn, anchor,
+				firstBend, prevSegmentOrientation);
+		for (int i = 0; i < prependSegment.length; i++) {
+			Point next = prependSegment[i];
+			if (i == prependSegment.length - 1 && next.equals(firstBend)) {
+				continue; // actually break
+			}
+			line.insertPoint(next, i);
+		}
+	}
+
+	protected void addSegmentToTargetAnchor(PointList line, Connection conn,
+			OrthogonalConnectionAnchor anchor) {
+		Point lastBend = line.getLastPoint();
+		int prevSegmentOrientation = line.size() < 2 ? PositionConstants.NONE
+				: getSegmentOrientation(lastBend,
+						line.getPoint(line.size() - 2));
+		Point[] appendSegment = computeFirstOrLastSegment(conn, anchor,
+				lastBend, prevSegmentOrientation);
+		for (int i = appendSegment.length - 1; i >= 0; i--) {
+			Point next = appendSegment[i];
+			if (i == appendSegment.length - 1 && next.equals(lastBend)) {
+				continue;
+			}
+			line.addPoint(next);
+		}
+	}
+
+	/**
+	 * Returns the array of point that represents the routing from the point at
+	 * figure bounds to the given first or last bendpoint. Result array is
+	 * ordered from the figure bounds, so the 0-th index always represent the
+	 * point at figure.
+	 */
+	protected Point[] computeFirstOrLastSegment(Connection conn,
+			OrthogonalConnectionAnchor anchor, Point bendpoint,
+			int prevSegmentOrientation) {
+		LineSeg fromFigureToBendpoint = OrthogonalRouterUtilities
+				.getOrthogonalLineSegToAnchorLoc(conn, anchor, bendpoint);
+		Point figurePoint = fromFigureToBendpoint.getOrigin();
+		Point syntheticBend = bendpoint.getCopy();
+		EndRoutingHint hint = findEndRoutingHint(conn, anchor);
+		if (hint == EndRoutingHint.FixAnchorMoveBendpoint) {
+			Point bendpointInAbsCoordinates = bendpoint.getCopy();
+			conn.translateToAbsolute(bendpointInAbsCoordinates);
+			int orientation = getOrientationAgainst(anchor,
+					bendpointInAbsCoordinates);
+			if (orientation == PositionConstants.NONE) {
+				orientation = getPreferredOutgoingDirection(conn, anchor);
+			}
+			if (orientation == PositionConstants.NONE) {
+				orientation = flipOrientation(prevSegmentOrientation);
+			}
+			Point refPoint = anchor.getReferencePoint().getCopy();
+			conn.translateToRelative(refPoint);
+			switch (orientation) {
+			case PositionConstants.VERTICAL:
+				syntheticBend.setX(refPoint.x());
+				break;
+			case PositionConstants.HORIZONTAL:
+				syntheticBend.setY(refPoint.y());
+				break;
+			default:
+				// Activator.log.error(new
+				// IllegalStateException("Unexpected orientation: " +
+				// orientation + //
+				// ", bendpoint-abs: " + bendpointInAnchorCoordinates +
+				// ", bendpoint-link-rel:: [" + bendpoint + "], " + //
+				// ", anchor: " + anchor.getReferencePoint()
+				// ));
+				break;
+			}
+			fromFigureToBendpoint = OrthogonalRouterUtilities
+					.getOrthogonalLineSegToAnchorLoc(conn, anchor,
+							syntheticBend);
+			figurePoint = fromFigureToBendpoint.getOrigin();
+		}
+
+		Point[] result = new Point[] { figurePoint, syntheticBend };
+		return result;
+	}
+
+	/**
+	 * [GMFRT] extract to utility method
+	 * 
+	 * This code is partial copy of the
+	 * {@link BaseSlidableAnchor#getOrthogonalLocation(Point)}.
+	 * 
+	 * @deprecated
+	 */
+	private int getOrientationAgainst(ConnectionAnchor anchor,
+			Point orthoReference) {
+		// Note that <code>ownReference</code> is not used besides call the
+		// getClosestSide()
+		// so in contrast to the original code in BaseSlidableAnchor, we have
+		// commented out useless updates
+		PrecisionPoint ownReference = new PrecisionPoint(
+				anchor.getReferencePoint());
+		PrecisionRectangle bounds = new PrecisionRectangle(
+				FigureUtilities.getAnchorableFigureBounds(anchor.getOwner()));
+		anchor.getOwner().translateToAbsolute(bounds);
+		bounds.expand(0.000001, 0.000001);
+		PrecisionPoint preciseOrthoReference = new PrecisionPoint(
+				orthoReference);
+		int orientation = PositionConstants.NONE;
+		if (bounds.contains(preciseOrthoReference)) {
+			int side = SlidableSnapToGridAnchor.getClosestSide2(ownReference,
+					bounds);
+			switch (side) {
+			case PositionConstants.LEFT:
+			case PositionConstants.RIGHT:
+				// ownReference.preciseY = preciseOrthoReference.preciseY();
+				orientation = PositionConstants.HORIZONTAL;
+				break;
+			case PositionConstants.TOP:
+			case PositionConstants.BOTTOM:
+				// ownReference.preciseX = preciseOrthoReference.preciseX();
+				orientation = PositionConstants.VERTICAL;
+				break;
+			}
+		} else if (preciseOrthoReference.preciseX >= bounds.preciseX
+				&& preciseOrthoReference.preciseX <= bounds.preciseX
+						+ bounds.preciseWidth) {
+			// ownReference.preciseX = preciseOrthoReference.preciseX;
+			orientation = PositionConstants.VERTICAL;
+		} else if (preciseOrthoReference.preciseY >= bounds.preciseY
+				&& preciseOrthoReference.preciseY <= bounds.preciseY
+						+ bounds.preciseHeight) {
+			// ownReference.preciseY = preciseOrthoReference.preciseY;
+			orientation = PositionConstants.HORIZONTAL;
+		}
+		// ownReference.updateInts();
+		return orientation;
+	}
+
+	protected EndRoutingHint findEndRoutingHint(Connection conn,
+			OrthogonalConnectionAnchor anchor) {
+		EndRoutingHint result = myEndRoutingHints.get(conn);
+		if (result == null) {
+			result = getDefaultEndRoutingStrategy();
+		}
+		return result;
+	}
+
+	protected static int flipOrientation(int verticalOrHorizontal) {
+		switch (verticalOrHorizontal) {
+		case PositionConstants.VERTICAL:
+			return PositionConstants.HORIZONTAL;
+		case PositionConstants.HORIZONTAL:
+			return PositionConstants.VERTICAL;
+		default:
+			return PositionConstants.NONE;
+		}
+	}
+
+	protected static int getSegmentOrientation(Point start, Point end) {
+		if (start.x() == end.x()) {
+			return PositionConstants.VERTICAL;
+		}
+		if (start.y() == end.y()) {
+			return PositionConstants.HORIZONTAL;
+		}
+		return PositionConstants.NONE;
+	}
+
+	/**
+	 * When anchor is placed directly at the anchorable bound of its owner, this
+	 * method returns the direction orthogonal to the side this anchor is
+	 * attached to. Returns {@link PositionConstants#NONE} in all other cases.
+	 * 
+	 * @return {@link PositionConstants#HORIZONTAL} or
+	 *         {@link PositionConstants#VERTICAL} or
+	 *         {@link PositionConstants#NONE}
+	 */
+	protected int getPreferredOutgoingDirection(Connection conn,
+			ConnectionAnchor anchor) {
+		final double TOLERANCE = 2;
+
+		IFigure owner = anchor.getOwner();
+		if (owner == null) {
+			return PositionConstants.NONE;
+		}
+		PrecisionRectangle bounds = new PrecisionRectangle(
+				FigureUtilities.getAnchorableFigureBounds(owner));
+		owner.translateToAbsolute(bounds);
+		conn.translateToRelative(bounds);
+
+		PrecisionPoint anchorLoc = new PrecisionPoint(
+				anchor.getReferencePoint());
+		conn.translateToRelative(anchorLoc);
+
+		if (Math.abs(anchorLoc.preciseX() - bounds.preciseX()) < TOLERANCE) {
+			return PositionConstants.HORIZONTAL; // .WEST
+		}
+		if (Math.abs(anchorLoc.preciseX() - bounds.preciseRight()) < TOLERANCE) {
+			return PositionConstants.HORIZONTAL; // .EAST
+		}
+		if (Math.abs(anchorLoc.preciseY() - bounds.preciseY()) < TOLERANCE) {
+			return PositionConstants.VERTICAL; // .NORTH
+		}
+		if (Math.abs(anchorLoc.preciseY() - bounds.preciseBottom()) < TOLERANCE) {
+			return PositionConstants.VERTICAL; // .SOUTH
+		}
+		return PositionConstants.NONE;
+	}
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/RectilinearRouter2.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/RectilinearRouter2.java
new file mode 100644
index 0000000..638f249
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/RectilinearRouter2.java
@@ -0,0 +1,262 @@
+/******************************************************************************
+ * Copyright (c) 2002, 2010 IBM Corporation and others.
+ * 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:
+ *    IBM Corporation - initial API and implementation 
+ ****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.draw2d.Connection;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.PositionConstants;
+import org.eclipse.draw2d.geometry.Dimension;
+import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.draw2d.geometry.PointList;
+import org.eclipse.draw2d.geometry.PrecisionRectangle;
+import org.eclipse.draw2d.geometry.Rectangle;
+import org.eclipse.draw2d.geometry.Translatable;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
+
+/**
+ * Right now extending {@link RectilinearRouter} does not make sense, because
+ * all of the methods of interest are private there.
+ * <p/>
+ * We, however, have extracted this class to at least formally note the copy
+ * paste and distinguish it from copy-paste-with-modifications which are
+ * intended to be placed into subclasses.
+ * <p/>
+ * All of the methods in this class are just the copy-pasted versions of their
+ * private counterparts in {@link RectilinearRouter}, without any changes.
+ * <p>
+ * This class is intentionally made package local, and will be removed once the
+ * respective patch for bug 331779 is merged.
+ * 
+ * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=331779
+ * @since 3.3
+ */
+class RectilinearRouter2 extends RectilinearRouter {
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Iterates through points of a polyline and does the following: if 3 points
+	 * lie on the same line the middle point is removed
+	 * 
+	 * @param line
+	 *            polyline's points
+	 */
+	@Deprecated
+	public static boolean removeRedundantPoints2(PointList line) {
+		int initialNumberOfPoints = line.size();
+		if (line.size() > 2) {
+			PointList newLine = new PointList(line.size());
+			newLine.addPoint(line.removePoint(0));
+			while (line.size() >= 2) {
+				Point p0 = newLine.getLastPoint();
+				Point p1 = line.getPoint(0);
+				Point p2 = line.getPoint(1);
+				if (p0.x == p1.x && p0.x == p2.x) {
+					// Have two vertical segments in a row
+					// get rid of the point between
+					line.removePoint(0);
+				} else if (p0.y == p1.y && p0.y == p2.y) {
+					// Have two horizontal segments in a row
+					// get rid of the point between
+					line.removePoint(0);
+				} else {
+					newLine.addPoint(line.removePoint(0));
+				}
+			}
+			while (line.size() > 0) {
+				newLine.addPoint(line.removePoint(0));
+			}
+			line.removeAllPoints();
+			line.addAll(newLine);
+		}
+		return line.size() != initialNumberOfPoints;
+	}
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Calculates geographic position of a point located outside the given
+	 * rectangle relative to the rectangle
+	 * 
+	 * @param p
+	 *            point outside of rectangle
+	 * @param r
+	 *            the rectangle
+	 * @return geographic position of the point relative to the recatangle
+	 */
+	@Deprecated
+	protected static int getOutisePointOffRectanglePosition2(Point p,
+			Rectangle r) {
+		int position = PositionConstants.NONE;
+		if (r.x > p.x) {
+			position |= PositionConstants.WEST;
+		} else if (r.x + r.width < p.x) {
+			position |= PositionConstants.EAST;
+		}
+		if (r.y > p.y) {
+			position |= PositionConstants.NORTH;
+		} else if (r.y + r.height < p.y) {
+			position |= PositionConstants.SOUTH;
+		}
+		return position;
+	}
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Determines whether the rectilinear line segment coming out of the shape
+	 * should be horizontal or vertical based on the anchor geographic position
+	 * relative to the shape
+	 * 
+	 * @param anchorRelativeLocation
+	 * @return
+	 */
+	@Deprecated
+	protected static int getOffShapeDirection2(int anchorRelativeLocation) {
+		if (anchorRelativeLocation == PositionConstants.EAST
+				|| anchorRelativeLocation == PositionConstants.WEST) {
+			return PositionConstants.HORIZONTAL;
+		} else if (anchorRelativeLocation == PositionConstants.NORTH
+				|| anchorRelativeLocation == PositionConstants.SOUTH) {
+			return PositionConstants.VERTICAL;
+		}
+		return PositionConstants.NONE;
+	}
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Returns a translation dimension for the anchor point. Translation
+	 * dimension translates the anchor point off the shape. The off shape
+	 * direction is specified by the relative to the shape geographic position
+	 * of the anchor
+	 * 
+	 * @param position
+	 *            relative to the shape geographic position of the anchor
+	 * @param xFactorValue
+	 *            translation value along x-axis
+	 * @param yFactorValue
+	 *            translation value along y-axis
+	 * @return
+	 */
+	@Deprecated
+	protected static Dimension getTranslationValue2(int position,
+			int xFactorValue, int yFactorValue) {
+		Dimension translationDimension = new Dimension();
+		if (position == PositionConstants.EAST) {
+			translationDimension.width = xFactorValue;
+		} else if (position == PositionConstants.SOUTH) {
+			translationDimension.height = yFactorValue;
+		} else if (position == PositionConstants.WEST) {
+			translationDimension.width = -xFactorValue;
+		} else if (position == PositionConstants.NORTH) {
+			translationDimension.height = -yFactorValue;
+		}
+		return translationDimension;
+	}
+
+	@Override
+	public void routeLine(Connection conn, int nestedRoutingDepth,
+			PointList newLine) {
+		super.routeLine(conn, nestedRoutingDepth, newLine);
+	}
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Source bounding rectangle relative to connection figure coordinates
+	 * 
+	 * @param conn
+	 *            connection
+	 * @return <code>PrecisionRectangle</code> source bounds relative to
+	 *         connection's coordinate system
+	 */
+	@Deprecated
+	protected PrecisionRectangle sourceBoundsRelativeToConnection2(
+			Connection conn) {
+		PrecisionRectangle source = new PrecisionRectangle(conn
+				.getSourceAnchor().getOwner().getBounds());
+		conn.getSourceAnchor().getOwner().translateToAbsolute(source);
+		conn.translateToRelative(source);
+		return source;
+	}
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Target bounding rectangle relative to connection figure coordinates
+	 * 
+	 * @param conn
+	 *            connection
+	 * @return <code>PrecisionRectangle</code> target bounds relative to
+	 *         connection's coordinate system
+	 */
+	@Deprecated
+	protected PrecisionRectangle targetBoundsRelativeToConnection2(
+			Connection conn) {
+		PrecisionRectangle target = new PrecisionRectangle(conn
+				.getTargetAnchor().getOwner().getBounds());
+		conn.getTargetAnchor().getOwner().translateToAbsolute(target);
+		conn.translateToRelative(target);
+		return target;
+	}
+
+	/**
+	 * [GMFRT] make protected in {@link RectilinearRouter}
+	 * <p/>
+	 * Determines the relative to rectangle geographic location of a point.
+	 * Example: If shape is closer to the the top edge of the rectangle location
+	 * would be north. Method used to determine which side of shape's bounding
+	 * rectangle is closer to connection's anchor point. All geometric
+	 * quantities must be in the same coordinate system.
+	 * 
+	 * @param anchorPoint
+	 *            location of the anchor point
+	 * @param rect
+	 *            bounding rectangle of the shape
+	 * @return
+	 */
+	@Deprecated
+	protected int getAnchorOffRectangleDirection2(Point anchorPoint,
+			Rectangle rect) {
+		int position = PositionConstants.NORTH;
+		int criteriaValue = Math.abs(anchorPoint.y - rect.y);
+		int tempCriteria = Math.abs(anchorPoint.y - rect.y - rect.height);
+		if (tempCriteria < criteriaValue) {
+			criteriaValue = tempCriteria;
+			position = PositionConstants.SOUTH;
+		}
+
+		tempCriteria = Math.abs(anchorPoint.x - rect.x);
+		if (tempCriteria < criteriaValue) {
+			criteriaValue = tempCriteria;
+			position = PositionConstants.WEST;
+		}
+
+		tempCriteria = Math.abs(anchorPoint.x - rect.x - rect.width);
+		if (tempCriteria < criteriaValue) {
+			criteriaValue = tempCriteria;
+			position = PositionConstants.EAST;
+		}
+
+		return position;
+	}
+
+	protected static <T extends Translatable> T makeRelative(IFigure figure, T t) {
+		figure.translateToRelative(t);
+		return t;
+	}
+
+	protected static <T extends Translatable> T makeAbsolute(IFigure figure, T t) {
+		figure.translateToAbsolute(t);
+		return t;
+	}
+}
\ No newline at end of file