View Javadoc

1   package de.matthias_burbach.deputy.swing;
2   
3   import java.awt.event.ActionEvent;
4   import java.awt.event.MouseAdapter;
5   import java.awt.event.MouseEvent;
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.Comparator;
9   import java.util.Enumeration;
10  import java.util.Iterator;
11  import java.util.List;
12  
13  import javax.swing.AbstractAction;
14  import javax.swing.Action;
15  import javax.swing.JOptionPane;
16  import javax.swing.JPopupMenu;
17  import javax.swing.JTree;
18  import javax.swing.tree.TreeModel;
19  import javax.swing.tree.TreeNode;
20  import javax.swing.tree.TreePath;
21  import javax.swing.tree.TreeSelectionModel;
22  
23  import de.matthias_burbach.deputy.core.Deputy;
24  import de.matthias_burbach.deputy.core.project.Project;
25  import de.matthias_burbach.deputy.core.project.ProjectHolderTreeNode;
26  import de.matthias_burbach.deputy.core.repository.RepositoryArtifact;
27  import de.matthias_burbach.deputy.core.rule.DeprecationRule;
28  import de.matthias_burbach.deputy.core.rule.EnforcementRule;
29  import de.matthias_burbach.deputy.core.rule.RemovalRule;
30  import de.matthias_burbach.deputy.core.rule.ReplacementRule;
31  import de.matthias_burbach.deputy.core.rule.RetentionRule;
32  import de.matthias_burbach.deputy.core.rule.Rule;
33  import de.matthias_burbach.deputy.core.rule.RuleSet;
34  import de.matthias_burbach.deputy.core.util.Log;
35  
36  /***
37   * Is used as the project tree, the repositories tree, and the second
38   * repositories tree that pops up in a modal dialog to choose the replacement
39   * project for a replacement rule.
40   * <p/>
41   * Features various popup menus whose action items depend on the type of the
42   * selected node.
43   *
44   * @author Matthias Burbach
45   */
46  public class DeputyTree extends JTree {
47      /***
48       * Collapses the selected node deeply, i. e.
49       * all descendant nodes are collapsed as well.
50       * <p/>
51       * Is available on all types of nodes on both the project and the
52       * repositories tree (but not on the replacement chooser tree).
53       *
54       * @author Matthias Burbach
55       */
56      private class CollapseAllAction extends AbstractAction {
57          /***
58           * Constructs this action.
59           */
60          public CollapseAllAction() {
61              super("Collapse All");
62          }
63  
64          /*
65           * (non-Javadoc)
66           * @see java.awt.event.ActionListener#actionPerformed(
67           *          java.awt.event.ActionEvent)
68           */
69          /***
70           * {@inheritDoc}
71           */
72          public void actionPerformed(final ActionEvent e) {
73              TreePath treePath = getSelectionPath();
74              if (treePath != null) {
75                  Enumeration descsEnum = getDescendantToggledPaths(treePath);
76                  List descsList = enumToList(descsEnum);
77                  sortListByPathCountDescending(descsList);
78                  Iterator iter = descsList.iterator();
79                  while (iter.hasNext()) {
80                      TreePath descTreePath = (TreePath) iter.next();
81                      collapsePath(descTreePath);
82                  }
83              }
84          }
85  
86          /***
87           * @param anEnum The enumeration to be converted to a list.
88           * @return The list holding the elements of <code>enum</code>.
89           */
90          private List enumToList(final Enumeration anEnum) {
91              List result = new ArrayList();
92              while (anEnum.hasMoreElements()) {
93                  result.add(anEnum.nextElement());
94              }
95              return result;
96          }
97  
98          /***
99           * @param treePaths The tree paths to be sorted.
100          */
101         private void sortListByPathCountDescending(final List treePaths) {
102             Comparator comparator = new Comparator() {
103                 public int compare(final Object o1, final Object o2) {
104                     TreePath tp1 = (TreePath) o1;
105                     TreePath tp2 = (TreePath) o2;
106                     return tp2.getPathCount() - tp1.getPathCount();
107                 }
108             };
109             Collections.sort(treePaths, comparator);
110         }
111     }
112 
113     /***
114      * Searches the selected project in the repositories tree.
115      * If it finds it, it will expand the repositories tree and select the node
116      * found.<br/>
117      * Else, a message box pops up, which notifies the user that the project
118      * couldn't be found in the repositories.
119      *
120      * @author Matthias Burbach
121      */
122     private class FindInRepositoriesAction extends AbstractAction {
123         /***
124          * Constructs this action.
125          */
126         public FindInRepositoriesAction() {
127             super("Find In Repositories");
128         }
129 
130         /*
131          * (non-Javadoc)
132          * @see java.awt.event.ActionListener#actionPerformed(
133          *          java.awt.event.ActionEvent)
134          */
135         /***
136          * {@inheritDoc}
137          */
138         public void actionPerformed(final ActionEvent e) {
139             TreeNode node = getSelectedNode();
140             if (node instanceof ProjectTreeNode) {
141                 Project project = ((ProjectTreeNode) node).getProject();
142                 deputyFrame.findInRepositories(
143                     project.getGroupId(),
144                     project.getArtifactId(),
145                     project.getVersion());
146             }
147         }
148     }
149 
150     /***
151      * Launches the default browser to open the project's documentation web
152      * site.
153      *
154      * @author Matthias Burbach
155      */
156     private class OpenSiteAction extends AbstractAction {
157         /***
158          * Constructs this action.
159          */
160         public OpenSiteAction() {
161             super("Open Site");
162         }
163 
164         /*
165          * (non-Javadoc)
166          * @see java.awt.event.ActionListener#actionPerformed(
167          *          java.awt.event.ActionEvent)
168          */
169         /***
170          * {@inheritDoc}
171          */
172         public void actionPerformed(final ActionEvent e) {
173             TreeNode node = getSelectedNode();
174             if (node instanceof ProjectHolderTreeNode) {
175                 ProjectHolderTreeNode projectNode =
176                     (ProjectHolderTreeNode) node;
177                 deputyFrame.openProjectSite(
178                     projectNode.getProject());
179             }
180         }
181     }
182 
183     /***
184      * Changes the default rule of the dependency recursion to the configured
185      * value.
186      *
187      * @author Matthias Burbach
188      */
189     private class ChangeDefaultRuleAction extends AbstractAction {
190         /***
191          * The value to change the default rule to.
192          */
193         private String value = RuleSet.DEFAULT_RULE_LATEST_RELEASE;
194 
195         /***
196          * Constructs this action.
197          *
198          * @param value The value to change the default rule to.
199          * @param text The menu item text of this action.
200          */
201         public ChangeDefaultRuleAction(final String value, final String text) {
202             super("Change default to " + text);
203             this.value = value;
204         }
205 
206         /*
207          * (non-Javadoc)
208          * @see java.awt.event.ActionListener#actionPerformed(
209          *              java.awt.event.ActionEvent)
210          */
211         /***
212          * {@inheritDoc}
213          */
214         public void actionPerformed(final ActionEvent e) {
215             deputy.getRootProject().getRuleSet().setDefaultRule(value);
216         }
217     }
218 
219     /***
220      * Is the base class for adding a rule.
221      *
222      * @author Matthias Burbach
223      */
224     private abstract class AddRuleAction extends AbstractAction {
225         /***
226          * @param name The name of this action to be displayed on menu items
227          */
228         public AddRuleAction(final String name) {
229             super(name);
230         }
231 
232         /***
233          * @return Whether adding a rule is possible. Is called by sub classes.
234          */
235         protected boolean mayAdd() {
236             boolean result = true;
237             if (deputy.getRootProject() == null) {
238                 JOptionPane.showMessageDialog(
239                         DeputyTree.this,
240                         "You need to open a project "
241                         + "before you can add a rule.",
242                         "Info",
243                         JOptionPane.INFORMATION_MESSAGE);
244                 result = false;
245             }
246             return result;
247         }
248 
249         /***
250          * @param rule The rule to add to the rule set.
251          */
252         protected void addRule(final Rule rule) {
253             boolean doAdd = false;
254             RuleSet ruleSet = deputy.getRootProject().getRuleSet();
255             if (ruleSet.hasSameRule(rule)) {
256                 JOptionPane.showMessageDialog(
257                     DeputyTree.this,
258                     "The rule '" + rule + "' already exists. "
259                     + "It cannot be added twice.",
260                     "Info",
261                     JOptionPane.INFORMATION_MESSAGE);
262                 doAdd = false;
263             } else if (ruleSet.hasRuleForKey(rule)) {
264                 Rule oldRule = ruleSet.getRuleForKey(rule);
265                 int selectedOption = JOptionPane.showOptionDialog(
266                     DeputyTree.this,
267                     "Remove rule "
268                     + "'" + oldRule + "'\n"
269                     + "in favour of rule "
270                     + "'" + rule + "'"
271                     + "?",
272                     "Replace Existing Rule",
273                     JOptionPane.OK_CANCEL_OPTION,
274                     JOptionPane.QUESTION_MESSAGE,
275                     null,
276                     new Object[] {"OK", "Cancel"},
277                     null);
278                 if (selectedOption == 0) {
279                     doAdd = true;
280                 } else {
281                     doAdd = false;
282                 }
283             } else {
284                 doAdd = true;
285             }
286             if (doAdd) {
287                 ruleSet.add(rule);
288             }
289         }
290     }
291 
292     /***
293      * Adds an enforcement rule for the selected projects.
294      *
295      * @author Matthias Burbach
296      */
297     private class AddEnforcementRuleAction extends AddRuleAction {
298         /***
299          * Constructs this action.
300          */
301         public AddEnforcementRuleAction() {
302             super("Add Enforcement Rule");
303         }
304 
305         /*
306          * (non-Javadoc)
307          * @see java.awt.event.ActionListener#actionPerformed(
308          *          java.awt.event.ActionEvent)
309          */
310         /***
311          * {@inheritDoc}
312          */
313         public void actionPerformed(final ActionEvent e) {
314             if (mayAdd()) {
315                 Iterator iter =
316                     getSelectedProjectHolderTreeNodes().iterator();
317                 while (iter.hasNext()) {
318                     ProjectHolderTreeNode projectNode =
319                         (ProjectHolderTreeNode) iter.next();
320                     Project project = projectNode.getProject();
321                     EnforcementRule rule = new EnforcementRule();
322                     rule.setGroupId(project.getGroupId());
323                     rule.setArtifactId(project.getArtifactId());
324                     rule.setVersion(project.getVersion());
325                     addRule(rule);
326                 }
327             }
328         }
329     }
330 
331     /***
332      * Adds an enforcement rule for the selected projects.
333      *
334      * @author Matthias Burbach
335      */
336     private class AddSnapshotEnforcementRuleAction extends AddRuleAction {
337         /***
338          * Constructs this action.
339          */
340         public AddSnapshotEnforcementRuleAction() {
341             super("Add Enforcement Rule (SNAPSHOT)");
342         }
343 
344         /*
345          * (non-Javadoc)
346          * @see java.awt.event.ActionListener#actionPerformed(
347          *          java.awt.event.ActionEvent)
348          */
349         /***
350          * {@inheritDoc}
351          */
352         public void actionPerformed(final ActionEvent e) {
353             if (mayAdd()) {
354                 Iterator iter =
355                     getSelectedProjectHolderTreeNodes().iterator();
356                 boolean warn = false;
357                 while (iter.hasNext()) {
358                     ProjectHolderTreeNode projectNode =
359                         (ProjectHolderTreeNode) iter.next();
360                     Project project = projectNode.getProject();
361                     if (deputyFrame.existsInRepositories(
362                             project.getGroupId(),
363                             project.getArtifactId(),
364                             "SNAPSHOT")) {
365                         EnforcementRule rule = new EnforcementRule();
366                         rule.setGroupId(project.getGroupId());
367                         rule.setArtifactId(project.getArtifactId());
368                         rule.setVersion("SNAPSHOT");
369                         addRule(rule);
370                     } else {
371                         Log log = deputy.getLog();
372                         if (log != null) {
373                             log.log(Log.SEVERITY_WARNING,
374                                     "No SNAPSHOT in repositories for "
375                                     + project);
376                         }
377                         warn = true;
378                     }
379                 }
380                 if (warn) {
381                     JOptionPane.showMessageDialog(
382                             DeputyTree.this,
383                             "Could not create SNAPSHOT enforcement rules"
384                             + " for all selected projects"
385                             + " because such SNAPSHOTs don't exist.\n"
386                             + "See log for affected projects.",
387                             "Warning",
388                             JOptionPane.WARNING_MESSAGE);
389                 }
390             }
391         }
392     }
393 
394     /***
395      * Adds a deprecation rule for the selected project.
396      *
397      * @author Matthias Burbach
398      */
399     private class AddDeprecationRuleAction extends AddRuleAction {
400         /***
401          * Constructs this action.
402          */
403         public AddDeprecationRuleAction() {
404             super("Add Deprecation Rule");
405         }
406 
407         /*
408          * (non-Javadoc)
409          * @see java.awt.event.ActionListener#actionPerformed(
410          *          java.awt.event.ActionEvent)
411          */
412         /***
413          * {@inheritDoc}
414          */
415         public void actionPerformed(final ActionEvent e) {
416             if (mayAdd()) {
417                 Iterator iter =
418                     getSelectedProjectHolderTreeNodes().iterator();
419                 while (iter.hasNext()) {
420                     ProjectHolderTreeNode projectNode =
421                         (ProjectHolderTreeNode) iter.next();
422                     Project project = projectNode.getProject();
423                     DeprecationRule rule = new DeprecationRule();
424                     rule.setGroupId(project.getGroupId());
425                     rule.setArtifactId(project.getArtifactId());
426                     rule.setVersion(project.getVersion());
427                     addRule(rule);
428                 }
429             }
430         }
431     }
432 
433     /***
434      * Adds a replacement rule for the two selected projects (or artifacts).
435      *
436      * @author Matthias Burbach
437      */
438     private class AddReplacementRuleAction extends AddRuleAction {
439         /***
440          * if <code>true</code> replacement rules will be created
441          * without specific version of the artifact to be removed.
442          */
443         private boolean noVersion = false;
444 
445         /***
446          * Constructs this action.
447          * @param noVersion if <code>true</code> replacement rules will be
448          *                  created without specific version of the artifact to
449          *                  be replaced.
450          */
451         public AddReplacementRuleAction(final boolean noVersion) {
452             super("Add Replacement Rule");
453             this.noVersion = noVersion;
454             if (noVersion) {
455                 putValue(Action.NAME, "Add Replacement Rule (no version)");
456             }
457         }
458 
459         /*
460          * (non-Javadoc)
461          * @see java.awt.event.ActionListener#actionPerformed(
462          *          java.awt.event.ActionEvent)
463          */
464         /***
465          * {@inheritDoc}
466          */
467         public void actionPerformed(final ActionEvent e) {
468             if (mayAdd()) {
469                 TreeNode node = getSelectedNode();
470                 String groupId = null;
471                 String artifactId = null;
472                 String version = null;
473                 if (node instanceof RepositoryArtifact) {
474                     RepositoryArtifact artifact = (RepositoryArtifact) node;
475                     groupId = artifact.getGroupId();
476                     artifactId = artifact.getArtifactId();
477                 } else if (node instanceof ProjectHolderTreeNode) {
478                     Project project =
479                         ((ProjectHolderTreeNode) node).getProject();
480                     groupId = project.getGroupId();
481                     artifactId = project.getArtifactId();
482                     if (!noVersion) {
483                         version = project.getVersion();
484                     }
485                 }
486                 if (groupId != null && artifactId != null) {
487                     ReplacementRule rule = new ReplacementRule();
488                     rule.setDisplacedGroupId(groupId);
489                     rule.setDisplacedArtifactId(artifactId);
490                     rule.setDisplacedVersion(version);
491                     replacementChooserDialog.setRule(rule);
492                     replacementChooserDialog.activate();
493                     if (rule.getGroupId() != null
494                             && rule.getArtifactId() != null
495                             && rule.getVersion() != null) {
496                         addRule(rule);
497                     }
498                 }
499             }
500         }
501     }
502 
503     /***
504      * Adds a removal rule for the two selected projects (or artifacts).
505      *
506      * @author Matthias Burbach
507      */
508     private class AddRemovalRuleAction extends AddRuleAction {
509         /***
510          * if <code>true</code> removal rules will be created
511          * without specific version of the artifact to be removed.
512          */
513         private boolean noVersion = false;
514 
515         /***
516          * Constructs this action.
517          * @param noVersion if <code>true</code> removal rules will be created
518          *        without specific version of the artifact to be removed
519          */
520         public AddRemovalRuleAction(final boolean noVersion) {
521             super("Add Removal Rule");
522             this.noVersion = noVersion;
523             if (noVersion) {
524                 putValue(Action.NAME, "Add Removal Rule (no version)");
525             }
526         }
527 
528         /*
529          * (non-Javadoc)
530          * @see java.awt.event.ActionListener#actionPerformed(
531          *          java.awt.event.ActionEvent)
532          */
533         /***
534          * {@inheritDoc}
535          */
536         public void actionPerformed(final ActionEvent e) {
537             if (mayAdd()) {
538                 Iterator iter =
539                     getSelectedNodes().iterator();
540                 String groupId = null;
541                 String artifactId = null;
542                 String version = null;
543                 while (iter.hasNext()) {
544                     TreeNode node = (TreeNode) iter.next();
545                     if (node instanceof RepositoryArtifact) {
546                         RepositoryArtifact artifact = (RepositoryArtifact) node;
547                         groupId = artifact.getGroupId();
548                         artifactId = artifact.getArtifactId();
549                     } else if (node instanceof ProjectHolderTreeNode) {
550                         Project project =
551                             ((ProjectHolderTreeNode) node).getProject();
552                         groupId = project.getGroupId();
553                         artifactId = project.getArtifactId();
554                         if (!noVersion) {
555                             version = project.getVersion();
556                         }
557                     }
558                     if (groupId != null && artifactId != null) {
559                         RemovalRule rule = new RemovalRule();
560                         rule.setGroupId(groupId);
561                         rule.setArtifactId(artifactId);
562                         rule.setVersion(version);
563                         addRule(rule);
564                     }
565                 }
566             }
567         }
568     }
569 
570     /***
571      * Adds a retention rule for the selected project (or artifact).
572      *
573      * @author Matthias Burbach
574      */
575     private class AddRetentionRuleAction extends AddRuleAction {
576         /***
577          * if <code>true</code> retention rules will be created
578          * without specific version of the artifact to be removed.
579          */
580         private boolean noVersion = false;
581 
582         /***
583          * Constructs this action.
584          * @param noVersion if <code>true</code> retention rules will be created
585          *        without specific version of the artifact to be removed
586          */
587         public AddRetentionRuleAction(final boolean noVersion) {
588             super("Add Retention Rule");
589             this.noVersion = noVersion;
590             if (noVersion) {
591                 putValue(Action.NAME, "Add Retention Rule (no version)");
592             }
593         }
594 
595         /*
596          * (non-Javadoc)
597          * @see java.awt.event.ActionListener#actionPerformed(
598          *          java.awt.event.ActionEvent)
599          */
600         /***
601          * {@inheritDoc}
602          */
603         public void actionPerformed(final ActionEvent e) {
604             if (mayAdd()) {
605                 Iterator iter =
606                     getSelectedNodes().iterator();
607                 String groupId = null;
608                 String artifactId = null;
609                 String version = null;
610                 while (iter.hasNext()) {
611                     TreeNode node = (TreeNode) iter.next();
612                     if (node instanceof RepositoryArtifact) {
613                         RepositoryArtifact artifact = (RepositoryArtifact) node;
614                         groupId = artifact.getGroupId();
615                         artifactId = artifact.getArtifactId();
616                     } else if (node instanceof ProjectHolderTreeNode) {
617                         Project project =
618                             ((ProjectHolderTreeNode) node).getProject();
619                         groupId = project.getGroupId();
620                         artifactId = project.getArtifactId();
621                         if (!noVersion) {
622                             version = project.getVersion();
623                         }
624                     }
625                     if (groupId != null && artifactId != null) {
626                         RetentionRule rule = new RetentionRule();
627                         rule.setGroupId(groupId);
628                         rule.setArtifactId(artifactId);
629                         rule.setVersion(version);
630                         addRule(rule);
631                     }
632                 }
633             }
634         }
635     }
636 
637     /***
638      * Removes all selected rules.
639      *
640      * @author Matthias Burbach
641      */
642     private class RemoveRuleAction extends AbstractAction {
643         /***
644          * Constructs this action.
645          */
646         public RemoveRuleAction() {
647             super("Remove");
648         }
649 
650         /*
651          * (non-Javadoc)
652          * @see java.awt.event.ActionListener#actionPerformed(
653          *          java.awt.event.ActionEvent)
654          */
655         /***
656          * {@inheritDoc}
657          */
658         public void actionPerformed(final ActionEvent e) {
659             Iterator iter = getSelectedRuleNodes().iterator();
660             RuleSet ruleSet = deputy.getRootProject().getRuleSet();
661             while (iter.hasNext()) {
662                 RuleTreeNode ruleTreeNode = (RuleTreeNode) iter.next();
663                 Rule rule = ruleTreeNode.getRule();
664                 ruleSet.remove(rule);
665             }
666         }
667     }
668 
669     /***
670      * Removes all selected top level dependencies.
671      *
672      * @author Matthias Burbach
673      */
674     private class RemoveDependencyAction extends AbstractAction {
675         /***
676          * Constructs this action.
677          */
678         public RemoveDependencyAction() {
679             super("Remove");
680         }
681 
682         /*
683          * (non-Javadoc)
684          * @see java.awt.event.ActionListener#actionPerformed(
685          *          java.awt.event.ActionEvent)
686          */
687         /***
688          * {@inheritDoc}
689          */
690         public void actionPerformed(final ActionEvent e) {
691             Iterator iter =
692                 getSelectedDirectDependencyNodes().iterator();
693             Project rootProject = deputy.getRootProject();
694             while (iter.hasNext()) {
695                 ProjectTreeNode projectTreeNode = (ProjectTreeNode) iter.next();
696                 Project dependency = projectTreeNode.getProject();
697                 if (rootProject.hasDependency(dependency)) {
698                     rootProject.removeDependency(dependency);
699                 }
700             }
701         }
702     }
703 
704     /***
705      * Adds a top level dependency.
706      *
707      * @author Matthias Burbach
708      */
709     private class AddDependencyAction extends AbstractAction {
710         /***
711          * Constructs this action.
712          */
713         public AddDependencyAction() {
714             super("Add Dependency");
715         }
716 
717         /*
718          * (non-Javadoc)
719          * @see java.awt.event.ActionListener#actionPerformed(
720          *              java.awt.event.ActionEvent)
721          */
722         /***
723          * {@inheritDoc}
724          */
725         public void actionPerformed(final ActionEvent e) {
726             if (deputy.getRootProject() == null) {
727                 JOptionPane.showMessageDialog(
728                         DeputyTree.this,
729                         "You need to open a project "
730                         + "before you can add a dependency.",
731                         "Info",
732                         JOptionPane.INFORMATION_MESSAGE);
733                 return;
734             }
735             Iterator nodeIter = getSelectedNodes().iterator();
736             while (nodeIter.hasNext()) {
737                 TreeNode node = (TreeNode) nodeIter.next();
738                 handleNode(node);
739             }
740         }
741 
742         /***
743          * @param node The node to handle.
744          */
745         private void handleNode(final TreeNode node) {
746             if (node instanceof ProjectHolderTreeNode) {
747                 boolean doAdd = true;
748                 boolean doRemoveFromDependencies = false;
749                 boolean doRemoveFromIndirectDependencies = false;
750                 ProjectHolderTreeNode projectNode =
751                     (ProjectHolderTreeNode) node;
752                 Project dependency = projectNode.getProject();
753                 Project topProject = deputy.getRootProject();
754                 if (isRootProject(dependency)) {
755                     JOptionPane.showMessageDialog(
756                         DeputyTree.this,
757                         dependency + " is the top project. "
758                         + "It cannot be made a dependency of itself.",
759                         "Info",
760                         JOptionPane.INFORMATION_MESSAGE);
761                     doAdd = false;
762                 } else if (topProject.hasDependency(dependency)) {
763                     JOptionPane.showMessageDialog(
764                         DeputyTree.this,
765                         "The dependency '" + dependency + "' already exists. "
766                         + "It cannot be added twice.",
767                         "Info",
768                         JOptionPane.INFORMATION_MESSAGE);
769                     doAdd = false;
770                 } else if (topProject.hasDependencyToSameArtifact(dependency)
771                             && !topProject.getRuleSet().isRetained(
772                                     dependency.getArtifactId(),
773                                     dependency.getVersion())) {
774                     int selectedOption = JOptionPane.showOptionDialog(
775                         DeputyTree.this,
776                         "The dependency '" + dependency + "' already exists "
777                         + "except for the version.\n"
778                         + "If you really need to change the version, "
779                         + "better use rules to do so, if possible!\n"
780                         + "If you want to add multiple versions of the same "
781                         + "artifact you must create a retention rule first.",
782                         "Really change the version of this dependency "
783                         + "this way?",
784                         JOptionPane.OK_CANCEL_OPTION,
785                         JOptionPane.QUESTION_MESSAGE,
786                         null,
787                         new Object[] {
788                             "Yes, change the version now",
789                             "No, cancel" },
790                         null);
791                     if (selectedOption == 0) {
792                         doRemoveFromDependencies = true;
793                     } else {
794                         doAdd = false;
795                     }
796                 } else if (topProject.isAssembly()
797                               && topProject.hasIndirectDependencyToSameArtifact(
798                                     dependency)) {
799                     int selectedOption = JOptionPane.showOptionDialog(
800                         DeputyTree.this,
801                         dependency + " is currently an indirect dependency. "
802                         + "If you really must, you can change it to a direct "
803                         + "dependency. ",
804                         "Really change this to a direct dependency?",
805                         JOptionPane.OK_CANCEL_OPTION,
806                         JOptionPane.QUESTION_MESSAGE,
807                         null,
808                         new Object[] {
809                             "Yes, change to a direct dependency",
810                             "No, cancel" },
811                         null);
812                     if (selectedOption == 0) {
813                         doRemoveFromIndirectDependencies = true;
814                     } else {
815                         doAdd = false;
816                     }
817                 }
818                 Project removable = null;
819                 if (doRemoveFromDependencies) {
820                     removable =
821                         topProject.getDependencyToSameArtifact(
822                             dependency);
823                     topProject.removeDependency(removable);
824                 }
825                 if (doRemoveFromIndirectDependencies) {
826                     removable =
827                         topProject.getIndirectDependencyToSameArtifact(
828                             dependency);
829                     topProject.removeIndirectDependency(removable);
830                 }
831                 if (doAdd) {
832                     if (removable != null) {
833                         /*
834                          * Transfer properties from removable to addable
835                          */
836                         dependency.setType(removable.getType());
837                         dependency.setJar(removable.getJar());
838                         dependency.setUrl(removable.getUrl());
839                     }
840                     topProject.addDependency(dependency);
841                 }
842             }
843         }
844 
845         /***
846          * @param project The project to check.
847          * @return <code>true</code> if the project is the root project.
848          */
849         private boolean isRootProject(final Project project) {
850             boolean result = project.isRootProject();
851             if (!result) {
852                 Project topProject = deputy.getRootProject();
853                 if (topProject.getGroupId().equals(project.getGroupId())
854                         && topProject.getArtifactId().equals(
855                                 project.getArtifactId())) {
856                     result = true;
857                 }
858             }
859             return result;
860         }
861     }
862 
863     /***
864      * Pops up the appropriate menu depending on the type of node selected.
865      *
866      * @author Matthias Burbach
867      */
868     private class PopupTrigger extends MouseAdapter {
869         /*
870          * (non-Javadoc)
871          * @see java.awt.event.MouseListener#mouseReleased(
872          *              java.awt.event.MouseEvent)
873          */
874         /***
875          * {@inheritDoc}
876          */
877         public void mouseReleased(final MouseEvent e) {
878             if (e.isPopupTrigger()) {
879                 /*
880                  * Enable all items that might have been disabled the last
881                  * time this popup menu was shown
882                  */
883                 directDependencyNodePopupMenu.enableAllItems();
884                 indirectDependencyNodePopupMenu.enableAllItems();
885 
886                 int x = e.getX();
887                 int y = e.getY();
888 
889                 /*
890                  * Handle the cases where multiple selections are supported by
891                  * a popup menu
892                  */
893                 int numberOfSelectedNodes = getNumberOfSelectedNodes();
894                 if (numberOfSelectedNodes > 0
895                         && numberOfSelectedNodes
896                             == getSelectedRuleNodes().size()) {
897                     ruleNodePopupMenu.show(DeputyTree.this, x, y);
898                     /*
899                      * 'Dirty' exit
900                      */
901                     return;
902                 } else if (getSelectedDirectDependencyNodes().size()
903                             == numberOfSelectedNodes
904                             && numberOfSelectedNodes > 1) {
905                     directDependencyNodePopupMenu.disableSingleSelectionItems();
906                     directDependencyNodePopupMenu.show(DeputyTree.this, x, y);
907                     /*
908                      * 'Dirty' exit
909                      */
910                     return;
911                 } else if (getSelectedIndirectDependencyNodes().size()
912                         == numberOfSelectedNodes
913                         && numberOfSelectedNodes > 1) {
914                     indirectDependencyNodePopupMenu
915                         .disableSingleSelectionItems();
916                     indirectDependencyNodePopupMenu.show(DeputyTree.this, x, y);
917                     /*
918                      * 'Dirty' exit
919                      */
920                     return;
921                 }
922 
923                 /*
924                  * Handle the cases where single selections are supported by
925                  * a popup menu
926                  */
927                 int selectedRow =
928                     DeputyTree.this.getClosestRowForLocation(x, y);
929                 DeputyTree.this.setSelectionRow(selectedRow);
930                 TreeNode selectedNode = DeputyTree.this.getSelectedNode();
931                 if (selectedNode instanceof ProjectHolderTreeNode) {
932                     boolean showTopDependencyPopupMenu = false;
933                     TreeNode parentNode = selectedNode.getParent();
934                     if (parentNode instanceof DependenciesTreeNode) {
935                         parentNode = parentNode.getParent();
936                         if (parentNode instanceof ProjectTreeNode) {
937                             Project project =
938                                 ((ProjectTreeNode) parentNode).getProject();
939                             if (project.isRootProject()) {
940                                 showTopDependencyPopupMenu = true;
941                             }
942                         }
943                     }
944                     if (showTopDependencyPopupMenu) {
945                         directDependencyNodePopupMenu.show(
946                                 DeputyTree.this, x, y);
947                     } else {
948                         indirectDependencyNodePopupMenu.show(
949                                 DeputyTree.this, x, y);
950                     }
951                 } else if (selectedNode instanceof RuleTreeNode) {
952                     ruleNodePopupMenu.show(DeputyTree.this, x, y);
953                 } else if (selectedNode instanceof RuleSetTreeNode) {
954                     ruleSetNodePopupMenu.show(DeputyTree.this, x, y);
955                 } else if (selectedNode instanceof RepositoryArtifact) {
956                     artifactNodePopupMenu.show(DeputyTree.this, x, y);
957                 } else {
958                     treeNodePopupMenu.show(DeputyTree.this, x, y);
959                 }
960             }
961         }
962     }
963 
964     /***
965      * @author Matthias Burbach
966      */
967     private class DependencyPopupMenu extends JPopupMenu {
968         /***
969          * Holds all items of this menu which must not be enabled when
970          * multiple nodes are selected at the same time.
971          */
972         private List singleSelectionItems = new ArrayList();
973 
974         /***
975          * Constructs this menu.
976          * @param direct Whether this menu is for direct or indirect
977          *               dependencies.
978          */
979         public DependencyPopupMenu(final boolean direct) {
980             Action action = new OpenSiteAction();
981             singleSelectionItems.add(action);
982             add(action);
983 
984             addSeparator();
985 
986             action = new AddEnforcementRuleAction();
987             //singleSelectionItems.add(action);
988             add(action);
989 
990             action = new AddSnapshotEnforcementRuleAction();
991             //singleSelectionItems.add(action);
992             add(action);
993 
994             action = new AddDeprecationRuleAction();
995             //singleSelectionItems.add(action);
996             add(action);
997 
998             action = new AddReplacementRuleAction(false);
999             singleSelectionItems.add(action);
1000             add(action);
1001 
1002             action = new AddReplacementRuleAction(true);
1003             singleSelectionItems.add(action);
1004             add(action);
1005 
1006             action = new AddRemovalRuleAction(false);
1007             //singleSelectionItems.add(action);
1008             add(action);
1009 
1010             action = new AddRemovalRuleAction(true);
1011             //singleSelectionItems.add(action);
1012             add(action);
1013 
1014             action = new AddRetentionRuleAction(true);
1015             //singleSelectionItems.add(action);
1016             add(action);
1017 
1018             addSeparator();
1019 
1020             action = new FindInRepositoriesAction();
1021             singleSelectionItems.add(action);
1022             add(action);
1023 
1024             action = new CollapseAllAction();
1025             singleSelectionItems.add(action);
1026             add(action);
1027 
1028             addSeparator();
1029 
1030             if (direct) {
1031                 add(new RemoveDependencyAction());
1032             } else {
1033                 add(new AddDependencyAction());
1034             }
1035         }
1036 
1037         /***
1038          * Enables all menu items of this popup menu.
1039          */
1040         public void enableAllItems() {
1041             Iterator iter = singleSelectionItems.iterator();
1042             while (iter.hasNext()) {
1043                 Action action = (Action) iter.next();
1044                 action.setEnabled(true);
1045             }
1046         }
1047 
1048         /***
1049          * Disables all menu items of this menu which are only useful for
1050          * single tree node selections.
1051          */
1052         public void disableSingleSelectionItems() {
1053             Iterator iter = singleSelectionItems.iterator();
1054             while (iter.hasNext()) {
1055                 Action action = (Action) iter.next();
1056                 action.setEnabled(false);
1057             }
1058         }
1059     }
1060 
1061     /***
1062      * The popup menu on project tree nodes which are direct dependencies of the
1063      * top project.
1064      */
1065     private DependencyPopupMenu directDependencyNodePopupMenu;
1066 
1067     /***
1068      * The popup menu on project tree nodes which are projects but not
1069      * direct dependencies the top project.
1070      */
1071     private DependencyPopupMenu indirectDependencyNodePopupMenu;
1072 
1073     /***
1074      * The popup menu on the artifact id displaying nodes.
1075      */
1076     private JPopupMenu artifactNodePopupMenu;
1077 
1078     /***
1079      * The popup menu on rule nodes.
1080      */
1081     private JPopupMenu ruleNodePopupMenu;
1082 
1083     /***
1084      * The popup menu on the rule set node.
1085      */
1086     private JPopupMenu ruleSetNodePopupMenu;
1087 
1088     /***
1089      * The popup menu on general tree nodes.
1090      */
1091     private JPopupMenu treeNodePopupMenu;
1092 
1093     /***
1094      * The main GUI application object.
1095      */
1096     private DeputyFrame deputyFrame;
1097 
1098     /***
1099      * The core application object.
1100      */
1101     private Deputy deputy;
1102 
1103     /***
1104      * The modal dialog to choose an artifact replacement as part of a
1105      * replacement rule.
1106      */
1107     private ReplacementChooserDialog replacementChooserDialog;
1108 
1109     /***
1110      * @param deputy The core application object.
1111      * @param deputyFrame The gui application object.
1112      * @param treeModel The tree model to delegate change operations to.
1113      * @param replacementChooserDialog The modal dialog to choose an artifact
1114      *                                 replacement as part of a replacement
1115      *                                 rule.
1116      */
1117     public DeputyTree(
1118             final Deputy deputy,
1119             final DeputyFrame deputyFrame,
1120             final TreeModel treeModel,
1121             final ReplacementChooserDialog replacementChooserDialog) {
1122         this.deputy = deputy;
1123         this.deputyFrame = deputyFrame;
1124         this.replacementChooserDialog = replacementChooserDialog;
1125 
1126         setModel(treeModel);
1127         getSelectionModel().setSelectionMode(
1128             TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
1129 
1130         DeputyTreeCellRenderer treeCellRenderer =
1131             new DeputyTreeCellRenderer(deputy);
1132         setCellRenderer(treeCellRenderer);
1133 
1134         if (replacementChooserDialog != null) {
1135             artifactNodePopupMenu = new JPopupMenu();
1136             artifactNodePopupMenu.add(new AddReplacementRuleAction(true));
1137             artifactNodePopupMenu.add(new AddRemovalRuleAction(true));
1138             artifactNodePopupMenu.add(new AddRetentionRuleAction(true));
1139             add(artifactNodePopupMenu);
1140 
1141             directDependencyNodePopupMenu = new DependencyPopupMenu(true);
1142             add(directDependencyNodePopupMenu);
1143 
1144             indirectDependencyNodePopupMenu = new DependencyPopupMenu(false);
1145             add(indirectDependencyNodePopupMenu);
1146 
1147             ruleSetNodePopupMenu = new JPopupMenu();
1148             ruleSetNodePopupMenu.add(
1149                 new ChangeDefaultRuleAction(
1150                     RuleSet.DEFAULT_RULE_SNAPSHOT,
1151                     "SNAPSHOT"));
1152             ruleSetNodePopupMenu.add(
1153                 new ChangeDefaultRuleAction(
1154                     RuleSet.DEFAULT_RULE_LATEST_RELEASE,
1155                     "LATEST RELEASE"));
1156             ruleSetNodePopupMenu.add(
1157                     new ChangeDefaultRuleAction(
1158                         RuleSet.DEFAULT_RULE_LATEST_RELEASE_NO_SCAN,
1159                         "LATEST RELEASE (no scan)"));
1160             ruleSetNodePopupMenu.add(
1161                     new ChangeDefaultRuleAction(
1162                         RuleSet.DEFAULT_RULE_PRESENT_RELEASE,
1163                         "PRESENT RELEASE"));
1164             add(ruleSetNodePopupMenu);
1165 
1166             ruleNodePopupMenu = new JPopupMenu();
1167             ruleNodePopupMenu.add(new RemoveRuleAction());
1168             add(ruleNodePopupMenu);
1169 
1170             treeNodePopupMenu = new JPopupMenu();
1171             treeNodePopupMenu.add(new CollapseAllAction());
1172             add(treeNodePopupMenu);
1173 
1174             addMouseListener(new PopupTrigger());
1175         }
1176     }
1177 
1178     /***
1179      * @return The currently selected tree node or <code>null</code>.
1180      */
1181     public TreeNode getSelectedNode() {
1182         TreeNode result = null;
1183         TreePath treePath = getSelectionPath();
1184         if (treePath != null) {
1185             result = (TreeNode) treePath.getLastPathComponent();
1186         }
1187         return result;
1188     }
1189 
1190     /***
1191      * @return The list of currently selected nodes of type {@link TreeNode}.
1192      */
1193     public List getSelectedNodes() {
1194         List result = new ArrayList();
1195         TreePath[] treePaths = getSelectionPaths();
1196         if (treePaths != null) {
1197             for (int i = 0; i < treePaths.length; i++) {
1198                 TreeNode candidateNode =
1199                     (TreeNode) treePaths[i].getLastPathComponent();
1200                 result.add(candidateNode);
1201             }
1202         }
1203         return result;
1204     }
1205 
1206     /***
1207      * @return The list of currently selected rule nodes of type
1208      *         {@link RuleTreeNode}.
1209      */
1210     public List getSelectedRuleNodes() {
1211         List result = new ArrayList();
1212         TreePath[] treePaths = getSelectionPaths();
1213         if (treePaths != null) {
1214             for (int i = 0; i < treePaths.length; i++) {
1215                 TreeNode candidateNode =
1216                     (TreeNode) treePaths[i].getLastPathComponent();
1217                 if (candidateNode instanceof RuleTreeNode) {
1218                     result.add(candidateNode);
1219                 }
1220             }
1221         }
1222         return result;
1223     }
1224 
1225     /***
1226      * @return The number of currently selected rule nodes.
1227      */
1228     private int getNumberOfSelectedNodes() {
1229         int result = 0;
1230         TreePath[] treePaths = getSelectionPaths();
1231         if (treePaths != null) {
1232           result = treePaths.length;
1233         }
1234         return result;
1235     }
1236 
1237     /***
1238      * @return The list of currently selected direct dependency nodes of the top
1239      *         project of type {@link Project}.
1240      */
1241     private List getSelectedDirectDependencyNodes() {
1242         List result = new ArrayList();
1243         TreePath[] treePaths = getSelectionPaths();
1244         if (treePaths != null) {
1245             for (int i = 0; i < treePaths.length; i++) {
1246                 TreeNode candidateNode =
1247                     (TreeNode) treePaths[i].getLastPathComponent();
1248                 if (candidateNode instanceof ProjectTreeNode) {
1249                     TreeNode parentNode = candidateNode.getParent();
1250                     if (parentNode instanceof DependenciesTreeNode) {
1251                         TreeNode grandParentNode = parentNode.getParent();
1252                         if (grandParentNode instanceof ProjectTreeNode) {
1253                             Project project =
1254                                 ((ProjectTreeNode) grandParentNode)
1255                                     .getProject();
1256                             if (project.isRootProject()) {
1257                                 result.add(candidateNode);
1258                             }
1259                         }
1260                     }
1261                 }
1262             }
1263         }
1264         return result;
1265     }
1266 
1267     /***
1268      * @return The list of currently selected indirect dependency nodes of the
1269      *         top project of type {@link Project}.
1270      */
1271     private List getSelectedIndirectDependencyNodes() {
1272         List result = new ArrayList();
1273         TreePath[] treePaths = getSelectionPaths();
1274         if (treePaths != null) {
1275             for (int i = 0; i < treePaths.length; i++) {
1276                 TreeNode candidateNode =
1277                     (TreeNode) treePaths[i].getLastPathComponent();
1278                 if (candidateNode instanceof ProjectTreeNode) {
1279                     TreeNode parentNode = candidateNode.getParent();
1280                     if (parentNode instanceof IndirectDependenciesTreeNode) {
1281                         TreeNode grandParentNode = parentNode.getParent();
1282                         if (grandParentNode instanceof ProjectTreeNode) {
1283                             Project project =
1284                                 ((ProjectTreeNode) grandParentNode)
1285                                     .getProject();
1286                             if (project.isRootProject()) {
1287                                 result.add(candidateNode);
1288                             }
1289                         }
1290                     }
1291                 }
1292             }
1293         }
1294         return result;
1295     }
1296 
1297     /***
1298      * @return The list of currently selected dependency nodes
1299      *         of type {@link ProjectHolderTreeNode}.
1300      */
1301     private List getSelectedProjectHolderTreeNodes() {
1302         List result = new ArrayList();
1303         TreePath[] treePaths = getSelectionPaths();
1304         if (treePaths != null) {
1305             for (int i = 0; i < treePaths.length; i++) {
1306                 TreeNode candidateNode =
1307                     (TreeNode) treePaths[i].getLastPathComponent();
1308                 if (candidateNode instanceof ProjectHolderTreeNode) {
1309                     result.add(candidateNode);
1310                 }
1311            }
1312         }
1313         return result;
1314     }
1315 }