private Group build(long start) {
    TLongSet group = new TLongHashSet();
    group.add(start);

    TLongSet left = new TLongHashSet();
    left.addAll(graph.getEdgesOut(start));

    while (!left.isEmpty()) {
      long next = any(left);

      if (group.contains(next)) {
        continue;
      }
      group.add(next);
      Set<Long> out = graph.getEdgesOut(next);
      left.addAll(out);
    }

    return new Group(start, group);
  }
  public List<Group> buildGroupsDirected() {
    List<Group> groups = new LinkedList<>();

    // First determine 'start' relations, i.e. relations without incoming
    // edges in the relation graph
    TLongSet starts = new TLongHashSet();
    Collection<Long> ids = graph.getNodes();
    for (long id : ids) {
      if (graph.getEdgesIn(id).isEmpty()) {
        starts.add(id);
      }
    }
    // Build sub-graphs reachable from 'start' relations
    logger.debug("Number of start relations: " + starts.size());
    for (long start : starts.toArray()) {
      groups.add(build(start));
    }

    // In case of circles within the relation graph that are not reachable
    // from any start relation, there may be some relations left, that have
    // not been put into groups yet.
    TLongSet remaining = new TLongHashSet();
    remaining.addAll(ids);
    for (Group group : groups) {
      remaining.removeAll(group.getRelationIds());
    }
    if (remaining.size() > 0) {
      logger.debug("remaining: " + remaining.size());
      while (!remaining.isEmpty()) {
        long id = any(remaining);

        TLongSet reachable = reachable(graph, id);
        remaining.removeAll(reachable);

        long lowest = IdUtil.lowestId(reachable);
        groups.add(new Group(lowest, reachable));
      }
    }

    return groups;
  }