docs: protected final boolean mergeLists(List<UnclosedBranchDescriptor> child1open,
  										List<UnclosedBranchDescriptor> child2open,
  										List<UnclosedBranchDescriptor> result,
  										boolean markJoinedBranchesAsPipelineBreaking) {
  
  		//remove branches which have already been closed
  		removeClosedBranches(child1open);
  		removeClosedBranches(child2open);
  		
  		result.clear();
  		
  		// check how many open branches we have. the cases:
  		// 1) if both are null or empty, the result is null
  		// 2) if one side is null (or empty), the result is the other side.
  		// 3) both are set, then we need to merge.
  		if (child1open == null || child1open.isEmpty()) {
  			if(child2open != null && !child2open.isEmpty()) {
  				result.addAll(child2open);
  			}
  			return false;
  		}
  		
  		if (child2open == null || child2open.isEmpty()) {
  			result.addAll(child1open);
  			return false;
  		}
  
  		int index1 = child1open.size() - 1;
  		int index2 = child2open.size() - 1;
  		
  		boolean didCloseABranch = false;
  
  		// as both lists (child1open and child2open) are sorted in ascending ID order
  		// we can do a merge-join-like loop which preserved the order in the result list
  		// and eliminates duplicates
  		while (index1 >= 0 || index2 >= 0) {
  			int id1 = -1;
  			int id2 = index2 >= 0 ? child2open.get(index2).getBranchingNode().getId() : -1;
  
  			while (index1 >= 0 && (id1 = child1open.get(index1).getBranchingNode().getId()) > id2) {
  				result.add(child1open.get(index1));
  				index1--;
  			}
  			while (index2 >= 0 && (id2 = child2open.get(index2).getBranchingNode().getId()) > id1) {
  				result.add(child2open.get(index2));
  				index2--;
  			}
  
  			// match: they share a common branching child
  			if (id1 == id2) {
  				didCloseABranch = true;
  				
  				// if this is the latest common child, remember it
  				OptimizerNode currBanchingNode = child1open.get(index1).getBranchingNode();
  				
  				long vector1 = child1open.get(index1).getJoinedPathsVector();
  				long vector2 = child2open.get(index2).getJoinedPathsVector();
  				
  				// check if this is the same descriptor, (meaning that it contains the same paths)
  				// if it is the same, add it only once, otherwise process the join of the paths
  				if (vector1 == vector2) {
  					result.add(child1open.get(index1));
  				}
  				else {
  					// we merge (re-join) a branch
  
  					// mark the branch as a point where we break the pipeline
  					if (markJoinedBranchesAsPipelineBreaking) {
  						currBanchingNode.markAllOutgoingConnectionsAsPipelineBreaking();
  					}
  
  					if (this.hereJoinedBranches == null) {
  						this.hereJoinedBranches = new ArrayList<OptimizerNode>(2);
  					}
  					this.hereJoinedBranches.add(currBanchingNode);
  
  					// see, if this node closes the branch
  					long joinedInputs = vector1 | vector2;
  
  					// this is 2^size - 1, which is all bits set at positions 0..size-1
  					long allInputs = (0x1L << currBanchingNode.getOutgoingConnections().size()) - 1;
  
  					if (joinedInputs == allInputs) {
  						// closed - we can remove it from the stack
  						addClosedBranch(currBanchingNode);
  					} else {
  						// not quite closed
  						result.add(new UnclosedBranchDescriptor(currBanchingNode, joinedInputs));
  					}
  				}
  
  				index1--;
  				index2--;
  			}
  		}
  
  		// merged. now we need to reverse the list, because we added the elements in reverse order
  		Collections.reverse(result);
  		return didCloseABranch;
  	}