// $Id: TestDependencies.java 16595 2009-01-14 14:14:16Z mvw $
// Copyright (c) 2007-2009 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package org.argouml;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;

import jdepend.framework.JDepend;
import jdepend.framework.JavaPackage;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Test for Dependency cycles with JDepend. <p>
 * 
 * This test will guarantee that once 
 * a package is made free of dependency-cycles, 
 * (and it has been added here,)
 * it will stay dependency-cycle free. <p>
 * 
 * It also checks that the top level package(s) are 
 * (and remain) unused by other packages. <p>
 * 
 * It also checks that low level packages do not
 * use other argouml packages.
 * 
 * @author Michiel
 */
public class TestDependencies extends TestCase {
    /**  
     * Tests that a list of packages does not contain
     * any package dependency cycles.
     *
     * @return a list of tests.
     */
    public static Test suite() {
        JDepend jdepend = new JDepend();

        try {
	    jdepend.addDirectory("build-eclipse");
	} catch (IOException e) {
	    // Ignore if the directory does not exist.
	    // This error will throw when running from the ant setup.
	    System.out.println("Assuming running from ant!");
	}

	try {
	    jdepend.addDirectory("build/classes");
	} catch (IOException e) {
	    // Ignore if the directory does not exist.
	    // This error will throw when running from the Eclipse setup.
	    System.out.println("Assuming running from Eclipse!");
	}
        
        jdepend.analyze();

        TestSuite suite =
            new TestSuite("Tests for dependencies using Jdepend");

        String[] clean = {
            "org.argouml.application.api",
            "org.argouml.application.events",
            "org.argouml.application.helpers",
            "org.argouml.application.security",
            "org.argouml.cognitive.checklist",
            //"org.argouml.cognitive.critics",
            "org.argouml.configuration",
            "org.argouml.i18n",
            "org.argouml.gefext",
            "org.argouml.language.ui",
            "org.argouml.moduleloader",
            //"org.argouml.notation.providers",//fails because sub-packages fail (?)
            //"org.argouml.notation.providers.java",
            //"org.argouml.notation.providers.uml",
            //"org.argouml.notation",//fails because sub-packages fail (?)
            //"org.argouml.notation.ui",
            "org.argouml.swingext",
            "org.argouml.taskmgmt",
            "org.argouml.uml.diagram.layout",
            "org.argouml.uml.generator",
            "org.argouml.uml.util.namespace",
            "org.argouml.util.logging",
            "org.argouml.util.osdep",
            "org.argouml.util",
            // There was a comment saying that the below has no cycles, but
            // Classycle thinks there's a cycle here too, so I believe there
            // really is one - tfm 20070702
//            "org.argouml.uml.cognitive.critics",
        };

        suite.addTest(new TimeStamp());
        for (int i = 0; i < clean.length; i++) {
            suite.addTest(new CheckDependencyCycle(jdepend, clean[i]));
        }

        String[] top = {
            "org.argouml.application",
        };
        for (int i = 0; i < top.length; i++) {
            suite.addTest(new CheckTopLevel(jdepend, top[i]));
        }

        String[] low = {
            "org.argouml.application.security",
            "org.argouml.configuration",
            "org.argouml.i18n",
            "org.argouml.swingext",
            "org.argouml.taskmgmt",
            "org.argouml.util.logging",
            "org.argouml.util.osdep",
        };
        for (int i = 0; i < low.length; i++) {
            suite.addTest(new CheckLowLevel(jdepend, low[i]));
        }

        String[][] dep = {
            // There shall not be a dependency from ... to ...
            {"org.argouml.persistence", "org.argouml.ui"},
            {"org.argouml.moduleloader", "org.argouml.persistence"},
            {"org.argouml.notation", "org.argouml.notation.ui"},
            {"org.argouml.ui.targetmanager", "org.argouml.ui"},
            {"org.argouml.moduleloader", "org.argouml.ui"},
            {"org.argouml.cognitive", "org.argouml.ui"},
            {"org.argouml.cognitive.critics", "org.argouml.cognitive.ui"},
            {"org.argouml.ui", "org.argouml.cognitive.critics.ui"},
//TODO:{"org.argouml.ui", "org.argouml.cognitive.ui"},//fails due to subpackages
//TODO:{"org.argouml.cognitive", "org.argouml.cognitive.critics"},
            {"org.argouml.uml.diagram", "org.argouml.ui"},
            {"org.argouml.ui", "org.argouml.notation.ui"},
            {"org.argouml.util", "org.argouml.ui.cmd"},
            //{"org.argouml.kernel", "org.argouml.uml.diagram.ui"},//why does this fail?
        };
        for (int i = 0; i < dep.length; i++) {
            suite.addTest(new CheckNoDependency(jdepend, dep[i]));
        }

        return suite;
    }
    
    static class DateUtils {
        static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss Z";

        public static String now() {
            Calendar cal = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
            return sdf.format(cal.getTime());
        }

    }

    static class TimeStamp extends TestCase {

