org.architecturerules.services.CyclicRedundancyServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.architecturerules.services.CyclicRedundancyServiceImpl.java

Source

/**
 * Copyright 2007 - 2009 the original author or authors.
 *
 * 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
 *
 * For more information visit
 *         http://wiki.architecturerules.org/ and
 *         http://blog.architecturerules.org/
 */
package org.architecturerules.services;

import jdepend.framework.JavaClass;
import jdepend.framework.JavaPackage;

import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.architecturerules.api.services.CyclicRedundancyService;
import org.architecturerules.configuration.Configuration;
import org.architecturerules.exceptions.CyclicRedundancyException;
import org.architecturerules.exceptions.NoPackagesFoundException;
import org.architecturerules.exceptions.SourceNotFoundException;

/**
 * <p>Checks for cyclic redundancy among application packages in the source folders.</p>
 *
 * @author mikenereson
 * @see AbstractArchitecturalRules
 */
public class CyclicRedundancyServiceImpl extends AbstractArchitecturalRules implements CyclicRedundancyService {

    private static final String CHAR_92 = new String(new char[] { 92 });

    /**
     * <p>To log with. See <tt>log4j.xml</tt>.</p>
     *
     * @parameter log Log
     */
    protected static final Log log = LogFactory.getLog(CyclicRedundancyServiceImpl.class);

    /**
     * <p>Constructor instantiates a new <code>CyclicRedundancyService</code></p>
     *
     * @param configuration Configuration which contains the source directories to inspect
     * @throws SourceNotFoundException when an required source directory does not exist and when
     * <tt>exception</tt>=<tt>"true"</tt> in the source configuration
     * @throws NoPackagesFoundException when none of the source directories exist and
     * <tt>no-packages</tt>="<tt>ignore</tt>" in the sources configuration
     */
    public CyclicRedundancyServiceImpl(final Configuration configuration) {
        super(configuration);

        log.info("instantiating new CyclicRedundancyService");
    }

    /**
     * <p>Check all the packages in all of the source directories and search for any cyclic redundancy</p>
     */
    public void performCyclicRedundancyCheck() {

        configuration.onCyclicDependencyTestBegin();
        log.info("cyclic redundancy check requested");

        final Collection<JavaPackage> packages = getPackages();

        /**
         *  TODO: report classes involved
         *  Report on which class is causing the cyclic redundancy.
         *  The information is all available from within the JavaPackage
         *  class.
         */

        /**
         * Holds any cycles that are found. The structure of this Map is
         * <JavaPackage, Set> where the first String is the name of the package,
         * and the Set contains JavaPackge of packages that are in a cycle with
         * the named package.
         */
        final Map<JavaPackage, Set<JavaPackage>> cycles = new HashMap<JavaPackage, Set<JavaPackage>>();

        for (final JavaPackage javaPackage : packages) {

            final Collection<JavaPackage> afferents = javaPackage.getAfferents();
            final Collection<JavaPackage> efferents = javaPackage.getEfferents();

            /**
             * afferents Collection is no longer a reference to the afferents,
             * but now a Collection of packages involved in a cyclic dependency
             * with the javaPackage. By calling retainAll the afferents
             * Collection now contains only those javaPackages that were in both
             * the efferents and afferents Collections. And by definition, when
             * a package is both uses the given package, and used by the given
             * package, a cyclic dependency exists.
             */
            afferents.retainAll(efferents);

            final boolean cyclesFound = afferents.size() > 0;

            if (cyclesFound) {

                addCycle(cycles, javaPackage, afferents);
            }
        }

        if (!cycles.isEmpty()) {

            final CyclicRedundancyException cyclicRedundancyException = buildCyclicRedundancyException(cycles);
            throw cyclicRedundancyException;
        }

        configuration.onCyclicDependencyTestEnd();
    }

    /**
     * <p>Updates a Map, or puts a new record into a Map of a JavaPackage and its cyclic dependency packages.</p>
     *
     * @param cycles Map of cycles already discovered.
     * @param javaPackage JavaPackage involved in a cyclic dependency
     * @param dependencies Collection of JavaPackages involved in a cyclic dependency with the given javaPackage
     * argument.
     */
    private void addCycle(final Map cycles, final JavaPackage javaPackage, final Collection dependencies) {

        final boolean exists = cycles.containsKey(javaPackage);
        final Set cyclicPackages;

        if (exists) {

            /**
             * Get existing Set, this JavaPackage has already been
             * discovered for being in a cycle
             */
            final Object value = cycles.get(javaPackage);
            cyclicPackages = (HashSet) value;
        } else {

            /**
             * Build a new Set
             */
            cyclicPackages = new HashSet();
        }

        cyclicPackages.addAll(dependencies);
        cycles.put(javaPackage, cyclicPackages);
    }

