View Javadoc

1   package de.matthias_burbach.deputy.swing;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Dimension;
5   import java.awt.FlowLayout;
6   import java.awt.Frame;
7   import java.awt.event.ActionEvent;
8   import java.awt.event.ActionListener;
9   import java.awt.event.MouseEvent;
10  import java.awt.event.MouseMotionAdapter;
11  import java.io.File;
12  import java.util.ArrayList;
13  import java.util.HashMap;
14  import java.util.HashSet;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.Set;
19  
20  import javax.swing.JButton;
21  import javax.swing.JDialog;
22  import javax.swing.JOptionPane;
23  import javax.swing.JPanel;
24  import javax.swing.JScrollPane;
25  import javax.swing.JTable;
26  import javax.swing.ListSelectionModel;
27  import javax.swing.WindowConstants;
28  import javax.swing.table.DefaultTableModel;
29  import javax.swing.table.JTableHeader;
30  import javax.swing.table.TableColumn;
31  import javax.swing.table.TableColumnModel;
32  
33  import de.matthias_burbach.deputy.core.repository.RepositoryConfig;
34  
35  /***
36   * Is a modal dialog to edit the configuration of the repositories to be
37   * displayed and scanned by Deputy.
38   *
39   * @author Matthias Burbach
40   */
41  public class RepositoryConfigsEditor extends JDialog {
42      /***
43       * Is the model of the editor's table of configuration records.
44       *
45       * @author Matthias Burbach
46       */
47      private class ConfigTableModel extends DefaultTableModel {
48          /***
49           * The number of columns of the table.
50           */
51          private final int numberOfColumns = 3;
52  
53          /***
54           * The column name for the repository path.
55           */
56          public static final String COLUMN_PATH = "Path";
57  
58          /***
59           * The column name for the display name of the repository in the
60           * repository browser.
61           */
62          public static final String COLUMN_DISPLAY_NAME = "Display Name";
63  
64          /***
65           * The column name of the boolean flag indicating whether the repository
66           * is to be scanned for new versions of artifacts when applying the
67           * rules.
68           */
69          public static final String COLUMN_SCANNABLE = "Scannable";
70  
71          /***
72           * Constructs the table model.
73           *
74           * @param repositoryConfigs The list of configs of type
75           *        {@link RepositoryConfig} to be displayed and edited.
76           */
77          public ConfigTableModel(final List repositoryConfigs) {
78              String[] columnNames =
79                  {COLUMN_PATH, COLUMN_DISPLAY_NAME, COLUMN_SCANNABLE};
80              Object[][] tableData =
81                  new Object[repositoryConfigs.size()][numberOfColumns];
82              Iterator iter = repositoryConfigs.iterator();
83              int row = 0;
84              while (iter.hasNext()) {
85                  RepositoryConfig config = (RepositoryConfig) iter.next();
86                  tableData[row][0] = config.getPath();
87                  tableData[row][1] = config.getDisplayName();
88                  tableData[row][2] =
89                      new Boolean(config.isToBeScannedForVersions());
90                  row++;
91              }
92              setDataVector(tableData, columnNames);
93          }
94  
95          /*
96           * (non-Javadoc)
97           * @see javax.swing.table.DefaultTableModel#addRow(java.lang.Object[])
98           */
99          /***
100          * {@inheritDoc}
101          */
102         public void addRow(final Object[] rowData) {
103             for (int i = 0; i < numberOfColumns; i++) {
104                 rowData[i] = "";
105             }
106             super.addRow(rowData);
107         }
108 
109         /*(non-Javadoc)
110          * @see javax.swing.table.TableModel#getColumnClass(int)
111          */
112         /***
113          * {@inheritDoc}
114          */
115         public Class getColumnClass(final int columnIndex) {
116             Class result = String.class;
117             switch (columnIndex) {
118                 case 0: result = String.class;
119                     break;
120                 case 1: result = String.class;
121                     break;
122                 case 2: result = Boolean.class;
123                     break;
124                 default: result = String.class;
125             }
126             return result;
127         }
128 
129     }
130 
131     /***
132      * Provides tool tips on column headers of the table.
133      *
134      * @author The Java Developers Almanac 1.4
135      */
136     public class ColumnHeaderToolTips extends MouseMotionAdapter {
137         /***
138          * The current column whose tooltip is being displayed.
139          * Is used to minimize the calls to setToolTipText().
140          */
141         private TableColumn currentColumn;
142 
143         /***
144          * Maps TableColumn objects to tooltips
145          */
146         private Map tips = new HashMap();
147 
148         /***
149          * If tooltip is <code>null</code>, removes any tooltip text.
150          *
151          * @param column The column to set the tool tip on.
152          * @param tooltip The tool tip text to set
153          */
154         public void setToolTip(final TableColumn column, final String tooltip) {
155             if (tooltip == null) {
156                 tips.remove(column);
157             } else {
158                 tips.put(column, tooltip);
159             }
160         }
161 
162         /*
163          * (non-Javadoc)
164          * @see java.awt.event.MouseMotionListener
165          *          #mouseMoved(java.awt.event.MouseEvent)
166          */
167         /***
168          * {@inheritDoc}
169          */
170         public void mouseMoved(final MouseEvent event) {
171             TableColumn column = null;
172             JTableHeader header = (JTableHeader) event.getSource();
173             JTable table = header.getTable();
174             TableColumnModel colModel = table.getColumnModel();
175             int vColIndex = colModel.getColumnIndexAtX(event.getX());
176 
177             /*
178              * Return if not clicked on any column header
179              */
180             if (vColIndex >= 0) {
181                 column = colModel.getColumn(vColIndex);
182             }
183 
184             if (column != currentColumn) {
185                 header.setToolTipText((String) tips.get(column));
186                 currentColumn = column;
187             }
188         }
189     }
190 
191     /***
192      * The model of the editor's table of configuration records.
193      */
194     private ConfigTableModel configTableModel;
195 
196     /***
197      * The list of configs of type {@link RepositoryConfig} to be displayed and
198      * edited.
199      */
200     private List repositoryConfigs;
201 
202     /***
203      * Whether the editor dialog was cancelled by the user.
204      */
205     private boolean wasCancelled = true;
206 
207     /***
208      * Constructs the repository configs editor.
209      *
210      * @param parent The parent component of this dialog.
211      * @param repositoryConfigs The list of configs of type
212      *                          {@link RepositoryConfig} to be displayed and
213      *                          edited.
214      */
215     public RepositoryConfigsEditor(
216             final Frame parent,
217             final List repositoryConfigs) {
218         super(parent, "Edit Repository Configs", true);
219         this.repositoryConfigs = repositoryConfigs;
220 
221         /*
222          * Create the table model and the table
223          */
224         configTableModel = new ConfigTableModel(repositoryConfigs);
225         final JTable configTable = new JTable(configTableModel);
226         configTable.setSelectionMode(
227             ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
228         configTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
229         /*
230          * Get the columns
231          */
232         TableColumn pathColumn =
233             configTable.getColumn(ConfigTableModel.COLUMN_PATH);
234         TableColumn displayNameColumn =
235             configTable.getColumn(ConfigTableModel.COLUMN_DISPLAY_NAME);
236         TableColumn scannableColumn =
237             configTable.getColumn(ConfigTableModel.COLUMN_SCANNABLE);
238         /*
239          * Assign reasonable sizes to the columns
240          */
241         final int preferredPanelWidth = 600;
242         pathColumn.setPreferredWidth(preferredPanelWidth / 2);
243         final int three = 3;
244         displayNameColumn.setPreferredWidth(
245                 (preferredPanelWidth / 2) / three * 2);
246         scannableColumn.setPreferredWidth(
247                 (preferredPanelWidth / 2) / three * 1);
248         /*
249          * Assign tool tips to the columns
250          */
251         JTableHeader header = configTable.getTableHeader();
252         ColumnHeaderToolTips tips = new ColumnHeaderToolTips();
253         tips.setToolTip(
254             pathColumn,
255             "The absolute path to a Maven repository in the file system");
256         tips.setToolTip(
257             displayNameColumn,
258             "The display name of the repository in the repositories browser");
259         tips.setToolTip(
260             scannableColumn,
261             "If checked the repository will be scanned for the latest releases"
262             + " of artifacts when applying the rules");
263         header.addMouseMotionListener(tips);
264 
265         JPanel panel = new JPanel(new BorderLayout());
266         panel.add(new JScrollPane(configTable), BorderLayout.CENTER);
267 
268         /*
269          * Create the button panel
270          */
271         JPanel buttonPanel = new JPanel(new FlowLayout());
272 
273         JButton addButton = new JButton("Add Config");
274         addButton.addActionListener(new ActionListener() {
275             public void actionPerformed(final ActionEvent e) {
276                 int colCount = configTableModel.getColumnCount();
277                 configTableModel.addRow(new String[colCount]);
278                 int rowCount = configTableModel.getRowCount();
279                 configTableModel.setValueAt(
280                     "<<Enter a path>>",
281                     rowCount - 1,
282                     0);
283                 configTableModel.setValueAt(
284                     "<<Enter a display name>>",
285                     rowCount - 1,
286                     1);
287                 configTableModel.setValueAt(Boolean.FALSE, rowCount - 1, 2);
288             }
289         });
290         buttonPanel.add(addButton);
291 
292         JButton deleteButton = new JButton("Delete Selected Configs");
293         deleteButton.addActionListener(new ActionListener() {
294             public void actionPerformed(final ActionEvent e) {
295                 int selectedRowIdx = configTable.getSelectedRow();
296                 while (selectedRowIdx >= 0) {
297                     configTableModel.removeRow(selectedRowIdx);
298                     selectedRowIdx = configTable.getSelectedRow();
299                 }
300             }
301         });
302         buttonPanel.add(deleteButton);
303 
304         JButton okButton = new JButton("OK");
305         okButton.addActionListener(new ActionListener() {
306             public void actionPerformed(final ActionEvent e) {
307                 if (validateRepositoryConfigs()) {
308                     updateRepositoryConfigs();
309                     wasCancelled = false;
310                     dispose();
311                 }
312             }
313         });
314         buttonPanel.add(okButton);
315 
316         JButton cancelButton = new JButton("Cancel");
317         cancelButton.addActionListener(new ActionListener() {
318             public void actionPerformed(final ActionEvent e) {
319                 wasCancelled = true;
320                 dispose();
321             }
322         });
323         buttonPanel.add(cancelButton);
324 
325         panel.add(buttonPanel, BorderLayout.SOUTH);
326 
327         setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
328         final int preferredHeight = 200;
329         panel.setPreferredSize(
330                 new Dimension(preferredPanelWidth, preferredHeight));
331         getContentPane().add(panel);
332         pack();
333         int x = Math.max(0, (parent.getWidth() - getWidth()) / 2);
334         int y = Math.max(0, (parent.getHeight() - getHeight()) / 2);
335         setBounds(x, y, getWidth(), getHeight());
336     }
337 
338     /***
339      * @return The list of configs of type {@link RepositoryConfig} to be
340      *         displayed and edited. Can be queried by the creator of this
341      *         dialog when the dialog was closed with 'OK'.
342      */
343     public List getRepositoryConfigs() {
344         return repositoryConfigs;
345     }
346 
347     /***
348      * @return Whether this dialog was closed with 'Cancel'.
349      */
350     public boolean wasCancelled() {
351         return wasCancelled;
352     }
353 
354     /***
355      * Transfers edited data back from the table model to the repositoryConfigs
356      * list.
357      */
358     private void updateRepositoryConfigs() {
359         repositoryConfigs = new ArrayList();
360         for (int row = 0; row < configTableModel.getRowCount(); row++) {
361             String path = (String) configTableModel.getValueAt(row, 0);
362             String displayName =
363                 (String) configTableModel.getValueAt(row, 1);
364             Boolean scannable =
365                 (Boolean) configTableModel.getValueAt(row, 2);
366             RepositoryConfig config = new RepositoryConfig(path);
367             config.setDisplayName(displayName);
368             config.setToBeScannedForVersions(scannable.booleanValue());
369             repositoryConfigs.add(config);
370         }
371     }
372 
373     /***
374      * Validates the configs the user edited.
375      *
376      * @return <code>true</code> if the data is valid.
377      */
378     private boolean validateRepositoryConfigs() {
379         boolean result = true;
380         if (configTableModel.getRowCount() == 0) {
381             int selectedOption = JOptionPane.showOptionDialog(
382                 this,
383                 "Do you really want to remove all repositories?\n"
384                 + "Deputy will not be of much help to you then.",
385                 "Remove all repositories?",
386                 JOptionPane.YES_NO_OPTION,
387                 JOptionPane.QUESTION_MESSAGE,
388                 null,
389                 new Object[] {"Yes, eat this!", "No, cancel"},
390                 null);
391             if (selectedOption == 1) {
392                 result = false;
393             }
394         } else {
395             List paths = new ArrayList();
396             List displayNames = new ArrayList();
397             for (int row = 0; row < configTableModel.getRowCount(); row++) {
398                 String path = (String) configTableModel.getValueAt(row, 0);
399                 result = validatePath(path);
400                 if (!result) {
401                     break;
402                 }
403                 paths.add(path);
404                 String displayName =
405                     (String) configTableModel.getValueAt(row, 1);
406                 result = validateDisplayName(displayName);
407                 if (!result) {
408                     break;
409                 }
410                 displayNames.add(displayName);
411             }
412             Set uniquePaths = new HashSet(paths);
413             Set uniqueDisplayNames = new HashSet(displayNames);
414             if (uniquePaths.size() < paths.size()) {
415                 JOptionPane.showMessageDialog(
416                     this,
417                     "Paths are not unique. Please, fix this!",
418                     "Duplicate Paths",
419                     JOptionPane.ERROR_MESSAGE);
420                 result = false;
421             } else if (uniqueDisplayNames.size() < displayNames.size()) {
422                 JOptionPane.showMessageDialog(
423                     this,
424                     "Display names are not unique. Please, fix this!",
425                     "Duplicate Display Names",
426                     JOptionPane.ERROR_MESSAGE);
427                 result = false;
428             }
429         }
430         return result;
431     }
432 
433     /***
434      * Validates a path. Checks existence, readability, and if the path is a
435      * directory.
436      *
437      * @param path The path to validate.
438      * @return <code>true</code> if the path is valid.
439      */
440     private boolean validatePath(final String path) {
441         boolean result = true;
442         String message = null;
443         if (path == null || path.length() == 0) {
444             JOptionPane.showMessageDialog(
445                 this,
446                 "You must not use empty paths!",
447                 "Empty Path",
448                 JOptionPane.ERROR_MESSAGE);
449             result = false;
450         } else {
451             File file = new File(path);
452             if (!file.exists()) {
453                 message = "The path '" + path + "' does not exist.";
454             } else if (!file.canRead()) {
455                 message = "The path '" + path + "' exists but cannot be read.";
456             } else if (!file.isDirectory()) {
457                 message = "The path '" + path + "' is not a directory.";
458             }
459             if (message != null) {
460                 int selectedOption = JOptionPane.showOptionDialog(
461                     this,
462                     message
463                     + "\nDo you really still want to save this configuration?",
464                     "Invalid Path In Configuration",
465                     JOptionPane.YES_NO_OPTION,
466                     JOptionPane.QUESTION_MESSAGE,
467                     null,
468                     new Object[] {"Yes, eat this!", "No, cancel"},
469                     null);
470                 if (selectedOption == 1) {
471                     result = false;
472                 }
473             }
474         }
475         return result;
476     }
477 
478     /***
479      * Validates the display name. Checks whether it is a non-null, non-empty
480      * string.
481      *
482      * @param displayName The display name to check.
483      * @return <code>true</code> if the display name is valid.
484      */
485     private boolean validateDisplayName(final String displayName) {
486         boolean result = true;
487         if (displayName == null || displayName.length() == 0) {
488             JOptionPane.showMessageDialog(
489                 this,
490                 "You must not use empty display names!",
491                 "Empty Display Name",
492                 JOptionPane.ERROR_MESSAGE);
493             result = false;
494         }
495         return result;
496     }
497 }