private void mergeIntermediateStates() { Map<Adjacency, Set<FSANode>> adjToNodes = new HashMap<>(); Set<FSANode> newCurMergeLevel = new HashSet<>(); Map<FSANode, FSANode> formerNodeToMergedNode = new HashMap<>(); for (FSANode node : curMergeLevel) { boolean needToBreak = false; for (Set<FSANode> targets : node.getLabelToTargets().values()) { if (targets.size() > 1) { updateBasedOnMerges(targets, formerNodeToMergedNode); FSANode newNode = mergeNodes(targets); targets.stream().forEach(n -> formerNodeToMergedNode.put(n, newNode)); if (newNode.needsToMergeOutgoingEdges()) { newCurMergeLevel.add(newNode); } needToBreak = true; } } if (needToBreak) { break; } for (FSAEdge edge : node.getOutgoingEdges()) { Adjacency adj = new Adjacency(edge); Set<FSANode> nodesWithSameLabelToSameTarget = adjToNodes.get(adj); if (nodesWithSameLabelToSameTarget == null) { nodesWithSameLabelToSameTarget = new HashSet<>(); adjToNodes.put(adj, nodesWithSameLabelToSameTarget); } nodesWithSameLabelToSameTarget.add(node); } } for (FSANode node : curMergeLevel) { newCurMergeLevel.addAll( node.getIncomingEdges().stream().map(e -> e.getSource()).collect(Collectors.toSet())); } for (Set<FSANode> nodes : adjToNodes.values()) { updateBasedOnMerges(nodes, formerNodeToMergedNode); if (nodes.size() > 1) { FSANode newNode = mergeNodes(nodes); if (!Collections.disjoint(newCurMergeLevel, nodes)) { newCurMergeLevel.removeAll(nodes); newCurMergeLevel.add(newNode); } for (FSANode node : nodes) { formerNodeToMergedNode.put(node, newNode); } } } // remove any nodes that are now contained by other nodes curMergeLevel = newCurMergeLevel.stream().filter(n -> this.nodes.contains(n)).collect(Collectors.toSet()); }