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

Quick Search    Search Deep

Source code: com/virtuosotechnologies/asaph/standardmodel/StdSong.java


1   /*
2   ================================================================================
3   
4     FILE:  StdSong.java
5     
6     PROJECT:
7     
8       Asaph
9     
10    CONTENTS:
11    
12      Standard implementation of Song
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.standardmodel;
43  
44  
45  import java.io.IOException;
46  import java.util.List;
47  import java.util.ArrayList;
48  import java.util.Iterator;
49  import java.util.StringTokenizer;
50  import java.util.Locale;
51  import javax.swing.undo.AbstractUndoableEdit;
52  import javax.swing.undo.CannotUndoException;
53  import javax.swing.undo.CannotRedoException;
54  import javax.swing.event.UndoableEditListener;
55  import org.xml.sax.SAXException;
56  import org.xml.sax.Attributes;
57  import org.xml.sax.ErrorHandler;
58  import org.xml.sax.Locator;
59  
60  import com.virtuosotechnologies.lib.util.CollectingCompoundEdit;
61  import com.virtuosotechnologies.lib.util.StringID;
62  import com.virtuosotechnologies.lib.xml.XMLUnparser;
63  
64  import com.virtuosotechnologies.asaph.model.Song;
65  import com.virtuosotechnologies.asaph.model.SongID;
66  import com.virtuosotechnologies.asaph.model.SongDatabase;
67  import com.virtuosotechnologies.asaph.model.ChordSet;
68  import com.virtuosotechnologies.asaph.model.Variation;
69  import com.virtuosotechnologies.asaph.model.SongBlock;
70  import com.virtuosotechnologies.asaph.model.StringField;
71  import com.virtuosotechnologies.asaph.model.StringListField;
72  import com.virtuosotechnologies.asaph.model.Field;
73  import com.virtuosotechnologies.asaph.model.notation.Note;
74  import com.virtuosotechnologies.asaph.model.notation.NotationFactory;
75  import com.virtuosotechnologies.asaph.modelutils.SongUtils;
76  import com.virtuosotechnologies.asaph.notationmanager.NotationManager;
77  
78  
79  /**
80   * Standard implementation of Song
81   */
82  /*package*/ class StdSong
83  extends BaseSongMember
84  implements
85    Song
86  {
87    private SongID songID_;
88    private Locale locale_;
89    private StringID version_;
90    private StringID lastBlockID_;
91    private StringID lastChordSetID_;
92    private StringID lastVariationID_;
93    
94    private ListHelper chordSetList_;
95    private ListHelper variationList_;
96    private ListHelper blockList_;
97    private ListHelper fieldList_;
98    private StdChordSet defaultChordSet_;
99    
100   
101   /**
102    * Constructor for standalone songs
103    */
104   /*package*/ StdSong(
105     Locale locale)
106   {
107     this(null, new StringID(), locale);
108   }
109   
110   
111   /**
112    * Constructor for new songs
113    */
114   /*package*/ StdSong(
115     SongID songID,
116     Locale locale)
117   {
118     this(songID, new StringID("a"), locale);
119   }
120   
121   
122   /**
123    * Constructor
124    */
125   /*package*/ StdSong(
126     SongID songID,
127     StringID version,
128     Locale locale)
129   {
130     super(null);
131     songID_ = songID;
132     locale_ = locale;
133     lastBlockID_ = new StringID();
134     lastChordSetID_ = new StringID();
135     lastVariationID_ = new StringID();
136     version_ = version;
137     chordSetList_ = new ListHelper(this,
138       new ListHelper.KeyExtractor()
139       {
140         protected String extractKey(
141           BaseSongMember member)
142         {
143           return ((StdChordSet)member).getSerializableID();
144         }
145       });
146     variationList_ = new ListHelper(this,
147       new ListHelper.KeyExtractor()
148       {
149         protected String extractKey(
150           BaseSongMember member)
151         {
152           return ((StdVariation)member).getSerializableID();
153         }
154       });
155     blockList_ = new ListHelper(this,
156       new ListHelper.KeyExtractor()
157       {
158         protected String extractKey(
159           BaseSongMember member)
160         {
161           return ((BaseSongBlock)member).getSerializableID();
162         }
163       });
164     fieldList_ = new ListHelper(this,
165       new ListHelper.KeyExtractor()
166       {
167         protected String extractKey(
168           BaseSongMember member)
169         {
170           return ((Field)member).getFieldName();
171         }
172       });
173     defaultChordSet_ = null;
174   }
175   
176   
177   /**
178    * Constructor
179    */
180   /*package*/ StdSong(
181     StdSong original,
182     SongUtils utils)
183   {
184     this(original.getSongID(), original.getVersion(), original.getLocale());
185     utils.copySong(original, this);
186     lastBlockID_ = original.lastBlockID_;
187     lastChordSetID_ = original.lastChordSetID_;
188     lastVariationID_ = original.lastVariationID_;
189   }
190   
191   
192   /*package*/ StringID getVersion()
193   {
194     return version_;
195   }
196   
197   
198   /*package*/ void setVersion(
199     StringID version)
200   {
201     version_ = version;
202   }
203   
204   
205   /*package*/ boolean containsChordSet(
206     ChordSet cs)
207   {
208     return chordSetList_.containsObject(cs);
209   }
210   
211   
212   /*package*/ void unparse(
213     XMLUnparser unparser)
214   throws
215     IOException
216   {
217     unparser.startMultiLineElement(XMLConstants.SONG_ELEMENT);
218     if (songID_ != null)
219     {
220       unparser.addAttribute(XMLConstants.SONG_ID_ATTRIBUTE,
221         songID_.getStringRepresentation());
222     }
223     else
224     {
225       unparser.addAttribute(XMLConstants.SONG_ID_ATTRIBUTE, "");
226     }
227     unparser.addAttribute(XMLConstants.SONG_VERSION_ATTRIBUTE,
228       version_.getValue());
229     unparser.addAttribute(XMLConstants.SONG_LOCALE_ATTRIBUTE,
230       locale_.toString());
231     
232     unparser.startMultiLineElement(XMLConstants.FIELDLIST_ELEMENT);
233     for (Field field = (Field)fieldList_.getNextObject(null);
234       field != null; field = (Field)fieldList_.getNextObject(field))
235     {
236       if (field instanceof StdStringListField)
237       {
238         ((StdStringListField)field).unparse(unparser, XMLConstants.STRINGLISTFIELD_ELEMENT);
239       }
240       else
241       {
242         ((StdStringField)field).unparse(unparser);
243       }
244     }
245     unparser.endElement(XMLConstants.FIELDLIST_ELEMENT);
246     
247     unparser.startMultiLineElement(XMLConstants.CHORDSETLIST_ELEMENT);
248     unparser.addAttribute(XMLConstants.CHORDSETLIST_LASTID_ATTRIBUTE,
249       lastChordSetID_.getValue());
250     if (defaultChordSet_ != null)
251     {
252       unparser.addAttribute(XMLConstants.CHORDSETLIST_DEFAULTID_ATTRIBUTE,
253         defaultChordSet_.getSerializableID());
254     }
255     for (StdChordSet chordSet = (StdChordSet)chordSetList_.getNextObject(null);
256       chordSet != null; chordSet = (StdChordSet)chordSetList_.getNextObject(chordSet))
257     {
258       chordSet.unparse(unparser);
259     }
260     unparser.endElement(XMLConstants.CHORDSETLIST_ELEMENT);
261     
262     unparser.startMultiLineElement(XMLConstants.VARIATIONLIST_ELEMENT);
263     unparser.addAttribute(XMLConstants.VARIATIONLIST_LASTID_ATTRIBUTE,
264       lastVariationID_.getValue());
265     for (StdVariation variation = (StdVariation)variationList_.getNextObject(null);
266       variation != null; variation = (StdVariation)variationList_.getNextObject(variation))
267     {
268       variation.unparse(unparser);
269     }
270     unparser.endElement(XMLConstants.VARIATIONLIST_ELEMENT);
271     
272     unparser.startMultiLineElement(XMLConstants.BLOCKLIST_ELEMENT);
273     unparser.addAttribute(XMLConstants.BLOCKLIST_LASTID_ATTRIBUTE,
274       lastBlockID_.getValue());
275     for (BaseSongBlock block = (BaseSongBlock)blockList_.getNextObject(null);
276       block != null; block = (BaseSongBlock)blockList_.getNextObject(block))
277     {
278       block.unparse(unparser);
279     }
280     unparser.endElement(XMLConstants.BLOCKLIST_ELEMENT);
281     
282     unparser.endElement(XMLConstants.SONG_ELEMENT);
283   }
284   
285   
286   /*package*/ class ParseHandler
287   extends ParseHandlerBase
288   {
289     private NotationFactory notationFactory_;
290     
291     
292     /*package*/ ParseHandler(
293       ErrorHandler errorHandler,
294       Locator locator,
295       NotationManager notationManager)
296     {
297       super(errorHandler, locator, XMLConstants.SONG_ELEMENT);
298       notationFactory_ = notationManager.getNotationFactoryForLocale(locale_);
299     }
300     
301     
302     /**
303      * Handle elements at the top level. Override this.
304      */
305     /*package*/ ParseHandlerBase localStartElement(
306       String uri,
307       String localName,
308       String qName,
309       Attributes attributes)
310     throws
311       SAXException
312     {
313       if (localName.equals(XMLConstants.MAINTITLE_ELEMENT))
314       {
315         // Deprecated element
316         Object fieldObj = fieldList_.getObjectForKey(MAINTITLE_FIELD);
317         if (fieldObj != null)
318         {
319           fieldList_.removeObject(fieldObj, null);
320         }
321         StdStringField field = new StdStringField(StdSong.this, MAINTITLE_FIELD, "");
322         fieldList_.appendObject(field, null);
323         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(),
324           XMLConstants.MAINTITLE_ELEMENT);
325       }
326       else if (localName.equals(XMLConstants.ALTTITLELIST_ELEMENT))
327       {
328         // Deprecated element
329         Object fieldObj = fieldList_.getObjectForKey(ALTERNATETITLELIST_FIELD);
330         if (fieldObj != null)
331         {
332           fieldList_.removeObject(fieldObj, null);
333         }
334         StdStringListField field = new StdStringListField(
335           StdSong.this, ALTERNATETITLELIST_FIELD);
336         fieldList_.appendObject(field, null);
337         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(),
338           XMLConstants.ALTTITLELIST_ELEMENT);
339       }
340       else if (localName.equals(XMLConstants.CREDITS_ELEMENT))
341       {
342         // Deprecated element
343         Object fieldObj = fieldList_.getObjectForKey(AUTHOR_FIELD);
344         if (fieldObj != null)
345         {
346           fieldList_.removeObject(fieldObj, null);
347         }
348         StdStringField field = new StdStringField(StdSong.this, AUTHOR_FIELD, "");
349         fieldList_.appendObject(field, null);
350         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(),
351           XMLConstants.CREDITS_ELEMENT);
352       }
353       else if (localName.equals(XMLConstants.COPYRIGHT_ELEMENT))
354       {
355         // Deprecated element
356         Object fieldObj = fieldList_.getObjectForKey(COPYRIGHT_FIELD);
357         if (fieldObj != null)
358         {
359           fieldList_.removeObject(fieldObj, null);
360         }
361         StdStringField field = new StdStringField(StdSong.this, COPYRIGHT_FIELD, "");
362         fieldList_.appendObject(field, null);
363         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(),
364           XMLConstants.COPYRIGHT_ELEMENT);
365       }
366       else if (localName.equals(XMLConstants.KEYWORDLIST_ELEMENT))
367       {
368         // Deprecated element
369         Object fieldObj = fieldList_.getObjectForKey(KEYWORDLIST_FIELD);
370         if (fieldObj != null)
371         {
372           fieldList_.removeObject(fieldObj, null);
373         }
374         StdStringListField field = new StdStringListField(
375           StdSong.this, KEYWORDLIST_FIELD);
376         fieldList_.appendObject(field, null);
377         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(),
378           XMLConstants.KEYWORDLIST_ELEMENT);
379       }
380       else if (localName.equals(XMLConstants.CHORDSETLIST_ELEMENT) ||
381         localName.equals(XMLConstants.CHORDSETLIST_ELEMENT_ALT))
382       {
383         String lastID = attributes.getValue(XMLConstants.CHORDSETLIST_LASTID_ATTRIBUTE);
384         if (lastID != null)
385         {
386           if (StringID.isWellFormed(lastID))
387           {
388             lastChordSetID_ = new StringID(lastID);
389           }
390         }
391         String defaultID = attributes.getValue(XMLConstants.CHORDSETLIST_DEFAULTID_ATTRIBUTE);
392         return new ChordSetListParseHandler(getErrorHandler(),
393           getDocumentLocator(), localName, defaultID, notationFactory_);
394       }
395       else if (localName.equals(XMLConstants.VARIATIONLIST_ELEMENT))
396       {
397         String lastID = attributes.getValue(XMLConstants.VARIATIONLIST_LASTID_ATTRIBUTE);
398         if (lastID != null)
399         {
400           if (StringID.isWellFormed(lastID))
401           {
402             lastVariationID_ = new StringID(lastID);
403           }
404         }
405         return new VariationListParseHandler(getErrorHandler(), getDocumentLocator());
406       }
407       else if (localName.equals(XMLConstants.BLOCKLIST_ELEMENT))
408       {
409         String lastID = attributes.getValue(XMLConstants.BLOCKLIST_LASTID_ATTRIBUTE);
410         if (lastID != null)
411         {
412           if (StringID.isWellFormed(lastID))
413           {
414             lastBlockID_ = new StringID(lastID);
415           }
416         }
417         return new BlockListParseHandler(getErrorHandler(), getDocumentLocator(), notationFactory_);
418       }
419       else if (localName.equals(XMLConstants.FIELDLIST_ELEMENT))
420       {
421         return new FieldListParseHandler(getErrorHandler(), getDocumentLocator());
422       }
423       else if (localName.equals(XMLConstants.NOTES_ELEMENT))
424       {
425         // Deprecated element
426         Object fieldObj = fieldList_.getObjectForKey(NOTESLIST_FIELD);
427         if (fieldObj != null)
428         {
429           fieldList_.removeObject(fieldObj, null);
430         }
431         StdStringListField field = new StdStringListField(
432           StdSong.this, NOTESLIST_FIELD);
433         fieldList_.appendObject(field, null);
434         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(),
435           XMLConstants.NOTES_ELEMENT);
436       }
437       else
438       {
439         return super.localStartElement(uri, localName, qName, attributes);
440       }
441     }
442   }
443   
444   
445   /*package*/ class FieldListParseHandler
446   extends ParseHandlerBase
447   {
448     /*package*/ FieldListParseHandler(
449       ErrorHandler errorHandler,
450       Locator locator)
451     {
452       super(errorHandler, locator, XMLConstants.FIELDLIST_ELEMENT);
453     }
454     
455     /*package*/ ParseHandlerBase localStartElement(
456       String uri,
457       String localName,
458       String qName,
459       Attributes attributes)
460     throws
461       SAXException
462     {
463       if (localName.equals(XMLConstants.STRINGFIELD_ELEMENT) ||
464         localName.equals(XMLConstants.STRINGFIELD_ELEMENT_ALT))
465       {
466         String fieldName = attributes.getValue(XMLConstants.FIELD_NAME_ATTRIBUTE);
467         if (fieldName == null)
468         {
469           reportFatalError(ResourceAccess.Strings.buildString("xml_MissingAttribute",
470             XMLConstants.FIELD_NAME_ATTRIBUTE, localName));
471         }
472         StdStringField field = new StdStringField(StdSong.this, fieldName, "");
473         fieldList_.appendObject(field, null);
474         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(), localName);
475       }
476       else if (localName.equals(XMLConstants.STRINGLISTFIELD_ELEMENT))
477       {
478         String fieldName = attributes.getValue(XMLConstants.FIELD_NAME_ATTRIBUTE);
479         if (fieldName == null)
480         {
481           reportFatalError(ResourceAccess.Strings.buildString("xml_MissingAttribute",
482             XMLConstants.FIELD_NAME_ATTRIBUTE, XMLConstants.STRINGLISTFIELD_ELEMENT));
483         }
484         StdStringListField field = new StdStringListField(StdSong.this, fieldName);
485         fieldList_.appendObject(field, null);
486         return field.new ParseHandler(getErrorHandler(), getDocumentLocator(), localName);
487       }
488       else
489       {
490         return super.localStartElement(uri, localName, qName, attributes);
491       }
492     }
493   }
494   
495   
496   /*package*/ class VariationListParseHandler
497   extends ParseHandlerBase
498   {
499     /*package*/ VariationListParseHandler(
500       ErrorHandler errorHandler,
501       Locator locator)
502     {
503       super(errorHandler, locator, XMLConstants.VARIATIONLIST_ELEMENT);
504     }
505     
506     /*package*/ ParseHandlerBase localStartElement(
507       String uri,
508       String localName,
509       String qName,
510       Attributes attributes)
511     throws
512       SAXException
513     {
514       if (localName.equals(XMLConstants.VARIATION_ELEMENT))
515       {
516         String id = attributes.getValue(XMLConstants.VARIATION_ID_ATTRIBUTE);
517         if (id == null)
518         {
519           reportFatalError(ResourceAccess.Strings.buildString("xml_MissingAttribute",
520             XMLConstants.VARIATION_ID_ATTRIBUTE, XMLConstants.VARIATION_ELEMENT));
521         }
522         if (!StringID.isWellFormed(id))
523         {
524           reportFatalError(ResourceAccess.Strings.buildString(
525             "xml_MalformedVariationID", id));
526         }
527         StringID sid = new StringID(id);
528         StdVariation var = new StdVariation(StdSong.this, "", sid);
529         variationList_.appendObject(var, null);
530         if (sid.compareTo(lastVariationID_) > 0)
531         {
532           lastVariationID_ = sid;
533         }
534         return var.new ParseHandler(getErrorHandler(), getDocumentLocator());
535       }
536       else
537       {
538         return super.localStartElement(uri, localName, qName, attributes);
539       }
540     }
541   }
542   
543   
544   /*package*/ class ChordSetListParseHandler
545   extends ParseHandlerBase
546   {
547     private String defaultID_;
548     private NotationFactory notationFactory_;
549     
550     /*package*/ ChordSetListParseHandler(
551       ErrorHandler errorHandler,
552       Locator locator,
553       String localElement,
554       String defaultID,
555       NotationFactory notationFactory)
556     {
557       super(errorHandler, locator, localElement);
558       defaultID_ = defaultID;
559       notationFactory_ = notationFactory;
560     }
561     
562     /*package*/ ParseHandlerBase localStartElement(
563       String uri,
564       String localName,
565       String qName,
566       Attributes attributes)
567     throws
568       SAXException
569     {
570       if (localName.equals(XMLConstants.CHORDSET_ELEMENT) ||
571         localName.equals(XMLConstants.CHORDSET_ELEMENT_ALT))
572       {
573         String id = attributes.getValue(XMLConstants.CHORDSET_ID_ATTRIBUTE);
574         if (id == null)
575         {
576           reportFatalError(ResourceAccess.Strings.buildString("xml_MissingAttribute",
577             XMLConstants.CHORDSET_ID_ATTRIBUTE, localName));
578         }
579         if (!StringID.isWellFormed(id))
580         {
581           reportFatalError(ResourceAccess.Strings.buildString(
582             "xml_MalformedChordSetID", id));
583         }
584         StringID sid = new StringID(id);
585         StdChordSet chordSet = new StdChordSet(StdSong.this, "", "",
586           notationFactory_.getDefaultNote(), "", sid);
587         chordSetList_.appendObject(chordSet, null);
588         if (id.equals(defaultID_))
589         {
590           defaultChordSet_ = chordSet;
591         }
592         if (sid.compareTo(lastChordSetID_) > 0)
593         {
594           lastChordSetID_ = sid;
595         }
596         return chordSet.new ParseHandler(getErrorHandler(), getDocumentLocator(), localName,
597           notationFactory_);
598       }
599       else
600       {
601         return super.localStartElement(uri, localName, qName, attributes);
602       }
603     }
604   }
605   
606   
607   /*package*/ class BlockListParseHandler
608   extends ParseHandlerBase
609   {
610     private NotationFactory notationFactory_;
611     
612     /*package*/ BlockListParseHandler(
613       ErrorHandler errorHandler,
614       Locator locator,
615       NotationFactory notationFactory)
616     {
617       super(errorHandler, locator, XMLConstants.BLOCKLIST_ELEMENT);
618       notationFactory_ = notationFactory;
619     }
620     
621     /*package*/ ParseHandlerBase localStartElement(
622       String uri,
623       String localName,
624       String qName,
625       Attributes attributes)
626     throws
627       SAXException
628     {
629       if (localName.equals(XMLConstants.BLOCK_ELEMENT))
630       {
631         String id = attributes.getValue(XMLConstants.BLOCK_ID_ATTRIBUTE);
632         if (id == null)
633         {
634           reportFatalError(ResourceAccess.Strings.buildString("xml_MissingAttribute",
635             XMLConstants.BLOCK_ID_ATTRIBUTE, XMLConstants.BLOCK_ELEMENT));
636         }
637         if (!StringID.isWellFormed(id))
638         {
639           reportFatalError(ResourceAccess.Strings.buildString(
640             "xml_MalformedBlockID", id));
641         }
642         StringID sid = new StringID(id);
643         String indentStr = attributes.getValue(XMLConstants.BLOCK_INDENT_ATTRIBUTE);
644         int indent = 0;
645         if (indentStr != null)
646         {
647           try
648           {
649             indent = Integer.parseInt(indentStr);
650           }
651           catch (NumberFormatException ex)
652           {
653             reportFatalError(ResourceAccess.Strings.buildString(
654               "xml_MalformedIndent", indentStr));
655           }
656         }
657         BaseSongBlock block;
658         String addVarStr = attributes.getValue(XMLConstants.BLOCK_ADDVARIATION_ATTRIBUTE);
659         if (addVarStr != null)
660         {
661           StdVariation addVar = (StdVariation)variationList_.getObjectForKey(addVarStr);
662           if (addVar == null)
663           {
664             reportFatalError(ResourceAccess.Strings.buildString(
665               "xml_AddVariationNotFound", addVarStr));
666           }
667           block = new StdAddedSongBlock(StdSong.this, addVar, sid, indent);
668         }
669         else
670         {
671           StdStandardSongBlock stdblock = new StdStandardSongBlock(StdSong.this, sid, indent);
672           String omitVarStr = attributes.getValue(XMLConstants.BLOCK_OMITVARIATIONS_ATTRIBUTE);
673           if (omitVarStr != null)
674           {
675             for (StringTokenizer tokenizer = new StringTokenizer(omitVarStr);
676               tokenizer.hasMoreTokens(); )
677             {
678               String token = tokenizer.nextToken();
679               StdVariation var = (StdVariation)variationList_.getObjectForKey(token);
680               if (var == null)
681               {
682                 reportFatalError(ResourceAccess.Strings.buildString(
683                   "xml_OmitVariationNotFound", token));
684               }
685               stdblock.omitVariation(var, null);
686             }
687           }
688           block = stdblock;
689         }
690         
691         blockList_.appendObject(block, null);
692         if (sid.compareTo(lastBlockID_) > 0)
693         {
694           lastBlockID_ = sid;
695         }
696         return block.new ParseHandler(getErrorHandler(), getDocumentLocator(), StdSong.this,
697           notationFactory_);
698       }
699       else
700       {
701         return super.localStartElement(uri, localName, qName, attributes);
702       }
703     }
704   }
705   
706   
707   //-------------------------------------------------------------------------
708   // Methods of Song
709   //-------------------------------------------------------------------------
710   
711   /**
712    * Get the id used to check out the song from its database. Returns null if
713    * this song is not associated with a database.
714    *
715    * @return song id
716    */
717   public SongID getSongID()
718   {
719     return songID_;
720   }
721   
722   
723   /**
724    * Get the locale of the song.
725    *
726    * @return Locale
727    */
728   public Locale getLocale()
729   {
730     return locale_;
731   }
732   
733   
734   /**
735    * Get the number of ChordSets.
736    *
737    * @return number of ChordSets
738    */
739   public int getChordSetCount()
740   {
741     return chordSetList_.getCount();
742   }
743   
744   
745   /**
746    * Get the nth ChordSet
747    *
748    * @param n index
749    * @return ChordSet
750    */
751   public ChordSet getNthChordSet(
752     int n)
753   {
754     return (StdChordSet)chordSetList_.getNthObject(n);
755   }
756   
757   
758   /**
759    * Get the next chord set following reference.
760    * If reference is null, returns the first chord set.
761    * If reference is the last chord set, returns null;
762    *
763    * @param reference reference ChordSet
764    * @return next chord set
765    * @exception IllegalArgumentException reference is not a member
766    */
767   public ChordSet getNextChordSet(
768     ChordSet reference)
769   {
770     return (StdChordSet)chordSetList_.getNextObject(reference);
771   }
772   
773   
774   /**
775    * Get the previous chord set preceding reference.
776    * If reference is null, returns the last chord set.
777    * If reference is the first chord set, returns null;
778    *
779    * @param reference reference ChordSet
780    * @return previous chord set
781    * @exception IllegalArgumentException reference is not a member
782    */
783   public ChordSet getPreviousChordSet(
784     ChordSet reference)
785   {
786     return (StdChordSet)chordSetList_.getPreviousObject(reference);
787   }
788   
789   
790   /**
791    * Get default chord set. Returns null if there is no default.
792    *
793    * @return default ChordSet
794    */
795   public ChordSet getDefaultChordSet()
796   {
797     return defaultChordSet_;
798   }
799   
800   
801   /**
802    * Get a ChordSet given a serializable ID. Returns null if there
803    * is no ChordSet with the given ID.
804    *
805    * @param serializableID serializable ID string
806    * @return the ChordSet with the given ID
807    */
808   public ChordSet getChordSetForSerializableID(
809     String serializableID)
810   {
811     return (StdChordSet)chordSetList_.getObjectForKey(serializableID);
812   }
813   
814   
815   /**
816    * Get the number of Variations.
817    *
818    * @return number of Variations
819    */
820   public int getVariationCount()
821   {
822     return variationList_.getCount();
823   }
824   
825   
826   /**
827    * Get the nth Variation
828    *
829    * @param n index
830    * @return Variation
831    */
832   public Variation getNthVariation(
833     int n)
834   {
835     return (StdVariation)variationList_.getNthObject(n);
836   }
837   
838   
839   /**
840    * Get the next variation following reference.
841    * If reference is null, returns the first variation.
842    * If reference is the last variation, returns null;
843    *
844    * @param reference reference Variation
845    * @return next variation
846    * @exception IllegalArgumentException reference is not a member
847    */
848   public Variation getNextVariation(
849     Variation reference)
850   {
851     return (StdVariation)variationList_.getNextObject(reference);
852   }
853   
854   
855   /**
856    * Get the previous variation preceding reference.
857    * If reference is null, returns the last variation.
858    * If reference is the first variation, returns null;
859    *
860    * @param reference reference Variation
861    * @return previous variation
862    * @exception IllegalArgumentException reference is not a member
863    */
864   public Variation getPreviousVariation(
865     Variation reference)
866   {
867     return (StdVariation)variationList_.getPreviousObject(reference);
868   }
869   
870   
871   /**
872    * Get a Variation given a serializable ID. Returns null if there
873    * is no Variation with the given ID.
874    *
875    * @param serializableID serializable ID string
876    * @return the Variation with the given ID
877    */
878   public Variation getVariationForSerializableID(
879     String serializableID)
880   {
881     return (StdVariation)variationList_.getObjectForKey(serializableID);
882   }
883   
884   
885   /**
886    * Get the total number of SongBlocks in the song. This list may not correspond
887    * to any particular variation of the song.
888    *
889    * @return number of SongBlocks
890    */
891   public int getBlockCount()
892   {
893     return blockList_.getCount();
894   }
895   
896   
897   /**
898    * Get the nth SongBlock
899    *
900    * @param n index
901    * @return SongBlock
902    */
903   public SongBlock getNthBlock(
904     int n)
905   {
906     return (BaseSongBlock)blockList_.getNthObject(n);
907   }
908   
909   
910   /**
911    * Get the next block following reference.
912    * If reference is null, returns the first block.
913    * If reference is the last block, returns null;
914    *
915    * @param reference reference SongBlock
916    * @return next block
917    * @exception IllegalArgumentException reference is not a member
918    */
919   public SongBlock getNextBlock(
920     SongBlock reference)
921   {
922     return (BaseSongBlock)blockList_.getNextObject(reference);
923   }
924   
925   
926   /**
927    * Get the previous block preceding reference.
928    * If reference is null, returns the last block.
929    * If reference is the first block, returns null;
930    *
931    * @param reference reference SongBlock
932    * @return previous block
933    * @exception IllegalArgumentException reference is not a member
934    */
935   public SongBlock getPreviousBlock(
936     SongBlock reference)
937   {
938     return (BaseSongBlock)blockList_.getPreviousObject(reference);
939   }
940   
941   
942   private class VariationFilter
943   extends ListHelper.ObjectFilter
944   {
945     private StdVariation variation_;
946     
947     /*package*/ VariationFilter(
948       Variation var)
949     {
950       if (var != null && !variationList_.containsObject(var))
951       {
952         throw new IllegalArgumentException("Unknown variation");
953       }
954       variation_ = (StdVariation)var;
955     }
956     
957     protected boolean isRelevant(
958       BaseSongMember member)
959     {
960       if (member instanceof StdStandardSongBlock)
961       {
962         return variation_ == null ||
963           !((StdStandardSongBlock)member).getOmittingVariations().contains(variation_);
964       }
965       else
966       {
967         return ((StdAddedSongBlock)member).getAddingVariation() == variation_;
968       }
969     }
970   }
971   
972   
973   /**
974    * Get the number of SongBlocks present for the given variation. Pass null
975    * to specify the default variation.
976    *
977    * @param variation Variation to query, or null for the default variation
978    * @return number of SongBlocks
979    * @exception IllegalArgumentException variation is not a member
980    */
981   public int getBlockCount(
982     Variation variation)
983   {
984     return blockList_.getCount(new VariationFilter(variation));
985   }
986   
987   
988   /**
989    * Get the nth SongBlock in the given variation. Pass null to specify the
990    * default variation
991    *
992    * @param n index
993    * @param variation Variation to query, or null for the default variation
994    * @return SongBlock
995    */
996   public SongBlock getNthBlock(
997     int n,
998     Variation variation)
999   {
1000    return (SongBlock)blockList_.getNthObject(n, new VariationFilter(variation));
1001  }
1002  
1003  
1004  /**
1005   * Get the next block following reference, in the given variation.
1006   * If reference is null, returns the first block.
1007   * If reference is the last block, returns null;
1008   * It is legal for reference to not be a member of the given variation. In such
1009   * a case, the method will return the next block that is part of this variation.
1010   *
1011   * @param reference reference SongBlock
1012   * @param variation Variation to query, or null for the default variation
1013   * @return next block
1014   * @exception IllegalArgumentException variation or reference is not a member
1015   */
1016  public SongBlock getNextBlock(
1017    SongBlock reference,
1018    Variation variation)
1019  {
1020    return (SongBlock)blockList_.getNextObject(reference,
1021      new VariationFilter(variation));
1022  }
1023  
1024  
1025  /**
1026   * Get the previous block preceding reference, in the given variation.
1027   * If reference is null, returns the last block.
1028   * If reference is the first block, returns null;
1029   * It is legal for reference to not be a member of the given variation. In such
1030   * a case, the method will return the previous block that is part of this variation.
1031   *
1032   * @param reference reference SongBlock
1033   * @param variation Variation to query, or null for the default variation
1034   * @return previous block
1035   * @exception IllegalArgumentException variation or reference is not a member
1036   */
1037  public SongBlock getPreviousBlock(
1038    SongBlock reference,
1039    Variation variation)
1040  {
1041    return (SongBlock)blockList_.getPreviousObject(reference,
1042      new VariationFilter(variation));
1043  }
1044  
1045  
1046  /**
1047   * Get a SongBlock given a serializable ID. Returns null if there
1048   * is no SongBlock with the given ID.
1049   *
1050   * @param serializableID serializable ID string
1051   * @return the SongBlock with the given ID
1052   */
1053  public SongBlock getBlockForSerializableID(
1054    String serializableID)
1055  {
1056    return (SongBlock)blockList_.getObjectForKey(serializableID);
1057  }
1058  
1059  
1060  /**
1061   * Get the number of fields.
1062   *
1063   * @return number of FieldStrings
1064   */
1065  public int getFieldCount()
1066  {
1067    return fieldList_.getCount();
1068  }
1069  
1070  
1071  /**
1072   * Get the nth field
1073   *
1074   * @param n index
1075   * @return Field
1076   */
1077  public Field getNthField(
1078    int n)
1079  {
1080    return (Field)fieldList_.getNthObject(n);
1081  }
1082  
1083  
1084  /**
1085   * Get the next field following reference.
1086   * If reference is null, returns the first field.
1087   * If reference is the last field, returns null;
1088   *
1089   * @param reference reference Field
1090   * @return next field
1091   * @exception IllegalArgumentException reference is not a member
1092   */
1093  public Field getNextField(
1094    Field reference)
1095  {
1096    return (Field)fieldList_.getNextObject(reference);
1097  }
1098  
1099  
1100  /**
1101   * Get the previous field preceding reference.
1102   * If reference is null, returns the last field.
1103   * If reference is the first field, returns null;
1104   *
1105   * @param reference reference Field
1106   * @return previous field
1107   * @exception IllegalArgumentException reference is not a member
1108   */
1109  public Field getPreviousField(
1110    Field reference)
1111  {
1112    return (Field)fieldList_.getPreviousObject(reference);
1113  }
1114  
1115  
1116  /**
1117   * Search for the field with the given name.
1118   * Returns null if there is no such field.
1119   * Implementations of this method may be faster than doing a linear
1120   * search with getNextField. 
1121   *
1122   * @param name name to search for
1123   * @return the field, or null if there is no such field
1124   * @exception NullPointerException name is null
1125   */
1126  public Field getNamedField(
1127    String name)
1128  {
1129    return (Field)fieldList_.getObjectForKey(name);
1130  }
1131  
1132  
1133  /**
1134   * Clear the song body, removes all titles, keywords, chord sets, fields
1135   * and notes, and clears the credits and copyright.
1136   *
1137   * @param undoListener listener to notify if an undoable edit is generated,
1138   *     or null to suppress generation of undoable edits
1139   */
1140  public void clear(
1141    UndoableEditListener undoListener)
1142  {
1143    CollectingCompoundEdit editCollector = null;
1144    if (undoListener != null)
1145    {
1146      editCollector = new CollectingCompoundEdit();
1147    }
1148    
1149    fieldList_.clear(editCollector);
1150    blockList_.clear(editCollector);
1151    variationList_.clear(editCollector);
1152    chordSetList_.clear(editCollector);
1153    
1154    if (undoListener != null)
1155    {
1156      editCollector.end();
1157      internalReportUndoableEdit(undoListener, editCollector);
1158    }
1159  }
1160  
1161  
1162  /**
1163   * Add a ChordSet
1164   *
1165   * @param keyType key type string
1166   * @param nativeKeyNote native key Note
1167   * @param undoListener listener to notify if an undoable edit is generated,
1168   *     or null to suppress generation of undoable edits
1169   * @return ChordSet for new set
1170   * @exception NullPointerException keyType or nativeKeyNode was null
1171   */
1172  public ChordSet addChordSet(
1173    String keyType,
1174    Note nativeKeyNote,
1175    UndoableEditListener undoListener)
1176  {
1177    lastChordSetID_ = lastChordSetID_.getNext();
1178    StdChordSet chordSet = new StdChordSet(this, "", keyType, nativeKeyNote, "",
1179      lastChordSetID_);
1180    chordSetList_.appendObject(chordSet, undoListener);
1181    return chordSet;
1182  }
1183  
1184  
1185  /**
1186   * Remove a ChordSet.
1187   *
1188   * @param cs ChordSet
1189   * @param undoListener listener to notify if an undoable edit is generated,
1190   *     or null to suppress generation of undoable edits
1191   * @exception IllegalArgumentException cs is not present
1192   * @exception NullPointerException cs was null
1193   */
1194  public void removeChordSet(
1195    ChordSet cs,
1196    UndoableEditListener undoListener)
1197  {
1198    if (cs == null)
1199    {
1200      throw new NullPointerException();
1201    }
1202    if (!chordSetList_.containsObject(cs))
1203    {
1204      throw new IllegalArgumentException("ChordSet is not a member");
1205    }
1206    
1207    CollectingCompoundEdit editCollector = null;
1208    if (undoListener != null)
1209    {
1210      editCollector = new CollectingCompoundEdit();
1211    }
1212    
1213    StdChordSet stdChordSet = (StdChordSet)cs;
1214    for (BaseSongBlock block = (BaseSongBlock)blockList_.getNextObject(null);
1215      block != null; block = (BaseSongBlock)blockList_.getNextObject(block))
1216    {
1217      block.handleChordSetDeleted(stdChordSet, editCollector);
1218    }
1219    if (cs == defaultChordSet_)
1220    {
1221      setDefaultChordSet(null, editCollector);
1222    }
1223    chordSetList_.removeObject(cs, editCollector);
1224    
1225    if (undoListener != null)
1226    {
1227      editCollector.end();
1228      internalReportUndoableEdit(undoListener, editCollector);
1229    }
1230  }
1231  
1232  
1233  /**
1234   * Set a ChordSet as the default. The ChordSet given must be
1235   * a member of this song. You may pass null, which sets no default.
1236   *
1237   * @param cs ChordSet to set as the default.
1238   * @param undoListener listener to notify if an undoable edit is generated,
1239   *     or null to suppress generation of undoable edits
1240   * @exception IllegalArgumentException cs is not a member of this song
1241   */
1242  public void setDefaultChordSet(
1243    ChordSet cs,
1244    UndoableEditListener undoListener)
1245  {
1246    if (cs != null && !chordSetList_.containsObject(cs))
1247    {
1248      throw new IllegalArgumentException("ChordSet not a member of this song.");
1249    }
1250    if (defaultChordSet_ == cs)
1251    {
1252      return;
1253    }
1254    final StdChordSet oldValue = defaultChordSet_;
1255    final StdChordSet newValue = (StdChordSet)cs;
1256    defaultChordSet_ = newValue;
1257    if (undoListener != null)
1258    {
1259      internalReportUndoableEdit(undoListener,
1260        new AbstractUndoableEdit()
1261        {
1262          public void undo()
1263          throws CannotUndoException
1264          {
1265            super.undo();
1266            defaultChordSet_ = oldValue;
1267          }
1268          
1269          public void redo()
1270          throws CannotRedoException
1271          {
1272            super.redo();
1273            defaultChordSet_ = newValue;
1274          }
1275        });
1276    }
1277  }
1278  
1279  
1280  /**
1281   * Add a Variation
1282   *
1283   * @param undoListener listener to notify if an undoable edit is generated,
1284   *     or null to suppress generation of undoable edits
1285   * @return added Variation
1286   */
1287  public Variation addVariation(
1288    UndoableEditListener undoListener)
1289  {
1290    lastVariationID_ = lastVariationID_.getNext();
1291    StdVariation variation = new StdVariation(this, "", lastVariationID_);
1292    variationList_.appendObject(variation, undoListener);
1293    return variation;
1294  }
1295  
1296  
1297  /**
1298   * Remove a Variation.
1299   *
1300   * @param variation Variation
1301   * @param undoListener listener to notify if an undoable edit is generated,
1302   *     or null to suppress generation of undoable edits
1303   * @exception IllegalArgumentException variation is not present
1304   * @exception NullPointerException variation was null
1305   */
1306  public void removeVariation(
1307    final Variation variation,
1308    UndoableEditListener undoListener)
1309  {
1310    if (variation == null)
1311    {
1312      throw new NullPointerException();
1313    }
1314    if (!variationList_.containsObject(variation))
1315    {
1316      throw new IllegalArgumentException("Variation is not a member");
1317    }
1318    
1319    CollectingCompoundEdit editCollector = null;
1320    if (undoListener != null)
1321    {
1322      editCollector = new CollectingCompoundEdit();
1323    }
1324    
1325    List toRemove = new ArrayList();
1326    for (BaseSongMember member = blockList_.getPreviousObject(null); member != null;
1327      member = blockList_.getPreviousObject(member))
1328    {
1329      if (member instanceof StdAddedSongBlock)
1330      {
1331        if (((StdAddedSongBlock)member).getAddingVariation() == variation)
1332        {
1333          toRemove.add(member);
1334        }
1335      }
1336      else
1337      {
1338        ((StdStandardSongBlock)member).unOmitVariation(variation, editCollector);
1339      }
1340    }
1341    for (Iterator iter = toRemove.iterator(); iter.hasNext(); )
1342    {
1343      BaseSongMember member = (BaseSongMember)iter.next();
1344      blockList_.