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_.