package org.simantics.diagram.flag;
import gnu.trove.map.hash.TObjectIntHashMap;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import org.simantics.databoard.Bindings;
import org.simantics.db.ReadGraph;
import org.simantics.db.Resource;
import org.simantics.db.Statement;
import org.simantics.db.WriteGraph;
import org.simantics.db.common.request.PossibleTypedParent;
import org.simantics.db.common.utils.NameUtils;
import org.simantics.db.common.utils.OrderedSetUtils;
import org.simantics.db.exception.DatabaseException;
import org.simantics.db.service.SerialisationSupport;
import org.simantics.diagram.adapter.RouteGraphUtils;
import org.simantics.diagram.connection.RouteGraph;
import org.simantics.diagram.connection.RouteLine;
import org.simantics.diagram.connection.RouteNode;
import org.simantics.diagram.connection.RoutePoint;
import org.simantics.diagram.connection.RouteTerminal;
import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
import org.simantics.diagram.content.ConnectionUtil;
import org.simantics.diagram.stubs.DiagramResource;
import org.simantics.diagram.synchronization.graph.AddElement;
import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
import org.simantics.diagram.synchronization.graph.RouteGraphModification;
import org.simantics.g2d.elementclass.FlagClass;
import org.simantics.layer0.Layer0;
import org.simantics.modeling.ModelingResources;
import org.simantics.structural.stubs.StructuralResource2;
/**
* A class that handles splitting a route graph connection in two with diagram
* local flags.
*
* The connection splitting process consists of the following steps:
*
* - Disconnect the end points of the selected connection edge segment (SEG).
* An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This
* operation will always split a valid connection into two separate parts.
* - Calculate the contents of the two separated connection parts (branch
* points and connectors) and decide which part to leave with the existing
* connection (=P1) and which part to move into a new connection (=P2). The
* current strategy is to move the parts that
* do not contain output terminal connectors into a new connection.
* This works well with diagram to composite mappings where output terminals
* generate modules behind connections.
* - Create new connection C' with the same type and STR.HasConnectionType as
* the original connection C.
* - Copy connection routing settings from C to C'.
* - Move P2 into C'.
* - Create two new flag elements into the same diagram, set their type and
* interpolate a proper transformation for both along the existing connection
* line.
* - Connect the new flags to begin(SEG) and end(SEG) connectors.
* - Join the flags together.
*
*
* @author Tuukka Lehtonen
* @author Hannu Niemistö
*/
public class RouteGraphConnectionSplitter {
private final static boolean DEBUG = false;
Layer0 L0;
DiagramResource DIA;
StructuralResource2 STR;
ModelingResources MOD;
SerialisationSupport ss;
public RouteGraphConnectionSplitter(ReadGraph graph) {
this.L0 = Layer0.getInstance(graph);
this.DIA = DiagramResource.getInstance(graph);
this.STR = StructuralResource2.getInstance(graph);
this.MOD = ModelingResources.getInstance(graph);
this.ss = graph.getService(SerialisationSupport.class);
}
public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {
// Create modification writer
RouteGraphModification modis = new RouteGraphModification(ss, rg);
TObjectIntHashMap idMap = modis.getIdMap();
// Find the edge to disconnect in the graph.
// Bisect the nearest route line.
RouteLine line = SplittedRouteGraph.findNearestLine(rg, splitCanvasPos);
if (DEBUG)
rg.print();
if (line == null)
return;
if (DEBUG) {
line.print(System.out);
for (RoutePoint rp : line.getPoints())
System.out.println("RP: " + rp.getX() + ", " + rp.getY());
}
// Get exact intersection point on the line
double isectX = splitCanvasPos.getX();
double isectY = splitCanvasPos.getY();
SplittedRouteGraph srg;
if (line.isHorizontal()) {
isectY = line.getPosition();
srg = rg.splitGraph(line, isectX);
}
else {
isectX = line.getPosition();
srg = rg.splitGraph(line, isectY);
}
if (DEBUG)
System.out.println(srg);
// Disconnect
if(rg.isSimpleConnection()) {
RouteNode na = srg.terminals1.iterator().next();
RouteNode nb = srg.terminals2.iterator().next();
Resource a = ss.getResource((Long)na.getData());
Resource b = ss.getResource((Long)nb.getData());
graph.deny(a, DIA.AreConnected, b);
modis.addModi(new RouteGraphModification.RemoveLink(
idMap.get(na), idMap.get(nb)
));
}
else if(srg.splitLine.isTransient()) {
RouteTerminal terminal = srg.splitLine.getTerminal();
Resource connector = ss.getResource((Long)terminal.getData());
graph.deny(connector, DIA.AreConnected);
modis.addModi(new RouteGraphModification.RemoveLink(
idMap.get(terminal), idMap.get(terminal.getLine())
));
}
else {
graph.deny(ss.getResource((Long)srg.splitLine.getData()));
// TODO remove links
modis.addModi(new RouteGraphModification.RemoveLine(
idMap.get(srg.splitLine)
));
}
ArrayList interfaceNodes1Resources = new ArrayList(srg.interfaceNodes1.size());
for(RouteNode n : srg.interfaceNodes1)
interfaceNodes1Resources.add(ss.getResource((Long)n.getData()));
ArrayList interfaceNodes2Resources = new ArrayList(srg.interfaceNodes2.size());
for(RouteNode n : srg.interfaceNodes2)
interfaceNodes2Resources.add(ss.getResource((Long)n.getData()));
ArrayList lines2Resources = new ArrayList(srg.lines2.size());
for(RouteLine n : srg.lines2)
lines2Resources.add(ss.getResource((Long)n.getData()));
ArrayList terminals1Resources = new ArrayList(srg.terminals1.size());
for(RouteTerminal n : srg.terminals1)
terminals1Resources.add(ss.getResource((Long)n.getData()));
ArrayList terminals2Resources = new ArrayList(srg.terminals2.size());
for(RouteTerminal n : srg.terminals2)
terminals2Resources.add(ss.getResource((Long)n.getData()));
doSplit(graph, connection,
interfaceNodes1Resources,
interfaceNodes2Resources,
lines2Resources,
terminals1Resources,
terminals2Resources,
line.isHorizontal(),
isectX, isectY);
modis.addModi(new RouteGraphModification.Split(
modis.toIds(interfaceNodes1Resources),
modis.toIds(interfaceNodes2Resources),
modis.toIds(lines2Resources),
modis.toIds(terminals1Resources),
modis.toIds(terminals2Resources),
line.isHorizontal(),
isectX, isectY
));
}
public void doSplit(WriteGraph graph,
Resource connection,
ArrayList interfaceNodes1Resources,
ArrayList interfaceNodes2Resources,
ArrayList lines2Resources,
ArrayList terminals1Resources,
ArrayList terminals2Resources,
boolean isHorizontal,
double isectX, double isectY) throws DatabaseException {
if (DEBUG) {
System.out.println("doSplit:");
System.out.println(NameUtils.getSafeName(graph, connection, true));
for (Resource i : interfaceNodes1Resources)
System.out.println("i1: " + NameUtils.getSafeName(graph, i, true));
for (Resource i : interfaceNodes2Resources)
System.out.println("i2: " + NameUtils.getSafeName(graph, i, true));
for (Resource l : lines2Resources)
System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true));
for (Resource t : terminals1Resources)
System.out.println("t1: " + NameUtils.getSafeName(graph, t, true));
for (Resource t : terminals2Resources)
System.out.println("t2: " + NameUtils.getSafeName(graph, t, true));
System.out.println("is horizontal: " + isHorizontal);
System.out.println("@(x,y): " + isectX + ", " + isectY);
}
ConnectionUtil cu = new ConnectionUtil(graph);
Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
Resource connectionType = graph.getSingleType(connection, DIA.Connection);
Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
Resource newConnection = cu.newConnection(diagram, connectionType);
if (hasConnectionType != null)
graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);
// Give running name to connection increment the counter attached to the diagram.
AddElement.claimFreshElementName(graph, diagram, newConnection);
// WORKAROUND for mapping problems:
// If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed
// to give mapping a chance to fix them properly.
removeFlagJoins(graph, cu, connection, terminals1Resources);
removeFlagJoins(graph, cu, connection, terminals2Resources);
// Move route nodes to correct connections
for(Resource rn : lines2Resources) {
// TODO: use same predicate that was removed
graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);
graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);
}
for(Resource rn : terminals2Resources) {
Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);
Resource predicate = stat.getPredicate();
graph.deny(rn, predicate);
graph.claim(rn, predicate, newConnection);
}
// 1 = output, 2 = input
FlagClass.Type type1, type2;
FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
String commonLabel = scheme.generateLabel(graph, diagram);
// Create flags and connect both disconnected ends to them.
Point2D pos1, pos2;
double theta;
double flagDist = 3.0;
if(isHorizontal) {
theta = 0.0;
pos1 = new Point2D.Double(isectX-flagDist, isectY);
pos2 = new Point2D.Double(isectX+flagDist, isectY);
}
else {
theta = Math.PI*0.5;
pos1 = new Point2D.Double(isectX, isectY-flagDist);
pos2 = new Point2D.Double(isectX, isectY+flagDist);
}
// Chooses flag directions
{
@SuppressWarnings("unused")
int inputs1 = 0, outputs1 = 0;
for(Resource connector : terminals1Resources) {
if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
++inputs1;
else
++outputs1;
}
@SuppressWarnings("unused")
int inputs2 = 0, outputs2 = 0;
for(Resource connector : terminals2Resources) {
if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
++inputs2;
else
++outputs2;
}
if(outputs1 == 0) {
type1 = FlagClass.Type.In;
type2 = FlagClass.Type.Out;
theta += Math.PI;
}
else {
type1 = FlagClass.Type.Out;
type2 = FlagClass.Type.In;
}
if (DEBUG) {
System.out.println("inputs1: " + inputs1);
System.out.println("outputs1: " + outputs1);
System.out.println("=> type1: " + type1);
System.out.println("inputs2: " + inputs2);
System.out.println("outputs2: " + outputs2);
System.out.println("=> type2: " + type2);
}
}
Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);
Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);
if (DEBUG) {
System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));
System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));
}
// System.out.println("conn1: " + NameUtils.getSafeLabel(graph, type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));
// System.out.println("conn2: " + NameUtils.getSafeLabel(graph, type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));
Resource flagConnector1 = cu.newConnector(connection,
type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);
Resource flagConnector2 = cu.newConnector(newConnection,
type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);
graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);
graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);
double position = isHorizontal ? isectY : isectX;
connectFlag(graph, isHorizontal, position, connection, flagConnector1,
interfaceNodes1Resources);
connectFlag(graph, isHorizontal, position, newConnection, flagConnector2,
interfaceNodes2Resources);
FlagUtil.join(graph, flag1, flag2);
// Move mapping relations to new connection if necessary
if(type1 == FlagClass.Type.In) {
moveStatements(graph, connection, newConnection, MOD.ElementToComponent);
moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnection);
moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnectionSpecial);
FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(newConnection, MOD.DiagramConnectionToConnection));
}
else
FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));
}
/**
* A workaround for problems with mapping not removing the necessary
* STR.Joins relations from flags after a split.
*
* @param graph
* @param cu
* @param connection
* @param connectors
* @throws DatabaseException
*/
private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList connectors) throws DatabaseException {
for (Resource connector : connectors) {
Resource e = cu.getConnectedComponent(connection, connector);
if (graph.isInstanceOf(e, DIA.Flag)) {
Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));
if (diagram == null)
continue;
for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {
Collection joinsComposites = graph.getObjects(join, STR.JoinsComposite);
if (joinsComposites.size() == 1) {
// Only remove joins that are internal to a diagram.
graph.deny(join, STR.Joins);
} else if (joinsComposites.size() == 2) {
// Only remove the joins relations that refer to
// connections that are part of the same diagram.
for (Resource joins : graph.getObjects(join, STR.Joins)) {
Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);
if (diagramConnection == null)
continue;
Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));
if (diagram.equals(partOfDiagram)) {
graph.deny(join, STR.Joins, joins);
}
}
}
}
}
}
}
private static void moveStatements(WriteGraph graph, Resource from, Resource to, Resource relation) throws DatabaseException {
if(from.equals(to))
return;
for(Statement stat : graph.getStatements(from, relation))
if(stat.getSubject().equals(from))
graph.claim(to, stat.getPredicate(), stat.getObject());
graph.deny(from, relation);
}
private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection interfaceNodes)
throws DatabaseException {
if(interfaceNodes.size() > 1) {
Resource routeLine = graph.newResource();
graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);
graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);
graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);
graph.claimLiteral(routeLine, DIA.HasPosition, position);
graph.claim(routeLine, DIA.AreConnected, flagConnector);
flagConnector = routeLine;
}
for(Resource rn : interfaceNodes) {
graph.claim(flagConnector, DIA.AreConnected, rn);
}
}
private AffineTransform getFlagTransform(Point2D pos, double theta) {
AffineTransform at = AffineTransform.getTranslateInstance(pos.getX(), pos.getY());
at.rotate(theta);
return at;
}
private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {
DiagramResource DIA = DiagramResource.getInstance(graph);
Resource flag = graph.newResource();
graph.claim(flag, L0.InstanceOf, null, DIA.Flag);
AddElement.claimFreshElementName(graph, diagram, flag);
graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);
DiagramGraphUtil.setTransform(graph, flag, tr);
if (type != null)
FlagUtil.setFlagType(graph, flag, type);
if (label != null)
graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
OrderedSetUtils.add(graph, diagram, flag);
return flag;
}
public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {
RouteGraph rg = RouteGraphUtils.load(graph, null, connection);
new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));
}
}