Program: 4617
Project: 4617
Buggy source:
/** 
 * Insert connector nodes into the dag. A connector node is a boundary at which the pipeline can be split into sub dags. It is treated as a sink within one subdag and as a source in another subdag. A connector is inserted in front of a reduce node (aggregator plugin type, etc) when there is a path from some source to one or more reduce nodes or sinks. This is required because in a single mapper, we can't write to both a sink and do a reduce. We also can't have 2 reducers in a single mapreduce job. A connector is also inserted in front of any node if the inputs into the node come from multiple sources. A connector is also inserted in front of a reduce node that has another reduce node as its input. After splitting, the result will be a collection of subdags, with each subdag representing a single mapreduce job (or possibly map-only job). Or in spark, each subdag would be a series of operations from one rdd to another rdd.
 * @return the nodes that had connectors inserted in front of them
 */
public Set<String> insertConnectors(){
  Set<String> addedAlready=new HashSet<>();
  for (  String isolationNode : isolationNodes) {
    isolate(isolationNode,addedAlready);
  }
  for (  String node : getTopologicalOrder()) {
    if (!sources.contains(node) && !connectors.containsKey(node)) {
      continue;
    }
    Set<String> accessibleByNode=accessibleFrom(node,Sets.union(connectors.keySet(),reduceNodes));
    Set<String> sinksAndReduceNodes=Sets.intersection(accessibleByNode,Sets.union(connectors.keySet(),Sets.union(sinks,reduceNodes)));
    sinksAndReduceNodes=Sets.difference(sinksAndReduceNodes,ImmutableSet.of(node));
    if (sinksAndReduceNodes.size() > 1) {
      for (      String reduceNodeConnector : Sets.intersection(sinksAndReduceNodes,reduceNodes)) {
        addConnectorInFrontOf(reduceNodeConnector,addedAlready);
      }
    }
  }
  for (  String reduceNode : reduceNodes) {
    Set<String> accessibleByNode=accessibleFrom(reduceNode,Sets.union(connectors.keySet(),reduceNodes));
    Set<String> accessibleReduceNodes=Sets.intersection(accessibleByNode,reduceNodes);
    accessibleReduceNodes=Sets.difference(accessibleReduceNodes,ImmutableSet.of(reduceNode));
    for (    String accessibleReduceNode : accessibleReduceNodes) {
      addConnectorInFrontOf(accessibleReduceNode,addedAlready);
    }
  }
  Map<Set<String>,Set<ConnectorHead>> connectorsToMerge=new HashMap<>();
  Set<String> stopNodes=Sets.union(connectors.keySet(),Sets.union(isolationNodes,reduceNodes));
  for (  String connector : connectors.keySet()) {
    List<String> branch=getBranch(connector,stopNodes);
    String branchHead=branch.iterator().next();
    Set<String> branchInputs=new HashSet<>(getNodeInputs(branchHead));
    if (branchInputs.isEmpty() || !Sets.intersection(multiPortNodes,branchInputs).isEmpty()) {
      continue;
    }
    Set<ConnectorHead> connectorsWithSameInput=connectorsToMerge.get(branchInputs);
    if (connectorsWithSameInput == null) {
      connectorsWithSameInput=new HashSet<>();
      connectorsToMerge.put(branchInputs,connectorsWithSameInput);
    }
    connectorsWithSameInput.add(new ConnectorHead(connector,branchHead));
  }
  for (  Map.Entry<Set<String>,Set<ConnectorHead>> entry : connectorsToMerge.entrySet()) {
    Set<String> branchInputs=entry.getKey();
    Set<ConnectorHead> toMerge=entry.getValue();
    if (toMerge.size() < 2) {
      continue;
    }
    List<String> binputs=new ArrayList<>(branchInputs);
    Collections.sort(binputs);
    String connectorName=getConnectorName(Joiner.on("String_Node_Str").join(binputs).concat("String_Node_Str"));
    nodes.add(connectorName);
    for (    String branchInput : branchInputs) {
      addConnection(branchInput,connectorName);
    }
    connectors.put(connectorName,connectorName);
    for (    ConnectorHead connectorHead : toMerge) {
      addConnection(connectorName,connectorHead.branchHead);
      for (      String branchInput : branchInputs) {
        removeConnection(branchInput,connectorHead.branchHead);
      }
      for (      String connectorInput : getNodeInputs(connectorHead.connector)) {
        for (        String connectorOutput : getNodeOutputs(connectorHead.connector)) {
          addConnection(connectorInput,connectorOutput);
        }
      }
      removeNode(connectorHead.connector);
      connectors.remove(connectorHead.connector);
    }
  }
  return addedAlready;
}

