Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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