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

Quick Search    Search Deep

Source code: com/virtuosotechnologies/asaph/maingui/SongListModel.java


1   /*
2   ================================================================================
3   
4     FILE:  SongListModel.java
5     
6     PROJECT:
7     
8       Asaph
9     
10    CONTENTS:
11    
12      ListModel implementation for a song list
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.maingui;
43  
44  
45  import java.text.CollationKey;
46  import java.text.Collator;
47  import java.util.List;
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.Collections;
51  import java.util.Comparator;
52  import java.util.Iterator;
53  import javax.swing.AbstractListModel;
54  import javax.swing.SwingUtilities;
55  
56  import com.virtuosotechnologies.lib.base.LinkedObject;
57  
58  import com.virtuosotechnologies.asaph.model.Song;
59  import com.virtuosotechnologies.asaph.model.SongID;
60  import com.virtuosotechnologies.asaph.model.SongDatabaseFailedException;
61  import com.virtuosotechnologies.asaph.model.SongDatabase;
62  import com.virtuosotechnologies.asaph.model.SongIDResultSet;
63  import com.virtuosotechnologies.asaph.model.opsemantics.GetFieldAsStringSemantics;
64  import com.virtuosotechnologies.asaph.model.opsemantics.PredicateSemantics;
65  import com.virtuosotechnologies.asaph.model.opsemantics.TruePredicateSemantics;
66  import com.virtuosotechnologies.asaph.model.opsemantics.PredicateFilterSemantics;
67  import com.virtuosotechnologies.asaph.modelutils.SongUtils;
68  
69  
70  /**
71   * ListModel implementation for a song list
72   */
73  /*package*/ class SongListModel
74  extends AbstractListModel
75  {
76    private static final String STR_SongList_SongNotFoundName =
77      ResourceAccess.Strings.buildString("SongList_SongNotFoundName");
78    private static final String STR_SongList_NoTitleName =
79      ResourceAccess.Strings.buildString("SongList_NoTitleName");
80    private static final String STR_SongList_DatabaseErrorName =
81      ResourceAccess.Strings.buildString("SongList_DatabaseErrorName");
82    
83    /**
84     * Definition of ordering for songs
85     */
86    /*package*/ static abstract class ItemComparator
87    {
88      /**
89       * Compare two items.
90       */
91      /*package*/ abstract int compare(
92        Item item1,
93        Item item2);
94    }
95    
96    
97    /*package*/ static interface SizeChangeListener
98    {
99      public void sizeChanged(
100       SongListModel model);
101   }
102   
103   
104   private SongDatabase database_;
105   private SongUtils songUtils_;
106   private List list_;
107   private LinkedObject delim_;
108   private Collator collator_;
109   private ItemComparator curComparator_;
110   private PredicateSemantics filterPredicate_;
111   private SizeChangeListener sizeChangeListener_;
112   private int totalSize_;
113   
114   
115   /**
116    * JList element type
117    */
118   /*package*/ class Item
119   extends LinkedObject
120   {
121     private SongID songID_;
122     private String name_;
123     private CollationKey collationKey_;
124     
125     private Item(
126       SongID songID)
127     {
128       collationKey_ = null;
129       songID_ = songID;
130       update();
131     }
132     
133     private Item(
134       SongID songID,
135       String name)
136     {
137       collationKey_ = null;
138       songID_ = songID;
139       name_ = name;
140     }
141     
142     private void update()
143     {
144       try
145       {
146         SongDatabase database = songID_.getDatabase();
147         SongIDResultSet resultSet = database.createEmptyResultSet();
148         resultSet.add(songID_);
149         database.performOperation(
150           new GetFieldAsStringSemantics.DefaultImplementation(
151             Song.MAINTITLE_FIELD, songUtils_),
152           resultSet);
153         SongIDResultSet.Entry entry = resultSet.getEntryFor(songID_);
154         if (entry.getData() != null)
155         {
156           setName((String)entry.getData());
157         }
158         else
159         {
160           setDatabaseErrorName();
161         }
162       }
163       catch (SongDatabaseFailedException ex)
164       {
165         setDatabaseErrorName();
166       }
167     }
168     
169     private void setName(
170       String name)
171     {
172       if (name == null)
173       {
174         name_ = STR_SongList_SongNotFoundName;
175       }
176       else if (name.length() == 0)
177       {
178         name_ = STR_SongList_NoTitleName;
179       }
180       else
181       {
182         name_ = name;
183       }
184       collationKey_ = null;
185     }
186     
187     private void setDatabaseErrorName()
188     {
189       name_ = STR_SongList_DatabaseErrorName;
190     }
191     
192     
193     /*package*/ SongID getSongID()
194     {
195       return songID_;
196     }
197     
198     public String toString()
199     {
200       return name_;
201     }
202     
203     public CollationKey getCollationKey()
204     {
205       if (collationKey_ == null)
206       {
207         collationKey_ = collator_.getCollationKey(name_);
208       }
209       return collationKey_;
210     }
211   }
212   
213   
214   /**
215    * Constructor
216    */
217   /*package*/ SongListModel(
218     SongDatabase database,
219     SongUtils songUtils,
220     ItemComparator comparator,
221     PredicateSemantics filter)
222   {
223     database_ = database;
224     songUtils_ = songUtils;
225     list_ = new ArrayList();
226     delim_ = new LinkedObject();
227     curComparator_ = comparator;
228     collator_ = Collator.getInstance();
229     collator_.setStrength(Collator.PRIMARY);
230     filterPredicate_ = filter;
231     totalSize_ = 0;
232     sizeChangeListener_ = null;
233   }
234   
235   
236   /**
237    * Required by ListModel interface
238    */
239   public Object getElementAt(
240     int index)
241   {
242     return list_.get(index);
243   }
244   
245   
246   /**
247    * Required by ListModel interface
248    */
249   public int getSize()
250   {
251     return list_.size();
252   }
253   
254   
255   /**
256    * Get the total size including filtered out elements
257    */
258   /*package*/ int getTotalSize()
259   {
260     return totalSize_;
261   }
262   
263   
264   /**
265    * Add a song id to the list model
266    */
267   /*package*/ int addSongID(
268     SongID songID)
269   {
270     assert songID.getDatabase().equals(database_);
271     
272     Item item = new Item(songID);
273     item.linkThisBefore(delim_);
274     ++totalSize_;
275     
276     int index = -1;
277     try
278     {
279       SongIDResultSet resultSet = database_.createEmptyResultSet();
280       resultSet.add(songID);
281       database_.performOperation(filterPredicate_, resultSet);
282       if (Boolean.TRUE.equals(resultSet.getEntryFor(songID).getData()))
283       {
284         if (curComparator_ == null)
285         {
286           list_.add(item);
287           index = list_.size()-1;
288         }
289         else
290         {
291           index = Collections.binarySearch(list_, item,
292             new Comparator()
293             {
294               public int compare(
295                 Object o1,
296                 Object o2)
297               {
298                 return curComparator_.compare((Item)o1, (Item)o2);
299               }
300             });
301           if (index < 0)
302           {
303             index = -index-1;
304           }
305           list_.add(index, item);
306         }
307         fireIntervalAdded(this, index, index);
308       }
309     }
310     catch (SongDatabaseFailedException ex)
311     {
312     }
313     if (sizeChangeListener_ != null)
314     {
315       sizeChangeListener_.sizeChanged(this);
316     }
317     return index;
318   }
319   
320   
321   /**
322    * Add a collection of song ids to the list model
323    */
324   /*package*/ void addSongIDs(
325     Collection songIDs)
326   {
327     if (songIDs.isEmpty())
328     {
329       return;
330     }
331     
332     SongIDResultSet resultSet = database_.createEmptyResultSet();
333     
334     for (Iterator iter = songIDs.iterator(); iter.hasNext(); )
335     {
336       SongID songID = (SongID)iter.next();
337       assert songID.getDatabase().equals(database_);
338       resultSet.add(songID);
339       resultSet.getEntryFor(songID).setData(Boolean.FALSE);
340     }
341     
342     try
343     {
344       database_.performOperation(filterPredicate_, resultSet);
345     }
346     catch (SongDatabaseFailedException ex)
347     {
348     }
349     
350     int oldSize = list_.size();
351     for (Iterator iter = resultSet.getEntryCollection().iterator(); iter.hasNext(); )
352     {
353       SongIDResultSet.Entry entry = (SongIDResultSet.Entry)iter.next();
354       Item item = new Item(entry.getSongID());
355       item.linkThisBefore(delim_);
356       ++totalSize_;
357       if (Boolean.TRUE.equals(entry.getData()))
358       {
359         list_.add(item);
360       }
361     }
362     
363     if (list_.size() > oldSize)
364     {
365       fireIntervalAdded(this, oldSize, list_.size()-1);
366     }
367     if (curComparator_ != null && !list_.isEmpty())
368     {
369       Collections.sort(list_, 
370         new Comparator()
371         {
372           public int compare(
373             Object o1,
374             Object o2)
375           {
376             return curComparator_.compare((Item)o1, (Item)o2);
377           }
378         });
379       fireContentsChanged(this, 0, list_.size()-1);
380     }
381     if (sizeChangeListener_ != null)
382     {
383       sizeChangeListener_.sizeChanged(this);
384     }
385   }
386   
387   
388   /**
389    * Clear the whole list
390    */
391   /*package*/ void clear()
392   {
393     int s = list_.size();
394     list_.clear();
395     delim_.unlinkThis();
396     totalSize_ = 0;
397     if (s > 0)
398     {
399       fireIntervalRemoved(this, 0, s-1);
400     }
401     if (sizeChangeListener_ != null)
402     {
403       sizeChangeListener_.sizeChanged(this);
404     }
405   }
406   
407   
408   private int findSongIndex(
409     SongID songID)
410   {
411     for (int i=list_.size()-1; i>=0; --i)
412     {
413       Item info = (Item)list_.get(i);
414       if (info.getSongID().equals(songID))
415       {
416         return i;
417       }
418     }
419     return -1;
420   }
421   
422   
423   private Item findItem(
424     SongID songID)
425   {
426     for (LinkedObject elem = delim_.getNext(); elem != delim_; elem = elem.getNext())
427     {
428       Item item = (Item)elem;
429       if (item.getSongID().equals(songID))
430       {
431         return item;
432       }
433     }
434     return null;
435   }
436   
437   
438   /**
439    * Remove a song from the given list model
440    */
441   /*package*/ void removeSongID(
442     SongID songID)
443   {
444     int index = findSongIndex(songID);
445     if (index == -1)
446     {
447       Item item = findItem(songID);
448       if (item != null)
449       {
450         item.unlinkThis();
451         --totalSize_;
452       }
453     }
454     else
455     {
456       Item item = (Item)list_.remove(index);
457       item.unlinkThis();
458       --totalSize_;
459       fireIntervalRemoved(this, index, index);
460     }
461     if (sizeChangeListener_ != null)
462     {
463       sizeChangeListener_.sizeChanged(this);
464     }
465   }
466   
467   
468   /**
469    * Notify that a song name has been updated
470    */
471   /*package*/ int updateSongID(
472     SongID songID)
473   {
474     int index = findSongIndex(songID);
475     if (index == -1)
476     {
477       Item item = findItem(songID);
478       if (item != null)
479       {
480         item.update();
481       }
482     }
483     else
484     {
485       Item item = (Item)list_.get(index);
486       item.update();
487       if (curComparator_ != null &&
488         (index > 0 &&
489           curComparator_.compare((Item)list_.get(index-1), item) > 0) ||
490         (index < list_.size()-1 &&
491           curComparator_.compare(item, (Item)list_.get(index+1)) > 0))
492       {
493         list_.remove(index);
494         fireIntervalRemoved(this, index, index);
495         index = Collections.binarySearch(list_, item,
496           new Comparator()
497           {
498             public int compare(
499               Object o1,
500               Object o2)
501             {
502               return curComparator_.compare((Item)o1, (Item)o2);
503             }
504           });
505         if (index < 0)
506         {
507           index = -index-1;
508         }
509         list_.add(index, item);
510         fireIntervalAdded(this, index, index);
511       }
512       else
513       {
514         fireContentsChanged(this, index, index);
515       }
516       if (sizeChangeListener_ != null)
517       {
518         sizeChangeListener_.sizeChanged(this);
519       }
520     }
521     return index;
522   }
523   
524   
525   /*package*/ String getNameForSongID(
526     SongID id)
527   {
528     Item item = findItem(id);
529     if (item == null)
530     {
531       return null;
532     }
533     return item.name_;
534   }
535   
536   
537   /**
538    * Set the comparator used to order songs.
539    * A null comparator makes the ordering undefined.
540    */
541   /*package*/ void setComparator(
542     ItemComparator comparator)
543   {
544     if (!curComparator_.equals(comparator))
545     {
546       curComparator_ = comparator;
547       if (curComparator_ != null && !list_.isEmpty())
548       {
549         Collections.sort(list_,
550           new Comparator()
551           {
552             public int compare(
553               Object o1,
554               Object o2)
555             {
556               return curComparator_.compare((Item)o1, (Item)o2);
557             }
558           });
559         fireContentsChanged(this, 0, list_.size()-1);
560       }
561     }
562   }
563   
564   
565   /**
566    * Get the comparator currently used to order songs.
567    * A null comparator makes the ordering undefined.
568    */
569   /*package*/ ItemComparator getComparator()
570   {
571     return curComparator_;
572   }
573   
574   
575   /**
576    * This method is okay to be called from outside the AWT thread
577    */
578   /*package*/ void setFilter(
579     final PredicateSemantics filter)
580   {
581     try
582     {
583       SongIDResultSet resultSet = database_.performOperation(
584         new PredicateFilterSemantics.DefaultImplementation(filter), null);
585       final List newList = new ArrayList();
586       for (LinkedObject elem = delim_.getNext(); elem != delim_; elem = elem.getNext())
587       {
588         Item item = (Item)elem;
589         if (resultSet.contains(item.getSongID()))
590         {
591           newList.add(item);
592         }
593       }
594       if (curComparator_ != null)
595       {
596         Collections.sort(newList, 
597           new Comparator()
598           {
599             public int compare(
600               Object o1,
601               Object o2)
602             {
603               return curComparator_.compare((Item)o1, (Item)o2);
604             }
605           });
606       }
607       SwingUtilities.invokeLater(
608         new Runnable()
609         {
610           public void run()
611           {
612             if (!list_.isEmpty())
613             {
614               fireIntervalRemoved(SongListModel.this, 0, list_.size()-1);
615             }
616             list_ = newList;
617             if (!list_.isEmpty())
618             {
619               fireIntervalAdded(SongListModel.this, 0, list_.size()-1);
620             }
621             filterPredicate_ = filter;
622             if (sizeChangeListener_ != null)
623             {
624               sizeChangeListener_.sizeChanged(SongListModel.this);
625             }
626           }
627         });
628     }
629     catch (SongDatabaseFailedException ex)
630     {
631     }
632   }
633   
634   
635   /*package*/ PredicateSemantics getFilter()
636   {
637     return filterPredicate_;
638   }
639   
640   
641   /*package*/ void setSizeChangeListener(
642     SizeChangeListener listener)
643   {
644     sizeChangeListener_ = listener;
645   }
646 }