a-foundation/src/main/java/com/ajjpj/afoundation/collection/graph/ADiGraph.java
public List<N> sortedNodesByReachability() throws AGraphCircularityException {
        if (hasCycles()) {
            throw new AGraphCircularityException ();
        }

        final Object[] result = new Object[nodes.length];
        int nextIdx = 0;

        final Set<N> unprocessed = new HashSet<> ();
        for (Object node: nodes) {
            //noinspection unchecked
            unprocessed.add ((N) node);
        }

        //TODO Map<N,Integer> with 'remaining' incoming edges, decrement when a node is 'processed' --> JMH

        while (! unprocessed.isEmpty ()) {
            final Set<N> nextBatch = ACollectionHelper.filter (unprocessed, new APredicateNoThrow<N> () {
                @Override public boolean apply (N n) {
                    for (E e : incomingEdges (n)) {
                        if (unprocessed.contains (e.getFrom ())) {
                            return false;
                        }
                    }
                    return true;
                }
            });

            unprocessed.removeAll (nextBatch);
            for (N n: nextBatch) {
                result[nextIdx] = n;
                nextIdx += 1;
            }
        }

        return new ArrayIterable<> (result);
    }
public List<N> sortedNodesByReachability() throws AGraphCircularityException {
        if (hasCycles()) {
            throw new AGraphCircularityException ();
        }

        final Object[] result = new Object[nodes.length];
        int nextIdx = 0;

        final Set<N> unprocessed = new HashSet<> ();
        for (Object node: nodes) {
            //noinspection unchecked
            unprocessed.add ((N) node);
        }

        //TODO Map<N,Integer> with 'remaining' incoming edges, decrement when a node is 'processed' --> JMH

        while (! unprocessed.isEmpty ()) {
            final Set<N> nextBatch = ACollectionHelper.filter (unprocessed, new APredicateNoThrow<N> () {
                @Override public boolean apply (N n) {
                    for (E e : incomingEdges (n)) {
                        if (unprocessed.contains (e.getFrom ())) {
                            return false;
                        }
                    }
                    return true;
                }
            });

            unprocessed.removeAll (nextBatch);
            for (N n: nextBatch) {
                result[nextIdx] = n;
                nextIdx += 1;
            }
        }

        return new ArrayIterable<> (result);
    }
A directed graph defines a partial order through 'reachability', and this method sorts the graph's nodes based on that
partial order.

@return all nodes, sorted in such a way that a node is guaranteed to come before all nodes that can be reached from it.
@throws AGraphCircularityException if the graph has cycles and can therefore not be sorted by reachability