View Javadoc

1   /***
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * For more information visit
11   *         http://72miles.com and
12   *         http://architecturerules.googlecode.com/svn/docs/index.html
13   */
14  
15  package com.seventytwomiles.architecturerules.services;
16  
17  
18  import com.seventytwomiles.architecturerules.configuration.Configuration;
19  import com.seventytwomiles.architecturerules.exceptions.CyclicRedundancyException;
20  import com.seventytwomiles.architecturerules.exceptions.NoPackagesFoundException;
21  import com.seventytwomiles.architecturerules.exceptions.SourceNotFoundException;
22  import javassist.ClassPool;
23  import javassist.NotFoundException;
24  import jdepend.framework.JavaClass;
25  import jdepend.framework.JavaPackage;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import java.util.*;
30  
31  
32  
33  /***
34   * <p>Checks for cyclic redundancy among application packages in the source
35   * folders.</p>
36   *
37   * @author mikenereson
38   * @see AbstractArchitecturalRules
39   */
40  public class CyclicRedundancyServiceImpl extends AbstractArchitecturalRules
41          implements CyclicRedundancyService {
42  
43  
44      /***
45       * <p>Log to log with</p>
46       *
47       * @parameter log Log
48       */
49      protected static final Log log = LogFactory.getLog(
50              CyclicRedundancyServiceImpl.class);
51  
52  
53      /***
54       * <p>Constructor instantiates a new <code>CyclicRedundancyService</code></p>
55       *
56       * @param configuration Configuration which contains the source directories
57       * to inspect
58       * @throws SourceNotFoundException when an required source directory does
59       * not exist and when <tt>exception</tt>=<tt>"true"</tt> in the source
60       * configuration
61       * @throws NoPackagesFoundException when none of the source directories
62       * exist and <tt>no-packages</tt>="<tt>ignore</tt>" in the sources
63       * configuration
64       */
65      public CyclicRedundancyServiceImpl(final Configuration configuration) {
66  
67          super(configuration);
68  
69          log.info("instantiating new CyclicRedundancyService");
70      }
71  
72  
73      /***
74       * <p>Check all the packages in all of the source directories and search for
75       * any cyclic redundancy</p>
76       */
77      public void performCyclicRedundancyCheck() {
78  
79          log.info("cyclic redundancy check requested");
80  
81          final Collection<JavaPackage> packages = getPackages();
82  
83          /***
84           *  TODO: report classes involved
85           *  Report on which class is causing the cyclic redundancy.
86           *  The information is all available from within the JavaPackage
87           *  class.
88           */
89  
90          /***
91           * Holds any cycles that are found. The structure of this Map is
92           * <JavaPackage, Set> where the first String is the name of the package,
93           * and the Set contains JavaPackge of packages that are in a cycle with
94           * the named package.
95           */
96          final Map<JavaPackage, Set<JavaPackage>> cycles
97                  = new HashMap<JavaPackage, Set<JavaPackage>>();
98  
99          for (final JavaPackage javaPackage : packages) {
100 
101             final Collection<JavaPackage> afferents
102                     = javaPackage.getAfferents();
103             final Collection<JavaPackage> efferents
104                     = javaPackage.getEfferents();
105 
106             /***
107              * afferents Collection is no longer a reference to the afferents,
108              * but now a Collection of packages involved in a cyclic dependency
109              * with the javaPackage. By calling retainAll the afferents
110              * Collection now contains only those javaPackages that were in both
111              * the efferents and afferents Collections. And by definition, when
112              * a package is both uses the given package, and used by the given
113              * package, a cyclic dependency exists.
114              */
115             afferents.retainAll(efferents);
116 
117             final boolean cyclesFound = afferents.size() > 0;
118 
119             if (cyclesFound)
120                 addCycle(cycles, javaPackage, afferents);
121         }
122 
123         if (cycles.isEmpty()) {
124 
125             log.info("found no cyclic redundancies");
126 
127         } else {
128 
129             log.warn("found " + cycles.size() + " cyclic redundancies");
130 
131             final CyclicRedundancyException cyclicRedundancyException
132                     = buildCyclicRedundancyException(cycles);
133 
134             throw cyclicRedundancyException;
135         }
136 
137         log.info("cyclic redundancy test completed");
138     }
139 
140 
141     private CyclicRedundancyException buildCyclicRedundancyException(
142             final Map<JavaPackage, Set<JavaPackage>> cycles) {
143 
144         final String message = buildCyclicRedundancyMessage(cycles);
145 
146         final CyclicRedundancyException exception
147                 = new CyclicRedundancyException(message);
148 
149         final Map<String, Set<String>> cycleStrings = exception.getCycles();
150 
151         for (final Map.Entry<JavaPackage, Set<JavaPackage>> cycle : cycles.entrySet()) {
152 
153             final String packageName = cycle.getKey().getName();
154             final Set<JavaPackage> packageCycles = cycle.getValue();
155 
156             final Set<String> cyclicPackages;
157 
158             if (cycleStrings.containsKey(packageName)) {
159 
160                 cyclicPackages = cycleStrings.get(packageName);
161 
162             } else {
163 
164                 cyclicPackages = new HashSet<String>();
165             }
166 
167             for (final JavaPackage packageCycle : packageCycles) {
168 
169                 final String cycleName = packageCycle.getName();
170                 cyclicPackages.add(cycleName);
171             }
172 
173             cycleStrings.put(packageName, cyclicPackages);
174         }
175 
176         exception.getCycles().putAll(cycleStrings);
177 
178         return exception;
179     }
180 
181 
182     /***
183      * <p>Updates a Map, or puts a new record into a Map of a JavaPackage and
184      * its cyclic dependency packages.</p>
185      *
186      * @param cycles Map of cycles already discovered.
187      * @param javaPackage JavaPackage involved in a cyclic dependency
188      * @param dependencies Collection of JavaPackages involved in a cyclic
189      * dependency with the given javaPackage argument.
190      */
191     private void addCycle(final Map cycles, final JavaPackage javaPackage,
192                           final Collection dependencies) {
193 
194         final boolean exists = cycles.containsKey(javaPackage);
195         final Set cyclicPackages;
196 
197         if (exists) {
198             /***
199              * Get existing Set, this JavaPackage has already been
200              * discovered for being in a cycle
201              */
202             final Object value = cycles.get(javaPackage);
203             cyclicPackages = (HashSet) value;
204 
205         } else {
206 
207             /***
208              * Build a new Set
209              */
210             cyclicPackages = new HashSet();
211         }
212 
213         cyclicPackages.addAll(dependencies);
214         cycles.put(javaPackage, cyclicPackages);
215     }
216 
217 
218     /***
219      * <p>Builds a message detailing all of the java packages that are involved
220      * in a cyclic dependency and the packages involved with.</p>
221      *
222      * @param cycles Map of cycles discovered.
223      * @return String a complete message detailing all of the cyclic
224      *         dependencies found.
225      */
226     private String buildCyclicRedundancyMessage(
227             final Map<JavaPackage, Set<JavaPackage>> cycles) {
228 
229         final StringBuffer message = new StringBuffer();
230 
231         message.append(cycles.size())
232                 .append(" cyclic dependencies found:")
233                 .append("\r\n")
234                 .append("\r\n\t");
235 
236         for (final Map.Entry<JavaPackage, Set<JavaPackage>> entry : cycles.entrySet()) {
237 
238             final JavaPackage javaPackage = entry.getKey();
239             final Set<JavaPackage> cyclicDependencies = entry.getValue();
240 
241             message.append("-- ")
242                     .append(javaPackage.getName())
243                     .append("\r\n\t");
244 
245             message.append("|  |")
246                     .append(" (depends on one or more of)")
247                     .append("\r\n\t");
248 
249             for (Iterator dependencyIterator = cyclicDependencies.iterator();
250                  dependencyIterator.hasNext();) {
251 
252                 final JavaPackage dependency
253                         = (JavaPackage) dependencyIterator.next();
254 
255                 final String listOfClasses
256                         = buildListOfClasses(javaPackage, dependency);
257 
258                 message.append("|  |");
259                 message.append("\r\n\t");
260                 message.append("|  |-- ");
261                 message.append(dependency.getName());
262                 message.append("\n").append(listOfClasses);
263                 message.append("\r\n\t");
264 
265                 if (!dependencyIterator.hasNext())
266                     message.append("|").append("\r\n\t");
267             }
268         }
269 
270         return message.toString();
271     }
272 
273 
274     /***
275      * <p></p>
276      *
277      * @param javaPackage <code>JavaPackage</code> package to describe
278      * @param dependency <code>JavaPackage</code> that the javaPackage argument
279      * depends on
280      * @return String that can be output to the console that describes the given
281      *         javaPackages's dependency.
282      */
283     private String buildListOfClasses(final JavaPackage javaPackage,
284                                       final JavaPackage dependency) {
285 
286         final StringBuffer listOfClasses = new StringBuffer();
287         final Collection<JavaClass> classes = dependency.getClasses();
288 
289         for (final JavaClass javaClass : classes) {
290 
291             final Collection<JavaPackage> importedPackages
292                     = javaClass.getImportedPackages();
293 
294             final Collection<JavaClass> referencedClassNames
295                     = buildListOfImports(javaPackage, javaClass);
296 
297             if (importedPackages.contains(javaPackage)) {
298 
299                 final String referencesToPrint;
300 
301                 if (referencedClassNames.isEmpty()) {
302 
303                     referencesToPrint = "referenced classes not found";
304 
305                 } else {
306 
307                     referencesToPrint = referencedClassNames.toString();
308                 }
309 
310                 listOfClasses.append("\t|\t")
311                         .append(javaClass.getName())
312                         .append(": ")
313                         .append(referencesToPrint)
314                         .append("\n");
315             }
316         }
317 
318         return listOfClasses.toString();
319     }
320 
321 
322     /***
323      * <p>Builds a List of class names that the given classWithImports argument
324      * are involved with cycle with</p>
325      *
326      * @param packageInCycle JavaPackage involved in cyclic dependency
327      * @param classWithImports JavaClass that is involved in the cyclic
328      * dependency
329      * @return List of class names which this class imports from the package
330      *         involved in the cycle
331      */
332     private List buildListOfImports(final JavaPackage packageInCycle,
333                                     final JavaClass classWithImports) {
334 
335         final List<String> referencedClassNames = new LinkedList<String>();
336 
337         try {
338 
339             final ClassPool classPool = ClassPool.getDefault();
340             final String name = classWithImports.getName();
341             final Collection<String> refClasses
342                     = classPool.get(name).getRefClasses();
343 
344             for (final String importedClass : refClasses) {
345 
346                 final boolean notSelfClass = !name.equals(importedClass);
347 
348                 try {
349 
350                     final String packageName
351                             = classPool.get(importedClass).getPackageName();
352 
353                     final boolean importFromCycle
354                             = packageName.equals(packageInCycle.getName());
355 
356                     if (notSelfClass && importFromCycle)
357                         referencedClassNames.add(importedClass);
358 
359                 } catch (NotFoundException e) {
360 
361                     log.error("could not load reference class", e);
362                 }
363             }
364 
365         } catch (NotFoundException e) {
366 
367             log.error("could not load reference class", e);
368         }
369 
370         return referencedClassNames;
371     }
372 }