View Javadoc

1   package de.matthias_burbach.deputy.swing;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Event;
5   import java.awt.event.ActionEvent;
6   import java.awt.event.KeyEvent;
7   import java.awt.event.WindowAdapter;
8   import java.awt.event.WindowEvent;
9   import java.io.File;
10  import java.io.FileNotFoundException;
11  import java.util.ArrayList;
12  import java.util.HashMap;
13  import java.util.Iterator;
14  import java.util.List;
15  import java.util.Map;
16  
17  import javax.swing.AbstractAction;
18  import javax.swing.Action;
19  import javax.swing.BorderFactory;
20  import javax.swing.Icon;
21  import javax.swing.JFileChooser;
22  import javax.swing.JFrame;
23  import javax.swing.JOptionPane;
24  import javax.swing.JScrollPane;
25  import javax.swing.JSplitPane;
26  import javax.swing.JToggleButton;
27  import javax.swing.JToolBar;
28  import javax.swing.KeyStroke;
29  import javax.swing.SwingUtilities;
30  import javax.swing.UIManager;
31  import javax.swing.WindowConstants;
32  import javax.swing.filechooser.FileFilter;
33  import javax.swing.tree.DefaultTreeModel;
34  import javax.swing.tree.TreeNode;
35  import javax.swing.tree.TreePath;
36  
37  import de.matthias_burbach.deputy.core.Deputy;
38  import de.matthias_burbach.deputy.core.DeputyChangeListener;
39  import de.matthias_burbach.deputy.core.project.Project;
40  import de.matthias_burbach.deputy.core.repository.Repository;
41  import de.matthias_burbach.deputy.core.repository.RepositorySet;
42  import de.matthias_burbach.deputy.core.rule.RuleSet;
43  import de.matthias_burbach.deputy.core.util.BrowserLauncher;
44  import de.matthias_burbach.deputy.core.util.Log;
45  import de.matthias_burbach.deputy.core.util.P4;
46  import de.matthias_burbach.deputy.core.util.SimpleLog;
47  
48  /***
49   * Is the main GUI class of Deputy. Holds a map of actions that can be triggered
50   * by menu bar, tool bar and popup menu items outside of this class.
51   *
52   * @author Matthias Burbach
53   */
54  public class DeputyFrame extends JFrame implements DeputyChangeListener {
55      /***
56       * The 'new Maven file' action.
57       */
58      public static final String ACTION_NEW =
59          "New";
60  
61      /***
62       * The 'open Maven file' action.
63       */
64      public static final String ACTION_OPEN =
65          "Open";
66  
67      /***
68       * The 'save currently opened Maven file' action.
69       */
70      public static final String ACTION_SAVE =
71          "Save";
72  
73      /***
74       * The 'save currently opened Maven file as...' action.
75       */
76      public static final String ACTION_SAVE_AS =
77          "SaveAs";
78  
79      /***
80       * The 'derive enforcement rules from project dependencies' action.
81       */
82      public static final String ACTION_DERIVE_ENFORCEMENTS_FROM_PROJECT =
83          "DeriveEnforcementsFromProject";
84  
85      /***
86       * The 'export currently opened Maven file as dependency graph' action.
87       */
88      public static final String ACTION_EXPORT =
89          "Export";
90  
91      /***
92       * The 'exit application' action.
93       */
94      public static final String ACTION_EXIT =
95          "Exit";
96  
97      /***
98       * The 'apply rules on currently opened Maven file' action.
99       */
100     public static final String ACTION_APPLY_RULES =
101         "ApplyRules";
102 
103     /***
104      * The 'remove all currently selected rules' action.
105      */
106     public static final String ACTION_REMOVE_SELECTED_RULES =
107         "RemoveSelectedRules";
108 
109     /***
110      * The 'remove all SNAPSHOT enforcement rules' action.
111      */
112     public static final String ACTION_REMOVE_SNAPSHOT_ENFORCEMENTS =
113         "RemoveSnapshotEnforcementRules";
114 
115     /***
116      * The 'remove all derived rules' action.
117      */
118     public static final String ACTION_REMOVE_DERIVED_RULES =
119         "RemoveDerivedRules";
120 
121     /***
122      * The 'sort snapshots topologically' action.
123      */
124     public static final String ACTION_SORT_SNAPSHOTS_TOPOLOGICALLY =
125         "SortSnapshotsTopologically";
126 
127     /***
128      * The 'edit repository configs' action.
129      */
130     public static final String ACTION_EDIT_REPOSITORY_CONFIGS =
131         "EditRepositoryConfigs";
132 
133     /***
134      * The 'sort snapshots topologically' action.
135      */
136     public static final String ACTION_TOGGLE_VIRTUAL_REPOSITORY =
137         "ToggleVirtualRepository";
138 
139     /***
140      * The 'refresh repository browser' action.
141      */
142     public static final String ACTION_REFRESH_REPOSITORY_BROWSER =
143         "RefreshRepositoryBrowser";
144 
145     /***
146      * The 'Homepage' action.
147      */
148     public static final String ACTION_HOMEPAGE =
149         "Homepage";
150 
151     /***
152      * The 'Release Notes' action.
153      */
154     public static final String ACTION_RELEASE_NOTES =
155         "ReleaseNotes";
156 
157     /***
158      * The 'show about dialog' action.
159      */
160     public static final String ACTION_SHOW_ABOUT =
161         "ShowAbout";
162 
163     /***
164      * Internally flags to open a project as is.
165      */
166     private static final int STRATEGY_AS_IS = 0;
167 
168     /***
169      * Internally flags to apply the rules to the open project.
170      */
171     private static final int STRATEGY_APPLY_RULES = 1;
172 
173     /***
174      * The deputy core application object.
175      */
176     private Deputy deputy;
177 
178     /***
179      * The map of supported actions of type ActionListener.
180      */
181     private Map actionListeners;
182 
183     /***
184      * The tool bar.
185      */
186     private JToolBar toolBar;
187 
188     /***
189      * The button to switch on/off the usage of the virtual repository.
190      */
191     private JToggleButton virtualRepositoryToggleButton;
192 
193     /***
194      * Indicates the virtual repository is active (green light).
195      */
196     private Icon virtualRepositoryOnIcon;
197 
198     /***
199      * Indicates the virtual repository is inactive (red light).
200      */
201     private Icon virtualRepositoryOffIcon;
202 
203     /***
204      * The cached file chooser for open and save actions.
205      * Must only be created after the look and feel has been set!
206      */
207     private JFileChooser fileChooser;
208 
209     /***
210      * The cached file chooser for the export action.
211      * Must only be created after the look and feel has been set!
212      */
213     private JFileChooser exportFileChooser;
214 
215     /***
216      * The file filter for the export format 'GraphML'.
217      */
218     private FileFilter graphMlFilter;
219 
220     /***
221      * The file filter for the export format 'Deputy XML'.
222      */
223     private FileFilter deputyXmlFilter;
224 
225     /***
226      * The log panel at the bottom of the main frame. Used to display messages.
227      */
228     private DeputyLogPanel logPanel;
229 
230     /***
231      * The split pane separating the project from the repositories.
232      */
233     private JSplitPane horizontalSplitPane;
234 
235     /***
236      * The split pane separating project and repositories from the log.
237      */
238     private JSplitPane verticalSplitPane;
239 
240     /***
241      * The current tree of dependencies, conflicts, and rules.
242      */
243     private DeputyTree currentProjectTree;
244 
245     /***
246      * The tree of repositories.
247      */
248     private DeputyTree currentRepositoriesTree;
249 
250     /***
251      * The additional chooser dialog to complete the entry of a new replacement
252      * rule.
253      */
254     private ReplacementChooserDialog chooserDialog;
255 
256     /***
257      * Constructs the frame.
258      *
259      * @param deputy The core application.
260      * @throws Exception if anything goes unexpectedly wrong
261      */
262     public DeputyFrame(final Deputy deputy) throws Exception {
263         this.deputy = deputy;
264         this.deputy.addChangeListener(this);
265 
266         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
267 
268         actionListeners = createActions();
269         fileChooser = new JFileChooser();
270 
271         /*
272          * Create export file chooser with filters for supported formats
273          */
274         exportFileChooser = new JFileChooser();
275         graphMlFilter = new FileFilter() {
276             public boolean accept(final File f) {
277                 boolean result = false;
278                 if (f.isDirectory() || f.getName().endsWith(".graphml")) {
279                     result = true;
280                 }
281                 return result;
282             }
283 
284             public String getDescription() {
285                 return "GraphML format (.graphml)";
286             }
287         };
288 
289         deputyXmlFilter = new FileFilter() {
290             public boolean accept(final File f) {
291                 boolean result = false;
292                 if (f.isDirectory() || f.getName().endsWith(".xml")) {
293                     result = true;
294                 }
295                 return result;
296             }
297 
298             public String getDescription() {
299                 return "Deputy XML format (.xml)";
300             }
301         };
302 
303         setTitle("Deputy");
304         setJMenuBar(new DeputyMenuBar(this));
305         toolBar = createToolBar();
306 
307         int x = 1;
308         try {
309             x = Integer.parseInt(deputy.getProperty("deputyFrame.x"));
310         } catch (Exception e) {
311             e.printStackTrace();
312         }
313         int y = 1;
314         try {
315             y = Integer.parseInt(deputy.getProperty("deputyFrame.y"));
316         } catch (Exception e) {
317             e.printStackTrace();
318         }
319         final int defaultWidth = 650;
320         int width = defaultWidth;
321         try {
322             width = Integer.parseInt(deputy.getProperty("deputyFrame.width"));
323         } catch (Exception e) {
324             e.printStackTrace();
325         }
326         final int defaultHeight = 600;
327         int height = defaultHeight;
328         try {
329             height = Integer.parseInt(deputy.getProperty("deputyFrame.height"));
330         } catch (Exception e) {
331             e.printStackTrace();
332         }
333         setBounds(x, y, width, height);
334 
335         setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
336         WindowAdapter windowListener = new WindowAdapter() {
337             /*
338              * (non-Javadoc)
339              * @see java.awt.event.WindowListener#windowClosing(
340              *          java.awt.event.WindowEvent)
341              */
342             /***
343              * {@inheritDoc}
344              */
345             public void windowClosing(final WindowEvent event) {
346                 DeputyFrame.this.exit();
347             }
348         };
349         addWindowListener(windowListener);
350 
351         logPanel = new DeputyLogPanel();
352         deputy.setLog(logPanel);
353 
354         updateContentPane();
355         setVisible(true);
356     }
357 
358     /***
359      * Fills the content pane on startup and everytime the project tree or the
360      * repositories tree was (re-)loaded.
361      */
362     private void updateContentPane() {
363         /*
364          * Create project scroll pane
365          */
366         JScrollPane projectScrollPane = new JScrollPane();
367         if (currentProjectTree != null) {
368             projectScrollPane.getViewport().add(currentProjectTree);
369         }
370         projectScrollPane.setBorder(
371             BorderFactory.createTitledBorder("Project"));
372 
373         /*
374          * Create repositories scroll pane
375          */
376         JScrollPane repositoriesScrollPane = new JScrollPane();
377         if (currentRepositoriesTree != null) {
378             repositoriesScrollPane.getViewport().add(currentRepositoriesTree);
379         }
380         repositoriesScrollPane.setBorder(
381             BorderFactory.createTitledBorder("Repositories"));
382 
383         /*
384          * Create horizontal split pane
385          */
386         int horizontalDividerLocation = getWidth() / 2;
387         if (horizontalSplitPane != null) {
388             horizontalDividerLocation =
389                 horizontalSplitPane.getDividerLocation();
390         } else {
391             try {
392                 horizontalDividerLocation =
393                     Integer.parseInt(
394                         deputy.getProperty(
395                             "deputyFrame.horizontalSplitPane.dividerLocation"));
396             } catch (Exception e) {
397                 e.printStackTrace();
398             }
399         }
400         horizontalSplitPane =
401             new JSplitPane(
402                 JSplitPane.HORIZONTAL_SPLIT,
403                 projectScrollPane,
404                 repositoriesScrollPane);
405         horizontalSplitPane.setDividerLocation(horizontalDividerLocation);
406 
407         /*
408          * Create vertical split pane
409          */
410         final int four = 4;
411         int verticalDividerLocation = getHeight() - (getHeight() / four);
412         if (verticalSplitPane != null) {
413             verticalDividerLocation = verticalSplitPane.getDividerLocation();
414         } else {
415             try {
416                 verticalDividerLocation =
417                     Integer.parseInt(
418                         deputy.getProperty(
419                             "deputyFrame.verticalSplitPane.dividerLocation"));
420             } catch (Exception e) {
421                 e.printStackTrace();
422             }
423         }
424         verticalSplitPane =
425             new JSplitPane(
426                 JSplitPane.VERTICAL_SPLIT,
427                 horizontalSplitPane,
428                 logPanel);
429         verticalSplitPane.setDividerLocation(verticalDividerLocation);
430 
431         /*
432          * Add everything to the content pane
433          */
434         getContentPane().removeAll();
435         getContentPane().add(toolBar, BorderLayout.NORTH);
436         getContentPane().add(verticalSplitPane, BorderLayout.CENTER);
437         validate();
438     }
439 
440     /***
441      * @return The tool bar created.
442      */
443     private JToolBar createToolBar() {
444         JToolBar result = new JToolBar();
445         //result.add(getAction(ACTION_NEW));
446         result.add(getAction(ACTION_OPEN));
447         result.add(getAction(ACTION_SAVE));
448         result.add(getAction(ACTION_APPLY_RULES));
449         result.add(getAction(ACTION_REMOVE_SELECTED_RULES));
450 
451         /*
452          * Create toggle button to switch on/off the virtual repository
453          */
454         virtualRepositoryToggleButton =
455             new JToggleButton(getAction(ACTION_TOGGLE_VIRTUAL_REPOSITORY));
456         result.add(virtualRepositoryToggleButton);
457         boolean active = deputy.isVirtualRepositoryActive();
458         virtualRepositoryToggleButton.setSelected(active);
459         setVirtualRepositoryActive(active);
460 
461         return result;
462     }
463 
464     /***
465      * @return The map of supported actions of type {@link Action}.
466      */
467     private Map createActions() {
468         Map result = new HashMap();
469         result.putAll(createFileMenuActions());
470         result.putAll(createEditMenuActions());
471         result.putAll(createViewMenuActions());
472         result.putAll(createHelpMenuActions());
473         return result;
474     }
475 
476     /***
477      * @return The map of supported file actions of type {@link Action}.
478      */
479     private Map createFileMenuActions() {
480         Map result = new HashMap();
481 
482         Icon icon = SwingHelper.createImageIcon("small-new.gif", "");
483         Action action = new AbstractAction("New...", icon) {
484             public void actionPerformed(final ActionEvent event) {
485                 createProject();
486            }
487         };
488         action.putValue(Action.SHORT_DESCRIPTION, "New");
489         action.putValue(
490             Action.ACCELERATOR_KEY,
491             KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK));
492         result.put(ACTION_NEW, action);
493 
494         icon = SwingHelper.createImageIcon("small-open.gif", "");
495         action = new AbstractAction("Open...", icon) {
496             public void actionPerformed(final ActionEvent event) {
497                 openProject();
498            }
499         };
500         action.putValue(Action.SHORT_DESCRIPTION, "Open");
501         action.putValue(
502             Action.ACCELERATOR_KEY,
503             KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK));
504         result.put(ACTION_OPEN, action);
505 
506         icon = SwingHelper.createImageIcon("small-save.gif", "");
507         action = new AbstractAction("Save", icon) {
508             public void actionPerformed(final ActionEvent event) {
509                 save();
510            }
511         };
512         action.setEnabled(false);
513         action.putValue(Action.SHORT_DESCRIPTION, "Save");
514         action.putValue(
515             Action.ACCELERATOR_KEY,
516             KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK));
517         result.put(ACTION_SAVE, action);
518 
519         action = new AbstractAction("Save As...") {
520             public void actionPerformed(final ActionEvent event) {
521                 saveAs();
522            }
523         };
524         action.setEnabled(false);
525         result.put(ACTION_SAVE_AS, action);
526 
527         action = new AbstractAction("Derive Enforcements from a Project...") {
528             public void actionPerformed(final ActionEvent event) {
529                 deriveEnforcementsFromProject();
530             }
531         };
532         action.setEnabled(false);
533         action.putValue(
534             Action.SHORT_DESCRIPTION,
535             "Add a Project's Dependencies As Derived Enforcement Rules");
536         action.putValue(
537                 Action.ACCELERATOR_KEY,
538                 KeyStroke.getKeyStroke(KeyEvent.VK_E, Event.CTRL_MASK));
539         result.put(ACTION_DERIVE_ENFORCEMENTS_FROM_PROJECT, action);
540 
541         action = new AbstractAction("Export As Dependency Graph...") {
542             public void actionPerformed(final ActionEvent event) {
543                 exportDependencyGraph();
544             }
545         };
546         action.setEnabled(false);
547         action.putValue(Action.SHORT_DESCRIPTION, "Export As Dependency Graph");
548         result.put(ACTION_EXPORT, action);
549 
550         action = new AbstractAction("Exit") {
551             public void actionPerformed(final ActionEvent event) {
552                 exit();
553            }
554         };
555         action.putValue(Action.SHORT_DESCRIPTION, "Exit");
556         result.put(ACTION_EXIT, action);
557 
558         return result;
559     }
560 
561     /***
562      * @return The map of supported edit actions of type {@link Action}.
563      */
564     private Map createEditMenuActions() {
565         Map result = new HashMap();
566 
567         Icon icon = SwingHelper.createImageIcon("apply-rules.gif", "");
568         Action action = new AbstractAction("Apply Rules", icon) {
569             public void actionPerformed(final ActionEvent event) {
570                 applyRules();
571            }
572         };
573         action.setEnabled(false);
574         action.putValue(Action.SHORT_DESCRIPTION, "Apply Rules");
575         action.putValue(
576             Action.ACCELERATOR_KEY,
577             KeyStroke.getKeyStroke(KeyEvent.VK_R, Event.CTRL_MASK));
578         result.put(ACTION_APPLY_RULES, action);
579 
580         icon = SwingHelper.createImageIcon("small-cut.gif", "");
581         action = new AbstractAction("Remove Selected Rules", icon) {
582             public void actionPerformed(final ActionEvent event) {
583                 removeSelectedRules();
584            }
585         };
586         action.setEnabled(false);
587         action.putValue(Action.SHORT_DESCRIPTION, "Remove Selected Rules");
588         result.put(ACTION_REMOVE_SELECTED_RULES, action);
589 
590         action = new AbstractAction("Remove SNAPSHOT Enforcements") {
591             public void actionPerformed(final ActionEvent event) {
592                 removeSnapshotEnforcementRules();
593            }
594         };
595         action.setEnabled(false);
596         action.putValue(Action.SHORT_DESCRIPTION,
597                 "Remove SNAPSHOT Enforcements");
598         action.putValue(
599                 Action.ACCELERATOR_KEY,
600                 KeyStroke.getKeyStroke(KeyEvent.VK_K, Event.CTRL_MASK));
601         result.put(ACTION_REMOVE_SNAPSHOT_ENFORCEMENTS, action);
602 
603         action = new AbstractAction("Remove Derived Rules") {
604             public void actionPerformed(final ActionEvent event) {
605                 removeDerivedRules();
606            }
607         };
608         action.setEnabled(false);
609         action.putValue(Action.SHORT_DESCRIPTION,
610                 "Remove Derived Rules");
611         action.putValue(
612                 Action.ACCELERATOR_KEY,
613                 KeyStroke.getKeyStroke(KeyEvent.VK_D, Event.CTRL_MASK));
614         result.put(ACTION_REMOVE_DERIVED_RULES, action);
615 
616         action = new AbstractAction("Sort SNAPSHOTS topologically...") {
617             public void actionPerformed(final ActionEvent event) {
618                 sortSnapshotsTopologically();
619             }
620         };
621         action.setEnabled(false);
622         action.putValue(
623             Action.SHORT_DESCRIPTION,
624             "Sort SNAPSHOTs topologically");
625         action.putValue(
626                 Action.ACCELERATOR_KEY,
627                 KeyStroke.getKeyStroke(KeyEvent.VK_T, Event.CTRL_MASK));
628         result.put(ACTION_SORT_SNAPSHOTS_TOPOLOGICALLY, action);
629 
630         action = new AbstractAction("Edit Repository Configs...") {
631             public void actionPerformed(final ActionEvent event) {
632                 editRepositoryConfigs();
633            }
634         };
635         action.putValue(Action.SHORT_DESCRIPTION, "Edit Repository Configs");
636         result.put(ACTION_EDIT_REPOSITORY_CONFIGS, action);
637 
638         virtualRepositoryOnIcon =
639             SwingHelper.createImageIcon("virtual-repository-on.gif", "");
640         virtualRepositoryOffIcon =
641             SwingHelper.createImageIcon("virtual-repository-off.gif", "");
642         icon = virtualRepositoryOffIcon;
643         action = new AbstractAction("Toggle Virtual Repository", icon) {
644             public void actionPerformed(final ActionEvent event) {
645                 boolean active = virtualRepositoryToggleButton.isSelected();
646                 setVirtualRepositoryActive(active);
647            }
648         };
649         action.setEnabled(true);
650         action.putValue(Action.SHORT_DESCRIPTION, "Toggle Virtual Repository");
651         result.put(ACTION_TOGGLE_VIRTUAL_REPOSITORY, action);
652 
653         return result;
654     }
655 
656     /***
657      * @return The map of supported view actions of type {@link Action}.
658      */
659     private Map createViewMenuActions() {
660         Map result = new HashMap();
661 
662         Action action = new AbstractAction("Refresh Repository Browser") {
663             public void actionPerformed(final ActionEvent event) {
664                 openRepositories();
665            }
666         };
667         action.putValue(Action.SHORT_DESCRIPTION, "Refresh Repository Browser");
668         action.putValue(
669                 Action.ACCELERATOR_KEY,
670                 KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
671         result.put(ACTION_REFRESH_REPOSITORY_BROWSER, action);
672 
673         return result;
674     }
675 
676     /***
677      * @return The map of supported help actions of type {@link Action}.
678      */
679     private Map createHelpMenuActions() {
680         Map result = new HashMap();
681 
682         Action action = new AbstractAction("Homepage") {
683             public void actionPerformed(final ActionEvent event) {
684                 openUrl("http://deputy.sourceforge.net/");
685            }
686         };
687         action.putValue(Action.SHORT_DESCRIPTION, "Homepage");
688         result.put(ACTION_HOMEPAGE, action);
689 
690         action = new AbstractAction("Release Notes") {
691             public void actionPerformed(final ActionEvent event) {
692                 openUrl("http://deputy.sourceforge.net/changes-report.html");
693            }
694         };
695         action.putValue(Action.SHORT_DESCRIPTION, "Release Notes");
696         result.put(ACTION_RELEASE_NOTES, action);
697 
698         action = new AbstractAction("About Deputy") {
699             public void actionPerformed(final ActionEvent event) {
700                 showAbout();
701            }
702         };
703         result.put(ACTION_SHOW_ABOUT, action);
704 
705         return result;
706     }
707 
708     /***
709      * @param name The name of the action. Must be one of the
710      *             ACTION_* constants defined in this class.
711      * @return The action for the name supplied or <code>null</code>
712      *         if the name is invalid.
713      */
714     public Action getAction(final String name) {
715         return (Action) actionListeners.get(name);
716     }
717 
718     /***
719      * Launches the system's default browser with the site URL of the project
720      * passed in.
721      *
722      * @param project The project whose site to open in the browser.
723      */
724     public void openProjectSite(final Project project) {
725         String url = project.getUrl();
726         if (url == null) {
727             url = project.getDefaultUrl();
728         }
729         if (url != null) {
730             BrowserLauncher.openUrl(url);
731         } else {
732             JOptionPane.showMessageDialog(
733                     this,
734                     "Sorry, no site URL available on this node.",
735                     "Info",
736                     JOptionPane.INFORMATION_MESSAGE);
737         }
738     }
739 
740     /*
741      * (non-Javadoc)
742      * @see de.matthias_burbach.deputy.core.DeputyChangeListener
743      *          #projectHasChanged()
744      */
745     /***
746      * {@inheritDoc}
747      */
748     public void projectHasChanged() {
749         updateTitle();
750         updateMenuItems();
751     }
752 
753     /***
754      * Updates the title of this main frame.
755      */
756     private void updateTitle() {
757         String prefix = "";
758         if (!deputy.isSaved()) {
759             prefix = "*";
760         }
761         setTitle(
762             "Deputy - "
763             + prefix + deputy.getProjectFile());
764     }
765 
766     /***
767      * Updates the enabled state of the menu items' actions.
768      */
769     private void updateMenuItems() {
770         boolean enabled = !deputy.isSaved();
771         getAction(ACTION_SAVE).setEnabled(enabled);
772 
773         enabled = deputy.getRootProject() != null;
774         getAction(ACTION_SAVE_AS).setEnabled(enabled);
775         getAction(ACTION_DERIVE_ENFORCEMENTS_FROM_PROJECT).setEnabled(true);
776         getAction(ACTION_EXPORT).setEnabled(enabled);
777         getAction(ACTION_APPLY_RULES).setEnabled(enabled);
778         getAction(ACTION_REMOVE_SELECTED_RULES).setEnabled(enabled);
779         getAction(ACTION_REMOVE_SNAPSHOT_ENFORCEMENTS).setEnabled(enabled);
780         getAction(ACTION_REMOVE_DERIVED_RULES).setEnabled(enabled);
781         getAction(ACTION_SORT_SNAPSHOTS_TOPOLOGICALLY).setEnabled(enabled);
782     }
783 
784     /***
785      * Creates a new project using a template.
786      */
787     private void createProject() {
788         //empty
789     }
790 
791     /***
792      * Opens the project whose file name has been set using
793      * {@link Deputy#setProjectFile(String)}.
794      */
795     private void openProject() {
796         boolean doOpen = true;
797         if (!deputy.isSaved()) {
798             int selectedOption = JOptionPane.showOptionDialog(
799                 this,
800                 "The current state of the project has not been saved.\n"
801                 + "Do you really want to open another project without "
802                 + "saving this one?",
803                 "Open",
804                 JOptionPane.YES_NO_OPTION,
805                 JOptionPane.QUESTION_MESSAGE,
806                 null,
807                 new Object[] {"Yes", "No"},
808                 null);
809             if (selectedOption == 0) {
810                 doOpen = true;
811             } else {
812                 doOpen = false;
813             }
814         }
815         if (doOpen) {
816             try {
817                 String projectFile = deputy.getProjectFile();
818                 if (projectFile != null) {
819                     try {
820                         File file = new File(projectFile);
821                         fileChooser.setCurrentDirectory(file);
822                         fileChooser.setSelectedFile(file);
823                     } catch (Exception e) {
824                         //ignore
825                         e.printStackTrace();
826                     }
827                 }
828                 int result =
829                     fileChooser.showOpenDialog(this);
830                 if (result == JFileChooser.APPROVE_OPTION) {
831                     File file = fileChooser.getSelectedFile();
832                     if (isValidProjectFile(file)) {
833                         logPanel.clear();
834                         deputy.setProjectFile(file.getAbsolutePath());
835                         doOpenProject(STRATEGY_AS_IS);
836                     }
837                 }
838             } catch (Exception e) {
839                 e.printStackTrace();
840                 JOptionPane.showMessageDialog(
841                     this,
842                     "Encountered '" + e.getMessage() + "'",
843                     "Error",
844                     JOptionPane.ERROR_MESSAGE);
845             }
846         }
847     }
848 
849     /***
850      * Checks the file name of a project about to be opened for validity.
851      * Warns if it seems to be suspicious.
852      *
853      * @param file The file to validate.
854      * @return <code>true</code> if the file is unsuspicious or if the user
855      *         insists to open it.
856      */
857     private boolean isValidProjectFile(final File file) {
858         boolean result = false;
859         String name = file.getName();
860         if (name.endsWith(".pom")
861                 || name.equals("test.xml")
862                 || name.equals("project.xml")) {
863             result = true;
864         } else {
865             int selectedOption = JOptionPane.showOptionDialog(
866                 this,
867                 "Guessing from the name, the file you are about to open does "
868                 + "not seem to be a Maven project.\n"
869                 + " Do you still want to open it?",
870                 "Open " + name + "?",
871                 JOptionPane.YES_NO_OPTION,
872                 JOptionPane.QUESTION_MESSAGE,
873                 null,
874                 new Object[] {"Yes", "No"},
875                 null);
876             if (selectedOption == 0) {
877                 result = true;
878             }
879         }
880         return result;
881     }
882 
883     /***
884      * Really does attempt to open the project. Is called both by
885      * {@link #openProject()} and by {@link #applyRules()}.
886      *
887      * @param strategy Either {@link #STRATEGY_AS_IS} or
888      *                 {@link #STRATEGY_APPLY_RULES}.
889      * @throws Exception if anything goes unexpectedly wrong
890      */
891     private void doOpenProject(final int strategy) throws Exception {
892         final WaitDialog waitDialog = new WaitDialog(this, true);
893         Runnable runnable = new Runnable() {
894             public void run() {
895                 try {
896                     if (chooserDialog == null) {
897                         doOpenRepositories();
898                     }
899                     Project tmpProject = null;
900                     if (strategy == STRATEGY_AS_IS) {
901                         tmpProject = deputy.openProjectAsIs();
902                     } else {
903                         tmpProject = deputy.applyRulesToProject();
904                     }
905                     final Project topProject = tmpProject;
906                     SwingUtilities.invokeAndWait(new Runnable() {
907                         public void run() {
908                             updateGuiAfterOpenProject(topProject);
909                         }
910                     });
911                 } catch (Exception e) {
912                     e.printStackTrace();
913                     final String message = e.getMessage();
914                     try {
915                         SwingUtilities.invokeAndWait(new Runnable() {
916                             public void run() {
917                                 waitDialog.dispose();
918                                 JOptionPane.showMessageDialog(
919                                     DeputyFrame.this,
920                                     "Encountered '" + message + "'",
921                                     "Error",
922                                     JOptionPane.ERROR_MESSAGE);
923                             }
924                         });
925                     } catch (Exception f) {
926                         f.printStackTrace();
927                     }
928                 } finally {
929                     try {
930                         SwingUtilities.invokeAndWait(new Runnable() {
931                             public void run() {
932                                 waitDialog.dispose();
933                             }
934                         });
935                     } catch (Exception e) {
936                         e.printStackTrace();
937                     }
938                 }
939             }
940         };
941 
942         Thread thread = new Thread(runnable);
943         thread.start();
944         waitDialog.setVisible(true);
945     }
946 
947     /***
948      * Updates the GUI after a project was opened.
949      *
950      * @param topProject The top project that was loaded.
951      */
952     private void updateGuiAfterOpenProject(final Project topProject) {
953         DefaultTreeModel treeModel = new DefaultTreeModel(null);
954         TreeNode rootNode =
955             new ProjectTreeNode(
956                 topProject,
957                 treeModel);
958         treeModel.setRoot(rootNode);
959         currentProjectTree =
960             new DeputyTree(
961                 deputy,
962                 DeputyFrame.this,
963                 treeModel,
964                 chooserDialog);
965         final int rulesNodeRow = 5;
966         currentProjectTree.expandRow(rulesNodeRow);
967         final int dependenciesNodeRow = 2;
968         currentProjectTree.expandRow(dependenciesNodeRow);
969         updateContentPane();
970         projectHasChanged();
971     }
972 
973     /***
974      * Finds the artifact in one of the repositories and expands the repository
975      * path to it accordingly. Prompts with a message if the artifact cannot
976      * be found in the repository.
977      *
978      * @param groupId The group id to look for.
979      * @param artifactId The artifact id to look for.
980      * @param version The version to look for.
981      */
982     public void findInRepositories(
983             final String groupId,
984             final String artifactId,
985             final String version) {
986         TreeNode leafNode = findVersionNode(groupId, artifactId, version);
987         if (leafNode != null) {
988             List selectionPath = new ArrayList();
989             selectionPath.add(leafNode);
990             TreeNode node = leafNode;
991             while (node.getParent() != null) {
992                 selectionPath.add(0, node.getParent());
993                 node = node.getParent();
994             }
995             currentRepositoriesTree.setSelectionPath(
996                 new TreePath(selectionPath.toArray()));
997             /*
998              * Programmatically scroll the repositories tree so that the
999              * selected node becomes visible.
1000              */
1001             TreePath selectionTreePath =
1002                 currentRepositoriesTree.getSelectionPath();
1003             currentRepositoriesTree.scrollPathToVisible(selectionTreePath);
1004         } else {
1005             JOptionPane.showMessageDialog(
1006                 this,
1007                 "Sorry, didn't find it. "
1008                 + "Seems it has no POM in any of the repositories.",
1009                 "Deputy",
1010                 JOptionPane.INFORMATION_MESSAGE);
1011         }
1012     }
1013 
1014     /***
1015      * @param groupId The group id to look for.
1016      * @param artifactId The artifact id to look for.
1017      * @param version The version to look for.
1018      * @return <code>true</code> if it finds the artifact in one of the
1019      *         repositories.
1020      */
1021     public boolean existsInRepositories(
1022             final String groupId,
1023             final String artifactId,
1024             final String version) {
1025         TreeNode leafNode = findVersionNode(groupId, artifactId, version);
1026         return (leafNode != null);
1027     }
1028 
1029     /***
1030      * @param groupId The group id to look for.
1031      * @param artifactId The artifact id to look for.
1032      * @param version The version to look for.
1033      * @return The version node representing the version of the artifact in one
1034      *         of the repositories or <code>null</code> if it wasn't found.
1035      */
1036     private TreeNode findVersionNode(
1037             final String groupId,
1038             final String artifactId,
1039             final String version) {
1040         TreeNode root = (TreeNode) currentRepositoriesTree.getModel().getRoot();
1041         TreeNode leafNode = null;
1042         for (int i = 0; i < root.getChildCount(); i++) {
1043             Repository child = (Repository) root.getChildAt(i);
1044             leafNode = child.findVersionNode(groupId, artifactId, version);
1045             if (leafNode != null) {
1046                 break;
1047             }
1048         }
1049         return leafNode;
1050     }
1051 
1052     /***
1053      * (Re-)opens the repositories tree. Can be called at any time during a
1054      * user session to refresh the displayed contents.
1055      */
1056     public void openRepositories() {
1057         final WaitDialog waitDialog = new WaitDialog(this, true);
1058         Runnable runnable = new Runnable() {
1059             public void run() {
1060                 try {
1061                     doOpenRepositories();
1062                     SwingUtilities.invokeAndWait(new Runnable() {
1063                         public void run() {
1064                             updateContentPane();
1065                         }
1066                     });
1067                 } catch (Exception e) {
1068                     e.printStackTrace();
1069                     final String message = e.getMessage();
1070                     try {
1071                         SwingUtilities.invokeAndWait(new Runnable() {
1072                             public void run() {
1073                                 waitDialog.dispose();
1074                                 JOptionPane.showMessageDialog(
1075                                     DeputyFrame.this,
1076                                     "Encountered '" + message + "'",
1077                                     "Error",
1078                                     JOptionPane.ERROR_MESSAGE);
1079                             }
1080                         });
1081                     } catch (Exception f) {
1082                         f.printStackTrace();
1083                     }
1084                 } finally {
1085                     try {
1086                         SwingUtilities.invokeAndWait(new Runnable() {
1087                             public void run() {
1088                                 waitDialog.dispose();
1089                             }
1090                         });
1091                     } catch (Exception e) {
1092                         e.printStackTrace();
1093                     }
1094                 }
1095             }
1096         };
1097 
1098         Thread thread = new Thread(runnable);
1099         thread.start();
1100         waitDialog.setVisible(true);
1101     }
1102 
1103     /***
1104      * Really does attempt to open the repositories.
1105      *
1106      * @throws Exception if anything goes unexpectedly wrong
1107      */
1108     private void doOpenRepositories() throws Exception {
1109         RepositorySet repositorySet =
1110             new RepositorySet(deputy.getRepositoryConfigs());
1111         DefaultTreeModel repositoriesTreeModel =
1112             new DefaultTreeModel(repositorySet);
1113 
1114         DeputyTree chooserTree =
1115             new DeputyTree(deputy, this, repositoriesTreeModel, null);
1116         chooserDialog =
1117             new ReplacementChooserDialog(chooserTree);
1118         chooserTree.expandRow(1);
1119 
1120         currentRepositoriesTree =
1121             new DeputyTree(deputy, this, repositoriesTreeModel, chooserDialog);
1122         currentRepositoriesTree.expandRow(1);
1123     }
1124 
1125     /***
1126      * Applies the rules on the project to compute the versions of the
1127      * direct dependencies and the indirect dependencies
1128      * (including their versions).
1129      */
1130     private void applyRules() {
1131         try {
1132             String mavenProjectFile = deputy.getProjectFile();
1133             if (mavenProjectFile != null) {
1134                 doOpenProject(STRATEGY_APPLY_RULES);
1135             }
1136         } catch (Exception e) {
1137             e.printStackTrace();
1138             JOptionPane.showMessageDialog(
1139                 this,
1140                 "Encountered '" + e.getMessage() + "'",
1141                 "Error",
1142                 JOptionPane.ERROR_MESSAGE);
1143         }
1144     }
1145 
1146     /***
1147      * Removes all the rules selected by the user if the user confirms.
1148      * Prompts with an info message if no rules were selected.
1149      */
1150     private void removeSelectedRules() {
1151         try {
1152             List selectedRules = currentProjectTree.getSelectedRuleNodes();
1153             if (selectedRules.size() == 0) {
1154                 JOptionPane.showMessageDialog(
1155                     this,
1156                     "No rules selected. Nothing will be removed.",
1157                     "Info",
1158                     JOptionPane.INFORMATION_MESSAGE);
1159             } else {
1160                 String questionPart = "the selected rule?";
1161                 if (selectedRules.size() > 1) {
1162                     questionPart =
1163                         "the " + selectedRules.size() + " selected rules?";
1164                 }
1165                 int selectedOption = JOptionPane.showOptionDialog(
1166                     this,
1167                     "Do you really want to remove "
1168                     + questionPart,
1169                     "Remove Selected Rules",
1170                     JOptionPane.YES_NO_OPTION,
1171                     JOptionPane.QUESTION_MESSAGE,
1172                     null,
1173                     new Object[] {"Yes", "No"},
1174                     null);
1175                 if (selectedOption == 0) {
1176                     RuleSet ruleSet = deputy.getRootProject().getRuleSet();
1177                     Iterator iter = selectedRules.iterator();
1178                     while (iter.hasNext()) {
1179                         RuleTreeNode ruleNode = (RuleTreeNode) iter.next();
1180                         ruleSet.remove(ruleNode.getRule());
1181                     }
1182                 }
1183             }
1184         } catch (Exception e) {
1185             e.printStackTrace();
1186             JOptionPane.showMessageDialog(
1187                 this,
1188                 "Encountered '" + e.getMessage() + "'",
1189                 "Error",
1190                 JOptionPane.ERROR_MESSAGE);
1191         }
1192     }
1193 
1194     /***
1195      * Removes all the SNAPSHOT enforcement rules if the user confirms.
1196      */
1197     private void removeSnapshotEnforcementRules() {
1198         try {
1199             RuleSet ruleSet = deputy.getRootProject().getRuleSet();
1200             int numberOfRules = ruleSet.getNumberOfSnapshotEnforcements();
1201             if (numberOfRules > 0) {
1202                 int selectedOption = JOptionPane.showOptionDialog(
1203                     this,
1204                     "Do you really want to remove all "
1205                     + "SNAPSHOT enforcements?",
1206                     "Remove SNAPSHOT Enforcement Rules",
1207                     JOptionPane.YES_NO_OPTION,
1208                     JOptionPane.QUESTION_MESSAGE,
1209                     null,
1210                     new Object[] {"Yes", "No"},
1211                     null);
1212                 if (selectedOption == 0) {
1213                     ruleSet.removeAllSnapshotEnforcements();
1214                 }
1215             } else {
1216                 JOptionPane.showMessageDialog(
1217                         this,
1218                         "There are no SNAPSHOT enforcements to remove.",
1219                         "No SNAPSHOT enforcement rules",
1220                         JOptionPane.INFORMATION_MESSAGE);
1221             }
1222         } catch (Exception e) {
1223             e.printStackTrace();
1224             JOptionPane.showMessageDialog(
1225                 this,
1226                 "Encountered '" + e.getMessage() + "'",
1227                 "Error",
1228                 JOptionPane.ERROR_MESSAGE);
1229         }
1230     }
1231 
1232     /***
1233      * Removes all the derived rules if the user confirms.
1234      */
1235     private void removeDerivedRules() {
1236         try {
1237             RuleSet ruleSet = deputy.getRootProject().getRuleSet();
1238             int numberOfRules = ruleSet.getNumberOfDerivedRules();
1239             if (numberOfRules > 0) {
1240                 int selectedOption = JOptionPane.showOptionDialog(
1241                     this,
1242                     "Do you really want to remove all "
1243                     + "derived rules?",
1244                     "Remove Derived Rules",
1245                     JOptionPane.YES_NO_OPTION,
1246                     JOptionPane.QUESTION_MESSAGE,
1247                     null,
1248                     new Object[] {"Yes", "No"},
1249                     null);
1250                 if (selectedOption == 0) {
1251                     ruleSet.removeAllDerivedRules();
1252                 }
1253             } else {
1254                 JOptionPane.showMessageDialog(
1255                         this,
1256                         "There are no derived rules to remove.",
1257                         "No Derived rules",
1258                         JOptionPane.INFORMATION_MESSAGE);
1259             }
1260         } catch (Exception e) {
1261             e.printStackTrace();
1262             JOptionPane.showMessageDialog(
1263                 this,
1264                 "Encountered '" + e.getMessage() + "'",
1265                 "Error",
1266                 JOptionPane.ERROR_MESSAGE);
1267         }
1268     }
1269 
1270     /***
1271      * Saves the current project under the name that has been set using
1272      * {@link Deputy#setProjectFile(String)}.
1273      */
1274     private void save() {
1275         if (reallySave()) {
1276             try {
1277                File file = new File(deputy.getProjectFile());
1278                if (isFileWritable(file)) {
1279                    deputy.saveProjectAs(file.getAbsolutePath());
1280                }
1281             } catch (Exception e) {
1282                 e.printStackTrace();
1283                 JOptionPane.showMessageDialog(
1284                     this,
1285                     "Encountered '" + e.getMessage() + "'",
1286                     "Error",
1287                     JOptionPane.ERROR_MESSAGE);
1288             }
1289         }
1290     }
1291 
1292     /***
1293      * Prompts the user with a save as dialog and saves the file under the
1294      * selected path if the user doesn't cancel the dialog.
1295      */
1296     private void saveAs() {
1297         if (reallySave()) {
1298             try {
1299                 fileChooser.setCurrentDirectory(
1300                     new File(deputy.getProjectFile()));
1301                 fileChooser.setSelectedFile(
1302                     new File(deputy.getProjectFile()));
1303                 int result =
1304                     fileChooser.showSaveDialog(this);
1305                 if (result == JFileChooser.APPROVE_OPTION) {
1306                     File file = fileChooser.getSelectedFile();
1307                     if (isFileWritable(file)) {
1308                         deputy.saveProjectAs(file.getAbsolutePath());
1309                     }
1310                 }
1311             } catch (Exception e) {
1312                 e.printStackTrace();
1313                 JOptionPane.showMessageDialog(
1314                     this,
1315                     "Encountered '" + e.getMessage() + "'",
1316                     "Error",
1317                     JOptionPane.ERROR_MESSAGE);
1318             }
1319         }
1320     }
1321 
1322     /***
1323      * Checks whether the file exists and is writable.
1324      * <p/>
1325      * Tries some very special magic to make it writable if it isn't
1326      * (asks the user if it shall be opened for edit in Perforce
1327      * if it is a Perforce controlled file on a Perforce system).
1328      *
1329      * @param file The file to check writability for.
1330      * @return if the file is writable
1331      */
1332     private boolean isFileWritable(final File file) {
1333         boolean result = true;
1334         if (file.exists() && !file.canWrite()) {
1335             result = false;
1336             P4 p4 = new P4();
1337             if (p4.isP4Controlled(file.getAbsolutePath())) {
1338                 int selectedOption = JOptionPane.showOptionDialog(
1339                         this,
1340                         "The file is Perforce controlled "
1341                         + "but not opened for edit yet. "
1342                         + "Do you want Deputy to open it for edit now?",
1343                         "Open for Edit?",
1344                         JOptionPane.YES_NO_OPTION,
1345                         JOptionPane.QUESTION_MESSAGE,
1346                         null,
1347                         new Object[] {"Yes", "No"},
1348                         null);
1349                 if (selectedOption == 0) {
1350                     if (!p4.openForEdit(file.getAbsolutePath())) {
1351                         JOptionPane.showMessageDialog(
1352                                 this,
1353                                 "Could not open file for edit!",
1354                                 "Error",
1355                                 JOptionPane.ERROR_MESSAGE);
1356                     } else {
1357                         deputy.getLog().log(
1358                                 Log.SEVERITY_INFO,
1359                                 "Opened "
1360                                 + file.getAbsolutePath()
1361                                 + " for edit.");
1362                         result = true;
1363                     }
1364                 }
1365             }
1366         }
1367         if (!result) {
1368             JOptionPane.showMessageDialog(
1369                 this,
1370                 "The file cannot be saved, it is not writable!",
1371                 "Error",
1372                 JOptionPane.ERROR_MESSAGE);
1373         }
1374         return result;
1375     }
1376 
1377     /***
1378      * Checks if the user wants to save without having applied the rules after
1379      * changes in the dependencies or rules.
1380      *
1381      * @return <code>true</code> if the user really wants to save the project.
1382      */
1383     private boolean reallySave() {
1384         boolean result = true;
1385         boolean isApplyRulesRecommended =
1386             (deputy.getLastChangeAction()
1387                 == Deputy.CHANGE_ACTION_CHANGE_DEPENDENCIES)
1388             || (deputy.getLastChangeAction()
1389                 == Deputy.CHANGE_ACTION_CHANGE_RULES);
1390         if (isApplyRulesRecommended) {
1391             int selectedOption = JOptionPane.showOptionDialog(
1392                 this,
1393                 "You added or removed dependencies or rules,"
1394                 + " but you didn't apply the rules afterwards.\n"
1395                 + "This means your configuration may be inconsistent with the "
1396                 + "rules or the true dependency structure."
1397                 + "\n"
1398                 + "Do you really want to save without "
1399                 + "applying the rules before?",
1400                 "Save without applying the rules?",
1401                 JOptionPane.YES_NO_OPTION,
1402                 JOptionPane.QUESTION_MESSAGE,
1403                 null,
1404                 new Object[] {
1405                     "Save without applying the rules",
1406                     "Don't save" },
1407                 null);
1408             if (selectedOption == 1) {
1409                 result = false;
1410             }
1411         }
1412         return result;
1413     }
1414 
1415     /***
1416      * Prompts the user with a save as dialog and exports the project under the
1417      * selected path as a dependency graph in XML format if the user doesn't
1418      * cancel the dialog.
1419      */
1420     private void exportDependencyGraph() {
1421         try {
1422             exportFileChooser.setCurrentDirectory(
1423                 new File(deputy.getDependencyGraphFile()));
1424             exportFileChooser.setSelectedFile(
1425                 new File(deputy.getDependencyGraphFile()));
1426             FileFilter[] filters =
1427                 exportFileChooser.getChoosableFileFilters();
1428             for (int i = 0; i < filters.length; i++) {
1429                 exportFileChooser.removeChoosableFileFilter(filters[i]);
1430             }
1431             if (deputy.getDependencyGraphFile().endsWith(".graphml")) {
1432                 exportFileChooser.addChoosableFileFilter(deputyXmlFilter);
1433                 exportFileChooser.addChoosableFileFilter(graphMlFilter);
1434             } else {
1435                 exportFileChooser.addChoosableFileFilter(graphMlFilter);
1436                 exportFileChooser.addChoosableFileFilter(deputyXmlFilter);
1437             }
1438             int result =
1439                 exportFileChooser.showSaveDialog(this);
1440             if (result == JFileChooser.APPROVE_OPTION) {
1441                 File file = exportFileChooser.getSelectedFile();
1442                 String description =
1443                     exportFileChooser.getFileFilter().getDescription();
1444                 int selectedFormat = Deputy.FORMAT_DEPUTYXML;
1445                 if (description.indexOf("graphml") != -1) {
1446                     selectedFormat = Deputy.FORMAT_GRAPHML;
1447                 }
1448                 String absolutePath = file.getAbsolutePath();
1449                 if (selectedFormat == Deputy.FORMAT_DEPUTYXML
1450                         && !file.getName().endsWith(".xml")) {
1451                     absolutePath = absolutePath + ".xml";
1452                 } else if (selectedFormat == Deputy.FORMAT_GRAPHML
1453                                 && !file.getName().endsWith(".graphml")) {
1454                     absolutePath = absolutePath + ".graphml";
1455                 }
1456                 deputy.setDependencyGraphFile(absolutePath);
1457                 deputy.exportDependencyGraph(selectedFormat);
1458             }
1459         } catch (Exception e) {
1460             e.printStackTrace();
1461             JOptionPane.showMessageDialog(
1462                 this,
1463                 "Encountered '" + e.getMessage() + "'",
1464                 "Error",
1465                 JOptionPane.ERROR_MESSAGE);
1466         }
1467     }
1468 
1469     /***
1470      * Sorts all SNAPSHOT dependencies topologically
1471      * and prints them to the log in this order.
1472      */
1473     private void sortSnapshotsTopologically() {
1474         try {
1475             deputy.sortSnapshotsTopologically();
1476         } catch (Exception e) {
1477             e.printStackTrace();
1478             JOptionPane.showMessageDialog(
1479                 this,
1480                 "Encountered '" + e.getMessage() + "'",
1481                 "Error",
1482                 JOptionPane.ERROR_MESSAGE);
1483         }
1484     }
1485 
1486     /***
1487      * Adds a project's dependencies as derived enforcement rules.
1488      */
1489     private void deriveEnforcementsFromProject() {
1490         try {
1491             String projectFile = deputy.getImportFile();
1492             if (projectFile != null) {
1493                 try {
1494                     File file = new File(projectFile);
1495                     fileChooser.setCurrentDirectory(file);
1496                     fileChooser.setSelectedFile(file);
1497                 } catch (Exception e) {
1498                     //ignore
1499                     e.printStackTrace();
1500                 }
1501             }
1502             int result =
1503                 fileChooser.showOpenDialog(this);
1504             if (result == JFileChooser.APPROVE_OPTION) {
1505                 File file = fileChooser.getSelectedFile();
1506                 if (isValidProjectFile(file)) {
1507                     logPanel.clear();
1508                     deputy.deriveEnforcementsFromProject(
1509                             file.getAbsolutePath());
1510                 }
1511             }
1512         } catch (Exception e) {
1513             e.printStackTrace();
1514             JOptionPane.showMessageDialog(
1515                 this,
1516                 "Encountered '" + e.getMessage() + "'",
1517                 "Error",
1518                 JOptionPane.ERROR_MESSAGE);
1519         }
1520     }
1521 
1522     /***
1523      * Prompts the user with a dialog to change the current list of configs of
1524      * the Maven repositories to use.
1525      * The user is informed that using the changed repository configs requires
1526      * a re-start of Deputy.
1527      */
1528     private void editRepositoryConfigs() {
1529         try {
1530             RepositoryConfigsEditor editor =
1531                 new RepositoryConfigsEditor(
1532                     this,
1533                     deputy.getRepositoryConfigs());
1534             editor.setVisible(true);
1535             if (!editor.wasCancelled()) {
1536                 deputy.setRepositoryConfigs(editor.getRepositoryConfigs());
1537                 JOptionPane.showMessageDialog(
1538                     this,
1539                     "You must re-start Deputy to let the change "
1540                     + "of repository configs become effective.",
1541                     "Re-start Required",
1542                     JOptionPane.INFORMATION_MESSAGE);
1543             }
1544         } catch (FileNotFoundException e) {
1545             e.printStackTrace();
1546             JOptionPane.showMessageDialog(
1547                 this,
1548                 "The paths you entered are not valid. "
1549                 + "Ensure all paths exist and are accessible.",
1550                 "Error",
1551                 JOptionPane.ERROR_MESSAGE);
1552         } catch (Exception e) {
1553             e.printStackTrace();
1554             JOptionPane.showMessageDialog(
1555                 this,
1556                 "Encountered '" + e.getMessage() + "'",
1557                 "Error",
1558                 JOptionPane.ERROR_MESSAGE);
1559         }
1560     }
1561 
1562     /***
1563      * Switches on/off the usage of the virtual repository in the dependency
1564      * recursion. Updates the appaearance of the toggle button according to the
1565      * state.
1566      *
1567      * @param active The selected state to propagate.
1568      */
1569     private void setVirtualRepositoryActive(final boolean active) {
1570         if (active) {
1571             virtualRepositoryToggleButton.setIcon(virtualRepositoryOnIcon);
1572             virtualRepositoryToggleButton.setText(
1573                     "Virtual Repository Active");
1574         } else {
1575             virtualRepositoryToggleButton.setIcon(virtualRepositoryOffIcon);
1576             virtualRepositoryToggleButton.setText(
1577                     "Virtual Repository Inactive");
1578         }
1579         deputy.setVirtualRepositoryActive(active);
1580     }
1581 
1582     /***
1583      * Exits the Deputy application. Warns if the current state of the project
1584      * has not been saved.
1585      */
1586     private void exit() {
1587         boolean doExit = true;
1588         if (!deputy.isSaved()) {
1589             int selectedOption = JOptionPane.showOptionDialog(
1590                 this,
1591                 "The current state of the project has not been saved.\n"
1592                 + "Do you really want to exit without saving it?",
1593                 "Exit",
1594                 JOptionPane.YES_NO_OPTION,
1595                 JOptionPane.QUESTION_MESSAGE,
1596                 null,
1597                 new Object[] {"Exit without saving", "Don't exit"},
1598                 null);
1599             if (selectedOption == 0) {
1600                 doExit = true;
1601             } else {
1602                 doExit = false;
1603             }
1604         }
1605         if (doExit) {
1606             deputy.setProperty("deputyFrame.x", getX() + "");
1607             deputy.setProperty("deputyFrame.y", getY() + "");
1608             deputy.setProperty("deputyFrame.height", getHeight() + "");
1609             deputy.setProperty("deputyFrame.width", getWidth() + "");
1610             deputy.setProperty(
1611                 "deputyFrame.horizontalSplitPane.dividerLocation",
1612                 horizontalSplitPane.getDividerLocation() + "");
1613             deputy.setProperty(
1614                 "deputyFrame.verticalSplitPane.dividerLocation",
1615                 verticalSplitPane.getDividerLocation() + "");
1616             deputy.exitApplication();
1617         }
1618     }
1619 
1620     /***
1621      * Launches a browser to display a URL.
1622      *
1623      * @param url The URL to display.
1624      */
1625     private void openUrl(final String url) {
1626         BrowserLauncher.openUrl(url);
1627     }
1628 
1629     /***
1630      * Shows the about dialog of Deputy.
1631      */
1632     private void showAbout() {
1633         JOptionPane.showMessageDialog(
1634             this,
1635             "Version: " + deputy.getVersion() + "\n\n"
1636             + "(c) 2005, 2006, 2007 Matthias Burbach. All rights reserved.\n\n"
1637             + "This open source product is licensed under the BSD licence\n"
1638             + "and includes software developed by other open source "
1639             + "initiatives."
1640             ,
1641             "About Deputy",
1642             JOptionPane.INFORMATION_MESSAGE);
1643     }
1644 
1645     /***
1646      * Starts the application.
1647      *
1648      * @param args Optional command line argument is -project=&lt;file name&gt;
1649      *             which will immediately load that file into Deputy on start
1650      *             up.
1651      */
1652     public static void main(final String[] args) {
1653         try {
1654             Deputy deputy = new Deputy(new SimpleLog());
1655             DeputyFrame deputyFrame = new DeputyFrame(deputy);
1656 
1657             if (args.length > 0 && args[0] != null) {
1658                 if (args[0].startsWith("-project=")) {
1659                     String projectFileName =
1660                         args[0].substring("-project=".length());
1661                     if ((new File(projectFileName)).exists()) {
1662                         deputy.setProjectFile(projectFileName);
1663                         deputyFrame.doOpenProject(STRATEGY_AS_IS);
1664                     } else {
1665                         throw new IllegalArgumentException();
1666                     }
1667                 } else {
1668                     throw new IllegalArgumentException();
1669                 }
1670             }
1671         } catch (IllegalArgumentException e) {
1672             System.err.println(
1673                 "Invalid arguments, usage is: "
1674                 + DeputyFrame.class.getName()
1675                 + " [-project=<absolute path of Maven project file>]");
1676         } catch (Exception e) {
1677             e.printStackTrace();
1678         }
1679     }
1680 }