Source code: com/virtuosotechnologies/asaph/xmldatabase/XMLDatabaseGui.java
1 /*
2 ================================================================================
3
4 FILE: XMLDatabaseGui.java
5
6 PROJECT:
7
8 Asaph
9
10 CONTENTS:
11
12 Interface with the gui for XML databases
13
14 PROGRAMMERS:
15
16 Daniel Azuma (DA) <dazuma@kagi.com>
17
18 COPYRIGHT:
19
20 Copyright (C) 2003 Daniel Azuma (dazuma@kagi.com)
21
22 This program is free software; you can redistribute it and/or
23 modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation; either version 2
25 of the License, or (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public
33 License along with this program; if not, write to
34 Free Software Foundation, Inc.
35 59 Temple Place, Suite 330
36 Boston, MA 02111-1307 USA
37
38 ================================================================================
39 */
40
41
42 package com.virtuosotechnologies.asaph.xmldatabase;
43
44
45 import java.net.URL;
46 import java.net.MalformedURLException;
47 import java.net.URISyntaxException;
48 import java.io.OutputStream;
49 import java.io.FileOutputStream;
50 import java.io.Reader;
51 import java.io.InputStreamReader;
52 import java.io.InputStream;
53 import java.io.FileInputStream;
54 import java.io.File;
55 import java.io.IOException;
56 import java.awt.Component;
57 import java.awt.GridBagLayout;
58 import java.awt.GridBagConstraints;
59 import java.awt.Frame;
60 import java.awt.Insets;
61 import java.awt.Color;
62 import java.awt.BorderLayout;
63 import java.awt.FlowLayout;
64 import java.awt.event.WindowAdapter;
65 import java.awt.event.WindowEvent;
66 import java.awt.event.ActionListener;
67 import java.awt.event.ActionEvent;
68 import java.util.Map;
69 import java.util.HashMap;
70 import java.util.logging.Logger;
71 import java.util.logging.Level;
72 import javax.swing.Icon;
73 import javax.swing.BorderFactory;
74 import javax.swing.Box;
75 import javax.swing.UIManager;
76 import javax.swing.SwingUtilities;
77 import javax.swing.JComponent;
78 import javax.swing.JFileChooser;
79 import javax.swing.JDialog;
80 import javax.swing.JPanel;
81 import javax.swing.JLabel;
82 import javax.swing.JTextArea;
83 import javax.swing.JTextField;
84 import javax.swing.JButton;
85 import javax.swing.JOptionPane;
86 import javax.swing.filechooser.FileFilter;
87 import javax.swing.filechooser.FileView;
88 import org.xml.sax.SAXException;
89 import org.xml.sax.SAXParseException;
90
91 import com.virtuosotechnologies.lib.command.CommandNode;
92 import com.virtuosotechnologies.lib.command.CommandEvent;
93 import com.virtuosotechnologies.lib.basiccommand.BasicCommandNode;
94 import com.virtuosotechnologies.lib.basiccommand.BasicItemCommandNode;
95 import com.virtuosotechnologies.lib.basiccommand.BasicSeparatorCommandNode;
96 import com.virtuosotechnologies.lib.util.ExceptionUtils;
97 import com.virtuosotechnologies.lib.util.FileUtils;
98 import com.virtuosotechnologies.lib.asyncjob.AsyncJob;
99 import com.virtuosotechnologies.lib.asyncjob.AbstractAsyncJob;
100 import com.virtuosotechnologies.lib.asyncjob.AsyncJobExecution;
101 import com.virtuosotechnologies.lib.asyncjob.AsyncJobRunner;
102 import com.virtuosotechnologies.lib.asyncjob.AsyncJobListener;
103 import com.virtuosotechnologies.lib.asyncjob.ThreadWorkerAsyncJobRunner;
104 import com.virtuosotechnologies.lib.asyncjob.BasicAsyncJobListener;
105 import com.virtuosotechnologies.lib.asyncjob.AsyncJobFailedEvent;
106 import com.virtuosotechnologies.lib.asyncjob.AsyncJobException;
107 import com.virtuosotechnologies.lib.asyncjob.AsyncJobProgressReporter;
108 import com.virtuosotechnologies.lib.swing.SwingUtils;
109 import com.virtuosotechnologies.lib.swing.DetailedMessageDialog;
110 import com.virtuosotechnologies.lib.xml.MessageCollectingErrorHandler;
111 import com.virtuosotechnologies.lib.platform.PlatformUtils;
112
113 import com.virtuosotechnologies.asaph.model.SongDatabase;
114 import com.virtuosotechnologies.asaph.maingui.CommandManager;
115 import com.virtuosotechnologies.asaph.maingui.DatabaseManager;
116 import com.virtuosotechnologies.asaph.maingui.GuiEnvironmentManager;
117 import com.virtuosotechnologies.asaph.maingui.PaneManager;
118 import com.virtuosotechnologies.asaph.maingui.SelectionManager;
119 import com.virtuosotechnologies.asaph.maingui.SelectionListener;
120 import com.virtuosotechnologies.asaph.maingui.DatabaseSelectionEvent;
121 import com.virtuosotechnologies.asaph.maingui.SongSelectionEvent;
122 import com.virtuosotechnologies.asaph.maingui.DatabaseConnector;
123 import com.virtuosotechnologies.asaph.maingui.DatabaseIdentifier;
124 import com.virtuosotechnologies.asaph.maingui.DuplicateConnectorException;
125 import com.virtuosotechnologies.asaph.maingui.DatabaseHandler;
126 import com.virtuosotechnologies.asaph.maingui.DatabaseController;
127 import com.virtuosotechnologies.asaph.maingui.PaneHandler;
128 import com.virtuosotechnologies.asaph.maingui.PaneController;
129 import com.virtuosotechnologies.asaph.standardmodel.StandardModelFactory;
130
131
132 /**
133 * Interface with the gui for XML databases
134 */
135 /*package*/ class XMLDatabaseGui
136 {
137 private static final String CONNECTOR_NAME = "com.virtuosotechnologies.asaph.xmldatabase.XMLDatabaseConnector";
138 private static final int CURRENT_CONNECTOR_VERSION = 0;
139
140 private static final String SIMPLE_DATABASE_SUFFIX = ".adml";
141 private static final String SIMPLE_DATABASE_SUFFIX_OLD = ".asmd";
142 private static final String BACKUP_SUFFIX = ".bak";
143
144 private static final String STR_menu_GenericSimpleDatabaseName =
145 ResourceAccess.Strings.buildString("menu_GenericSimpleDatabaseName");
146 private static final String STR_FileChooserFilterDescription =
147 ResourceAccess.Strings.buildString("FileChooserFilterDescription");
148 private static final String STR_dialog_ClosingTitle =
149 ResourceAccess.Strings.buildString("dialog_ClosingTitle");
150 private static final String STR_PreferredCharacterEncoding =
151 ResourceAccess.Strings.buildString("PreferredCharacterEncoding");
152
153 private static final String STR_dialog_ErrorTitle =
154 ResourceAccess.Strings.buildString("dialog_ErrorTitle");
155 private static final String STR_dialog_ErrorHeader =
156 ResourceAccess.Strings.buildString("dialog_ErrorHeader");
157 private static final String STR_dialog_FirstExceptionTemplate =
158 ResourceAccess.Strings.buildString("dialog_FirstExceptionTemplate");
159 private static final String STR_dialog_NextExceptionTemplate =
160 ResourceAccess.Strings.buildString("dialog_NextExceptionTemplate");
161 private static final String STR_message_ParsingErrors =
162 ResourceAccess.Strings.buildString("message_ParsingErrors");
163 private static final String STR_message_CantCreateDatabase =
164 ResourceAccess.Strings.buildString("message_CantCreateDatabase");
165 private static final String STR_message_CantOpenDatabase =
166 ResourceAccess.Strings.buildString("message_CantOpenDatabase");
167 private static final String STR_message_CantSaveDatabase =
168 ResourceAccess.Strings.buildString("message_CantSaveDatabase");
169
170 private static final String STR_OpenDatabaseAsyncJobName =
171 ResourceAccess.Strings.buildString("OpenDatabaseAsyncJobName");
172 private static final String STR_SaveDatabaseAsyncJobName =
173 ResourceAccess.Strings.buildString("SaveDatabaseAsyncJobName");
174
175 private static final String STR_OpenURLDialog_Title =
176 ResourceAccess.Strings.buildString("OpenURLDialog_Title");
177 private static final String STR_OpenURLDialog_URLLabel =
178 ResourceAccess.Strings.buildString("OpenURLDialog_URLLabel");
179 private static final String STR_OpenURLDialog_OpenButton =
180 ResourceAccess.Strings.buildString("OpenURLDialog_OpenButton");
181 private static final String STR_OpenURLDialog_CancelButton =
182 ResourceAccess.Strings.buildString("OpenURLDialog_CancelButton");
183
184 private static final String STR_DBInfoWind_YesSaved =
185 ResourceAccess.Strings.buildString("DBInfoWind_YesSaved");
186 private static final String STR_DBInfoWind_NoSaved =
187 ResourceAccess.Strings.buildString("DBInfoWind_NoSaved");
188 private static final String STR_DBInfoWind_TypeLabel =
189 ResourceAccess.Strings.buildString("DBInfoWind_TypeLabel");
190 private static final String STR_DBInfoWind_SimpleType =
191 ResourceAccess.Strings.buildString("DBInfoWind_SimpleType");
192 private static final String STR_DBInfoWind_IndexedType =
193 ResourceAccess.Strings.buildString("DBInfoWind_IndexedType");
194 private static final String STR_DBInfoWind_IndexedROType =
195 ResourceAccess.Strings.buildString("DBInfoWind_IndexedROType");
196 private static final String STR_DBInfoWind_SavedLabel =
197 ResourceAccess.Strings.buildString("DBInfoWind_SavedLabel");
198 private static final String STR_DBInfoWind_URLLabel =
199 ResourceAccess.Strings.buildString("DBInfoWind_URLLabel");
200 private static final String STR_DBInfoWind_FileLabel =
201 ResourceAccess.Strings.buildString("DBInfoWind_FileLabel");
202 private static final String STR_DBInfoWind_URILabel =
203 ResourceAccess.Strings.buildString("DBInfoWind_URILabel");
204
205
206 private Logger logger_;
207
208 private CommandManager commandManager_;
209 private DatabaseManager databaseManager_;
210 private GuiEnvironmentManager guiEnvironmentManager_;
211 private PaneManager paneManager_;
212 private SelectionManager selectionManager_;
213 private StandardModelFactory modelFactory_;
214 private IndexedDatabaseFactory indexedDatabaseFactory_;
215
216 private DatabaseConnector connector_;
217
218 private AsyncJobRunner jobRunner_;
219 private AsyncJobListener jobListener_;
220
221 private Map databaseInfo_;
222 private Map databasesByKey_;
223 private SongDatabase lastSelected_;
224
225 private BasicCommandNode newCommand_;
226 private BasicCommandNode newIndexedCommand_;
227 private BasicCommandNode openFileCommand_;
228 private BasicCommandNode openURLCommand_;
229 private BasicCommandNode saveCommand_;
230 private BasicCommandNode saveAsCommand_;
231
232 private Icon fileIcon_;
233 private Icon directoryIcon_;
234
235 private int lastUntitledNumber_;
236
237 private SelectionListener selectionListener_;
238
239
240 /**
241 * Constructor
242 */
243 /*package*/ XMLDatabaseGui(
244 CommandManager commandManager,
245 DatabaseManager databaseManager,
246 GuiEnvironmentManager guiEnvironmentManager,
247 PaneManager paneManager,
248 SelectionManager selectionManager,
249 StandardModelFactory modelFactory,
250 IndexedDatabaseFactory indexedDatabaseFactory)
251 throws
252 DuplicateConnectorException
253 {
254 logger_ = Logger.getLogger("com.virtuosotechnologies.asaph.xmldatabase");
255
256 commandManager_ = commandManager;
257 databaseManager_ = databaseManager;
258 guiEnvironmentManager_ = guiEnvironmentManager;
259 paneManager_ = paneManager;
260 selectionManager_ = selectionManager;
261 modelFactory_ = modelFactory;
262 indexedDatabaseFactory_ = indexedDatabaseFactory;
263
264 lastSelected_ = null;
265 databaseInfo_ = new HashMap();
266 databasesByKey_ = new HashMap();
267 lastUntitledNumber_ = 0;
268
269 newCommand_ = new BasicItemCommandNode()
270 {
271 public void commandInvoked(
272 CommandEvent ev)
273 {
274 doCreateDatabase();
275 }
276 };
277 newCommand_.setNameProperty(
278 ResourceAccess.Strings.buildString("menu_NewSimpleDatabaseItem"));
279
280 newIndexedCommand_ = new BasicItemCommandNode()
281 {
282 public void commandInvoked(
283 CommandEvent ev)
284 {
285 doCreateIndexedDatabase();
286 }
287 };
288 newIndexedCommand_.setNameProperty(
289 ResourceAccess.Strings.buildString("menu_NewIndexedDatabaseItem"));
290
291 openFileCommand_ = new BasicItemCommandNode()
292 {
293 public void commandInvoked(
294 CommandEvent ev)
295 {
296 doOpenDatabaseFile();
297 }
298 };
299 openFileCommand_.setNameProperty(
300 ResourceAccess.Strings.buildString("menu_OpenFileItem"));
301 openFileCommand_.setAcceleratorKeystrokeProperty(SwingUtils.buildAcceleratorKeyStroke(
302 ResourceAccess.Strings.buildString("menu_OpenFileKeyStroke")));
303
304 openURLCommand_ = new BasicItemCommandNode()
305 {
306 public void commandInvoked(
307 CommandEvent ev)
308 {
309 doOpenDatabaseURL();
310 }
311 };
312 openURLCommand_.setNameProperty(
313 ResourceAccess.Strings.buildString("menu_OpenURLItem"));
314
315 saveCommand_ = new BasicItemCommandNode()
316 {
317 public void commandInvoked(
318 CommandEvent ev)
319 {
320 doSaveDatabase();
321 }
322 };
323 saveCommand_.setDisabledProperty(true);
324 saveCommand_.setNameProperty(ResourceAccess.Strings.buildString(
325 "menu_SaveItem", STR_menu_GenericSimpleDatabaseName));
326 saveCommand_.setAcceleratorKeystrokeProperty(SwingUtils.buildAcceleratorKeyStroke(
327 ResourceAccess.Strings.buildString("menu_SaveKeyStroke")));
328
329 saveAsCommand_ = new BasicItemCommandNode()
330 {
331 public void commandInvoked(
332 CommandEvent ev)
333 {
334 doSaveDatabaseAs();
335 }
336 };
337 saveAsCommand_.setDisabledProperty(true);
338 saveAsCommand_.setNameProperty(ResourceAccess.Strings.buildString(
339 "menu_SaveAsItem", STR_menu_GenericSimpleDatabaseName));
340 saveAsCommand_.setAcceleratorKeystrokeProperty(SwingUtils.buildAcceleratorKeyStroke(
341 ResourceAccess.Strings.buildString("menu_SaveAsKeyStroke")));
342
343 SwingUtils.invokeOnSwingThread(
344 new Runnable()
345 {
346 public void run()
347 {
348 CommandNode group = commandManager_.createCommandGroup(CommandManager.OPEN_COMMANDS);
349 group.addChild(newCommand_);
350 group.addChild(newIndexedCommand_);
351 //group.addChild(BasicSeparatorCommandNode.getInstance());
352 group.addChild(openFileCommand_);
353 group.addChild(openURLCommand_);
354 group = commandManager_.createCommandGroup(CommandManager.SAVE_COMMANDS);
355 group.addChild(saveCommand_);
356 group.addChild(saveAsCommand_);
357
358 directoryIcon_ = UIManager.getIcon("FileView.directoryIcon");
359 fileIcon_ = UIManager.getIcon("FileView.fileIcon");
360 }
361 });
362
363 jobRunner_ = new ThreadWorkerAsyncJobRunner(1);
364 jobRunner_.addAsyncJobListener(
365 jobListener_ = new BasicAsyncJobListener()
366 {
367 public void jobFailed(
368 final AsyncJobFailedEvent ev)
369 {
370 SwingUtils.invokeOnSwingThread(
371 new Runnable()
372 {
373 public void run()
374 {
375 AsyncJobException ex = ev.getException();
376 Throwable cause = ex.getCause();
377 if (cause == null)
378 {
379 DetailedMessageDialog dialog = DetailedMessageDialog.create(
380 guiEnvironmentManager_.getDialogParent(),
381 STR_dialog_ErrorTitle, STR_message_ParsingErrors,
382 STR_dialog_ErrorHeader, ex.getMessage(),
383 null);
384 dialog.show();
385 }
386 else
387 {
388 DetailedMessageDialog dialog = DetailedMessageDialog.create(
389 guiEnvironmentManager_.getDialogParent(),
390 STR_dialog_ErrorTitle, ex.getMessage(),
391 STR_dialog_ErrorHeader,
392 ExceptionUtils.getDescriptionFor(cause,
393 STR_dialog_FirstExceptionTemplate,
394 STR_dialog_NextExceptionTemplate),
395 null);
396 dialog.show();
397 }
398 }
399 });
400 }
401 });
402 guiEnvironmentManager_.registerAsyncJobRunner(jobRunner_);
403
404 selectionManager_.addSelectionListener(
405 selectionListener_ = new SelectionListener()
406 {
407 public void databaseSelectionChanged(
408 DatabaseSelectionEvent ev)
409 {
410 handleDatabaseSelected(ev);
411 }
412
413 public void songSelectionChanged(
414 SongSelectionEvent ev)
415 {
416 }
417 });
418
419 databaseManager_.registerDatabaseConnector(CONNECTOR_NAME, CURRENT_CONNECTOR_VERSION,
420 connector_ = new DatabaseConnector()
421 {
422 public AsyncJobExecution openIdentifiedDatabase(
423 AsyncJobRunner runner,
424 DatabaseIdentifier identifier)
425 {
426 if (!identifier.getConnectorName().equals(CONNECTOR_NAME))
427 {
428 return null;
429 }
430 String locator = identifier.getDatabaseLocator();
431 File file = new File(locator);
432 if (file.exists())
433 {
434 return doOpenDatabase(file, runner);
435 }
436 try
437 {
438 URL url = new URL(locator);
439 return doOpenDatabase(url, runner);
440 }
441 catch (MalformedURLException ex)
442 {
443 }
444 return null;
445 }
446 });
447
448 logger_.fine("Initialized XMLDatabasePlugin");
449 }
450
451
452 /**
453 * A database selected.
454 */
455 private void handleDatabaseSelected(
456 DatabaseSelectionEvent ev)
457 {
458 SongDatabase database = ev.getNewSelection();
459 String name = STR_menu_GenericSimpleDatabaseName;
460 if (database != null)
461 {
462 DatabaseInfo info = (DatabaseInfo)databaseInfo_.get(database);
463 if (info != null && modelFactory_.isSimpleSongDatabase(database))
464 {
465 lastSelected_ = database;
466 saveCommand_.setDisabledProperty(!info.isDirty() || !database.isWritable());
467 saveAsCommand_.setDisabledProperty(false);
468 name = info.getName();
469 }
470 else
471 {
472 saveCommand_.setDisabledProperty(true);
473 saveAsCommand_.setDisabledProperty(true);
474 }
475 }
476 else
477 {
478 saveCommand_.setDisabledProperty(true);
479 saveAsCommand_.setDisabledProperty(true);
480 }
481 saveCommand_.setNameProperty(ResourceAccess.Strings.buildString(
482 "menu_SaveItem", name));
483 saveAsCommand_.setNameProperty(ResourceAccess.Strings.buildString(
484 "menu_SaveAsItem", name));
485 }
486
487
488 private void doCreateDatabase()
489 {
490 SongDatabase database = modelFactory_.createSimpleSongDatabase();
491 String name = ResourceAccess.Strings.buildString("UntitledSimpleDatabaseName",
492 Integer.toString(++lastUntitledNumber_));
493 DatabaseInfo info = new DatabaseInfo(database, name);
494 databaseInfo_.put(database, info);
495 info.setDatabaseController(databaseManager_.openDatabase(
496 database, name, fileIcon_, info.getDatabaseIdentifier(), info));
497 }
498
499
500 private void doCreateIndexedDatabase()
501 {
502 Component dialogParent = guiEnvironmentManager_.getDialogParent();
503 JFileChooser chooser = new JFileChooser(guiEnvironmentManager_.getFileChooserDirectory());
504 int returnVal = chooser.showSaveDialog(dialogParent);
505 guiEnvironmentManager_.setFileChooserDirectory(chooser.getCurrentDirectory());
506 if (returnVal == JFileChooser.APPROVE_OPTION)
507 {
508 File file = chooser.getSelectedFile();
509 try
510 {
511 file = file.getCanonicalFile();
512 SongDatabase database = indexedDatabaseFactory_.createIndexedDatabase(file);
513 DatabaseInfo info = new DatabaseInfo(database, file);
514 databaseInfo_.put(database, info);
515 info.setDatabaseController(databaseManager_.openDatabase(database,
516 file.getName(), directoryIcon_, info.getDatabaseIdentifier(), info));
517 }
518 catch (IOException ex)
519 {
520 DetailedMessageDialog dialog = DetailedMessageDialog.create(
521 dialogParent, STR_dialog_ErrorTitle,
522 STR_message_CantCreateDatabase, STR_dialog_ErrorHeader,
523 ExceptionUtils.getDescriptionFor(ex, STR_dialog_FirstExceptionTemplate,
524 STR_dialog_NextExceptionTemplate),
525 null);
526 dialog.show();
527 }
528 catch (SAXException ex)
529 {
530 DetailedMessageDialog dialog = DetailedMessageDialog.create(
531 dialogParent, STR_dialog_ErrorTitle,
532 STR_message_CantCreateDatabase, STR_dialog_ErrorHeader,
533 ExceptionUtils.getDescriptionFor(ex, STR_dialog_FirstExceptionTemplate,
534 STR_dialog_NextExceptionTemplate),
535 null);
536 dialog.show();
537 }
538 }
539 }
540
541
542 private void doOpenDatabaseFile()
543 {
544 Component dialogParent = guiEnvironmentManager_.getDialogParent();
545 JFileChooser chooser = new JFileChooser(guiEnvironmentManager_.getFileChooserDirectory());
546 final boolean isMacOSX = PlatformUtils.getCurrentPlatformID().equalsOrExtends(
547 PlatformUtils.MAC_OS_X_PLATFORM);
548
549 //chooser.setAcceptAllFileFilterUsed(false);
550 chooser.setFileFilter(
551 new FileFilter()
552 {
553 public boolean accept(
554 File f)
555 {
556 if (f.isDirectory())
557 {
558 return !isMacOSX || !f.getPath().endsWith(".app");
559 }
560 else
561 {
562 return f.getPath().endsWith(SIMPLE_DATABASE_SUFFIX) ||
563 f.getPath().endsWith(SIMPLE_DATABASE_SUFFIX_OLD);
564 }
565 }
566
567 public String getDescription()
568 {
569 return STR_FileChooserFilterDescription;
570 }
571 });
572 chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
573 chooser.setFileView(
574 new FileView()
575 {
576 public Boolean isTraversable(
577 File f)
578 {
579 if (new File(f, IndexConstants.INDEX_FILENAME).isFile())
580 {
581 return Boolean.FALSE;
582 }
583 if (new File(f, IndexConstants.INDEX_FILENAME_OLD).isFile())
584 {
585 return Boolean.FALSE;
586 }
587 return null;
588 }
589 });
590 int returnVal = chooser.showOpenDialog(dialogParent);
591 guiEnvironmentManager_.setFileChooserDirectory(chooser.getCurrentDirectory());
592 if (returnVal == JFileChooser.APPROVE_OPTION)
593 {
594 File file = chooser.getSelectedFile();
595 try
596 {
597 file = file.getCanonicalFile();
598 doOpenDatabase(file, jobRunner_);
599 }
600 catch (IOException ex)
601 {
602 DetailedMessageDialog dialog = DetailedMessageDialog.create(
603 dialogParent, STR_dialog_ErrorTitle,
604 ResourceAccess.Strings.buildString("message_CantFindFile", file.getPath()),
605 STR_dialog_ErrorHeader, ExceptionUtils.getDescriptionFor(ex,
606 STR_dialog_FirstExceptionTemplate, STR_dialog_NextExceptionTemplate),
607 null);
608 dialog.show();
609 }
610 }
611 }
612
613
614 private void doOpenDatabaseURL()
615 {
616 final JComponent dialogParent = guiEnvironmentManager_.getDialogParent();
617 final JDialog dialog = new JDialog((Frame)dialogParent.getTopLevelAncestor(),
618 STR_OpenURLDialog_Title, true);
619 dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
620 dialog.addWindowListener(
621 new WindowAdapter()
622 {
623 public void windowClosing(
624 WindowEvent ev)
625 {
626 dialog.dispose();
627 }
628 });
629
630 JPanel contentPane = new JPanel(new BorderLayout());
631 dialog.setContentPane(contentPane);
632 JPanel mainPanel = new JPanel(new GridBagLayout());
633 contentPane.add(mainPanel, BorderLayout.NORTH);
634 GridBagConstraints gbc = new GridBagConstraints();
635 gbc.gridx = 0;
636 gbc.weighty = 1;
637 gbc.weightx = 1;
638 gbc.fill = GridBagConstraints.HORIZONTAL;
639
640 JLabel label = new JLabel(STR_OpenURLDialog_URLLabel);
641 gbc.insets = new Insets(10, 10, 0, 10);
642 mainPanel.add(label, gbc);
643
644 final JTextField field = new JTextField(40);
645 gbc.insets = new Insets(5, 10, 0, 10);
646 mainPanel.add(field, gbc);
647
648 JPanel buttonPanel = new JPanel(new FlowLayout());
649 gbc.insets = new Insets(5, 5, 5, 5);
650 mainPanel.add(buttonPanel, gbc);
651
652 JButton okButton = new JButton(STR_OpenURLDialog_OpenButton);
653 okButton.addActionListener(
654 new ActionListener()
655 {
656 public void actionPerformed(
657 ActionEvent ev)
658 {
659 String urlStr = field.getText();
660 dialog.dispose();
661 try
662 {
663 doOpenDatabase(new URL(urlStr), jobRunner_);
664 }
665 catch (MalformedURLException ex)
666 {
667 JOptionPane.showMessageDialog(dialogParent,
668 ResourceAccess.Strings.buildString("message_MalformedURL", urlStr),
669 STR_dialog_ErrorTitle, JOptionPane.ERROR_MESSAGE);
670 }
671 }
672 });
673 buttonPanel.add(okButton);
674
675 JButton cancelButton = new JButton(STR_OpenURLDialog_CancelButton);
676 cancelButton.addActionListener(
677 new ActionListener()
678 {
679 public void actionPerformed(
680 ActionEvent ev)
681 {
682 dialog.dispose();
683 }
684 });
685 buttonPanel.add(cancelButton);
686
687 dialog.pack();
688 dialog.setLocationRelativeTo(dialogParent);
689 dialog.show();
690 }
691
692
693 private AsyncJobExecution doOpenDatabase(
694 Object key,
695 AsyncJobRunner runner)
696 {
697 SongDatabase existing = (SongDatabase)databasesByKey_.get(key);
698 if (existing != null)
699 {
700 return doOpenExisting(existing, runner);
701 }
702 if (key instanceof File)
703 {
704 File file = (File)key;
705 if (file.isDirectory())
706 {
707 return doOpenIndexed(file, runner);
708 }
709 else if (file.isFile())
710 {
711 return doOpenSimple(file, runner);
712 }
713 else
714 {
715 logger_.warning("Unable to find file " + file);
716 return null;
717 }
718 }
719 else
720 {
721 URL url = (URL)key;
722 if (url.toExternalForm().endsWith(SIMPLE_DATABASE_SUFFIX))
723 {
724 return doOpenSimple(url, runner);
725 }
726 else
727 {
728 return doOpenIndexed(url, runner);
729 }
730 }
731 }
732
733
734 private AsyncJobExecution doOpenExisting(
735 final SongDatabase existing,
736 AsyncJobRunner runner)
737 {
738 return runner.startJob(
739 new AbstractAsyncJob(STR_OpenDatabaseAsyncJobName, false, AsyncJob.INDETERMINATE_PROGRESS, null)
740 {
741 public Object run(
742 AsyncJobProgressReporter reporter)
743 throws
744 AsyncJobException
745 {
746 SwingUtilities.invokeLater(
747 new Runnable()
748 {
749 public void run()
750 {
751 selectionManager_.selectDatabase(existing);
752 }
753 });
754 return existing;
755 }
756 });
757 }
758
759
760 private AsyncJobExecution doOpenSimple(
761 final Object key,
762 AsyncJobRunner runner)
763 {
764 return runner.startJob(
765 new AbstractAsyncJob(STR_OpenDatabaseAsyncJobName, false, AsyncJob.INDETERMINATE_PROGRESS, null)
766 {
767 public Object run(
768 AsyncJobProgressReporter reporter)
769 throws
770 AsyncJobException
771 {
772 Reader reader = null;
773 MessageCollectingErrorHandler errorHandler = new MessageCollectingErrorHandler(10, 20);
774 try
775 {
776 InputStream stream;
777 if (key instanceof File)
778 {
779 stream = new FileInputStream((File)key);
780 }
781 else
782 {
783 stream = ((URL)key).openStream();
784 }
785 reader = FileUtils.createUTFReader(stream);
786 final SongDatabase database = modelFactory_.parseSimpleSongDatabase(
787 reader, errorHandler, true);
788 if (errorHandler.hasErrors())
789 {
790 throw new AsyncJobException(errorHandler.toString());
791 }
792 SwingUtilities.invokeLater(
793 new Runnable()
794 {
795 public void run()
796 {
797 DatabaseInfo info;
798 if (key instanceof File)
799 {
800 info = new DatabaseInfo(database, (File)key);
801 }
802 else
803 {
804 info = new DatabaseInfo(database, (URL)key);
805 }
806 databaseInfo_.put(database, info);
807 info.setDatabaseController(databaseManager_.openDatabase(
808 database, info.getName(), fileIcon_,
809 info.getDatabaseIdentifier(), info));
810 }
811 });
812 }
813 catch (IOException ex)
814 {
815 throw new AsyncJobException(STR_message_CantOpenDatabase, ex);
816 }
817 catch (SAXParseException ex)
818 {
819 try
820 {
821 errorHandler.error(ex);
822 }
823 catch (SAXException ex2)
824 {
825 }
826 throw new AsyncJobException(errorHandler.toString());
827 }
828 catch (SAXException ex)
829 {
830 throw new AsyncJobException(ex);
831 }
832 finally
833 {
834 if (reader != null)
835 {
836 try
837 {
838 reader.close();
839 }
840 catch (IOException ex)
841 {
842 }
843 }
844 }
845 return null;
846 }
847 });
848 }
849
850
851 private AsyncJobExecution doOpenIndexed(
852 final Object key,
853 AsyncJobRunner runner)
854 {
855 return runner.startJob(
856 new AbstractAsyncJob(STR_OpenDatabaseAsyncJobName, false, AsyncJob.INDETERMINATE_PROGRESS, null)
857 {
858 public Object run(
859 AsyncJobProgressReporter reporter)
860 throws
861 AsyncJobException
862 {
863 SongDatabase openedDatabase;
864 if (key instanceof File)
865 {
866 try
867 {
868 indexedDatabaseFactory_.updateDatabase04((File)key);
869 openedDatabase = indexedDatabaseFactory_.openIndexedDatabase((File)key);
870 }
871 catch (IOException ex)
872 {
873 throw new AsyncJobException(STR_message_CantOpenDatabase, ex);
874 }
875 catch (SAXException ex)
876 {
877 throw new AsyncJobException(STR_message_CantOpenDatabase, ex);
878 }
879 }
880 else
881 {
882 MessageCollectingErrorHandler errorHandler = new MessageCollectingErrorHandler(10, 20);
883 try
884 {
885 openedDatabase = indexedDatabaseFactory_.openIndexedDatabase((URL)key, errorHandler);
886 }
887 catch (IOException ex)
888 {
889 throw new AsyncJobException(STR_message_CantOpenDatabase, ex);
890 }
891 catch (SAXParseException ex)
892 {
893 try
894 {
895 errorHandler.error(ex);
896 }
897 catch (SAXException ex2)
898 {
899 }
900 throw new AsyncJobException(errorHandler.toString());
901 }
902 catch (SAXException ex)
903 {
904 throw new AsyncJobException(ex);
905 }
906 }
907 final SongDatabase database = openedDatabase;
908 SwingUtilities.invokeLater(
909 new Runnable()
910 {
911 public void run()
912 {
913 DatabaseInfo info;
914 if (key instanceof File)
915 {
916 info = new DatabaseInfo(database, (File)key);
917 }
918 else
919 {
920 info = new DatabaseInfo(database, (URL)key);
921 }
922 databaseInfo_.put(database, info);
923 info.setDatabaseController(databaseManager_.openDatabase(
924 database, info.getName(), directoryIcon_,
925 info.getDatabaseIdentifier(), info));
926 }
927 });
928 return null;
929 }
930 });
931 }
932
933
934 private void doSaveDatabase()
935 {
936 DatabaseInfo info = (DatabaseInfo)databaseInfo_.get(lastSelected_);
937 info.doSave();
938 }
939
940
941 private void doSaveDatabaseAs()
942 {
943 DatabaseInfo info = (DatabaseInfo)databaseInfo_.get(lastSelected_);
944 info.doSaveAs();
945 }
946
947
948 /**
949 * Database handler class.
950 */
951 /*package*/ class DatabaseInfo
952 implements DatabaseHandler, PaneHandler
953 {
954 private SongDatabase database_;
955 private boolean isDirty_;
956 private String name_;
957 private String defaultName_;
958 private File filePath_;
959 private URL url_;
960 private boolean monolithic_;
961 private DatabaseController databaseController_;
962
963 private PaneController paneController_;
964 private JTextArea fileField_;
965 private JTextArea uriField_;
966 private JLabel savedLabel_;
967
968
969 /*package*/ DatabaseInfo(
970 SongDatabase database,
971 File f)
972 {
973 this(database);
974 if (monolithic_ && f.getPath().endsWith(SIMPLE_DATABASE_SUFFIX_OLD))
975 {
976 f = new File(f.getPath()+SIMPLE_DATABASE_SUFFIX);
977 isDirty_ = true;
978 }
979 else
980 {
981 isDirty_ = false;
982 }
983 setKey(f);
984 defaultName_ = name_;
985 }
986
987 /*package*/ DatabaseInfo(
988 SongDatabase database,
989 URL url)
990 {
991 this(database);
992 setKey(url);
993 defaultName_ = name_;
994 isDirty_ = false;
995 }
996
997 /*package*/ DatabaseInfo(
998 SongDatabase database,
999 String n)
1000 {
1001 this(database);
1002 name_ = n;
1003 filePath_ = null;
1004 url_ = null;
1005 defaultName_ = name_;
1006 isDirty_ = true;
1007 }
1008
1009 private DatabaseInfo(
1010 SongDatabase database)
1011 {
1012 database_ = database;
1013 monolithic_ = modelFactory_.isSimpleSongDatabase(database_);
1014 databaseController_ = null;
1015 paneController_ = null;
1016 fileField_ = null;
1017 uriField_ = null;
1018 savedLabel_ = null;
1019 }
1020
1021
1022 /*package*/ void setDatabaseController(
1023 DatabaseController databaseController)
1024 {
1025 databaseController_ = databaseController;
1026 }
1027
1028
1029 /*package*/ boolean isDirty()
1030 {
1031 return isDirty_;
1032 }
1033
1034
1035 /*package*/ String getName()
1036 {
1037 return name_;
1038 }
1039
1040
1041 /*package*/ Object getKey()
1042 {
1043 if (filePath_ != null)
1044 {
1045 return filePath_;
1046 }
1047 else if (url_ != null)
1048 {
1049 return url_;
1050 }
1051 else
1052 {
1053 return null;
1054 }
1055 }
1056
1057
1058 /*package*/ void setKey(
1059 Object key)
1060 {
1061 Object oldKey = getKey();
1062 if (oldKey != null)
1063 {
1064 databasesByKey_.remove(oldKey);
1065 }
1066 if (key == null)
1067 {
1068 filePath_ = null;
1069 url_ = null;
1070 name_ = defaultName_;
1071 }
1072 else if (key instanceof File)
1073 {
1074 filePath_ = (File)key;
1075 url_ = null;
1076 name_ = filePath_.getName();
1077 databasesByKey_.put(key, database_);
1078 }
1079 else if (key instanceof URL)
1080 {
1081 url_ = (URL)key;
1082 filePath_ = null;
1083 name_ = url_.toExternalForm();
1084 databasesByKey_.put(key, database_);
1085 }
1086 else
1087 {
1088 assert false;
1089 }
1090 }
1091
1092
1093 /*package*/ String getLocationString()
1094 {
1095 if (filePath_ != null)
1096 {
1097 return filePath_.getPath();
1098 }
1099 if (url_ != null)
1100 {
1101 return url_.toExternalForm();
1102 }
1103 return "";
1104 }
1105
1106
1107 /*package*/ String getURIString()
1108 {
1109 DatabaseIdentifier ident = getDatabaseIdentifier();
1110 if (ident != null)
1111 {
1112 try
1113 {
1114 return ident.toURI().toString();
1115 }
1116 catch (URISyntaxException ex)
1117 {
1118 // Unexpected
1119 logger_.log(Level.WARNING, "Unexpected URISyntaxException", ex);
1120 }
1121 }
1122 return "";
1123 }
1124
1125
1126 /*package*/ DatabaseIdentifier getDatabaseIdentifier()
1127 {
1128 if (filePath_ != null)
1129 {
1130 return new DatabaseIdentifier(CONNECTOR_NAME, filePath_.getPath());
1131 }
1132 else if (url_ != null)
1133 {
1134 return new DatabaseIdentifier(CONNECTOR_NAME, url_.toExternalForm());
1135 }
1136 return null;
1137 }
1138
1139
1140 /*package*/ void doSave()
1141 {
1142 if (filePath_ == null)
1143 {
1144 doSaveAs();
1145 }
1146 else
1147 {
1148 handleSave(getKey());
1149 }
1150 }
1151
1152
1153 /*package*/ void doSaveAs()
1154 {
1155 Component dialogParent = guiEnvironmentManager_.getDialogParent();
1156 JFileChooser chooser = new JFileChooser();
1157 if (filePath_ != null)
1158 {
1159 chooser.setCurrentDirectory(filePath_.getParentFile());
1160 chooser.setSelectedFile(filePath_);
1161 }
1162 else
1163 {
1164 chooser.setCurrentDirectory(guiEnvironmentManager_.getFileChooserDirectory());
1165 }
1166 int returnVal = chooser.showSaveDialog(dialogParent);
1167 guiEnvironmentManager_.setFileChooserDirectory(chooser.getCurrentDirectory());
1168 if (returnVal == JFileChooser.APPROVE_OPTION)
1169 {
1170 File file = chooser.getSelectedFile();
1171 try
1172 {
1173 file = file.getCanonicalFile();
1174 String path = file.getPath();
1175 if (!path.endsWith(SIMPLE_DATABASE_SUFFIX))
1176 {
1177 file = new File(path + SIMPLE_DATABASE_SUFFIX);
1178 }
1179 Object oldKey = getKey();
1180 setKey(file);
1181 if (paneController_ != null)
1182 {
1183 paneController_.putValue(PaneController.TITLE_KEY, name_);
1184 fileField_.setText(getLocationString());
1185 uriField_.setText(getURIString());
1186 }
1187 handleSave(oldKey);
1188 }
1189 catch (IOException ex)
1190 {
1191 DetailedMessageDialog dialog = DetailedMessageDialog.create(
1192 dialogParent, STR_dialog_ErrorTitle,
1193 ResourceAccess.Strings.buildString("message_CantFindFile", file.getPath()),
1194 STR_dialog_ErrorHeader, ExceptionUtils.getDescriptionFor(ex,
1195 STR_dialog_FirstExceptionTemplate, STR_dialog_NextExceptionTemplate),
1196 null);
1197 dialog.show();
1198 }
1199 }
1200 }
1201
1202
1203 private AsyncJobExecution handleSave(
1204 final Object oldKey)
1205 {
1206 return jobRunner_.startJob(
1207 new AbstractAsyncJob(STR_SaveDatabaseAsyncJobName, false, AsyncJob.INDETERMINATE_PROGRESS, null)
1208 {
1209 public Object run(
1210 AsyncJobProgressReporter reporter)
1211 throws
1212 AsyncJobException
1213 {
1214 File bakFile = new File(filePath_.getPath()+BACKUP_SUFFIX);
1215 for (int i=0; bakFile.exists(); ++i)
1216 {
1217 bakFile = new File(filePath_.getPath()+BACKUP_SUFFIX+i);
1218 }
1219 OutputStream stream = null;
1220 try
1221 {
1222 if (filePath_.isFile())
1223 {
1224 if (!filePath_.renameTo(bakFile))
1225 {
1226 throw new IOException(ResourceAccess.Strings.buildString(
1227 "err_CantCreateFile", bakFile.getPath()));
1228 }
1229 }
1230 stream = new FileOutputStream(filePath_);
1231 modelFactory_.unparseSimpleSongDatabase(database_, stream,
1232 STR_PreferredCharacterEncoding);
1233 bakFile.delete();
1234 SwingUtils.invokeOnSwingThread(
1235 new Runnable()
1236