private void initialize(final LGraph graph) {
    layeredGraph = graph;
    int layerCount = graph.getLayers().size();
    bestNodeOrder = new LNode[layerCount][];
    currentNodeOrder = new LNode[layerCount][];
    originalNodeOrder = new LNode[layerCount][];

    ListIterator<Layer> layerIter = graph.getLayers().listIterator();
    while (layerIter.hasNext()) {
      Layer layer = layerIter.next();

      int layerNodeCount = layer.getNodes().size();
      assert layerNodeCount > 0;

      int layerIndex = layerIter.previousIndex();
      bestNodeOrder[layerIndex] = new LNode[layerNodeCount];
      currentNodeOrder[layerIndex] = new LNode[layerNodeCount];
      originalNodeOrder[layerIndex] = new LNode[layerNodeCount];

      ListIterator<LNode> nodeIter = layer.getNodes().listIterator();
      int id = 0;
      while (nodeIter.hasNext()) {
        LNode node = nodeIter.next();
        node.id = id++;

        currentNodeOrder[layerIndex][nodeIter.previousIndex()] = node;
        bestNodeOrder[layerIndex][nodeIter.previousIndex()] = node;
        originalNodeOrder[layerIndex][nodeIter.previousIndex()] = node;
      }
    }
    crossingCounter = new AllCrossingsCounter(currentNodeOrder);
    if (greedySwitchType.useHyperedgeCounter()) {
      crossingCounter.useHyperedgeCounter();
    }
  }
  /**
   * (Re-)Adds the ports to the node of a component, having at least one port with a non-loop edge.
   * The ports are added as the direct neighbor of a port they are connected to via an edge.
   *
   * @param component The component whose ports have to get re-added.
   */
  public void addComponentWithNonLoopEdges(final ConnectedSelfLoopComponent component) {
    // the node we are working on
    final LNode node = component.getNode();
    // Set of all ports of the components that are hidden. Initially all ports that CAN be hidden.
    final Set<LPort> hiddenPorts = Sets.newHashSet(component.getHidablePorts());
    // Iterator holding all ports that already have a portSide specified. Initially these are
    // all ports with an non-loop edge connected to them. While processing the elements, we will
    // add new ports to this Iterator. So we need a ListIterator.
    final ListIterator<LPort> portsWithSideIter =
        Lists.newLinkedList(component.getNonLoopPorts()).listIterator();

    while (portsWithSideIter.hasNext()) {
      final LPort portWithSide = portsWithSideIter.next();
      if (portWithSide.getOutgoingEdges().isEmpty()) {
        // inbound port
        for (final LEdge edgeWithHiddenPort : portWithSide.getIncomingEdges()) {
          final LPort hiddenPort = edgeWithHiddenPort.getSource();
          if (hiddenPorts.contains(hiddenPort)) {
            final ListIterator<LPort> nodePortIter = node.getPorts().listIterator();
            LPort portOnNode = nodePortIter.next();
            // find the port with side ...
            while (!portOnNode.equals(portWithSide)) {
              portOnNode = nodePortIter.next();
            }
            // ... and add the hidden port on it's right side
            nodePortIter.add(hiddenPort);
            portsWithSideIter.add(hiddenPort);
            setSideOfPort(hiddenPort, portWithSide.getSide());
            // ensure the next element will be our new added element
            portsWithSideIter.previous();
            portsWithSideIter.previous();
            hiddenPorts.remove(hiddenPort);
          }
        }
      } else {
        // outbound port
        for (final LEdge edgeWithHiddenPort : portWithSide.getOutgoingEdges()) {
          final LPort hiddenPort = edgeWithHiddenPort.getTarget();
          if (hiddenPorts.contains(hiddenPort)) {
            final ListIterator<LPort> nodePortIter = node.getPorts().listIterator();
            LPort portOnNode = nodePortIter.next();
            // find the port with side ...
            while (!portOnNode.equals(portWithSide)) {
              portOnNode = nodePortIter.next();
            }
            // ... and add the hidden port on it's left side
            nodePortIter.previous();
            nodePortIter.add(hiddenPort);
            portsWithSideIter.add(hiddenPort);
            setSideOfPort(hiddenPort, portWithSide.getSide());
            // ensure the next element will be our new added element
            portsWithSideIter.previous();
            portsWithSideIter.previous();
            hiddenPorts.remove(hiddenPort);
          }
        }
      }
    }
  }
    /**
     * Splits this linear segment before the given node. The returned segment contains all nodes
     * from the given node onward, with their ID set to the new segment's ID. Those nodes are
     * removed from this segment.
     *
     * @param node the node to split the segment at.
     * @param newId the new segment's id.
     * @return new linear segment with ID {@code -1} and all nodes from {@code node} onward.
     */
    LinearSegment split(final LNode node, final int newId) {
      int nodeIndex = nodes.indexOf(node);

      // Prepare the new segment
      LinearSegment newSegment = new LinearSegment();
      newSegment.id = newId;

      // Move nodes to the new segment
      ListIterator<LNode> iterator = nodes.listIterator(nodeIndex);
      while (iterator.hasNext()) {
        LNode movedNode = iterator.next();
        movedNode.id = newId;
        newSegment.nodes.add(movedNode);
        iterator.remove();
      }

      return newSegment;
    }
  /**
   * Merge regions by testing whether they would overlap after applying the deflection.
   *
   * @param layeredGraph the layered graph
   * @return true if any two regions have been merged
   */
  private boolean mergeRegions(final LGraph layeredGraph) {

    boolean changed = false;
    double threshold = OVERLAP_DETECT * spacings.nodeSpacing * spacings.inLayerSpacingFactor;
    for (Layer layer : layeredGraph) {
      Iterator<LNode> nodeIter = layer.getNodes().iterator();

      // Get the first node
      LNode node1 = nodeIter.next();
      LinearSegment region1 = linearSegments[node1.id].region();

      // While there are still nodes following the current node
      while (nodeIter.hasNext()) {
        // Test whether nodes have different regions
        LNode node2 = nodeIter.next();
        LinearSegment region2 = linearSegments[node2.id].region();

        if (region1 != region2) {
          // Calculate how much space is allowed between the nodes
          double spacing = spacings.getVerticalSpacing(node1, node2);

          double node1Extent =
              node1.getPosition().y
                  + node1.getSize().y
                  + node1.getMargin().bottom
                  + region1.deflection
                  + spacing;
          double node2Extent = node2.getPosition().y - node2.getMargin().top + region2.deflection;

          // Test if the nodes are overlapping
          if (node1Extent > node2Extent + threshold) {
            // Merge the first region under the second top level segment
            int weightSum = region1.weight + region2.weight;
            assert weightSum > 0;
            region2.deflection =
                (region2.weight * region2.deflection + region1.weight * region1.deflection)
                    / weightSum;
            region2.weight = weightSum;
            region1.refSegment = region2;
            changed = true;
          }
        }

        node1 = node2;
        region1 = region2;
      }
    }
    return changed;
  }
  /**
   * Post-process the balanced placement by moving linear segments where obvious improvements can be
   * made.
   *
   * @param layeredGraph the layered graph
   */
  private void postProcess(final LGraph layeredGraph) {

    // process each linear segment independently
    for (LinearSegment segment : linearSegments) {
      double minRoomAbove = Integer.MAX_VALUE, minRoomBelow = Integer.MAX_VALUE;

      for (LNode node : segment.nodes) {
        double roomAbove, roomBelow;
        int index = node.getIndex();

        // determine the amount by which the linear segment can be moved up without overlap
        if (index > 0) {
          LNode neighbor = node.getLayer().getNodes().get(index - 1);
          float spacing = spacings.getVerticalSpacing(node, neighbor);
          roomAbove =
              node.getPosition().y
                  - node.getMargin().top
                  - (neighbor.getPosition().y
                      + neighbor.getSize().y
                      + neighbor.getMargin().bottom
                      + spacing);
        } else {
          roomAbove = node.getPosition().y - node.getMargin().top;
        }
        minRoomAbove = Math.min(roomAbove, minRoomAbove);

        // determine the amount by which the linear segment can be moved down without
        // overlap
        if (index < node.getLayer().getNodes().size() - 1) {
          LNode neighbor = node.getLayer().getNodes().get(index + 1);
          float spacing = spacings.getVerticalSpacing(node, neighbor);
          roomBelow =
              neighbor.getPosition().y
                  - neighbor.getMargin().top
                  - (node.getPosition().y + node.getSize().y + node.getMargin().bottom + spacing);
        } else {
          roomBelow = 2 * node.getPosition().y;
        }
        minRoomBelow = Math.min(roomBelow, minRoomBelow);
      }

      double minDisplacement = Integer.MAX_VALUE;
      boolean foundPlace = false;

      // determine the minimal displacement that would make one incoming edge straight
      LNode firstNode = segment.nodes.get(0);
      for (LPort target : firstNode.getPorts()) {
        double pos = firstNode.getPosition().y + target.getPosition().y + target.getAnchor().y;
        for (LEdge edge : target.getIncomingEdges()) {
          LPort source = edge.getSource();
          double d =
              source.getNode().getPosition().y
                  + source.getPosition().y
                  + source.getAnchor().y
                  - pos;
          if (Math.abs(d) < Math.abs(minDisplacement)
              && Math.abs(d) < (d < 0 ? minRoomAbove : minRoomBelow)) {
            minDisplacement = d;
            foundPlace = true;
          }
        }
      }

      // determine the minimal displacement that would make one outgoing edge straight
      LNode lastNode = segment.nodes.get(segment.nodes.size() - 1);
      for (LPort source : lastNode.getPorts()) {
        double pos = lastNode.getPosition().y + source.getPosition().y + source.getAnchor().y;
        for (LEdge edge : source.getOutgoingEdges()) {
          LPort target = edge.getTarget();
          double d =
              target.getNode().getPosition().y
                  + target.getPosition().y
                  + target.getAnchor().y
                  - pos;
          if (Math.abs(d) < Math.abs(minDisplacement)
              && Math.abs(d) < (d < 0 ? minRoomAbove : minRoomBelow)) {
            minDisplacement = d;
            foundPlace = true;
          }
        }
      }

      // if such a displacement could be found, apply it to the whole linear segment
      if (foundPlace && minDisplacement != 0) {
        for (LNode node : segment.nodes) {
          node.getPosition().y += minDisplacement;
        }
      }
    }
  }
  /**
   * Calculate the force acting on the given linear segment. The force is stored in the segment's
   * deflection field.
   *
   * @param segment the linear segment whose force is to be calculated
   * @param incoming whether incoming edges should be considered
   * @param outgoing whether outgoing edges should be considered
   * @param deflectionDampening factor by which deflections are dampened
   */
  private void calcDeflection(
      final LinearSegment segment,
      final boolean incoming,
      final boolean outgoing,
      final double deflectionDampening) {

    double segmentDeflection = 0;
    int nodeWeightSum = 0;
    for (LNode node : segment.nodes) {
      double nodeDeflection = 0;
      int edgeWeightSum = 0;
      int inputPrio = incoming ? node.getProperty(INPUT_PRIO) : Integer.MIN_VALUE;
      int outputPrio = outgoing ? node.getProperty(OUTPUT_PRIO) : Integer.MIN_VALUE;
      int minPrio = Math.max(inputPrio, outputPrio);

      // Calculate force for every port/edge
      for (LPort port : node.getPorts()) {
        double portpos = node.getPosition().y + port.getPosition().y + port.getAnchor().y;
        if (outgoing) {
          for (LEdge edge : port.getOutgoingEdges()) {
            LPort otherPort = edge.getTarget();
            LNode otherNode = otherPort.getNode();
            if (segment != linearSegments[otherNode.id]) {
              int otherPrio =
                  Math.max(otherNode.getProperty(INPUT_PRIO), otherNode.getProperty(OUTPUT_PRIO));
              int prio = edge.getProperty(InternalProperties.PRIORITY);
              if (prio >= minPrio && prio >= otherPrio) {
                nodeDeflection +=
                    otherNode.getPosition().y
                        + otherPort.getPosition().y
                        + otherPort.getAnchor().y
                        - portpos;
                edgeWeightSum++;
              }
            }
          }
        }

        if (incoming) {
          for (LEdge edge : port.getIncomingEdges()) {
            LPort otherPort = edge.getSource();
            LNode otherNode = otherPort.getNode();
            if (segment != linearSegments[otherNode.id]) {
              int otherPrio =
                  Math.max(otherNode.getProperty(INPUT_PRIO), otherNode.getProperty(OUTPUT_PRIO));
              int prio = edge.getProperty(InternalProperties.PRIORITY);
              if (prio >= minPrio && prio >= otherPrio) {
                nodeDeflection +=
                    otherNode.getPosition().y
                        + otherPort.getPosition().y
                        + otherPort.getAnchor().y
                        - portpos;
                edgeWeightSum++;
              }
            }
          }
        }
      }

      // Avoid division by zero
      if (edgeWeightSum > 0) {
        segmentDeflection += nodeDeflection / edgeWeightSum;
        nodeWeightSum++;
      }
    }
    if (nodeWeightSum > 0) {
      segment.deflection = deflectionDampening * segmentDeflection / nodeWeightSum;
      segment.weight = nodeWeightSum;
    } else {
      segment.deflection = 0;
      segment.weight = 0;
    }
  }
  /**
   * Balance the initial placement by force-based movement of regions. First perform
   * <em>pendulum</em> iterations, where only one direction of edges is considered, then
   * <em>rubber</em> iterations, where both incoming and outgoing edges are considered. In each
   * iteration first determine the <em>deflection</em> of each linear segment, i.e. the optimal
   * position delta that leads to a balanced placement with respect to its adjacent segments. Then
   * merge regions that touch each other, building mean values of the involved deflections, and
   * finally apply the resulting deflection values to all segments. The iterations stop when no
   * further improvement is done.
   *
   * @param layeredGraph a layered graph
   */
  private void balancePlacement(final LGraph layeredGraph) {
    double deflectionDampening =
        layeredGraph.getProperty(Properties.LINEAR_SEGMENTS_DEFLECTION_DAMPENING).doubleValue();

    // Determine a suitable number of pendulum iterations
    int thoroughness = layeredGraph.getProperty(Properties.THOROUGHNESS);
    int pendulumIters = PENDULUM_ITERS;
    int finalIters = FINAL_ITERS;
    double threshold = THRESHOLD_FACTOR / thoroughness;

    // Iterate the balancing
    boolean ready = false;
    Mode mode = Mode.FORW_PENDULUM;
    double lastTotalDeflection = Integer.MAX_VALUE;
    do {

      // Calculate force for every linear segment
      boolean incoming = mode != Mode.BACKW_PENDULUM;
      boolean outgoing = mode != Mode.FORW_PENDULUM;
      double totalDeflection = 0;
      for (LinearSegment segment : linearSegments) {
        segment.refSegment = null;
        calcDeflection(segment, incoming, outgoing, deflectionDampening);
        totalDeflection += Math.abs(segment.deflection);
      }

      // Merge linear segments to form regions
      boolean merged;
      do {
        merged = mergeRegions(layeredGraph);
      } while (merged);

      // Move the nodes according to the deflection value of their region
      for (LinearSegment segment : linearSegments) {
        double deflection = segment.region().deflection;
        if (deflection != 0) {
          for (LNode node : segment.nodes) {
            node.getPosition().y += deflection;
          }
        }
      }

      // Update the balancing mode
      if (mode == Mode.FORW_PENDULUM || mode == Mode.BACKW_PENDULUM) {
        pendulumIters--;
        if (pendulumIters <= 0
            && (totalDeflection < lastTotalDeflection || -pendulumIters > thoroughness)) {
          mode = Mode.RUBBER;
          lastTotalDeflection = Integer.MAX_VALUE;
        } else if (mode == Mode.FORW_PENDULUM) {
          mode = Mode.BACKW_PENDULUM;
          lastTotalDeflection = totalDeflection;
        } else {
          mode = Mode.FORW_PENDULUM;
          lastTotalDeflection = totalDeflection;
        }
      } else {
        ready =
            totalDeflection >= lastTotalDeflection
                || lastTotalDeflection - totalDeflection < threshold;
        lastTotalDeflection = totalDeflection;
        if (ready) {
          finalIters--;
        }
      }
    } while (!(ready && finalIters <= 0));
  }
  /**
   * Creates an unbalanced placement for the sorted linear segments.
   *
   * @param layeredGraph the layered graph to create an unbalanced placement for.
   */
  private void createUnbalancedPlacement(final LGraph layeredGraph) {
    // How many nodes are currently placed in each layer
    int[] nodeCount = new int[layeredGraph.getLayers().size()];

    // The type of the node most recently placed in a given layer
    NodeType[] recentNodeType = new NodeType[layeredGraph.getLayers().size()];

    // Iterate through the linear segments (in proper order!) and place them
    for (LinearSegment segment : linearSegments) {
      // Determine the uppermost placement for the linear segment
      double uppermostPlace = 0.0f;
      for (LNode node : segment.nodes) {
        NodeType nodeType = node.getType();
        int layerIndex = node.getLayer().getIndex();
        nodeCount[layerIndex]++;

        // Calculate how much space to leave between the linear segment and the last
        // node of the given layer
        float spacing = spacings.edgeEdgeSpacing * spacings.inLayerSpacingFactor;
        if (nodeCount[layerIndex] > 0) {
          if (recentNodeType[layerIndex] != null) {
            spacing = spacings.getVerticalSpacing(recentNodeType[layerIndex], nodeType);
          }
        }

        uppermostPlace = Math.max(uppermostPlace, node.getLayer().getSize().y + spacing);
      }

      // Apply the uppermost placement to all elements
      for (LNode node : segment.nodes) {
        // Set the node position
        node.getPosition().y = uppermostPlace + node.getMargin().top;

        // Adjust layer size
        Layer layer = node.getLayer();
        layer.getSize().y =
            uppermostPlace + node.getMargin().top + node.getSize().y + node.getMargin().bottom;

        recentNodeType[layer.getIndex()] = node.getType();
      }
    }
  }
  /**
   * Put a node into the given linear segment and check for following parts of a long edge.
   *
   * @param node the node to put into the linear segment
   * @param segment a linear segment
   * @return {@code true} if the given node was not already part of another segment and was thus
   *     added to the given segment.
   */
  private boolean fillSegment(final LNode node, final LinearSegment segment) {
    NodeType nodeType = node.getType();

    // handle initial big nodes as big node type
    if (node.getProperty(InternalProperties.BIG_NODE_INITIAL)) {
      nodeType = NodeType.BIG_NODE;
    }

    if (node.id >= 0) {
      // The node is already part of another linear segment
      return false;
    } else if (segment.nodeType != null
        && (nodeType == NodeType.BIG_NODE && nodeType != segment.nodeType)) {
      // Big nodes are not allowed to share a linear segment with other dummy nodes
      return false;
    } else {
      // Add the node to the given linear segment
      node.id = segment.id;
      segment.nodes.add(node);
    }
    segment.nodeType = nodeType;

    if (nodeType == NodeType.LONG_EDGE
        || nodeType == NodeType.NORTH_SOUTH_PORT
        || nodeType == NodeType.BIG_NODE) {

      // This is a LONG_EDGE, NORTH_SOUTH_PORT or BIG_NODE dummy; check if any of its
      // successors are of one of these types too. If so, we can form a linear segment
      // with one of them. (not with more than one, though)
      // Note 1: LONG_EDGES and NORTH_SOUTH_PORTs can share a common linear segment
      // Note 2: we must take care not to make a segment out of nodes that are in the same layer
      // Note 3: for BIG_NODEs also the first BIG_NODE_INITIAL which is no actual dummy node has
      // to be considered here
      for (LPort sourcePort : node.getPorts()) {
        for (LPort targetPort : sourcePort.getSuccessorPorts()) {
          LNode targetNode = targetPort.getNode();
          NodeType targetNodeType = targetNode.getType();

          if (node.getLayer() != targetNode.getLayer()) {
            if (nodeType == NodeType.BIG_NODE) {
              // current AND the next node are BIG_NODE dummies
              if (targetNodeType == NodeType.BIG_NODE) {
                if (fillSegment(targetNode, segment)) {
                  // We just added another node to this node's linear segment.
                  // That's quite enough.
                  return true;
                }
              }
            } else {
              // current no bignode and next node is LONG_EDGE and NORTH_SOUTH_PORT
              if (targetNodeType == NodeType.LONG_EDGE
                  || targetNodeType == NodeType.NORTH_SOUTH_PORT) {
                if (fillSegment(targetNode, segment)) {
                  // We just added another node to this node's linear segment.
                  // That's quite enough.
                  return true;
                }
              }
            }
          }
        }
      }
    }

    return true;
  }
  /**
   * Sorts the linear segments of the given layered graph by finding a topological ordering in the
   * corresponding segment ordering graph.
   *
   * @param layeredGraph layered graph to process
   * @return a sorted array of linear segments
   */
  private LinearSegment[] sortLinearSegments(final LGraph layeredGraph) {
    // set the identifier and input / output priority for all nodes
    List<LinearSegment> segmentList = Lists.newArrayList();
    for (Layer layer : layeredGraph) {
      for (LNode node : layer) {
        node.id = -1;
        int inprio = Integer.MIN_VALUE, outprio = Integer.MIN_VALUE;
        for (LPort port : node.getPorts()) {
          for (LEdge edge : port.getIncomingEdges()) {
            int prio = edge.getProperty(InternalProperties.PRIORITY);
            inprio = Math.max(inprio, prio);
          }
          for (LEdge edge : port.getOutgoingEdges()) {
            int prio = edge.getProperty(InternalProperties.PRIORITY);
            outprio = Math.max(outprio, prio);
          }
        }
        node.setProperty(INPUT_PRIO, inprio);
        node.setProperty(OUTPUT_PRIO, outprio);
      }
    }

    // create linear segments for the layered graph, ignoring odd port side dummies
    int nextLinearSegmentID = 0;
    for (Layer layer : layeredGraph) {
      for (LNode node : layer) {
        // Test for the node ID; calls to fillSegment(...) may have caused the node ID
        // to be != -1.
        if (node.id < 0) {
          LinearSegment segment = new LinearSegment();
          segment.id = nextLinearSegmentID++;
          fillSegment(node, segment);
          segmentList.add(segment);
        }
      }
    }

    // create and initialize segment ordering graph
    List<List<LinearSegment>> outgoingList = Lists.newArrayListWithCapacity(segmentList.size());
    List<Integer> incomingCountList = Lists.newArrayListWithCapacity(segmentList.size());
    for (int i = 0; i < segmentList.size(); i++) {
      outgoingList.add(new ArrayList<LinearSegment>());
      incomingCountList.add(0);
    }

    // create edges for the segment ordering graph
    createDependencyGraphEdges(layeredGraph, segmentList, outgoingList, incomingCountList);

    // turn lists into arrays
    LinearSegment[] segments = segmentList.toArray(new LinearSegment[segmentList.size()]);

    @SuppressWarnings("unchecked")
    List<LinearSegment>[] outgoing = outgoingList.toArray(new List[outgoingList.size()]);

    int[] incomingCount = new int[incomingCountList.size()];
    for (int i = 0; i < incomingCount.length; i++) {
      incomingCount[i] = incomingCountList.get(i);
    }

    // gather the sources of the segment ordering graph
    int nextRank = 0;
    List<LinearSegment> noIncoming = Lists.newArrayList();
    for (int i = 0; i < segments.length; i++) {
      if (incomingCount[i] == 0) {
        noIncoming.add(segments[i]);
      }
    }

    // find a topological ordering of the segment ordering graph
    int[] newRanks = new int[segments.length];
    while (!noIncoming.isEmpty()) {
      LinearSegment segment = noIncoming.remove(0);
      newRanks[segment.id] = nextRank++;

      while (!outgoing[segment.id].isEmpty()) {
        LinearSegment target = outgoing[segment.id].remove(0);
        incomingCount[target.id]--;

        if (incomingCount[target.id] == 0) {
          noIncoming.add(target);
        }
      }
    }

    // apply the new ordering to the array of linear segments
    linearSegments = new LinearSegment[segments.length];
    for (int i = 0; i < segments.length; i++) {
      assert outgoing[i].isEmpty();
      LinearSegment ls = segments[i];
      int rank = newRanks[i];
      linearSegments[rank] = ls;
      ls.id = rank;
      for (LNode node : ls.nodes) {
        node.id = rank;
      }
    }

    return linearSegments;
  }
