package org.simantics.diagram.flag; import java.util.Collection; import java.util.HashSet; import java.util.Set; 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.utils.OrderedSetUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.RemoverUtil; import org.simantics.diagram.content.ConnectionUtil; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.g2d.elementclass.FlagClass.Type; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.structural.stubs.StructuralResource2; /** * A class that handles joining of diagram-local flag pairs into a direct * connections. * * @author Tuukka Lehtonen */ public class Joiner { Layer0 L0; DiagramResource DIA; StructuralResource2 STR; ModelingResources MOD; ConnectionUtil cu; public Joiner(ReadGraph graph) { this.L0 = Layer0.getInstance(graph); this.DIA = DiagramResource.getInstance(graph); this.STR = StructuralResource2.getInstance(graph); this.MOD = ModelingResources.getInstance(graph); this.cu = new ConnectionUtil(graph); } public void joinLocal(WriteGraph graph, Collection flags) throws DatabaseException { // Only those flags that are known to be removed during the joining of // two flags are added to this set to prevent processing them multiple times. Set removed = new HashSet(); for (Resource flag1 : flags) { Collection flag1Counterparts = FlagUtil.getCounterparts(graph, flag1); for (Resource flag2 : flag1Counterparts) { boolean flag1Removed = FlagUtil.countCounterparts(graph, flag1) <= 1; boolean flag2Removed = FlagUtil.countCounterparts(graph, flag2) <= 1; if (flag1Removed && !removed.add(flag1)) continue; if (flag2Removed && !removed.add(flag2)) continue; boolean switchFlags = !flag1Removed && flag2Removed; Resource f1 = switchFlags ? flag2 : flag1; Resource f2 = switchFlags ? flag1 : flag2; joinFlagPair(graph, f1, f2); } } } private boolean joinFlagPair(WriteGraph graph, Resource flag1, Resource flag2) throws DatabaseException { Type type1 = FlagUtil.getFlagType(graph, flag1); Type type2 = FlagUtil.getFlagType(graph, flag2); Resource connector1 = null; Resource connector2 = null; Resource connection1 = null; Resource connection2 = null; // #7781: prevent joining of flags where one of them or both // have no connections to them, i.e. are disconnected. // First ensure that both flags are connected to something, // even considering joining them. for (Resource connector : graph.getObjects(flag1, STR.IsConnectedTo)) { connector1 = graph.getPossibleObject(connector, DIA.AreConnected); connection1 = ConnectionUtil.getConnection(graph, connector1); } for (Resource connector : graph.getObjects(flag2, STR.IsConnectedTo)) { connector2 = graph.getPossibleObject(connector, DIA.AreConnected); connection2 = ConnectionUtil.getConnection(graph, connector2); } if (connection1 == null || connector1 == null || connection2 == null || connector2 == null) return false; // If a flag has more than 1 counterpart it must not be // removed because it is a merged flag that has multiple // connection joins attached to it. boolean removeFlag1 = FlagUtil.countCounterparts(graph, flag1) <= 1; boolean removeFlag2 = FlagUtil.countCounterparts(graph, flag2) <= 1; // Disconnect flags from their respective edges // This code relies on the fact that flag terminals are // functional and can only be connected once. This implies // that their :DIA.Connectors cannot have more than one // AreConnected relation. if (removeFlag1) { for (Resource connector : graph.getObjects(flag1, STR.IsConnectedTo)) { connector1 = graph.getSingleObject(connector, DIA.AreConnected); connection1 = ConnectionUtil.getConnection(graph, connector1); cu.removeConnectionPart(connector); } } if (removeFlag2) { for (Resource connector : graph.getObjects(flag2, STR.IsConnectedTo)) { connector2 = graph.getSingleObject(connector, DIA.AreConnected); connection2 = ConnectionUtil.getConnection(graph, connector2); cu.removeConnectionPart(connector); } } // Decide which connection to remove. The strategy is: // * always keep the connection that has an ElementToComponent relation. // * if there are no ElementToComponent relations, keep the connection on the OutputFlag side. // * if flag type information is not available, keep connection1. Resource connectionToKeep = connection1; Resource connectionToRemove = connection2; Resource hasElementToComponent1 = graph.getPossibleObject(connection1, MOD.ElementToComponent); Resource hasElementToComponent2 = graph.getPossibleObject(connection2, MOD.ElementToComponent); if (hasElementToComponent1 != null && hasElementToComponent2 != null) throw new UnsupportedOperationException("Both flag are connected with connections that have mapped components, can't decide which connection to remove in join operation"); if (hasElementToComponent2 != null || (type1 != Type.Out && type2 == Type.Out)) { connectionToKeep = connection2; connectionToRemove = connection1; } // Remove connection join and flags when necessary if (removeFlag1) for (Resource diagram : OrderedSetUtils.getOwnerLists(graph, flag1, DIA.Diagram)) OrderedSetUtils.remove(graph, diagram, flag1); if (removeFlag2) for (Resource diagram : OrderedSetUtils.getOwnerLists(graph, flag2, DIA.Diagram)) OrderedSetUtils.remove(graph, diagram, flag2); FlagUtil.disconnectFlag(graph, flag1); double[] transform1 = graph.getRelatedValue(flag1, DIA.HasTransform, Bindings.DOUBLE_ARRAY); double[] transform2 = graph.getRelatedValue(flag2, DIA.HasTransform, Bindings.DOUBLE_ARRAY); if (removeFlag1) RemoverUtil.remove(graph, flag1); if (removeFlag2) RemoverUtil.remove(graph, flag2); // Move connector from connection to remove to other connection for(Statement connectorStat : graph.getStatements(connectionToRemove, DIA.HasConnector)) { graph.deny(connectorStat); graph.claim(connectionToKeep, connectorStat.getPredicate(), connectorStat.getObject()); } for(Resource node : graph.getObjects(connectionToRemove, DIA.HasInteriorRouteNode)) { graph.deny(node, DIA.HasInteriorRouteNode_Inverse, connectionToRemove); graph.claim(node, DIA.HasInteriorRouteNode_Inverse, connectionToKeep); } // Remove obsolete connection cu.removeConnection(connectionToRemove); // Reconnect respective edges if(graph.isInstanceOf(connector1, DIA.RouteLine) && graph.isInstanceOf(connector2, DIA.RouteLine) && graph.getRelatedValue(connector1, DIA.IsHorizontal) == graph.getRelatedValue(connector2, DIA.IsHorizontal)) { boolean horizontal = graph.getRelatedValue(connector1, DIA.IsHorizontal); double position; if(horizontal) position = 0.5 * (transform1[4] + transform2[4]); else position = 0.5 * (transform1[5] + transform2[5]); Resource intermediateRouteLine = graph.newResource(); graph.claim(intermediateRouteLine, L0.InstanceOf, DIA.RouteLine); graph.claimLiteral(intermediateRouteLine, DIA.IsHorizontal, !horizontal); graph.claimLiteral(intermediateRouteLine, DIA.HasPosition, position); graph.claim(connectionToKeep, DIA.HasInteriorRouteNode, intermediateRouteLine); graph.claim(connector1, DIA.AreConnected, intermediateRouteLine); graph.claim(connector2, DIA.AreConnected, intermediateRouteLine); } else graph.claim(connector1, DIA.AreConnected, connector2); return true; } public static void joinFlagsLocal(WriteGraph graph, Collection flags) throws DatabaseException { new Joiner(graph).joinLocal(graph, flags); } }