/*
 * Copyright 2016-2021 Talsma ICT
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.talsmasoftware.umldoclet.javadoc.dependencies;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.RandomAccess;
import java.util.Set;

import static java.util.stream.Collectors.joining;

/**
 * A cycle of dependencies.
 *
 * <p>
 * Package Dependencies can form a cycle if the chain of package dependencies somehow 'return' to the
 * initial package. For example if you have three packages {@code a}, {@code b} and {@code c} and the following
 * dependencies: {@code a -> b}, {@code b -> c}, they will form a cycle if you somehow create a dependency back to
 * {@code a}, e.g. {@code b -> a} or {@code c -> a}.
 */
public class PackageDependencyCycle extends AbstractList<PackageDependency> implements RandomAccess {

    private final PackageDependency[] cycle;

    public PackageDependencyCycle(PackageDependency... dependencies) {
        if (dependencies.length < 1) {
            throw new IllegalArgumentException("A dependency cycle may not be empty.");
        }
        this.cycle = dependencies;
        // check for an actual cycle
        for (int i = 0; i < dependencies.length; i++) {
            String to = dependencies[i].toPackage;
            String from = dependencies[(i + 1) % dependencies.length].fromPackage;
            if (!to.equals(from)) {
                throw new IllegalArgumentException("Not a dependency cycle: '" + to + "' != '" + from + "': " + this);
            }
        }
    }

    public static Set<PackageDependencyCycle> detectCycles(Iterable<PackageDependency> dependencies) {
        List<PackageDependency[]> chains = new LinkedList<>();
        for (PackageDependency dependency : dependencies) {
            List<PackageDependency[]> newChains = new ArrayList<>();
            for (PackageDependency[] chain : chains) {
                if (dependency.fromPackage.equals(last(chain))) {
                    PackageDependency[] longerChain = growChain(chain, dependency);
                    if (longerChain != null) newChains.add(longerChain);
                }
            }
            chains.addAll(newChains);
            chains.add(new PackageDependency[]{dependency});
        }
        Set<PackageDependencyCycle> cycles = new LinkedHashSet<>();
        for (Iterator<PackageDependency[]> it = chains.iterator(); it.hasNext(); it.remove()) {
            PackageDependency[] chain = it.next();
            if (chain.length > 1 && chain[0].fromPackage.equals(last(chain))) {
                cycles.add(new PackageDependencyCycle(chain));
            }
        }
        return cycles;
    }

    private static String last(PackageDependency[] chain) {
        return chain.length == 0 ? null : chain[chain.length - 1].toPackage;
    }

    private static PackageDependency[] growChain(PackageDependency[] chain, PackageDependency dependency) {
        PackageDependency[] longerChain = new PackageDependency[chain.length + 1];
        for (int i = 0; i < chain.length; i++) {
            if (dependency.equals(chain[i])) return null;
            else longerChain[i] = chain[i];
        }
        longerChain[chain.length] = dependency;
        return longerChain;
    }

    @Override
    public PackageDependency get(int index) {
        return cycle[index];
    }

    @Override
    public int size() {
        return cycle.length;
    }

    @Override
    public String toString() {
        return cycle[0].fromPackage + " > " + stream().map(dep -> dep.toPackage).collect(joining(" > "));
    }
}

@@ -1354,20 +1354,48 @@
  -%3E a%7D.%0A
+ *%0A * @author Sjoerd Talsma%0A
  */%0A
-
 public c
@@ -1535,16 +1535,364 @@
 cycle;%0A%0A
+    /**%0A     * Create a new dependency cycle object.%0A     *%0A     * %3Cp%3E%0A     * This constructor validates that the specified package dependencies actually form a cycle and will throw%0A     * an %7B@link IllegalArgumentException%7D if they do not form an actual cycle.%0A     *%0A     * @param dependencies The package dependencies that form a cycle.%0A     */%0A
     publ
@@ -2535,24 +2535,449 @@
    %7D%0A    %7D%0A%0A
+    /**%0A     * Detect cycles in a collection of dependencies.%0A     *%0A     * %3Cp%3E%0A     * First the collection of dependencies is converted to a list of dependency %3Cem%3Echains%3C/em%3E.%0A     * Next, the dependency chains are selected from all chains and returned as a new set.%0A     *%0A     * @param dependencies The package dependencies to detect dependency cycles from.%0A     * @return A set with all found dependency cycles.%0A     */%0A
     public s
@@ -3360,32 +3360,43 @@
 kage.equals(last
+PackageName
 (chain))) %7B%0A    
@@ -3972,16 +3972,27 @@
 als(last
+PackageName
 (chain))
@@ -4104,32 +4104,32 @@
  cycles;%0A    %7D%0A%0A
-
     private stat
@@ -4142,16 +4142,27 @@
 ing last
+PackageName
 (Package

Add Javadoc.