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

Quick Search    Search Deep

Source code: com/virtuosotechnologies/asaph/xmldatabase/IndexedSongDatabase.java


1   /*
2   ================================================================================
3   
4     FILE:  IndexedSongDatabase.java
5     
6     PROJECT:
7     
8       Asaph
9     
10    CONTENTS:
11    
12      Indexed implementation of SongDatabase
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.io.Reader;
46  import java.io.FileInputStream;
47  import java.io.StringReader;
48  import java.io.OutputStream;
49  import java.io.FileOutputStream;
50  import java.io.File;
51  import java.io.FilenameFilter;
52  import java.io.IOException;
53  import java.io.FileNotFoundException;
54  import java.net.URL;
55  import java.net.MalformedURLException;
56  import java.util.Collections;
57  import java.util.List;
58  import java.util.ArrayList;
59  import java.util.Set;
60  import java.util.LinkedHashSet;
61  import java.util.Map;
62  import java.util.HashMap;
63  import java.util.LinkedHashMap;
64  import java.util.Iterator;
65  import java.util.Locale;
66  import java.util.logging.Logger;
67  import javax.xml.parsers.SAXParser;
68  import org.xml.sax.Attributes;
69  import org.xml.sax.SAXException;
70  import org.xml.sax.SAXParseException;
71  import org.xml.sax.ErrorHandler;
72  import org.xml.sax.InputSource;
73  import org.xml.sax.Locator;
74  import org.xml.sax.helpers.DefaultHandler;
75  
76  import com.virtuosotechnologies.lib.util.StringID;
77  import com.virtuosotechnologies.lib.util.FileUtils;
78  import com.virtuosotechnologies.lib.xml.XMLDocumentUnparser;
79  import com.virtuosotechnologies.lib.xml.ZeroToleranceErrorHandler;
80  import com.virtuosotechnologies.lib.xml.MessageCollectingErrorHandler;
81  
82  import com.virtuosotechnologies.asaph.model.SongDatabase;
83  import com.virtuosotechnologies.asaph.model.Song;
84  import com.virtuosotechnologies.asaph.model.SongID;
85  import com.virtuosotechnologies.asaph.model.SongOperation;
86  import com.virtuosotechnologies.asaph.model.SongIDResultSet;
87  import com.virtuosotechnologies.asaph.model.StringList;
88  import com.virtuosotechnologies.asaph.model.SimpleString;
89  import com.virtuosotechnologies.asaph.model.SongDatabaseFailedException;
90  import com.virtuosotechnologies.asaph.model.SongDeletedException;
91  import com.virtuosotechnologies.asaph.model.SongNotFreshException;
92  import com.virtuosotechnologies.asaph.model.DatabaseNotWritableException;
93  import com.virtuosotechnologies.asaph.model.opsemantics.GetFieldAsStringSemantics;
94  import com.virtuosotechnologies.asaph.model.opsemantics.GetFieldAsStringArraySemantics;
95  import com.virtuosotechnologies.asaph.model.opsemantics.SearchFieldPredicateSemantics;
96  import com.virtuosotechnologies.asaph.model.opsemantics.SearchParameters;
97  import com.virtuosotechnologies.asaph.modelutils.SongUtils;
98  import com.virtuosotechnologies.asaph.standardmodel.StandardModelFactory;
99  
100 
101 /**
102  * Indexed implementation of SongDatabase
103  */
104 /*package*/ class IndexedSongDatabase
105 implements SongDatabase
106 {
107   private static final String STR_err_CantFindIndex =
108     ResourceAccess.Strings.buildString("err_CantFindIndex");
109   private static final String STR_PreferredCharacterEncoding =
110     ResourceAccess.Strings.buildString("PreferredCharacterEncoding");
111   
112   
113   private StringID lastSongID_;
114   private Map songMap_;
115   private SongUtils songUtils_;
116   private StandardModelFactory modelFactory_;
117   private File fileDirectory_;
118   private URL urlDirectory_;
119   private Logger logger_;
120   
121   
122   /**
123    * Constructor for immutable database
124    */
125   /*package*/ IndexedSongDatabase(
126     SongUtils songUtils,
127     StandardModelFactory modelFactory,
128     URL urlDirectory,
129     SAXParser parser,
130     String dtdString,
131     ErrorHandler errorHandler)
132   throws
133     IOException,
134     SAXException
135   {
136     logger_ = Logger.getLogger("com.virtuosotechnologies.asaph.xmldatabase");
137     
138     lastSongID_ = new StringID();
139     songMap_ = new LinkedHashMap();
140     modelFactory_ = modelFactory;
141     songUtils_ = songUtils;
142     urlDirectory_ = urlDirectory;
143     
144     Reader reader = getReader(IndexConstants.INDEX_FILENAME);
145     if (reader == null)
146     {
147       throw new IOException(STR_err_CantFindIndex);
148     }
149     try
150     {
151       parser.parse(new InputSource(reader),
152         new IndexXMLHandler(dtdString, errorHandler));
153     }
154     finally
155     {
156       try
157       {
158         reader.close();
159       }
160       catch (IOException ex)
161       {
162       }
163     }
164   }
165   
166   
167   /**
168    * Constructor for mutable database
169    */
170   /*package*/ IndexedSongDatabase(
171     SongUtils songUtils,
172     StandardModelFactory modelFactory,
173     File fileDirectory,
174     SAXParser parser,
175     String dtdString)
176   throws
177     IOException
178   {
179     logger_ = Logger.getLogger("com.virtuosotechnologies.asaph.xmldatabase");
180     
181     lastSongID_ = new StringID();
182     songMap_ = new LinkedHashMap();
183     modelFactory_ = modelFactory;
184     songUtils_ = songUtils;
185     fileDirectory_ = fileDirectory;
186     
187     String[] contents = fileDirectory.list(
188       new FilenameFilter()
189       {
190         public boolean accept(
191           File dir,
192           String name)
193         {
194           return name.startsWith(IndexConstants.SONG_PREFIX) &&
195             name.endsWith(IndexConstants.SONG_SUFFIX);
196         }
197       });
198     
199     Map tempSongMap = new HashMap();
200     for (int i=0; i<contents.length; ++i)
201     {
202       String str = contents[i].substring(IndexConstants.SONG_PREFIX.length(),
203         contents[i].length()-IndexConstants.SONG_SUFFIX.length());
204       int separatorIndex = str.indexOf(IndexConstants.SONG_INTER);
205       if (separatorIndex == -1)
206       {
207         continue;
208       }
209       String idStr = str.substring(0, separatorIndex);
210       String versionStr = str.substring(separatorIndex+IndexConstants.SONG_INTER.length());
211       if (!StringID.isWellFormed(idStr) || !StringID.isWellFormed(versionStr))
212       {
213         continue;
214       }
215       StringID id = new StringID(idStr);
216       StringID version = new StringID(versionStr);
217       SongInfoCache info = (SongInfoCache)tempSongMap.get(id);
218       if (info != null)
219       {
220         if (version.compareTo(info.getVersion()) > 0)
221         {
222           info.setVersion(version);
223         }
224       }
225       else
226       {
227         info = new SongInfoCache(new SongIDImpl(id), version);
228         tempSongMap.put(id, info);
229       }
230       if (id.compareTo(lastSongID_) > 0)
231       {
232         lastSongID_ = id;
233       }
234     }
235     List idList = new ArrayList(tempSongMap.keySet());
236     Collections.sort(idList);
237     for (Iterator iter = idList.iterator(); iter.hasNext(); )
238     {
239       StringID id = (StringID)iter.next();
240       songMap_.put(id, tempSongMap.get(id));
241     }
242     
243     Reader reader = getReader(IndexConstants.INDEX_FILENAME);
244     if (reader != null)
245     {
246       try
247       {
248         parser.parse(new InputSource(reader),
249           new IndexXMLHandler(dtdString, new ZeroToleranceErrorHandler()));
250       }
251       catch (SAXException ex)
252       {
253         ex.printStackTrace();
254       }
255       catch (IOException ex)
256       {
257         ex.printStackTrace();
258       }
259       finally
260       {
261         try
262         {
263           reader.close();
264         }
265         catch (IOException ex)
266         {
267         }
268       }
269     }
270     else
271     {
272       commitIndex();
273     }
274   }
275   
276   
277   /**
278    * Implementation of SongID for this database
279    */
280   /*package*/ class SongIDImpl
281   implements SongID
282   {
283     private StringID id_;
284     
285     /*package*/ SongIDImpl(
286       StringID id)
287     {
288       id_ = id;
289     }
290     
291     /*package*/ StringID getStringID()
292     {
293       return id_;
294     }
295     
296     public SongDatabase getDatabase()
297     {
298       return IndexedSongDatabase.this;
299     }
300     
301     public String getStringRepresentation()
302     {
303       return id_.getValue();
304     }
305     
306     public boolean equals(
307       Object obj)
308     {
309       if (obj instanceof SongIDImpl)
310       {
311         SongIDImpl ssid = (SongIDImpl)obj;
312         return ssid.getDatabase() == IndexedSongDatabase.this &&
313           ssid.id_.equals(id_);
314       }
315       return false;
316     }
317     
318     public int hashCode()
319     {
320       return id_.hashCode() + IndexedSongDatabase.this.hashCode();
321     }
322   }
323   
324   
325   /**
326    * Cache of song info
327    */
328   /*package*/ class SongInfoCache
329   {
330     private SongIDImpl songID_;
331     private StringID version_;
332     private String mainTitle_;
333     private String comment_;
334     private String author_;
335     private String copyright_;
336     private String[] altTitles_;
337     private String[] keywords_;
338     
339     /*package*/ SongInfoCache(
340       SongIDImpl songID,
341       StringID version)
342     {
343       songID_ = songID;
344       version_ = version;
345       mainTitle_ = null;
346       comment_ = null;
347       author_ = null;
348       copyright_ = null;
349       altTitles_ = null;
350       keywords_ = null;
351     }
352     
353     /*package*/ StringID getVersion()
354     {
355       return version_;
356     }
357     
358     /*package*/ SongIDImpl getSongID()
359     {
360       return songID_;
361     }
362     
363     /*package*/ String getFileName()
364     {
365       return IndexConstants.SONG_PREFIX+songID_.getStringRepresentation()+
366         IndexConstants.SONG_INTER+version_.getValue()+IndexConstants.SONG_SUFFIX;
367     }
368     
369     /*package*/ String getMainTitle()
370     {
371       return mainTitle_;
372     }
373     
374     /*package*/ String forceGetMainTitle()
375     {
376       if (mainTitle_ == null)
377       {
378         updateNow();
379       }
380       return mainTitle_;
381     }
382     
383     /*package*/ String getComment()
384     {
385       return comment_;
386     }
387     
388     /*package*/ String forceGetComment()
389     {
390       if (comment_ == null)
391       {
392         updateNow();
393       }
394       return comment_;
395     }
396     
397     /*package*/ String getAuthor()
398     {
399       return author_;
400     }
401     
402     /*package*/ String forceGetAuthor()
403     {
404       if (author_ == null)
405       {
406         updateNow();
407       }
408       return author_;
409     }
410     
411     /*package*/ String getCopyright()
412     {
413       return copyright_;
414     }
415     
416     /*package*/ String forceGetCopyright()
417     {
418       if (copyright_ == null)
419       {
420         updateNow();
421       }
422       return copyright_;
423     }
424     
425     /*package*/ String[] getAltTitles()
426     {
427       return altTitles_;
428     }
429     
430     /*package*/ String[] forceGetAltTitles()
431     {
432       if (altTitles_ == null)
433       {
434         updateNow();
435       }
436       return altTitles_;
437     }
438     
439     /*package*/ String[] getKeywords()
440     {
441       return keywords_;
442     }
443     
444     /*package*/ String[] forceGetKeywords()
445     {
446       if (keywords_ == null)
447       {
448         updateNow();
449       }
450       return keywords_;
451     }
452     
453     
454     private void updateNow()
455     {
456       try
457       {
458         Song song = checkOutSong(songID_);
459         if (song != null)
460         {
461           updateFrom(song);
462         }
463       }
464       catch (SongDatabaseFailedException ex)
465       {
466       }
467     }
468     
469     
470     /*package*/ void updateFrom(
471       Song song)
472     {
473       mainTitle_ = songUtils_.getFieldValueAsString(song, Song.MAINTITLE_FIELD);
474       comment_ = songUtils_.getFieldValueAsString(song, Song.COMMENT_FIELD);
475       author_ = songUtils_.getFieldValueAsString(song, Song.AUTHOR_FIELD);
476       copyright_ = songUtils_.getFieldValueAsString(song, Song.COPYRIGHT_FIELD);
477       altTitles_ = songUtils_.getFieldValueAsStringArray(song, Song.ALTERNATETITLELIST_FIELD);
478       keywords_ = songUtils_.getFieldValueAsStringArray(song, Song.KEYWORDLIST_FIELD);
479     }
480     
481     /*package*/ void setVersion(
482       StringID version)
483     {
484       version_ = version;
485     }
486     
487     /*package*/ void clearMainTitle()
488     {
489       mainTitle_ = null;
490     }
491     
492     /*package*/ void setMainTitle(
493       String value)
494     {
495       mainTitle_ = value;
496     }
497     
498     /*package*/ void clearComment()
499     {
500       comment_ = null;
501     }
502     
503     /*package*/ void setComment(
504       String value)
505     {
506       comment_ = value;
507     }
508     
509     /*package*/ void clearAuthor()
510     {
511       author_ = null;
512     }
513     
514     /*package*/ void setAuthor(
515       String value)
516     {
517       author_ = value;
518     }
519     
520     /*package*/ void clearCopyright()
521     {
522       copyright_ = null;
523     }
524     
525     /*package*/ void setCopyright(
526       String value)
527     {
528       copyright_ = value;
529     }
530     
531     /*package*/ void clearAltTitles()
532     {
533       altTitles_ = null;
534     }
535     
536     /*package*/ void setAltTitles(
537       String[] array)
538     {
539       altTitles_ = array;
540     }
541     
542     /*package*/ void clearKeywords()
543     {
544       keywords_ = null;
545     }
546     
547     /*package*/ void setKeywords(
548       String[] array)
549     {
550       keywords_ = array;
551     }
552   }
553   
554   
555   /**
556    * Utility method
557    */
558   private Reader getReader(
559     String fileName)
560   {
561     Reader reader = null;
562     if (fileDirectory_ != null)
563     {
564       File file = new File(fileDirectory_, fileName);
565       if (file.isFile())
566       {
567         try
568         {
569           reader = FileUtils.createUTFReader(new FileInputStream(file));
570         }
571         catch (IOException ex)
572         {
573           // Unable to open stream; return null
574         }
575       }
576     }
577     else
578     {
579       try
580       {
581         URL indexURL = new URL(urlDirectory_, fileName);
582         try
583         {
584           reader = FileUtils.createUTFReader(indexURL.openStream());
585         }
586         catch (IOException ex)
587         {
588           // Unable to open stream; return null
589         }
590       }
591       catch (MalformedURLException ex)
592       {
593         // Unexpected
594         ex.printStackTrace();
595       }
596     }
597     return reader;
598   }
599   
600   
601   /**
602    * Utility method
603    */
604   private OutputStream getOutputStream(
605     String fileName)
606   {
607     OutputStream stream = null;
608     if (fileDirectory_ != null)
609     {
610       File file = new File(fileDirectory_, fileName);
611       if (!file.exists() || file.canWrite())
612       {
613         try
614         {
615           stream = new FileOutputStream(file);
616         }
617         catch (IOException ex)
618         {
619         }
620       }
621     }
622     return stream;
623   }
624   
625   
626   /**
627    * DefaultHandler for initial loading of the index file
628    */
629   /*package*/ class IndexXMLHandler
630   extends DefaultHandler
631   {
632     private ErrorHandler errorHandler_;
633     private Locator locator_;
634     private String dtdString_;
635     private SongInfoCache curSong_;
636     private String state_;
637     private StringBuffer buffer_;
638     private List list_;
639     
640     
641     /*package*/ IndexXMLHandler(
642       String dtdString,
643       ErrorHandler errorHandler)
644     {
645       dtdString_ = dtdString;
646       errorHandler_ = errorHandler;
647       if (errorHandler_ == null)
648       {
649         errorHandler_ = new ZeroToleranceErrorHandler();
650       }
651       curSong_ = null;
652       state_ = null;
653       buffer_ = new StringBuffer();
654       list_ = new ArrayList();
655     }
656     
657     
658     public void startElement(
659       String uri,
660       String localName,
661       String qName,
662       Attributes attributes)
663     throws
664       SAXException
665     {
666       if (localName.equals(IndexConstants.INDEX_ELEMENT))
667       {
668         String lastID = attributes.getValue(IndexConstants.INDEX_LASTID_ATTRIBUTE);
669         if (StringID.isWellFormed(lastID))
670         {
671           lastSongID_ = new StringID(lastID);
672         }
673       }
674       else if (localName.equals(IndexConstants.ENTRY_ELEMENT))
675       {
676         String idStr = attributes.getValue(IndexConstants.ENTRY_ID_ATTRIBUTE);
677         if (idStr == null)
678         {
679           errorHandler_.fatalError(new SAXParseException(
680             ResourceAccess.Strings.buildString("err_MissingAttribute",
681               IndexConstants.ENTRY_ID_ATTRIBUTE, IndexConstants.ENTRY_ELEMENT),
682             locator_));
683         }
684         if (!StringID.isWellFormed(idStr))
685         {
686           errorHandler_.fatalError(new SAXParseException(
687             ResourceAccess.Strings.buildString("err_MalformedSongID", idStr),
688             locator_));
689         }
690         StringID id = new StringID(idStr);
691         String versionStr = attributes.getValue(IndexConstants.ENTRY_VERSION_ATTRIBUTE);
692         if (versionStr == null)
693         {
694           errorHandler_.fatalError(new SAXParseException(
695             ResourceAccess.Strings.buildString("err_MissingAttribute",
696               IndexConstants.ENTRY_VERSION_ATTRIBUTE, IndexConstants.ENTRY_ELEMENT),
697             locator_));
698         }
699         if (!StringID.isWellFormed(versionStr))
700         {
701           errorHandler_.fatalError(new SAXParseException(
702             ResourceAccess.Strings.buildString("err_MalformedSongVersion", versionStr),
703             locator_));
704         }
705         StringID version = new StringID(versionStr);
706         if (fileDirectory_ != null)
707         {
708           curSong_ = (SongInfoCache)songMap_.get(id);
709           if (!curSong_.getVersion().equals(version))
710           {
711             curSong_ = null;
712           }
713         }
714         else
715         {
716           curSong_ = new SongInfoCache(new SongIDImpl(id), version);
717           songMap_.put(id, curSong_);
718           if (id.compareTo(lastSongID_) > 0)
719           {
720             lastSongID_ = id;
721           }
722         }
723         state_ = null;
724       }
725       else if (localName.equals(IndexConstants.MAINTITLE_ELEMENT))
726       {
727         state_ = IndexConstants.MAINTITLE_ELEMENT;
728         buffer_ = new StringBuffer();
729       }
730       else if (localName.equals(IndexConstants.COMMENT_ELEMENT))
731       {
732         state_ = IndexConstants.COMMENT_ELEMENT;
733         buffer_ = new StringBuffer();
734       }
735       else if (localName.equals(IndexConstants.AUTHOR_ELEMENT))
736       {
737         state_ = IndexConstants.AUTHOR_ELEMENT;
738         buffer_ = new StringBuffer();
739       }
740       else if (localName.equals(IndexConstants.COPYRIGHT_ELEMENT))
741       {
742         state_ = IndexConstants.COPYRIGHT_ELEMENT;
743         buffer_ = new StringBuffer();
744       }
745       else if (localName.equals(IndexConstants.ALTTITLES_ELEMENT))
746       {
747         state_ = IndexConstants.ALTTITLES_ELEMENT;
748         list_ = new ArrayList();
749       }
750       else if (localName.equals(IndexConstants.KEYWORDS_ELEMENT))
751       {
752         state_ = IndexConstants.KEYWORDS_ELEMENT;
753         list_ = new ArrayList();
754       }
755       else if (localName.equals(IndexConstants.STRING_ELEMENT))
756       {
757         buffer_ = new StringBuffer();
758       }
759     }
760     
761     
762     public void endElement(
763       String uri,
764       String localName,
765       String qName)
766     throws
767       SAXException
768     {
769       if (localName.equals(IndexConstants.MAINTITLE_ELEMENT))
770       {
771         state_ = null;
772         if (curSong_ != null)
773         {
774           curSong_.setMainTitle(new String(buffer_));
775         }
776       }
777       else if (localName.equals(IndexConstants.COMMENT_ELEMENT))
778       {
779         state_ = null;
780         if (curSong_ != null)
781         {
782           curSong_.setComment(new String(buffer_));
783         }
784       }
785       else if (localName.equals(IndexConstants.AUTHOR_ELEMENT))
786       {
787         state_ = null;
788         if (curSong_ != null)
789         {
790           curSong_.setAuthor(new String(buffer_));
791         }
792       }
793       else if (localName.equals(IndexConstants.COPYRIGHT_ELEMENT))
794       {
795         state_ = null;
796         if (curSong_ != null)
797         {
798           curSong_.setCopyright(new String(buffer_));
799         }
800       }
801       else if (localName.equals(IndexConstants.ALTTITLES_ELEMENT))
802       {
803         state_ = null;
804         if (curSong_ != null)
805         {
806           String[] array = new String[list_.size()];
807           list_.toArray(array);
808           curSong_.setAltTitles(array);
809         }
810       }
811       else if (localName.equals(IndexConstants.KEYWORDS_ELEMENT))
812       {
813         state_ = null;
814         if (curSong_ != null)
815         {
816           String[] array = new String[list_.size()];
817           list_.toArray(array);
818           curSong_.setKeywords(array);
819         }
820       }
821       else if (localName.equals(IndexConstants.STRING_ELEMENT))
822       {
823         if (curSong_ != null)
824         {
825           list_.add(new String(buffer_));
826         }
827       }
828     }
829     
830     
831     public void characters(
832       char[] chs,
833       int start,
834       int length)
835     throws
836       SAXException
837     {
838       buffer_.append(chs, start, length);
839     }
840     
841     
842     public InputSource resolveEntity(
843       String publicId,
844       String systemId)
845     throws
846       SAXException
847     {
848       if (systemId.equals(IndexConstants.XMLDATABASEINDEX_DTD) ||
849         systemId.equals(IndexConstants.XMLDATABASEINDEX_DTD_ALT))
850       {
851         return new InputSource(new StringReader(dtdString_));
852       }
853       else
854       {
855         return null;
856       }
857     }
858     
859     
860     public void error(
861       SAXParseException ex)
862     throws
863       SAXException
864     {
865       errorHandler_.error(ex);
866     }
867     
868     
869     public void fatalError(
870       SAXParseException ex)
871     throws
872       SAXException
873     {
874       errorHandler_.fatalError(ex);
875     }
876     
877     
878     public void warning(
879       SAXParseException ex)
880     throws
881       SAXException
882     {
883       errorHandler_.warning(ex);
884     }
885     
886     
887     public void setDocumentLocator(
888       Locator locator)
889     {
890       locator_ = locator;
891     }
892   }
893   
894   
895   private boolean commitIndex()
896   {
897     if (!isWritable())
898     {
899       return false;
900     }
901     File bakFile = new File(fileDirectory_, IndexConstants.INDEX_BAK_FILENAME);
902     File indexFile = new File(fileDirectory_, IndexConstants.INDEX_FILENAME);
903     bakFile.delete();
904     if (indexFile.exists())
905     {
906       if (!indexFile.renameTo(bakFile))
907       {
908         return false;
909       }
910     }
911     
912     OutputStream stream = getOutputStream(IndexConstants.INDEX_FILENAME);
913     if (stream == null)
914     {
915       bakFile.renameTo(indexFile);
916       return false;
917     }
918     boolean success = false;
919     try
920     {
921       writeIndex(stream);
922       success = true;
923     }
924     catch (IOException ex)
925     {
926     }
927     finally
928     {
929       try
930       {
931         stream.close();
932       }
933       catch (IOException ex)
934       {
935       }
936     }
937     if (success)
938     {
939       bakFile.delete();
940       return true;
941     }
942     else
943     {
944       indexFile.delete();
945       bakFile.renameTo(indexFile);
946       return false;
947     }
948   }
949   
950   
951   private void writeIndex(
952     OutputStream stream)
953   throws
954     IOException
955   {
956     XMLDocumentUnparser unparser = XMLDocumentUnparser.create(stream, STR_PreferredCharacterEncoding,
957       IndexConstants.XMLDATABASEINDEX_DTD, IndexConstants.INDEXROOT_ELEMENT);
958     unparser.addAttribute("xmlns", IndexConstants.XMLDATABASEINDEX_NAMESPACE);
959     
960     unparser.startMultiLineElement(IndexConstants.INDEX_ELEMENT);
961     unparser.addAttribute(IndexConstants.INDEX_LASTID_ATTRIBUTE, lastSongID_.getValue());
962     
963     for (Iterator iter = songMap_.entrySet().iterator(); iter.hasNext(); )
964     {
965       Map.Entry entry = (Map.Entry)iter.next();
966       SongInfoCache info = (SongInfoCache)entry.getValue();
967       unparser.startMultiLineElement(IndexConstants.ENTRY_ELEMENT);
968       unparser.addAttribute(IndexConstants.ENTRY_ID_ATTRIBUTE,
969         info.getSongID().getStringRepresentation());
970       unparser.addAttribute(IndexConstants.ENTRY_VERSION_ATTRIBUTE, info.getVersion().getValue());
971       
972       String mainTitle = info.getMainTitle();
973       if (mainTitle != null)
974       {
975         unparser.startSingleLineElement(IndexConstants.MAINTITLE_ELEMENT);
976         unparser.addString(mainTitle);
977         unparser.endElement(IndexConstants.MAINTITLE_ELEMENT);
978       }
979       
980       String comment = info.getComment();
981       if (comment != null)
982       {
983         unparser.startSingleLineElement(IndexConstants.COMMENT_ELEMENT);
984         unparser.addString(comment);
985         unparser.endElement(IndexConstants.COMMENT_ELEMENT);
986       }
987       
988       String author = info.getAuthor();
989       if (author != null)
990       {
991         unparser.startSingleLineElement(IndexConstants.AUTHOR_ELEMENT);
992         unparser.addString(author);
993         unparser.endElement(IndexConstants.AUTHOR_ELEMENT);
994       }
995       
996       /*
997       String copyright = info.getCopyright();
998       if (copyright != null)
999       {
1000        unparser.startSingleLineElement(IndexConstants.COPYRIGHT_ELEMENT);
1001        unparser.addString(copyright);
1002        unparser.endElement(IndexConstants.COPYRIGHT_ELEMENT);
1003      }
1004      */
1005      
1006      String[] altTitles = info.getAltTitles();
1007      if (altTitles != null)
1008      {
1009        unparser.startMultiLineElement(IndexConstants.ALTTITLES_ELEMENT);
1010        for (int i=0; i<altTitles.length; ++i)
1011        {
1012          unparser.startSingleLineElement(IndexConstants.STRING_ELEMENT);
1013          unparser.addString(altTitles[i]);
1014          unparser.endElement(IndexConstants.STRING_ELEMENT);
1015        }
1016        unparser.endElement(IndexConstants.ALTTITLES_ELEMENT);
1017      }
1018      
1019      /*
1020      String[] keywords = info.getKeywords();
1021      if (keywords != null)
1022      {
1023        unparser.startMultiLineElement(IndexConstants.KEYWORDS_ELEMENT);
1024        for (int i=0; i<keywords.length; ++i)
1025        {
1026          unparser.startSingleLineElement(IndexConstants.STRING_ELEMENT);
1027          unparser.addString(keywords[i]);
1028          unparser.endElement(IndexConstants.STRING_ELEMENT);
1029        }
1030        unparser.endElement(IndexConstants.KEYWORDS_ELEMENT);
1031      }
1032      */
1033      
1034      unparser.endElement(IndexConstants.ENTRY_ELEMENT);
1035    }
1036    
1037    unparser.endElement(IndexConstants.INDEX_ELEMENT);
1038    
1039    unparser.finishDocument(IndexConstants.INDEXROOT_ELEMENT);
1040  }
1041  
1042  
1043  //-------------------------------------------------------------------------
1044  // Methods of SongDatabase
1045  //-------------------------------------------------------------------------
1046  
1047  /**
1048   * Returns true if the database is writable. If this returns false, every mutation
1049   * method will throw DatabaseNotWritableException. Note that even if this returns
1050   * false, the database may still be changed by other entities. In other words,
1051   * this method reflects the ability to write to the database through this interface,
1052   * not necessarily through all interfaces.
1053   *
1054   * @return true if the database is writable, false if not.
1055   */
1056  public boolean isWritable()
1057  {
1058    return fileDirectory_ != null;
1059  }
1060  
1061  
1062  /**
1063   * Get a song by SongID. Note that the Song returned is a copy. Changes
1064   * do not get reflected in the database until commitSong() succeeds.
1065   * Returns null if the song doesn't exist in this database. (This could
1066   * mean the song was deleted, or it is part of a different database.)
1067   *
1068   * @param id SongID
1069   * @return the Song, or null if the id doesn't exist in this database.
1070   * @exception SongDatabaseFailedException Catch-all exception for database-related
1071   *     problems. This will often have a cause exception, which may be exceptions
1072   *     like IOException or SQLException.
1073   * @exception NullPointerException id was null
1074   */
1075  public synchronized Song checkOutSong(
1076    SongID id)
1077  throws
1078    SongDatabaseFailedException
1079  {
1080    if (id.getDatabase() != this)
1081    {
1082      return null;
1083    }
1084    StringID stringId = ((SongIDImpl)id).getStringID();
1085    SongInfoCache info = (SongInfoCache)songMap_.get(stringId);
1086    if (info == null)
1087    {
1088      return null;
1089    }
1090    
1091    Reader reader = getReader(info.getFileName());
1092    if (reader == null)
1093    {
1094      songMap_.remove(stringId);
1095      commitIndex();
1096      return null;
1097    }
1098    
1099    MessageCollectingErrorHandler errorHandler = new MessageCollectingErrorHandler(10, 20);
1100    try
1101    {
1102      Song song = modelFactory_.parseSong(reader, id, errorHandler);
1103      info.updateFrom(song);
1104      return song;
1105    }
1106    catch (IOException ex)
1107    {
1108      throw new SongDatabaseFailedException(ex);
1109    }
1110    catch (SAXParseException ex)
1111    {
1112      try
1113      {
1114        errorHandler.error(ex);
1115      }
1116      catch (SAXException ex2)
1117      {
1118      }
1119      throw new SongDatabaseFailedException(errorHandler.toString());
1120    }
1121    catch (SAXException ex)
1122    {
1123      throw new SongDatabaseFailedException(ex);
1124    }
1125    finally
1126    {
1127      try
1128      {
1129        reader.close();
1130      }
1131      catch (IOException ex)
1132      {
1133      }
1134    }
1135  }
1136  
1137  
1138  /**
1139   * Tests whether the given song id exists in this database. Returns
1140   * false if the song was deleted or if it is part of a different database.
1141   *
1142   * @param id SongID to test
1143   * @return true if the given SongID exists in this database.
1144   * @exception SongDatabaseFailedException Catch-all exception for database-related
1145   *     problems. This will often have a cause exception, which may be exceptions
1146   *     like IOException or SQLException.
1147   * @exception NullPointerException id was null
1148   */
1149  public synchronized boolean containsSongID(
1150    SongID id)
1151  throws
1152    SongDatabaseFailedException
1153  {
1154    if (id.getDatabase() != this)
1155    {
1156      return false;
1157    }
1158    StringID stringId = ((SongIDImpl)id).getStringID();
1159    return songMap_.containsKey(stringId);
1160  }
1161  
1162  
1163  /**
1164   * Get the SongID for the given string, if it exists. This is used to
1165   * deserialize a SongID that was stored persistently as its string
1166   * representation. Returns null if the string does not correspond to a
1167   * SongID that exists in this database.
1168   *
1169   * @param idStr serialized ID
1170   * @return SongID
1171   * @exception SongDatabaseFailedException Catch-all exception for database-related
1172   *     problems. This will often have a cause exception, which may be exceptions
1173   *     like IOException or SQLException.
1174   */
1175  public synchronized SongID getSongIDForString(
1176    String idStr)
1177  throws
1178    SongDatabaseFailedException
1179  {
1180    if (!StringID.isWellFormed(idStr))
1181    {
1182      return null;
1183    }
1184    StringID id = new StringID(idStr);
1185    if (songMap_.containsKey(id))
1186    {
1187      return new SongIDImpl(id);
1188    }
1189    else
1190    {
1191      return null;
1192    }
1193  }
1194  
1195  
1196  /**
1197   * Create an empty result set.
1198   *
1199   * @return empty SongIDResultSet
1200   */
1201  public SongIDResultSet createEmptyResultSet()
1202  {
1203    return modelFactory_.createSongIDResultSet(this);
1204  }
1205  
1206  
1207  /**
1208   * Perform an operation on a set of songs. Operations could be accessors for
1209   * information that could be cached by the database, such as the main title, or they
1210   * could be filters applied to the result set, or they could have other semantics
1211   * such as mass-checkouts or mass-checkins.
1212   * The client specifies as the parameter a SongIDResultSet specifying the songs and
1213   * parameter data for the operation. If null is passed, the database creates a new
1214   * SongIDResultSet whose values are all the SongIDs in the database with null data
1215   * for each one.
1216   * The client also specifies the operation to perform.
1217   * The method performs the operation on the result set in place, and then returns it.
1218   * <p>
1219   * Note that some SongDatabase implementations may choose to optimize this method
1220   * by not calling the operation directly, but by analzying the semantics of the
1221   * operation as declared by which operation semantics interfaces are implemented, and
1222   * performing accelerated operations such as checking caches. Thus, do not expect that
1223   * the SongOperation object you pass will actually be invoked.
1224   * <p>
1225   * One common use of this method is to get a result set containing all the SongIDs
1226   * in the database. This is accomplished by passing null for the parameter, and
1227   * a NopSemantics implementation for the operation.
1228   *
1229   * @param operation SongOperation to perform
1230   * @param param input result set
1231   * @return output result set
1232   * @exception SongDatabaseFailedException Catch-all exception for database-related
1233   *     problems. This will often have a cause exception, which may be exceptions
1234   *     like IOException or SQLException.
1235   */
1236  public SongIDResultSet performOperation(
1237    SongOperation operation,
1238    SongIDResultSet param)
1239  throws
1240    SongDatabaseFailedException
1241  {
1242    if (param == null)
1243    {
1244      param = modelFactory_.createSongIDResultSet(this);
1245      synchronized(this)
1246      {
1247        for (Iterator iter = songMap_.keySet().iterator(); iter.hasNext(); )
1248        {
1249          StringID id = (StringID)iter.next();
1250          param.add(new SongIDImpl(id));
1251        }
1252      }
1253    }
1254    
1255    if (operation instanceof GetFieldAsStringSemantics)
1256    {
1257      GetFieldAsStringSemantics getFieldOp = (GetFieldAsStringSemantics)operation;
1258      if (getFieldOp.getFieldName().equals(Song.MAINTITLE_FIELD))
1259      {
1260        performGetField(param, Song.MAINTITLE_FIELD);
1261        return param;
1262      }
1263      else if (getFieldOp.getFieldName().equals(Song.COMMENT_FIELD))
1264      {
1265        performGetField(param, Song.COMMENT_FIELD);
1266        return param;
1267      }
1268      else if (getFieldOp.getFieldName().equals(Song.AUTHOR_FIELD))
1269      {
1270        performGetField(param, Song.AUTHOR_FIELD);
1271        return param;
1272      }
1273      else if (getFieldOp.getFieldName().equals(Song.COPYRIGHT_FIELD))
1274      {
1275        performGetField(param, Song.COPYRIGHT_FIELD);
1276        return param;
1277      }
1278    }
1279    else if (operation instanceof GetFieldAsStringArraySemantics)
1280    {
1281      GetFieldAsStringArraySemantics getFieldOp = (GetFieldAsStringArraySemantics)operation;
1282      if (getFieldOp.getFieldName().equals(Song.ALTERNATETITLELIST_FIELD))
1283      {
1284        performGetField(param, Song.ALTERNATETITLELIST_FIELD);
1285        return param;
1286      }
1287      else if (getFieldOp.getFieldName().equals(Song.KEYWORDLIST_FIELD))
1288      {
1289        performGetField(param, Song.KEYWORDLIST_FIELD);
1290        return param;
1291      }
1292    }
1293    else if (operation instanceof SearchFieldPredicateSemantics)
1294    {
1295      SearchFieldPredicateSemantics searchFieldOp = (SearchFieldPredicateSemantics)operation;
1296      String fieldName = searchFieldOp.getFieldName();
1297      if (fieldName.equals(Song.MAINTITLE_FIELD))
1298      {
1299        performSearchField(param, Song.MAINTITLE_FIELD, searchFieldOp.getSearchParameters());
1300        return param;
1301      }
1302      else if (fieldName.equals(Song.COMMENT_FIELD))
1303      {
1304        performSearchField(param, Song.COMMENT_FIELD, searchFieldOp.getSearchParameters());
1305        return param;
1306      }
1307      else if (fieldName.equals(Song.AUTHOR_FIELD))
1308      {
1309        performSearchField(param, Song.AUTHOR_FIELD, searchFieldOp.getSearchParameters());
1310        return param;
1311      }
1312      else if (fieldName.equals(Song.COPYRIGHT_FIELD))
1313      {
1314        performSearchField(param, Song.COPYRIGHT_FIELD, searchFieldOp.getSearchParameters());
1315        return param;
1316      }
1317      else if (fieldName.equals(Song.ALTERNATETITLELIST_FIELD))
1318      {
1319        performSearchField(param, Song.ALTERNATETITLELIST_FIELD, searchFieldOp.getSearchParameters());
1320        return param;
1321      }
1322      else if (fieldName.equals(Song.KEYWORDLIST_FIELD))
1323      {
1324        performSearchField(param, Song.KEYWORDLIST_FIELD, searchFieldOp.getSearchParameters());
1325        return param;
1326      }
1327    }
1328    
1329    operation.perform(param);
1330    return param;
1331  }
1332  
1333  
1334  private void performGetField(
1335    SongIDResultSet resultSet,
1336    String fieldName)
1337  {
1338    for (Iterator iter = resultSet.getEntryCollection().iterator(); iter.hasNext(); )
1339    {
1340      SongIDResultSet.Entry entry = (SongIDResultSet.Entry)iter.next();
1341      SongIDImpl id = (SongIDImpl)entry.getSongID();
1342      SongInfoCache info = (SongInfoCache)songMap_.get(id.getStringID());
1343      entry.setData(null);
1344      if (info != null)
1345      {
1346        if (fieldName == Song.MAINTITLE_FIELD)
1347        {
1348          entry.setData(info.forceGetMainTitle());
1349        }
1350        else if (fieldName == Song.COMMENT_FIELD)
1351        {
1352          entry.setData(info.forceGetComment());
1353        }
1354        else if (fieldName == Song.AUTHOR_FIELD)
1355        {
1356          entry.setData(info.forceGetAuthor());
1357        }
1358        else if (fieldName == Song.COPYRIGHT_FIELD)
1359        {
1360          entry.setData(info.forceGetCopyright());
1361        }
1362        else if (fieldName == Song.ALTERNATETITLELIST_FIELD)
1363        {
1364          entry.setData(info.forceGetAltTitles());
1365        }
1366        else if (fieldName == Song.KEYWORDLIST_FIELD)
1367        {
1368          entry.setData(info.forceGetKeywords());
1369        }
1370        else
1371        {
1372          assert false;
1373        }
1374      }
1375    }
1376  }
1377  
1378  
1379  private void performSearchField(
1380    SongIDResultSet resultSet,
1381    String fieldName,
1382    SearchParameters params)
1383  {
1384    for (Iterator iter = resultSet.getEntryCollection().iterator(); iter.hasNext(); )
1385    {
1386      SongIDResultSet.Entry entry = (SongIDResultSet.Entry)iter.next();
1387      SongIDImpl id = (SongIDImpl)entry.getSongID();
1388      SongInfoCache info = (SongInfoCache)songMap_.get(id.getStringID());
1389      
1390      if (fieldName == Song.MAINTITLE_FIELD ||
1391        fieldName == Song.COMMENT_FIELD ||
1392        fieldName == Song.AUTHOR_FIELD ||
1393        fieldName == Song.COPYRIGHT_FIELD)
1394      {
1395        String value;
1396        if (fieldName == Song.MAINTITLE_FIELD)
1397        {
1398          value = info.forceGetMainTitle();
1399        }
1400        else if (fieldName == Song.COMMENT_FIELD)
1401        {
1402          value = info.forceGetComment();
1403        }
1404        else if (fieldName == Song.AUTHOR_FIELD)
1405        {
1406          value = info.forceGetAuthor();
1407        }
1408        else //if (fieldName == Song.COPYRIGHT_FIELD)
1409        {
1410          value = info.forceGetCopyright();
1411        }
1412        if (params.matches(value))
1413        {
1414          entry.setData(Boolean.TRUE);
1415        }
1416        else
1417        {
141