View Javadoc

1   package net.sf.jlayercheck.util;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FileNotFoundException;
6   import java.io.IOException;
7   import java.net.URL;
8   import java.util.ArrayList;
9   import java.util.HashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  import java.util.TreeMap;
14  import java.util.TreeSet;
15  import java.util.logging.Logger;
16  
17  import javax.swing.tree.TreeNode;
18  
19  import net.sf.jlayercheck.util.exceptions.OrphanedSearchException;
20  import net.sf.jlayercheck.util.exceptions.OverlappingModulesDefinitionException;
21  import net.sf.jlayercheck.util.model.ClassDependency;
22  import net.sf.jlayercheck.util.model.ClassSource;
23  import net.sf.jlayercheck.util.modeltree.ClassNode;
24  import net.sf.jlayercheck.util.modeltree.DefaultModelTree;
25  import net.sf.jlayercheck.util.modeltree.DefaultPackageNode;
26  import net.sf.jlayercheck.util.modeltree.DependenciesTreeModel;
27  import net.sf.jlayercheck.util.modeltree.DependentClassNode;
28  import net.sf.jlayercheck.util.modeltree.DependentModelTree;
29  import net.sf.jlayercheck.util.modeltree.DependentModuleNode;
30  import net.sf.jlayercheck.util.modeltree.DependentPackageNode;
31  import net.sf.jlayercheck.util.modeltree.ModelTree;
32  import net.sf.jlayercheck.util.modeltree.ModuleNode;
33  import net.sf.jlayercheck.util.modeltree.PackageNode;
34  import net.sf.jlayercheck.util.modeltree.UnallowedOrAllowedDependency;
35  
36  import org.objectweb.asm.ClassReader;
37  
38  /**
39   * <p>Contains a configuration that describes the architecture of the project.
40   * It consists of modules that contain packages. Modules can use other modules,
41   * but only in a strict directional order, no dependency loops.
42   * 
43   * <p>An important function is {@link #getModelTree(DependencyVisitor)} that
44   * creates a tree of all modules, packages and classes and their dependencies.
45   * 
46   * @author webmaster@earth3d.org
47   */
48  public class XMLConfiguration {
49  	protected static Logger logger = Logger.getLogger("JLayerCheck"); 
50  	
51  	/**
52  	 * Contains the packages that belong to one module.
53  	 */
54  	protected Map<String, Set<String>> modulePackages = new TreeMap<String, Set<String>>();
55  
56  	/**
57  	 * Contains the dependencies that belong to one module.
58  	 */
59  	protected Map<String, Set<String>> moduleDependencies = new TreeMap<String, Set<String>>();
60  
61  	/**
62  	 * Contains the sources defined in the configuration file.
63  	 */
64  	protected List<ClassSource> classSources = new ArrayList<ClassSource>();
65  	
66  	/**
67  	 * Contains the packages that are excluded from the analysis (like java.**).
68  	 */
69  	protected Set<String> excludeList = new TreeSet<String>();
70  	
71      /**
72       * Contains classnames of classes that are used as program entries for the
73       * orphaned classes search.
74       */
75      protected Set<String> entryClasses = new TreeSet<String>();
76  
77  	public XMLConfiguration() {
78  	}
79  
80  	protected void addPackageToExcludeList(String packageName) {
81  		excludeList.add(packageName);
82  	}
83  
84  	protected void addDependencyToModule(String moduleName, String dependencyName) {
85  		logger.finer("Add dependency "+dependencyName+" to module "+moduleName);
86  		
87  		Set<String> deps = moduleDependencies.get(moduleName);
88  		
89  		if (deps == null) {
90  			deps = new TreeSet<String>();
91  			moduleDependencies.put(moduleName, deps);
92  		}
93  		
94  		deps.add(dependencyName);
95  	}
96  
97  	protected void addPackageToModule(String moduleName, String packageName) {
98  		logger.finer("Add package "+packageName+" to module "+moduleName);
99  		packageName = packageName.replaceAll("\\.", "/");
100 
101 		Set<String> packs = modulePackages.get(moduleName);
102 		
103 		if (packs == null) {
104 			packs = new TreeSet<String>();
105 			modulePackages.put(moduleName, packs);
106 		}
107 		
108 		packs.add(packageName);
109 	}
110 	
111 	/**
112 	 * Returns the dependencies of all modules. The key is the element
113 	 * that depends from all values in the given set. E.g. a dataset
114 	 * a -> (b,c,d) means, that a depends on the modules b, c and d. This
115 	 * normally means that b, c and d are on a lower layer in the
116 	 * architecture.
117 	 * 
118 	 * @return Map with all module dependencies
119 	 */
120 	public Map<String, Set<String>> getModuleDependencies() {
121 		return moduleDependencies;
122 	}
123 
124 	/**
125 	 * Returns the packages contained in every module. The key of the
126 	 * Map is the name of the module and the Set contains all package
127 	 * names.
128 	 * 
129 	 * @return Map with all modules and their packages
130 	 */
131 	public Map<String, Set<String>> getModulePackages() {
132 		return modulePackages;
133 	}
134 
135 	/**
136 	 * <p>Returns the matching module for the given classname or null. If more than
137 	 * one module matches, an exception is thrown, because it is a configuration
138 	 * error.
139 	 * <p>The input must be a classname, not a package name. The last part is
140 	 * expected to be the name of the class. 
141 	 * 
142 	 * @return the matching module or null if none was found
143 	 * @throws OverlappingModulesDefinitionException 
144 	 */
145 	public String getMatchingModule(String classname) throws OverlappingModulesDefinitionException {
146 		String result = null;
147 		
148 		for(String modulename : getModulePackages().keySet()) {
149 			for(String packagename : getModulePackages().get(modulename)) {
150 				packagename = convertToRegularExpression(packagename);
151 				packagename = packagename + "/[^/]*"; // allow an appended classname 
152 
153 				if (classname.matches(packagename)) {
154 					if (result == null) {
155 						result = modulename;
156 					} else {
157 						throw new OverlappingModulesDefinitionException("Class "+classname+" is matched by more than one module definition.");
158 					}
159 				}
160 			}
161 		}
162 		
163 		return result;
164 	}
165 	
166 	/**
167 	 * Returns a map that contains the mapping from packages
168 	 * to modules.
169 	 * 
170 	 * @return
171 	 */
172 	public Map<String, String> getPackageModules() {
173 		Map<String, String> result = new TreeMap<String, String>();
174 		
175 		for(String modulename : getModulePackages().keySet()) {
176 			for(String packagename : getModulePackages().get(modulename)) {
177 				result.put(packagename, modulename);
178 			}
179 		}
180 		
181 		return result;
182 	}
183 	
184 	/**
185 	 * Returns the list of sources for the class and java files.
186 	 * 
187 	 * @return list of sources
188 	 */
189 	public List<ClassSource> getClassSources() {
190 		return classSources;
191 	}
192 
193     /**
194      * Returns a map containing all java class names and an URL
195      * that points to the source file.
196      * 
197      * @return
198      */
199     public Map<String, URL> getAllClassSources() {
200         Map<String, URL> result = new TreeMap<String, URL>();
201         for(ClassSource source : getClassSources()) {
202             result.putAll(source.getSourceFiles());
203         }
204         return result;
205     }
206 
207     /**
208      * Returns all package entries that are specified to exclude
209      * in the configuration file.
210      * 
211      * @return
212      */
213 	public Set<String> getExcludeList() {
214 		return excludeList;
215 	}
216 
217 	/**
218 	 * Returns true if the given class is excluded from the analysis.
219 	 * 
220 	 * @param dependency
221 	 * @return
222 	 */
223 	public boolean isExcluded(String classname) {
224 		for(String exclude : getExcludeList()) {
225 			exclude = convertToRegularExpression(exclude);
226 			
227 			if (classname.matches(exclude)) {
228 				return true;
229 			}
230 		}
231 		
232 		return false;
233 	}
234 
235 	/**
236 	 * Used internally to convert a string from the wildcard format used
237 	 * in the configuration file into a regular expression.
238 	 * 
239 	 * @param wildcardstring
240 	 * @return wildcardstring converted to regular expression
241 	 */
242 	protected String convertToRegularExpression(String wildcardstring) {
243 		wildcardstring = wildcardstring.replaceAll("\\.", "/");
244 //		wildcardstring = wildcardstring.replaceAll("/\\*[^\\*]", "/[^\\.]*");
245 		wildcardstring = wildcardstring.replaceAll("\\*", ".*");
246 		return wildcardstring;
247 	}
248 
249     /**
250      * Returns a Set of classes that are named as entry classes in the
251      * configuration file. These classes are points where the execution
252      * of the system can start. All classes that are directly or indirectly
253      * referenced from these named entry classes are marked as used, all
254      * others are marked as unused/orphaned. 
255      * 
256      * @return the entryClasses
257      */
258     public Set<String> getEntryClasses() {
259         return entryClasses;
260     }
261 
262     /**
263      * Calculates the orphaned classes based on the entry points of the configuration
264      * file and the dependency data from the DependencyVisitor.
265      * 
266      * @param dv the dependency data to use
267      * @throws OrphanedSearchException 
268      */
269     public Set<String> getOrphanedClasses(Map<String, Map<String, Set<Integer>>> dependencies) throws OrphanedSearchException {
270         Set<String> visitedClasses = new HashSet<String>();
271         
272         // add the entry points
273         visitedClasses.addAll(entryClasses);
274         
275         // add all dependend classes
276         Set<String> unvisited = null;
277         Map<String, URL> allClassSources = getAllClassSources();
278         do {
279             unvisited = getUnvisitedDependendClasses(visitedClasses, dependencies, allClassSources);
280             visitedClasses.addAll(unvisited);
281         } while(unvisited.size()>0);
282         
283         // find the missing classes and return them as orphaned
284         Set<String> orphanedClasses = new TreeSet<String>();
285         
286         for(String classname : dependencies.keySet()) {
287             if (!visitedClasses.contains(classname)) {
288                 orphanedClasses.add(classname);
289             }
290         }
291         
292         return orphanedClasses;
293     }
294 
295     /**
296      * Returns all classnames that are directly referenced by the visited classes
297      * but not yet contained.
298      * 
299      * @param visitedClasses
300      * @param dv the dependencies to use
301      * @param allClassSources a map containing all classnames for which java source files are available
302      * @return Set containing class names
303      * @throws OrphanedSearchException 
304      */
305     protected Set<String> getUnvisitedDependendClasses(Set<String> visitedClasses, Map<String, Map<String, Set<Integer>>> dependencies, Map<String, URL> allClassSources) throws OrphanedSearchException {
306         Set<String> result = new HashSet<String>();
307         
308         for(String visitedClass : visitedClasses) {
309             if (dependencies.get(visitedClass) != null) {
310                 for(String classname : dependencies.get(visitedClass).keySet()) {
311                     if (!visitedClasses.contains(classname)) {
312                         result.add(classname);
313                     }
314                 }
315             } else {
316                 if (allClassSources.containsKey(visitedClass)) {
317                     // a class file for a source file is missing!
318                     throw new OrphanedSearchException("Class file for "+visitedClass+" is not available! (Source file is "+allClassSources.get(visitedClass).toExternalForm());
319                 } else {
320                     logger.fine("Dependencies for "+visitedClass+" not found!");
321                 }
322             }
323         }
324         
325         return result;
326     }
327     
328     /**
329      * Returns a map containing the dependencies (from class, to class) that
330      * are not allowed by the rules.
331      * 
332      * @param xcp the configuration to use
333      * @return
334      * @throws OverlappingModulesDefinitionException 
335      */
336     public Map<String, Map<String, ClassDependency>> getUnallowedDependencies(Map<String, Map<String, Set<Integer>>> dependencies) throws OverlappingModulesDefinitionException {
337 		Map<String, Map<String, ClassDependency>> unallowedDependencies = new TreeMap<String, Map<String, ClassDependency>>();
338 		
339 		for(String classname : dependencies.keySet()) {
340 			for(String dependency : dependencies.get(classname).keySet()) {
341 				// check if packagename is an allowed dependency for classname
342 				if (isUnallowedDependency(classname, dependency)) {
343 					Map<String, ClassDependency> depList = unallowedDependencies.get(classname);
344 					if (depList == null) {
345 						depList = new TreeMap<String, ClassDependency>();
346 						unallowedDependencies.put(classname, depList);
347 					}
348 					ClassDependency cd = depList.get(dependency);
349 					if (cd == null) {
350 						cd = new ClassDependency(dependency);
351 						depList.put(dependency, cd);
352 					}
353 					for(Integer lineNumber : dependencies.get(classname).get(dependency)) {
354 						cd.addLineNumber(lineNumber);
355 					}
356 				}
357 			}
358 		}
359 		
360 		return unallowedDependencies;
361     }
362 
363     /**
364      * Returns true if the dependency from fromClass to toClass is not allowed,
365      * otherwise false.
366      * 
367      * @param fromClass
368      * @param toClass
369      * @return
370      * @throws OverlappingModulesDefinitionException
371      */
372     public boolean isUnallowedDependency(String fromClass, String toClass) throws OverlappingModulesDefinitionException {
373 		String classmodule = getMatchingModule(fromClass);
374 		String dependencymodule = getMatchingModule(toClass);
375 		
376 		if (classmodule == null) {
377 			// unspecified package
378 			return false;
379 		} else {
380 			if (!classmodule.equals(dependencymodule)) {
381 				if (!(isExcluded(fromClass) || isExcluded(toClass))) {
382 					if (isUnallowedModuleDependency(classmodule, dependencymodule)) {
383 						return true;
384 					}
385 				}
386 			}
387 		}
388 	
389 		return false;
390     }
391 
392     /**
393      * Returns true if the dependency from fromModule to toModule is not allowed,
394      * otherwise false.
395      * 
396      * @param fromModule
397      * @param toModule
398      * @return
399      */
400     public boolean isUnallowedModuleDependency(String fromModule, String toModule) {
401     	if (fromModule.equals(toModule)) return false;
402     	
403 		if (getModuleDependencies().get(fromModule) == null || toModule == null || 
404 				!getModuleDependencies().get(fromModule).contains(toModule)) {
405 			return true;
406 		}
407 		
408 		return false;
409     }
410     
411     /**
412      * Returns a list of packages that are not assigned to
413      * a module in the given configuration.
414      * 
415      * @param xcp
416      * @return
417      * @throws OverlappingModulesDefinitionException 
418      */
419     public Set<String> getUnspecifiedPackages(Map<String, Map<String, Set<Integer>>> dependencies) throws OverlappingModulesDefinitionException {
420 		Set<String> unspecifiedPackages = new TreeSet<String>();
421 		
422 		for(String classname : dependencies.keySet()) {
423 			String classPackageName = StringUtils.getPackageName(classname);
424 			
425 			// check if packagename is an allowed dependency for classname
426 			String classmodule = getMatchingModule(classPackageName+"/Dummy");
427 
428 			if (classmodule == null) {
429 				unspecifiedPackages.add(classPackageName);
430 			}
431 		}
432 		
433 		return unspecifiedPackages;
434     }
435     
436     /**
437      * Builds a model that contains all dependency information that was retrieved
438      * from the class files. It can be used for displaying the dependencies in a 
439      * tree view.
440      * 
441      * @param dv
442      * @return
443      * @throws OverlappingModulesDefinitionException
444      */
445     public ModelTree getModelTree(DependencyVisitor dv) throws OverlappingModulesDefinitionException {
446     	DefaultModelTree result = new DefaultModelTree();
447     	
448     	// build ModelTree
449     	for(String packagename : dv.getPackages().keySet()) {
450 
451     		// get the module for this package
452 			String packagemodule = getMatchingModule(packagename+"/Dummy");
453 			
454 			boolean packageUnassigned = false;
455 			if (packagemodule == null) {
456 				packagemodule = "unassigned";
457 				packageUnassigned = true;
458 			}
459 			
460 			ModuleNode module = result.getModule(packagemodule);
461 			
462 			if (module == null) {
463 				module = new DependentModuleNode(packagemodule, packageUnassigned);
464 				result.add(module);
465 			}
466 			
467 			// add the new package node
468     		DefaultPackageNode packagenode = new DependentPackageNode(packagename);
469 			module.add(packagenode);
470 
471     		for(String classname : dv.getPackages().get(packagename)) {
472     			// add class nodes for all packages
473     			packagenode.add(new DependentClassNode(new ClassDependency(classname)));
474     		}
475     	}
476 
477     	// add dependencies
478     	for(String classname : dv.getDependencies().keySet()) {
479     		for(String dep : dv.getDependencies().get(classname).keySet()) {
480     			// create ClassDependency
481     			ClassDependency cd = new ClassDependency(dep);
482     			
483     			for(Integer line : dv.getDependencies().get(classname).get(dep)) {
484     				cd.addLineNumber(line);
485     			}
486 
487     			cd.setUnallowedDependency(isUnallowedDependency(classname, dep));
488     			
489     			// and add it to the ClassNode
490 				ClassNode cn = result.getClassNode(classname);
491 				if (cn != null) {
492 					cn.addClassDependency(cd);
493 				} else {
494 					System.out.println("Class "+classname+" not found (to "+dep+")!");
495 				}
496     		}
497     	}
498 
499     	cumulateDependencyViolations(result);
500     	
501     	return result;
502     }
503 
504     /**
505      * Updates the given modeltree by replacing all information of the given classFile with
506      * new information.
507      * 
508      * @param mt
509      * @param dv
510      * @param classFile
511      * @throws OverlappingModulesDefinitionException 
512      * @throws IOException 
513      * @throws FileNotFoundException 
514      */
515     public void updateModelTree(ModelTree mt, File classFile) throws OverlappingModulesDefinitionException, FileNotFoundException, IOException {
516     	DependencyVisitor dv = new DependencyVisitor();
517     	
518     	// let the DependencyVisitor retrieve the information from the classFile
519 		new ClassReader(new FileInputStream(classFile)).accept(dv, 0);
520 		
521 		// replace the information in the modeltree
522 		for(Set<String> classes : dv.getPackages().values()) {
523 			for(String clazz : classes) {
524 				ClassNode cn = mt.getClassNode(clazz);
525 				if (cn != null) {
526 					cn.removeFromParent();
527 				}
528 			}
529 		}
530 		
531 		ModelTree additionalModelTree = getModelTree(dv);
532 		mt.merge(additionalModelTree);
533     }
534     
535     /**
536      * Returns true if the module sourceModule may access destModule.
537      * 
538      * @param sourceModule
539      * @param destModule
540      * @return
541      */
542 	public boolean isUnallowedDependency(ModuleNode sourceModule, ModuleNode destModule) {
543 		return isUnallowedModuleDependency(sourceModule.getModuleName(), destModule.getModuleName());
544 	}
545 	
546 	/**
547 	 * Recalculates the violations state of all nodes of the tree that contain children
548 	 * (e.g. packages and modules).
549 	 * @param ModelTree the tree that should be recalculated
550 	 */
551 	public void cumulateDependencyViolations(ModelTree mt) {
552     	// calculate state unallowed/allowed dependencies for the tree
553 		// compute unallowed dependency marks
554 		for(ModuleNode mn : mt.getModules()) {
555 			boolean mnUnallowed = false;
556 			for(PackageNode pn : mn.getPackages()) {
557 				boolean pnUnallowed = false;
558 				for (ClassNode cn : pn.getClasses()) {
559 					if (cn instanceof DependentClassNode) {
560 						DependentClassNode dcn = (DependentClassNode) cn;
561 						
562 						DependenciesTreeModel dtm = new DependenciesTreeModel();
563 						dtm.setRoot(createModel(dcn, mt, dtm));
564 						dcn.getClassDependency().setUnallowedDependency(((UnallowedOrAllowedDependency) dtm.getRoot()).isUnallowedDependency()); 
565 						dcn.setDependenciesTreeModel(dtm);
566 						
567 						if (dcn.getClassDependency().isUnallowedDependency()) {
568 							pnUnallowed = true;
569 							mnUnallowed = true;
570 						}
571 						
572 						dtm = new DependenciesTreeModel();
573 						dtm.setRoot(createIncomingModel(dcn, mt, dtm));
574 						dcn.setIncomingDependenciesTreeModel(dtm);
575 					}
576 				}
577 				
578 				if (pn instanceof DependentPackageNode) {
579 					((DependentPackageNode) pn).setUnallowedDependency(pnUnallowed);
580 				}
581 			}
582 
583 			if (mn instanceof DependentModuleNode) {
584 				((DependentModuleNode) mn).setUnallowedDependency(mnUnallowed);
585 			}
586 		}
587 	}
588 
589 	/**
590 	 * Creates a dependency tree model for the given node of the given ModelTree.
591 	 * 
592 	 * @param node
593 	 * @param treemodel
594 	 * @param xmlconf the configuration used to determine which dependencies are allowed
595 	 * @return
596 	 */
597 	public TreeNode createModel(ClassNode node, ModelTree treemodel, DependenciesTreeModel modelToUpdate) {
598 		DependentModelTree depTree = new DependentModelTree();
599 		
600 		// build tree from dependencies
601 		for(ClassDependency cd : node.getClassDependencies()) {
602 			
603 			ClassNode depClass = treemodel.getClassNode(cd.getDependency());
604 			
605 			if (!cd.getDependency().equals(node.getName())) {
606 				modelToUpdate.addClassNodeToDependentModelTree(depTree, cd, depClass);
607 			}
608 		}
609 		
610 		// compute unallowed dependency marks
611 		boolean treeUnallowed = false;
612 		for(ModuleNode mn : depTree.getModules()) {
613 			boolean mnUnallowed = false;
614 			for(PackageNode pn : mn.getPackages()) {
615 				boolean pnUnallowed = false;
616 				for (ClassNode cn : pn.getClasses()) {
617 					if (cn instanceof DependentClassNode) {
618 						DependentClassNode dcn = (DependentClassNode) cn;
619 						
620 						// recompute if dependency is allowed
621 						boolean unallowedDependency = false;
622 						if (!pn.isUnassignedPackage()) {
623 							ModuleNode dModule = mn;
624 							ClassNode sourceClass = node;
625 	
626 							if (sourceClass !=null) {
627 								PackageNode sourcePackage = (PackageNode) sourceClass.getParent();
628 	
629 								if (!sourcePackage.isUnassignedPackage()) {
630 									ModuleNode sourceModule = (ModuleNode) sourcePackage.getParent();
631 									
632 									if (!dModule.isUnassignedModule() && !sourceModule.isUnassignedModule()) {
633 										unallowedDependency = isUnallowedDependency(sourceModule, dModule);
634 									}
635 								}
636 							} else {
637 								unallowedDependency = true;
638 							}
639 						}
640 						dcn.getClassDependency().setUnallowedDependency(unallowedDependency);
641 						
642 						if (dcn.getClassDependency().isUnallowedDependency()) {
643 							pnUnallowed = true;
644 							mnUnallowed = true;
645 							treeUnallowed = true;
646 						}
647 					}
648 				}
649 				
650 				if (pn instanceof DependentPackageNode) {
651 					((DependentPackageNode) pn).setUnallowedDependency(pnUnallowed);
652 				}
653 			}
654 	
655 			if (mn instanceof DependentModuleNode) {
656 				((DependentModuleNode) mn).setUnallowedDependency(mnUnallowed);
657 			}
658 		}
659 		depTree.setUnallowedDependency(treeUnallowed);
660 		
661 		// sort nodes and sort the "unassigned" node to the end
662 		depTree.sortNodes();
663 		
664 		return depTree;
665 	}
666 
667 	/**
668 	 * Creates a dependency tree model for the given node of the given ModelTree.
669 	 * 
670 	 * @param node
671 	 * @param treemodel
672 	 * @param xmlconf the configuration used to determine which dependencies are allowed
673 	 * @return
674 	 */
675 	public TreeNode createIncomingModel(ClassNode node, ModelTree treemodel, DependenciesTreeModel modelToUpdate) {
676 		DependentModelTree depTree = new DependentModelTree();
677 		
678 		// build tree from incoming references
679 		for(ModuleNode mn : treemodel.getModules()) {
680 			for(PackageNode pn : mn.getPackages()) {
681 				for (ClassNode cn : pn.getClasses()) {
682 					for(ClassDependency cd : cn.getClassDependencies()) {
683 						if (cd.getDependency().equals(node.getName())) {
684 							// Class cn has a dependency to this class
685 							
686 							// only add it if dependency and origin are not the same
687 							if (!cn.getName().equals(node.getName())) {
688 								ClassNode depClass = treemodel.getClassNode(cn.getName());
689 								ClassDependency ncd = new ClassDependency(cn.getName());
690 								modelToUpdate.addClassNodeToDependentModelTree(depTree, ncd, depClass);
691 							}
692 						}
693 					}
694 				}
695 			}
696 			
697 		}
698 		
699 		// compute unallowed dependency marks
700 		boolean treeUnallowed = false;
701 		for(ModuleNode mn : depTree.getModules()) {
702 			boolean mnUnallowed = false;
703 			for(PackageNode pn : mn.getPackages()) {
704 				boolean pnUnallowed = false;
705 				for (ClassNode cn : pn.getClasses()) {
706 					if (cn instanceof DependentClassNode) {
707 						DependentClassNode dcn = (DependentClassNode) cn;
708 						
709 						// recompute if dependency is allowed
710 						boolean unallowedDependency = false;
711 						if (!pn.isUnassignedPackage()) {
712 							ModuleNode dModule = mn;
713 							ClassNode sourceClass = node;
714 
715 							if (sourceClass !=null) {
716 								PackageNode sourcePackage = (PackageNode) sourceClass.getParent();
717 
718 								if (!sourcePackage.isUnassignedPackage()) {
719 									ModuleNode sourceModule = (ModuleNode) sourcePackage.getParent();
720 									
721 									if (!dModule.isUnassignedModule() && !sourceModule.isUnassignedModule()) {
722 										unallowedDependency = isUnallowedDependency(dModule, sourceModule);
723 									}
724 								}
725 							} else {
726 								unallowedDependency = true;
727 							}
728 						}
729 						dcn.getClassDependency().setUnallowedDependency(unallowedDependency);
730 						
731 						if (dcn.getClassDependency().isUnallowedDependency()) {
732 							pnUnallowed = true;
733 							mnUnallowed = true;
734 							treeUnallowed = true;
735 						}
736 					}
737 				}
738 				
739 				if (pn instanceof DependentPackageNode) {
740 					((DependentPackageNode) pn).setUnallowedDependency(pnUnallowed);
741 				}
742 			}
743 
744 			if (mn instanceof DependentModuleNode) {
745 				((DependentModuleNode) mn).setUnallowedDependency(mnUnallowed);
746 			}
747 		}
748 		depTree.setUnallowedDependency(treeUnallowed);
749 		
750 		// sort nodes and sort the "unassigned" node to the end
751 		depTree.sortNodes();
752 		
753 		return depTree;
754 	}
755 
756 	/**
757 	 * Adds a new entry class. An entry class is a class where the program
758 	 * can start (e.g. a class that contains a main method, an Applet class
759 	 * or a servlet). It is used to detect orphaned classes.
760 	 * 
761 	 * @param entryClass
762 	 */
763 	public void addEntryClass(String entryClass) {
764 		getEntryClasses().add(entryClass);
765 	}
766 
767 	/**
768 	 * Adds dependencies from a module to other modules.
769 	 *  
770 	 * @param moduleName
771 	 * @param name
772 	 */
773 	public void addModuleDependency(String moduleName, TreeSet<String> name) {
774 		moduleDependencies.put(moduleName, name);
775 	}
776 
777 	/**
778 	 * Adds a new ClassSource.
779 	 * @param source
780 	 */
781 	public void addClassSource(ClassSource source) {
782 		classSources.add(source);
783 	}
784 }