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
97
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
110
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
164
165
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
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
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
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
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
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
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 }