package jdepend.framework;

import java.util.*;

/**
 * The <code>JavaPackage</code> class represents a Java package.
 * 
 * @author <b>Mike Clark</b>
 * @author Clarkware Consulting, Inc.
 */

public class JavaPackage {

    private String name;
    private int volatility;
    private HashSet classes;
    private List afferents;
    private List efferents;


    public JavaPackage(String name) {
        this(name, 1);
    }

    public JavaPackage(String name, int volatility) {
        this.name = name;
        setVolatility(volatility);
        classes = new HashSet();
        afferents = new ArrayList();
        efferents = new ArrayList();
    }

    public String getName() {
        return name;
    }

    /**
     * @return The package's volatility (0-1).
     */
    public int getVolatility() {
        return volatility;
    }

    /**
     * @param v Volatility (0-1).
     */
    public void setVolatility(int v) {
        volatility = v;
    }

    public boolean containsCycle() {
        return collectCycle(new ArrayList());
    }

    /**
     * Collects the packages participating in the first package dependency cycle
     * detected which originates from this package.
     * 
     * @param list Collecting object to be populated with the list of
     *            JavaPackage instances in a cycle.
     * @return <code>true</code> if a cycle exist; <code>false</code>
     *         otherwise.
     */
    public boolean collectCycle(List list) {

        if (list.contains(this)) {
            list.add(this);
            return true;
        }

        list.add(this);

        for (Iterator i = getEfferents().iterator(); i.hasNext();) {
            JavaPackage efferent = (JavaPackage)i.next();
            if (efferent.collectCycle(list)) {
                return true;
            }
        }

        list.remove(this);

        return false;
    }

    /**
     * Collects all the packages participating in a package dependency cycle
     * which originates from this package.
     * <p>
     * This is a more exhaustive search than that employed by
     * <code>collectCycle</code>.
     * 
     * @param list Collecting object to be populated with the list of
     *            JavaPackage instances in a cycle.
     * @return <code>true</code> if a cycle exist; <code>false</code>
     *         otherwise.
     */
    public boolean collectAllCycles(List list) {

        if (list.contains(this)) {
            list.add(this);
            return true;
        }

        list.add(this);

        boolean containsCycle = false;
        for (Iterator i = getEfferents().iterator(); i.hasNext();) {
            JavaPackage efferent = (JavaPackage)i.next();
            if (efferent.collectAllCycles(list)) {
                containsCycle = true;
            }
        }

        if (containsCycle) {
            return true;
        }
        
        list.remove(this);
        return false;
    }

    public void addClass(JavaClass clazz) {
        classes.add(clazz);
    }

    public Collection getClasses() {
        return classes;
    }

    public int getClassCount() {
        return classes.size();
    }

    public int getAbstractClassCount() {
        int count = 0;

        for (Iterator i = classes.iterator(); i.hasNext();) {
            JavaClass clazz = (JavaClass)i.next();
            if (clazz.isAbstract()) {
                count++;
            }
        }

        return count;
    }

    public int getConcreteClassCount() {
        int count = 0;

        for (Iterator i = classes.iterator(); i.hasNext();) {
            JavaClass clazz = (JavaClass)i.next();
            if (!clazz.isAbstract()) {
                count++;
            }
        }

        return count;
    }

    /**
     * Adds the specified Java package as an efferent of this package 
     * and adds this package as an afferent of it.
     * 
     * @param imported Java package.
     */
    public void dependsUpon(JavaPackage imported) {
        addEfferent(imported);
        imported.addAfferent(this);
    }

    /**
     * Adds the specified Java package as an afferent of this package.
     * 
     * @param jPackage Java package.
     */
    public void addAfferent(JavaPackage jPackage) {
        if (!jPackage.getName().equals(getName())) {
            if (!afferents.contains(jPackage)) {
                afferents.add(jPackage);
            }
        }
    }

    public Collection getAfferents() {
        return afferents;
    }

    public void setAfferents(Collection afferents) {
        this.afferents = new ArrayList(afferents);
    }

    public void addEfferent(JavaPackage jPackage) {
        if (!jPackage.getName().equals(getName())) {
            if (!efferents.contains(jPackage)) {
                efferents.add(jPackage);
            }
        }
    }

    public Collection getEfferents() {
        return efferents;
    }

    public void setEfferents(Collection efferents) {
        this.efferents = new ArrayList(efferents);
    }

    /**
     * @return The afferent coupling (Ca) of this package.
     */
    public int afferentCoupling() {
        return afferents.size();
    }

    /**
     * @return The efferent coupling (Ce) of this package.
     */
    public int efferentCoupling() {
        return efferents.size();
    }

    /**
     * @return Instability (0-1).
     */
    public float instability() {

        float totalCoupling = (float) efferentCoupling()
                + (float) afferentCoupling();

        if (totalCoupling > 0) {
            return efferentCoupling()/totalCoupling;
        }

        return 0;
    }

    /**
     * @return The package's abstractness (0-1).
     */
    public float abstractness() {

        if (getClassCount() > 0) {
            return (float) getAbstractClassCount() / (float) getClassCount();
        }

        return 0;
    }

    /**
     * @return The package's distance from the main sequence (D).
     */
    public float distance() {
        float d = Math.abs(abstractness() + instability() - 1);
        return d * volatility;
    }

    public boolean equals(Object other) {
        if (other instanceof JavaPackage) {
            JavaPackage otherPackage = (JavaPackage) other;
            return otherPackage.getName().equals(getName());
        }
        return false;
    }

    public int hashCode() {
        return getName().hashCode();
    }
}
