View Javadoc

1   package de.matthias_burbach.deputy.core.project;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.HashMap;
6   import java.util.HashSet;
7   import java.util.Iterator;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import de.matthias_burbach.deputy.core.rule.RuleSet;
13  
14  /***
15   * Represents a Maven project object model (only the parts relevant for Deputy).
16   *
17   * @author Matthias Burbach
18   */
19  public class Project implements ProjectQualifier {
20      /***
21       * Whether this project is the root of the dependency graph currently
22       * opened in Deputy. If so, it is the project which can be edited and
23       * changed by rules to update and save its dependencies.
24       */
25      private boolean isRootProject = false;
26  
27      /***
28       * Whether this project is an assembly or a component. By definition, an
29       * assembly contains the recursive hull of all its dependencies in the
30       * project.xml whereas a component does not.
31       */
32      private boolean isAssembly = false;
33  
34      /***
35       * The group id of this project.
36       */
37      private String groupId;
38  
39      /***
40       * The artifact id of this project.
41       * <p/>
42       * Is a mandatory property because many processings depend on it.<br/>
43       * Some processings assume that the artifact id is sufficient and needs not
44       * be complemented by the group id to uniquely identify the artifact.
45       */
46      private String artifactId;
47  
48      /***
49       * The version of this project. Is a mandatory property because many
50       * processings depend on it.
51       */
52      private String version;
53  
54      /***
55       * The optional name of this project.
56       */
57      private String name;
58  
59      /***
60       * The optional URL of this project's homepage.
61       */
62      private String url;
63  
64      /***
65       * The type of this project. This is a non-standard element in the
66       * project.xml. Its value is one of 'jar', 'war', ejb', 'dll'.<br/>
67       * It is optional and defaults to 'jar'.
68       */
69      private String type;
70  
71      /***
72       *  The name of jar file if it doesn't respect the
73       *  &lt;artifactId&gt;-&lt;version&gt;.jar pattern.
74       */
75      private String jar;
76  
77      /***
78       * The projects of type {@link Project} which depend on this project.
79       */
80      private List clients = new ArrayList();
81  
82      /***
83       * The projects of type {@link Project} this project depends on
84       * (literally as defined in the project.xml or
85       * as a decision made by Deputy).
86       */
87      private List dependencies = new ArrayList();
88  
89      /***
90       * The projects of type {@link Project} this project actually depends on
91       * according to the literal entries in the project.xml, but which are
92       * deprecated, i. e. overruled by a decision made by Deputy.
93       */
94      private List deprecatedDependencies = new ArrayList();
95  
96      /***
97       * The non-top-level dependencies, i. e. dependencies of dependencies of
98       * this project which are not direct dependencies of this project at the
99       * same time.
100      */
101     private List indirectDependencies = null;
102 
103     /***
104      * The version conflicts in the dependency graph that roots in this project.
105      */
106     private List conflicts;
107 
108     /***
109      * The set of rules defined for this project. They are used by Deputy to
110      * decide which version to take of which dependency in the dependency graph
111      * that roots in this project.
112      */
113     private RuleSet ruleSet = new RuleSet();
114 
115     /***
116      * The change listeners that need to be informed when this project changes
117      * state.
118      */
119     private List changeListeners = new ArrayList();
120 
121     /***
122      * Helps to sort projects in the dependency lists.
123      */
124     private ProjectComparator projectComparator = new ProjectComparator();
125 
126     /***
127      * Maps artifactIds of type {@link java.lang.String} to lists of
128      * XML property elements of type {@link org.jdom.Element} that were found as
129      * child elements of the properties XML element of direct and indirect
130      * dependencies defined in this project's POM file.
131      * <p/>
132      * These are re-written to the project.xml on save to preserve them. Note,
133      * that properties will even be preserved if the version of the
134      * corresponding dependency gets changed. This might not be appropriate
135      * in all cases.
136      * <p/>
137      * The dependency property 'top' is excluded from these and treated in a
138      * special way because it's a property defined by Deputy.
139      */
140     private Map dependencyProperties = new HashMap();
141 
142     /***
143      * Maps dependencies of type {@link Project} to the corresponding
144      * dependencies of type {@link ProjectQualifier}
145      * that caused the dependency during the application of the rules.
146      * If there is no mapping the project caused itself.
147      */
148     private Map dependencyCauses = new HashMap();
149 
150     /***
151      * Maps clients of type {@link Project} to their literal dependencies
152      * of type {@link ProjectQualifier} that caused the clientship during the
153      * application of the rules.
154      * If there is no mapping the project caused itself.
155      */
156     private Map clientCauses = new HashMap();
157 
158     /***
159      * Maps clients of type {@link Project} to their literal dependencies
160      * of type {@link ProjectQualifier} that caused the clientship during the
161      * application of the rules.
162      * If there is no mapping the project caused itself.
163      *
164      * @return The map described. Maybe empty but not <code>null</code>.
165      */
166     public Map getClientCauses() {
167         return clientCauses;
168     }
169 
170     /***
171      * Maps dependencies of type {@link Project} to the corresponding
172      * dependencies of type {@link ProjectQualifier}
173      * that caused the dependency during the application of the rules.
174      * If there is no mapping the project caused itself.
175      *
176      * @return The map described. Maybe empty but not <code>null</code>.
177      */
178     public Map getDependencyCauses() {
179         return dependencyCauses;
180     }
181 
182     /***
183      * @return The group id of this project.
184      */
185     public String getGroupId() {
186         return groupId;
187     }
188 
189     /***
190      * @param groupId The group id of this project.
191      */
192     public void setGroupId(final String groupId) {
193         this.groupId = groupId;
194     }
195 
196     /***
197      * @return The artifact id of this project.
198      */
199     public String getArtifactId() {
200         return artifactId;
201     }
202 
203     /***
204      * @param artifactId The artifact id of this project.
205      */
206     public void setArtifactId(final String artifactId) {
207         this.artifactId = artifactId;
208     }
209 
210     /***
211      * @return The version of this project.
212      */
213     public String getVersion() {
214         return version;
215     }
216 
217     /***
218      * @param version The version of this project.
219      */
220     public void setVersion(final String version) {
221         this.version = version;
222     }
223 
224     /***
225      * @return The type of this project. This is a non-standard element in the
226      *         project.xml. Its value is one of 'jar', 'war', ejb', 'dll'.
227      *         It is optional and defaults to 'jar'.
228      */
229     public String getType() {
230         return type;
231     }
232 
233     /***
234      * @param type The type of this project. This is a non-standard element in
235      *             the project.xml. Its value is one of 'jar', 'war', ejb',
236      *             'dll'. It is optional and defaults to 'jar'.
237      */
238     public void setType(final String type) {
239         this.type = type;
240     }
241 
242     /***
243      * @return The name of jar file if it doesn't respect the
244      *         &lt;artifactId&gt;-&lt;version&gt;.jar pattern.
245      */
246     public String getJar() {
247         return jar;
248     }
249 
250     /***
251      * @param jar The name of jar file if it doesn't respect the
252      *            &lt;artifactId&gt;-&lt;version&gt;.jar pattern.
253      */
254     public void setJar(final String jar) {
255         this.jar = jar;
256     }
257 
258     /***
259      * @return Whether this project is the root of the dependency graph
260      *         currently opened in Deputy. If so, it is the project which can be
261      *         edited and changed by rules to update and save its dependencies.
262      */
263     public boolean isRootProject() {
264         return isRootProject;
265     }
266 
267     /***
268      * @param rootProject Whether this project is the root of the dependency
269      *                      graph currently opened in Deputy. If so, it is the
270      *                      project which can be edited and changed by rules to
271      *                      update and save its dependencies.
272      */
273     public void setRootProject(final boolean rootProject) {
274         this.isRootProject = rootProject;
275     }
276 
277     /***
278      * @return The optional URL of this project's homepage.
279      */
280     public String getUrl() {
281         return url;
282     }
283 
284     /***
285      * @return The default URL of this project's homepage.
286      */
287     public String getDefaultUrl() {
288         String result = null;
289         /*
290          * Apply default logic to the URL, but only for VRP
291          */
292         if (artifactId.startsWith("vrp-")) {
293             result = "http://bugz-e.tui.de/maven/vrp/projects/";
294             if (getName().indexOf("assembly") == -1) {
295                 result = result + "component.";
296             }
297             result = result + getName() + "/release-notes.html";
298         }
299         return result;
300     }
301 
302     /***
303      * @param url The optional URL of this project's homepage.
304      */
305     public void setUrl(final String url) {
306         this.url = url;
307     }
308 
309     /***
310      * @return The optional name of this project
311      *         (for display purposes and the like).
312      */
313     public String getName() {
314         /*
315          * Apply default and repair logic to the name, but only for VRP
316          */
317         if (name == null) {
318             if (artifactId.startsWith("vrp-")) {
319                 name = artifactId.substring("vrp-".length()); //default
320             }
321         }
322         if (artifactId.startsWith("vrp-")) {
323             name = name.replace('-', '.'); //repair
324         }
325         return name;
326     }
327 
328     /***
329      * @param name The optional name of this project
330      *             (for display purposes and the like).
331      */
332     public void setName(final String name) {
333         this.name = name;
334     }
335 
336     /***
337      * Tries to split off the numerical major version from the version.
338      * The major version is defined to be the numerical prefix of the version
339      * up to the first dot in the version or the whole version if it is an
340      * integer without any dots.
341      *
342      * @return The major version part of the version string or <code>null</code>
343      *         if parsing this fails.
344      */
345     public Integer getMajorVersion() {
346         Integer result = null;
347         String[] parts = version.split("//.");
348         if (parts.length > 0) {
349             try {
350                 result = new Integer(Integer.parseInt(parts[0]));
351             } catch (NumberFormatException e) {
352                 // ignore
353                 result = null;
354             }
355         }
356         return result;
357     }
358 
359     /***
360      * Tries to split off the numerical minor version from the version between
361      * the first and the second dot in the version string.
362      * If the version is literally only a major version and nothing more,
363      * the minor version defaults to 0.
364      *
365      * @return The minor version part of the version string or <code>null</code>
366      *         if parsing this fails.
367      */
368     public Integer getMinorVersion() {
369         if (getMajorVersion() != null) {
370             if (getMajorVersion().toString().length() == version.length()) {
371                 return new Integer(0);
372             }
373         }
374         Integer result = null;
375         String[] parts = version.split("//.");
376         if (parts.length > 1) {
377             try {
378                 result = new Integer(Integer.parseInt(parts[1]));
379             } catch (NumberFormatException e) {
380                 // ignore
381                 result = null;
382             }
383         }
384         return result;
385     }
386 
387     /***
388      * Tries to split off the numerical micro version from the version after
389      * the second dot in the version string. The micro version either extends up
390      * to the third dot, if it exists, or up to the end of the version.<br/>
391      * If the version is literally only a major version and nothing more, or a
392      * major version, a dot and a minor version and nothing more,
393      * the micro version defaults to 0.
394      *
395      * @return The micro version part of the version string or <code>null</code>
396      *         if parsing this fails.
397      */
398     public Integer getMicroVersion() {
399         if (getMajorVersion() != null) {
400             if (getMajorVersion().toString().length() == version.length()) {
401                 return new Integer(0);
402             }
403         }
404         if (getMajorVersion() != null && getMinorVersion() != null) {
405             if (getMajorVersion().toString().length()
406                     + ".".length()
407                     + getMinorVersion().toString().length()
408                     == version.length()) {
409                 return new Integer(0);
410             }
411         }
412         Integer result = null;
413         String[] parts = version.split("//.");
414         if (parts.length > 2) {
415             try {
416                 result = new Integer(Integer.parseInt(parts[2]));
417             } catch (NumberFormatException e) {
418                 // ignore
419                 result = null;
420             }
421         }
422         return result;
423     }
424 
425     /***
426      * @return The rule set of this project.
427      */
428     public RuleSet getRuleSet() {
429         return ruleSet;
430     }
431 
432     /***
433      * @param ruleSet The rule set of this project.
434      */
435     public void setRuleSet(final RuleSet ruleSet) {
436         this.ruleSet = ruleSet;
437     }
438 
439     /***
440      * @return <code>true</code> whether this project is an assembly for which
441      *         indirect dependencies need to be persisted in the project.xml.
442      */
443     public boolean isAssembly() {
444         return isAssembly;
445     }
446 
447     /***
448      * @param assembly <code>true</code> whether this project is an assembly
449      *                   for which indirect dependencies need to be persisted in
450      *                   the project.xml.
451      */
452     public void setAssembly(final boolean assembly) {
453         this.isAssembly = assembly;
454     }
455 
456     /***
457      * @param project The project to check.
458      * @return <code>true</code> if this project has a dependency to the project
459      *         passed in.
460      */
461     public boolean hasDependency(final Project project) {
462         boolean result =
463             projectOccursInProjectList(project, dependencies, true);
464         return result;
465     }
466 
467     /***
468      * @param project The project to check.
469      * @return <code>true</code> if this project has a dependency to the same
470      *         artifact represented by the project passed in.
471      */
472     public boolean hasDependencyToSameArtifact(final Project project) {
473         boolean result =
474             projectOccursInProjectList(project, dependencies, false);
475         return result;
476     }
477 
478     /***
479      * @param project Holds the artifact to find the dependency for.
480      * @return The dependency with the same artifact id as the project passed in
481      *         has or <code>null</code>.
482      */
483     public Project getDependencyToSameArtifact(final Project project) {
484         Project result =
485             getProjectFromProjectList(project, dependencies, false);
486         return result;
487     }
488 
489     /***
490      * @return All direct dependencies of type {@link Project} of this project.
491      */
492     public Iterator getDependencies() {
493         return dependencies.iterator();
494     }
495 
496     /***
497      * Adds a direct dependency to this project and notifies change listeners.
498      *
499      * @param dependency The dependency to add.
500      */
501     public void addDependency(final Project dependency) {
502         dependencies.add(dependency);
503         Collections.sort(dependencies, projectComparator);
504         int index = dependencies.indexOf(dependency);
505         fireAddedDependency(dependency, index);
506     }
507 
508     /***
509      * Removes a direct dependency from this project and notifies change
510      * listeners.
511      *
512      * @param dependency The dependency to remove.
513      */
514     public void removeDependency(final Project dependency) {
515         dependencies.remove(dependency);
516         fireRemovedDependency(dependency);
517     }
518 
519     /***
520      * @param project The project to check if it is an indirect dependency.
521      * @return <code>true</code> if the project passed in is an indirect
522      *         dependency of this project.
523      */
524     public boolean hasIndirectDependency(final Project project) {
525         if (indirectDependencies == null) {
526             indirectDependencies = findIndirectDependencies();
527         }
528         boolean result =
529             projectOccursInProjectList(project, indirectDependencies, true);
530         return result;
531     }
532 
533     /***
534      * @param project Holds the artifact to check the dependency for.
535      * @return <code>true</code> if this project has an indirect dependency to
536      *         the same artifact as the project passed in has.
537      */
538     public boolean hasIndirectDependencyToSameArtifact(final Project project) {
539         if (indirectDependencies == null) {
540             indirectDependencies = findIndirectDependencies();
541         }
542         boolean result =
543             projectOccursInProjectList(project, indirectDependencies, false);
544         return result;
545     }
546 
547     /***
548      * @param project Holds the artifact to find the indirect dependency for.
549      * @return The indirect dependency with the same artifact id as the project
550      *         passed in has or <code>null</code>.
551      */
552     public Project getIndirectDependencyToSameArtifact(final Project project) {
553         if (indirectDependencies == null) {
554             indirectDependencies = findIndirectDependencies();
555         }
556         Project result =
557             getProjectFromProjectList(project, indirectDependencies, false);
558         return result;
559     }
560 
561     /***
562      * @return The indirect dependencies of type {@link Project}.
563      */
564     public Iterator getIndirectDependencies() {
565         if (indirectDependencies == null) {
566             indirectDependencies = findIndirectDependencies();
567         }
568         return indirectDependencies.iterator();
569     }
570 
571     /***
572      * Adds an indirect dependency to this project. Does <i>not</i> notify
573      * change listeners (is not needed because a user will never directly add
574      * such a dependency through the user interface).
575      *
576      * @param dependency The indirect dependency to add.
577      */
578     public void addIndirectDependency(final Project dependency) {
579         if (indirectDependencies == null) {
580             indirectDependencies = new ArrayList();
581         }
582         indirectDependencies.add(dependency);
583         Collections.sort(indirectDependencies, projectComparator);
584     }
585 
586     /***
587      * Removes the indirect dependency and notifies change listeners.
588      *
589      * @param dependency The dependency to remove.
590      */
591     public void removeIndirectDependency(final Project dependency) {
592         if (indirectDependencies == null) {
593             indirectDependencies = findIndirectDependencies();
594         }
595         indirectDependencies.remove(dependency);
596         fireRemovedIndirectDependency(dependency);
597     }
598 
599     /***
600      * @param indirectDependencies The list of indirect dependencies of type
601      *                             {@link Project}.
602      */
603     public void setIndirectDependencies(final List indirectDependencies) {
604         Collections.sort(indirectDependencies, projectComparator);
605         this.indirectDependencies = indirectDependencies;
606     }
607 
608     /***
609      * @param project The dependency to check for.
610      * @return <code>true</code> if this project has a direct or indirect
611      *         dependency to the project passed in.
612      */
613     public boolean hasDirectOrIndirectDependency(final Project project) {
614         boolean result =
615             hasDependency(project) || hasIndirectDependency(project);
616         return result;
617     }
618 
619     /***
620      * Adds a dependency stated literally in this project's project.xml but
621      * which was overruled by some other version of the same artifact.
622      *
623      * @param deprecatedDependency The dependency to add.
624      */
625     public void addDeprecatedDependency(final Project deprecatedDependency) {
626         deprecatedDependencies.add(deprecatedDependency);
627         Collections.sort(deprecatedDependencies, projectComparator);
628     }
629 
630     /***
631      * @return The merged and globally sorted list of both the direct and the
632      *         indirect dependencies of this project. Elements of the list are
633      *         of type {@link Project}.
634      */
635     public List getAllDependencies() {
636         List result = new ArrayList(dependencies);
637         if (indirectDependencies == null) {
638             indirectDependencies = findIndirectDependencies();
639         }
640         result.addAll(indirectDependencies);
641         Collections.sort(result, projectComparator);
642         return result;
643     }
644 
645     /***
646      * @return The projects of type {@link Project} which depend on this
647      *         project.
648      */
649     public List getClients() {
650         return clients;
651     }
652 
653     /***
654      * @return The version conflicts in the dependency graph that roots in this
655      *         project.
656      */
657     public List getConflicts() {
658         if (conflicts == null) {
659             conflicts = findConflicts();
660         }
661         return conflicts;
662     }
663 
664     /***
665      * Adds a list of properties for the artifact id to this project.
666      *
667      * @param dependencyArtifactId The artifact id of the dependency that has
668      *                             these properties defined in this project's
669      *                             POM file.
670      * @param propertyXmlElements The list of associated properties of type
671      *                            {@link org.jdom.Element}.
672      */
673     public void addDependencyPropertyList(
674             final String dependencyArtifactId,
675             final List propertyXmlElements) {
676         dependencyProperties.put(dependencyArtifactId, propertyXmlElements);
677     }
678 
679     /***
680      * @param dependencyArtifactId The artifact id of the dependency to retrieve
681      *                             properties for.
682      * @return A list of properties of type {@link org.jdom.Element} or
683      *         <code>null</code>.
684      */
685     public List getDependencyPropertyList(final String dependencyArtifactId) {
686         List result = (List) dependencyProperties.get(dependencyArtifactId);
687         return result;
688     }
689 
690     /***
691      * Adds a change listener to this project which will be notified when this
692      * project changed in defined ways.
693      *
694      * @param listener The listener to add.
695      */
696     public void addChangeListener(final ProjectChangeListener listener) {
697         if (!changeListeners.contains(listener)) {
698             changeListeners.add(listener);
699         }
700     }
701 
702     /***
703      * Removes a change listener from this project.
704      *
705      * @param listener The listener to remove.
706      */
707     public void removeChangeListener(final ProjectChangeListener listener) {
708         changeListeners.remove(listener);
709     }
710 
711     /*(non-Javadoc)
712      * @see java.lang.Object#toString()
713      */
714     /***
715      * {@inheritDoc}
716      */
717     public String toString() {
718         StringBuffer buffer = new StringBuffer(groupId + "");
719         buffer.append("/");
720         buffer.append(artifactId);
721         buffer.append("-");
722         buffer.append(version);
723         return buffer.toString();
724     }
725 
726     /***
727      * @param project The project to check for in the <code>projectList</code>.
728      * @param projectList The projectList with objects of type {@link Project}.
729      * @param exactMatch Whether to compare by artifact id and version (=exact)
730      *                   or only by artifact id.
731      * @return <code>true</code> if the project logically occurs in the project
732      *         list.
733      */
734     private boolean projectOccursInProjectList(
735             final Project project,
736             final List projectList,
737             final boolean exactMatch) {
738         boolean result =
739             getProjectFromProjectList(project, projectList, exactMatch) != null;
740         return result;
741     }
742 
743     /***
744      * @param project The project to check for in the <code>projectList</code>.
745      * @param projectList The projectList with objects of type {@link Project}.
746      * @param exactMatch Whether to compare by artifact id and version (=exact)
747      *                   or only by artifact id.
748      * @return The project from the project list which is logically equivalent
749      *         to <code>project</code>.
750      */
751     private Project getProjectFromProjectList(
752             final Project project,
753             final List projectList,
754             final boolean exactMatch) {
755         Project result = null;
756         for (Iterator iter = projectList.iterator(); iter.hasNext();) {
757             Project dependency = (Project) iter.next();
758             if (dependency.getGroupId().equals(project.getGroupId())
759                     && dependency.getArtifactId().equals(
760                             project.getArtifactId())) {
761                 if (exactMatch) {
762                     if (dependency.getVersion().equals(project.getVersion())) {
763                         result = dependency;
764                     }
765                 } else {
766                     result = dependency;
767                 }
768                 if (result != null) {
769                     break;
770                 }
771             }
772         }
773         return result;
774     }
775 
776     /***
777      * Flattens the graph of indirect dependencies of this project and returns
778      * them without duplicates in a list.
779      *
780      * @return The list of indirect dependencies of type {@link Project} of this
781      *         project. Maybe empty but not <code>null</code>.
782      */
783     private List findIndirectDependencies() {
784         List flattenedDependencies = new ArrayList();
785         flattenDependenciesForIndirectDependencies(this, flattenedDependencies);
786         sortProjects(flattenedDependencies);
787         return flattenedDependencies;
788     }
789 
790     /***
791      * @return The list of version conflicts of type
792      *         {@link ProjectConflictGroup} of this project.
793      */
794     private List findConflicts() {
795         List flattenedDependencies = new ArrayList();
796         flattenDependenciesForConflicts(this, flattenedDependencies);
797         sortProjects(flattenedDependencies);
798         List results = new ArrayList();
799         ProjectConflictGroup conflictGroup = new ProjectConflictGroup();
800         String conflictArtifactId = "";
801         for (Iterator iter = flattenedDependencies.iterator();
802                 iter.hasNext();) {
803             Project project = (Project) iter.next();
804             if (conflictGroup.getProjects().size() == 0) {
805                 conflictGroup.getProjects().add(project);
806                 conflictArtifactId = project.getArtifactId();
807                 conflictGroup.setArtifactId(conflictArtifactId);
808             } else if (project.getArtifactId().equals(conflictArtifactId)) {
809                 conflictGroup.getProjects().add(project);
810             } else {
811                 if (conflictGroup.getProjects().size() > 1) {
812                     results.add(conflictGroup);
813                 }
814                 conflictGroup = new ProjectConflictGroup();
815                 conflictGroup.getProjects().add(project);
816                 conflictArtifactId = project.getArtifactId();
817                 conflictGroup.setArtifactId(conflictArtifactId);
818             }
819         }
820         return results;
821     }
822 
823     /***
824      * Flattens all dependencies and all overruled dependencies as the basis
825      * to find version conflicts in the dependency graph.
826      *
827      * @param project The project to be used as root of the (sub) graph to be
828      *                flattened and collected in the list.
829      * @param flattenedDependencies The list to collect all flattened
830      *                              dependencies in.
831      */
832     private void flattenDependenciesForConflicts(
833             final Project project,
834             final List flattenedDependencies) {
835         Set mergedDependencies = new HashSet(project.dependencies);
836         mergedDependencies.addAll(project.deprecatedDependencies);
837         for (Iterator iter = mergedDependencies.iterator(); iter.hasNext();) {
838             Project dependencyProject = (Project) iter.next();
839             if (!flattenedDependencies.contains(dependencyProject)) {
840                 flattenedDependencies.add(dependencyProject);
841                 flattenDependenciesForConflicts(
842                     dependencyProject, flattenedDependencies);
843             }
844         }
845     }
846 
847     /***
848      * Flattens all dependencies as the basis to find all indirect dependencies.
849      *
850      * @param project The project to be used as root of the (sub) graph to be
851      *                flattened and collected in the list. The
852      *                <code>project</code> itself will not be added to the list.
853      * @param flattenedDependencies The list to collect all flattened
854      *                              dependencies in.
855      */
856     private void flattenDependenciesForIndirectDependencies(
857             final Project project,
858             final List flattenedDependencies) {
859         Set dependencySet = new HashSet(project.dependencies);
860         for (Iterator iter = dependencySet.iterator(); iter.hasNext();) {
861             Project dependencyProject = (Project) iter.next();
862             if (!flattenedDependencies.contains(dependencyProject)) {
863                 flattenedDependencies.add(dependencyProject);
864                 flattenDependenciesForIndirectDependencies(
865                     dependencyProject, flattenedDependencies);
866             }
867         }
868         if (project == this) {
869             flattenedDependencies.removeAll(dependencies);
870         }
871     }
872 
873     /***
874      * @param projects The list of Maven projects to sort.
875      */
876     private void sortProjects(final List projects) {
877         Collections.sort(projects, new ProjectComparator());
878     }
879 
880     /***
881      * Notifies listeners that a direct dependency has been added to this
882      * project.
883      *
884      * @param addedDependency The added dependency.
885      * @param index The index of the added dependency in the sorted list of all
886      *              dependencies of this project.
887      */
888     private void fireAddedDependency(
889             final Project addedDependency,
890             final int index) {
891         for (Iterator iter = changeListeners.iterator(); iter.hasNext();) {
892             ProjectChangeListener listener =
893                 (ProjectChangeListener) iter.next();
894             listener.addedDependency(addedDependency, index);
895         }
896     }
897 
898     /***
899      * Notifies listeners that a direct dependency has been removed from this
900      * project.
901      *
902      * @param removedDependency The removed dependency.
903      */
904     private void fireRemovedDependency(final Project removedDependency) {
905         for (Iterator iter = changeListeners.iterator(); iter.hasNext();) {
906             ProjectChangeListener listener =
907                 (ProjectChangeListener) iter.next();
908             listener.removedDependency(removedDependency);
909         }
910     }
911 
912     /***
913      * Notifies listeners that an indirect dependency has been removed from this
914      * project.
915      *
916      * @param removedDependency The removed dependency.
917      */
918     private void fireRemovedIndirectDependency(
919             final Project removedDependency) {
920         for (Iterator iter = changeListeners.iterator(); iter.hasNext();) {
921             ProjectChangeListener listener =
922                 (ProjectChangeListener) iter.next();
923             listener.removedIndirectDependency(removedDependency);
924         }
925     }
926 }