    private CyclicRedundancyException buildCyclicRedundancyException(
            final Map<JavaPackage, Set<JavaPackage>> cycles) {

        final String message = buildCyclicRedundancyMessage(cycles);
        final CyclicRedundancyException exception = new CyclicRedundancyException(message);
        final Map<String, Set<String>> cycleStrings = exception.getCycles();

        for (final Map.Entry<JavaPackage, Set<JavaPackage>> cycle : cycles.entrySet()) {

            final String packageName = cycle.getKey().getName();
            final Set<JavaPackage> packageCycles = cycle.getValue();

            final Set<String> cyclicPackages;

            if (cycleStrings.containsKey(packageName)) {

                cyclicPackages = cycleStrings.get(packageName);
            } else {

                cyclicPackages = new HashSet<String>();
            }

            for (final JavaPackage packageCycle : packageCycles) {

                final String cycleName = packageCycle.getName();
                cyclicPackages.add(cycleName);
            }

            cycleStrings.put(packageName, cyclicPackages);
        }

        exception.getCycles().putAll(cycleStrings);

        return exception;
    }

    /**
     * <p>Builds a message detailing all of the java packages that are involved in a cyclic dependency and the packages
     * involved with.</p>
     *
     * @param cycles Map of cycles discovered.
     * @return String a complete message detailing all of the cyclic dependencies found.
     */
    private String buildCyclicRedundancyMessage(final Map<JavaPackage, Set<JavaPackage>> cycles) {

        final StringBuffer message = new StringBuffer();

        message.append(cycles.size()).append(" cyclic dependencies found:").append("\r\n").append("\r\n\t");

        for (final Map.Entry<JavaPackage, Set<JavaPackage>> entry : cycles.entrySet()) {

            final JavaPackage javaPackage = entry.getKey();
            final Set<JavaPackage> cyclicDependencies = entry.getValue();

            message.append("-- ").append(javaPackage.getName()).append(" depends on ").append("\r\n\t");

            message.append("|  |").append("\r\n\t");

            for (Iterator dependencyIterator = cyclicDependencies.iterator(); dependencyIterator.hasNext();) {

                final JavaPackage dependency = (JavaPackage) dependencyIterator.next();

                final String listOfClasses = buildListOfClasses(javaPackage, dependency);

                message.append("|  |-- ");
                message.append(dependency.getName());
                message.append("\n").append(listOfClasses);
                message.append("\r\n\t");

                if (!dependencyIterator.hasNext()) {

                    message.append("|").append("\r\n\t");
                }
            }
        }

        return message.toString();
    }

    /**
     * <p></p>
     *
     * @param javaPackage <code>JavaPackage</code> package to describe
     * @param dependency <code>JavaPackage</code> that the javaPackage argument depends on
     * @return String that can be output to the console that describes the given javaPackages's dependency.
     */
    private String buildListOfClasses(final JavaPackage javaPackage, final JavaPackage dependency) {

        String package1 = javaPackage.getName();
        String package2 = dependency.getName();

        Collection<JavaClass> classesInPackage1 = javaPackage.getClasses();
        Collection<JavaClass> classesInPackage2 = dependency.getClasses();

        Collection<String> package1DependenciesOnPackage2 = JDependUtils.buildClasses(package2, classesInPackage1);

        Collection<String> package2DependenciesOnPackage1 = JDependUtils.buildClasses(package1, classesInPackage2);

        configuration.onCyclicDependencyDiscovered(package1, package1DependenciesOnPackage2, package2,
                package2DependenciesOnPackage1);

        final StringBuffer listOfClasses = JDependUtils.prettyPrintListOfClasses(package1DependenciesOnPackage2);

        listOfClasses.append("\t|\t    ");
        listOfClasses.append(CHAR_92);
        listOfClasses.append(" while ");
        listOfClasses.append("\n");

        for (String javaClassName : package2DependenciesOnPackage1) {

            listOfClasses.append("\t|\t");
            listOfClasses.append("     |-- ");
            listOfClasses.append(javaClassName);
            listOfClasses.append("\n");
        }

        listOfClasses.append("\t|\t");
        listOfClasses.append("       ");
        listOfClasses.append(CHAR_92);
        listOfClasses.append(" depends on ");
        listOfClasses.append(package1);

        return listOfClasses.toString();
    }
}