        TimeStamp() {
            super("Timestamp: " + DateUtils.now());
        }

        @SuppressWarnings("unchecked")
        public void runTest() {
            // do nothing by design
        }
    }

    static class CheckDependencyCycle extends TestCase {
        private String packageName;
        private JDepend jdepend;

        CheckDependencyCycle(JDepend jd, String name) {
            super("Check dependency cycle in " + name);
            jdepend = jd;
            packageName = name;
        }

        @SuppressWarnings("unchecked")
        public void runTest() {
            JavaPackage p = jdepend.getPackage(packageName);
            assertNotNull(p);
            if (p.containsCycle()) {
                StringBuffer msg = new StringBuffer(
                        "JDepend indicates a dependency cycle in ");
                msg.append(p.getName());
                List<JavaPackage> firstCycle = new ArrayList<JavaPackage>();
                p.collectCycle(firstCycle);
                msg.append("(" + firstCycle.size());
                msg.append(" packages in first cycle: ");
                for (JavaPackage cp : firstCycle) {
                    msg.append(cp.getName()).append(" ");
                }
                msg.append(") -- ");
                List<JavaPackage> otherCycles = new ArrayList<JavaPackage>();
                p.collectAllCycles(otherCycles);
                otherCycles.removeAll(firstCycle);
                if (!otherCycles.isEmpty()) {
                    msg.append("(" + otherCycles.size());
                    msg.append(" packages in additional cycle(s): ");
                    for (JavaPackage cp : otherCycles) {
                        msg.append(cp.getName()).append(" ");
                    }
                    msg.append(") -- ");
                }
                
//                msg.append("(" + p.getClassCount() + " classes: ");
//                Collection<JavaClass> c = p.getClasses();
//                for (JavaClass jc : c) {
//                    msg.append(jc.getName());
//                    msg.append(" ");
//                }
//                msg.append(")");
                assertTrue(msg.toString(), false);
            }
        }
    }

    static class CheckTopLevel extends TestCase {
        private String packageName;
        private JDepend jdepend;

        CheckTopLevel(JDepend jd, String name) {
            super("Check top level dependencies for " + name);
            jdepend = jd;
            packageName = name;
        }

        @SuppressWarnings("unchecked")
        public void runTest() {
            JavaPackage p = jdepend.getPackage(packageName);
            assertNotNull(p);
            Collection<JavaPackage> afferents = p.getAfferents();
            if (afferents.size() > 0) {
                StringBuffer msg = new StringBuffer("JDepend "
                    + "indicates an afferent dependency "
                    + "to a top level package: ");
                msg.append(p.getName());
                msg.append(" is used by ");
                msg.append(afferents.size());
                msg.append(" packages: ");
                for (JavaPackage jp : afferents) {
                    msg.append(jp.getName());
                    msg.append(" ");
                }
                assertTrue(msg.toString(), false);
            }
        }
    }
    
    static class CheckLowLevel extends TestCase {
        private String packageName;
        private JDepend jdepend;

        CheckLowLevel(JDepend jd, String name) {
            super("Check low level dependencies for " + name);
            jdepend = jd;
            packageName = name;
        }

        @SuppressWarnings("unchecked")
        public void runTest() {
            JavaPackage p = jdepend.getPackage(packageName);
            assertNotNull(p);
            Collection<JavaPackage> efferents = p.getEfferents();
            Collection<JavaPackage> wrong = new ArrayList<JavaPackage>();
            for (JavaPackage jp : efferents) {
                if (jp.getName().startsWith("org.argouml")) {
                    wrong.add(jp);
                }
                if (!wrong.isEmpty()) {
                    StringBuffer msg = new StringBuffer("JDepend "
                            + "indicates a dependency from "
                            + "the low level package ");
                    msg.append(p.getName());
                    msg.append(" to the argouml package(s)");
                    for (JavaPackage jpWrong : wrong) {
                        msg.append(" ");
                        msg.append(jpWrong.getName());
                    }
                    assertTrue(msg.toString(), false);
                }
            }
        }
    }
    
    static class CheckNoDependency extends TestCase {
        private String nameFrom;
        private String nameTo;
        private JDepend jdepend;

        CheckNoDependency(JDepend jd, String[] name) {
            super("Check for dependency from " + name[0] + " to " + name[1]);
            jdepend = jd;
            nameFrom = name[0];
            nameTo = name[1];
        }

        @SuppressWarnings("unchecked")
        public void runTest() {
            JavaPackage packageFrom = jdepend.getPackage(nameFrom);
            JavaPackage packageTo = jdepend.getPackage(nameTo);
            assertNotNull("Missing package", packageFrom);
            Collection<JavaPackage> efferents = packageFrom.getEfferents();
            for (JavaPackage jp : efferents) {
                if (jp.equals(packageTo)) {
                    StringBuffer msg = new StringBuffer(
                        "JDepend indicates a dependency from ");
                    msg.append(nameFrom);
                    msg.append(" to ");
                    msg.append(nameTo);
                    assertTrue(msg.toString(), false);
                }
            }
        }
    }
}