Fixed version (ground truth):
/** 
 * Insert connector nodes into the dag. A connector node is a boundary at which the pipeline can be split into sub dags. It is treated as a sink within one subdag and as a source in another subdag. A connector is inserted in front of a reduce node (aggregator plugin type, etc) when there is a path from some source to one or more reduce nodes or sinks. This is required because in a single mapper, we can't write to both a sink and do a reduce. We also can't have 2 reducers in a single mapreduce job. A connector is also inserted in front of any node if the inputs into the node come from multiple sources. A connector is also inserted in front of a reduce node that has another reduce node as its input. After splitting, the result will be a collection of subdags, with each subdag representing a single mapreduce job (or possibly map-only job). Or in spark, each subdag would be a series of operations from one rdd to another rdd.
 * @return the nodes that had connectors inserted in front of them
 */
public Set<String> insertConnectors(){
  Set<String> addedAlready=new HashSet<>();
  for (  String isolationNode : isolationNodes) {
    isolate(isolationNode,addedAlready);
  }
  for (  String node : getTopologicalOrder()) {
    if (!sources.contains(node) && !connectors.containsKey(node)) {
      continue;
    }
    Set<String> accessibleByNode=accessibleFrom(node,Sets.union(connectors.keySet(),reduceNodes));
    Set<String> sinksAndReduceNodes=Sets.intersection(accessibleByNode,Sets.union(connectors.keySet(),Sets.union(sinks,reduceNodes)));
    sinksAndReduceNodes=Sets.difference(sinksAndReduceNodes,ImmutableSet.of(node));
    if (sinksAndReduceNodes.size() > 1) {
      for (      String reduceNodeConnector : Sets.intersection(sinksAndReduceNodes,reduceNodes)) {
        addConnectorInFrontOf(reduceNodeConnector,addedAlready);
      }
    }
  }
  for (  String reduceNode : reduceNodes) {
    Set<String> accessibleByNode=accessibleFrom(reduceNode,Sets.union(connectors.keySet(),reduceNodes));
    Set<String> accessibleReduceNodes=Sets.intersection(accessibleByNode,reduceNodes);
    accessibleReduceNodes=Sets.difference(accessibleReduceNodes,ImmutableSet.of(reduceNode));
    for (    String accessibleReduceNode : accessibleReduceNodes) {
      addConnectorInFrontOf(accessibleReduceNode,addedAlready);
    }
  }
  Map<Set<String>,Set<ConnectorHead>> connectorsToMerge=new HashMap<>();
  Set<String> stopNodes=Sets.union(connectors.keySet(),Sets.union(isolationNodes,reduceNodes));
  for (  String connector : connectors.keySet()) {
    List<String> branch=getBranch(connector,stopNodes);
    String branchHead=branch.iterator().next();
    Set<String> branchInputs=new HashSet<>(getNodeInputs(branchHead));
    if (branchInputs.isEmpty() || !Sets.intersection(multiPortNodes,branchInputs).isEmpty()) {
      continue;
    }
    Set<ConnectorHead> connectorsWithSameInput=connectorsToMerge.get(branchInputs);
    if (connectorsWithSameInput == null) {
      connectorsWithSameInput=new HashSet<>();
      connectorsToMerge.put(branchInputs,connectorsWithSameInput);
    }
    connectorsWithSameInput.add(new ConnectorHead(connector,branchHead));
  }
  for (  Map.Entry<Set<String>,Set<ConnectorHead>> entry : connectorsToMerge.entrySet()) {
    Set<String> branchInputs=entry.getKey();
    Set<ConnectorHead> toMerge=entry.getValue();
    if (toMerge.size() < 2) {
      continue;
    }
    List<String> binputs=new ArrayList<>(branchInputs);
    Collections.sort(binputs);
    String connectorName=getConnectorName(Joiner.on("String_Node_Str").join(binputs).concat("String_Node_Str"));
    nodes.add(connectorName);
    for (    String branchInput : branchInputs) {
      addConnection(branchInput,connectorName);
    }
    connectors.put(connectorName,connectorName);
    for (    ConnectorHead connectorHead : toMerge) {
      addConnection(connectorName,connectorHead.branchHead);
      for (      String branchInput : branchInputs) {
        removeConnection(branchInput,connectorHead.branchHead);
      }
      for (      String connectorInput : getNodeInputs(connectorHead.connector)) {
        for (        String connectorOutput : getNodeOutputs(connectorHead.connector)) {
          addConnection(connectorInput,connectorOutput);
        }
      }
      removeNode(connectorHead.connector);
      connectors.remove(connectorHead.connector);
    }
  }
  for (  String sink : sinks) {
    Set<String> sourcesAndReduceNodes=Sets.union(connectors.keySet(),Sets.union(sources,reduceNodes));
    Set<String> parents=parentsOf(sink,sourcesAndReduceNodes);
    Set<String> parentSources=Sets.intersection(sourcesAndReduceNodes,parents);
    Set<String> reduceParents=Sets.intersection(parentSources,reduceNodes);
    if (reduceParents.size() > 0 && parentSources.size() > 1) {
      addConnectorInFrontOf(sink,addedAlready);
    }
  }
  return addedAlready;
}