Example #11
0
 /**
  * @param n1 a node
  * @param n2 another node
  * @return the spacing to be preserved between {@code n1} and {@code n2}
  */
 public float getHorizontalSpacing(final LNode n1, final LNode n2) {
   return getHorizontalSpacing(n1.getType(), n2.getType());
 }
 /**
  * Removes all sides from the list of available sides, that already have an port places on it.
  * Use to make sure no loops will be places on sides with ports containing non-loop edges.
  */
 public void removeOccupiedSides() {
   for (final LPort port : node.getPorts()) {
     loopSides.removeSide(LoopSide.fromPortSides(port.getSide()));
   }
 }
  /** {@inheritDoc} */
  public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) {
    monitor.begin("Spline SelfLoop positioning", 1);

    /** Stores which loop placement strategy to choose. */
    final SelfLoopPlacement loopPlacement =
        layeredGraph.getProperty(Properties.SPLINE_SELF_LOOP_PLACEMENT);

    ////////////////////////////////////////////////////////
    // There are two main jobs to be done:
    // 1) Find a loop-side for each component.
    // 2) Position ports in the correct order around the node.
    ////////////////////////////////////////////////////////

    // Process all nodes on all layers.
    for (final Layer layer : layeredGraph) {
      for (final LNode node : layer) {

        // Read self-loops components.
        final List<ConnectedSelfLoopComponent> components =
            node.getProperty(InternalProperties.SPLINE_SELFLOOP_COMPONENTS);

        // Components to be distributed by the placement strategy.
        final List<ConnectedSelfLoopComponent> componentsToBePlaced = Lists.newArrayList();

        for (final ConnectedSelfLoopComponent component : components) {
          // Re-Add all hidden edges to their ports.
          component.unhideEdges();

          if (component.getConstrainedPorts().isEmpty()) {
            // If there is no constraint on any port, we have to process this component later
            componentsToBePlaced.add(component);
          } else {
            // If there is at least one port with a constraint to it's port-side,
            // we will set the port- and loop-sides by this constraint. (Job 1)
            setPortSideByConstraint(component);
            if (!component.getNonLoopPorts().isEmpty()) {
              // Position and re-add all ports to the node, that are part of a component
              // with at least one non-loop edge. (Job 2)
              addComponentWithNonLoopEdges(component);
            }
          }
        }

        // Now we have to find a loop-side (job 1) for the remaining components. They are all
        // stored in componentsToBePlaced. All these components don't have a port with a
        // constraint on it's portSide, so we can arrange them accordingly to the cosen strategy.
        switch (loopPlacement) {
          case EQUALLY_DISTRIBUTED:
            setPortSideSpreadEqually(componentsToBePlaced, node);
            break;
          case NORTH_SEQUENCE:
            for (final ConnectedSelfLoopComponent component : componentsToBePlaced) {
              component.setLoopSide(LoopSide.N, true);
            }
            break;
          case NORTH_STACKED:
            for (final ConnectedSelfLoopComponent component : componentsToBePlaced) {
              component.setLoopSide(LoopSide.N, true);
            }
            break;
          default:
            // Unknown strategy chosen.
            assert false;
            break;
        }

        // Position and re-add all ports to the node.
        // This is job 2)
        switch (loopPlacement) {
          case EQUALLY_DISTRIBUTED:
          case NORTH_STACKED:
            portStackedPositioning(components);
            break;
          case NORTH_SEQUENCE:
            portLinedPositioning(components);
            break;
          default:
            break;
        }
      }
    }
    monitor.done();
  }
  /**
   * Positions the ports of the given components around their node in a crossing minimizing order.
   * The components on straight sides are places stacked. All components in the given list must lay
   * on the same node.
   *
   * @param components The components those ports needs to be placed.
   */
  private void portStackedPositioning(final List<ConnectedSelfLoopComponent> components) {
    if (components.isEmpty()) {
      return;
    }
    final LNode node = components.get(0).getNode();
    // The componentHolder, holding our components.
    final PortReadder compHolder = new PortReadder(components);

    // An iterator over the portList of our node
    final ListIterator<LPort> itr = node.getPorts().listIterator();

    /////////////////////////////////////
    // Now we will re-add the ports to the portList of the node, again.

    // north-west, 1st part
    compHolder.addSourcePortsReversed(LoopSide.NW, itr);

    // south-north via west, 2nd part
    compHolder.addTargetPorts(LoopSide.SWN, itr);

    /////////////////////////////////////
    // find transition from north to east
    findNextSide(PortSide.NORTH, itr);
    // north
    compHolder.addAllPorts(LoopSide.N, itr, false);
    // south-north via east, 2nd part
    compHolder.addTargetPorts(LoopSide.SEN, itr);
    // east-north
    compHolder.addAllPorts(LoopSide.EN, itr, false);
    // east-west via north, 1st part
    compHolder.addSourcePortsReversed(LoopSide.ENW, itr);

    /////////////////////////////////////
    // find transition from east to south
    findNextSide(PortSide.EAST, itr);
    // east
    compHolder.addAllPorts(LoopSide.E, itr, false);
    // west-east via south, 1st part
    compHolder.addSourcePortsReversed(LoopSide.ESW, itr);
    // south-east
    compHolder.addAllPorts(LoopSide.SE, itr, false);
    // south-north via east, 1st part
    compHolder.addSourcePortsReversed(LoopSide.SEN, itr);

    /////////////////////////////////////
    // find transition from south to west
    findNextSide(PortSide.SOUTH, itr);
    // south
    compHolder.addAllPorts(LoopSide.S, itr, false);
    // south-north via west, 1st part
    compHolder.addSourcePortsReversed(LoopSide.SWN, itr);
    // west-south
    compHolder.addAllPorts(LoopSide.WS, itr, false);
    // east-west via south, 2nd part
    compHolder.addTargetPorts(LoopSide.ESW, itr);

    /////////////////////////////////////
    // find transition from west to north (the very end of the list)
    while (itr.hasNext()) {
      itr.next();
    }
    // west
    compHolder.addAllPorts(LoopSide.W, itr, false);
    // east-west via north, 2nd part
    compHolder.addTargetPorts(LoopSide.ENW, itr);
    // north-west, 2nd part
    compHolder.addTargetPorts(LoopSide.NW, itr);
  }