1 /* SimpleEntryGroup.java
2  *
3  * created: Wed Nov 11 1998
4  *
5  * This file is part of Artemis
6  *
7  * Copyright(C) 1998,1999,2000,2001,2002  Genome Research Limited
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or(at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SimpleEntryGroup.java,v 1.8 2008-06-11 15:15:23 tjc Exp $
24  **/
25 
26 package uk.ac.sanger.artemis;
27 
28 import uk.ac.sanger.artemis.sequence.*;
29 import uk.ac.sanger.artemis.components.MessageDialog;
30 import uk.ac.sanger.artemis.io.GFFDocumentEntry;
31 import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
32 import uk.ac.sanger.artemis.io.Range;
33 import uk.ac.sanger.artemis.io.StreamSequence;
34 import uk.ac.sanger.artemis.io.SimpleDocumentEntry;
35 import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
36 import uk.ac.sanger.artemis.io.DocumentEntry;
37 import uk.ac.sanger.artemis.util.DatabaseDocument;
38 import uk.ac.sanger.artemis.util.ReadOnlyException;
39 import uk.ac.sanger.artemis.util.OutOfRangeException;
40 
41 import java.util.Vector;
42 import java.util.NoSuchElementException;
43 
44 /**
45  *  This class implements a vector of Entry objects, with additional methods
46  *  for querying and changing the feature tables of all the entries at
47  *  once.  Objects of this class act a bit like single Entry objects.
48  *
49  *  @author Kim Rutherford
50  *  @version $Id: SimpleEntryGroup.java,v 1.8 2008-06-11 15:15:23 tjc Exp $
51  **/
52 
53 public class SimpleEntryGroup extends EntryVector
54                               implements EntryGroup
55 {
56 
57   /** vector of those objects listening for entry change events. */
58   final private Vector entry_group_listener_list = new Vector();
59 
60   /** vector of those objects listening for entry change events. */
61   final private Vector entry_listener_list = new Vector();
62 
63   /** vector of those objects listening for feature change events. */
64   final private Vector feature_listener_list = new Vector();
65 
66   /** vector of entries that are currently active (visible). */
67   final private EntryVector active_entries = new EntryVector();
68 
69   /**
70    *  The default Entry for this SimpleEntryGroup.  The "default" is the Entry
71    *  where new features are created.
72    **/
73   private Entry default_entry = null;
74 
75   /** Bases object that was passed to the constructor. */
76   private Bases bases;
77 
78   /** Incremented by ref(), decremented by unref(). */
79   private int reference_count = 0;
80 
81   /** The ActionController of this EntryGroup (used for undo). */
82   final private ActionController action_controller = new ActionController();
83 
84   /**
85    *  Create a new empty SimpleEntryGroup object.
86    **/
SimpleEntryGroup(final Bases bases)87   public SimpleEntryGroup(final Bases bases)
88   {
89     this.bases = bases;
90 
91     addFeatureChangeListener(getActionController());
92     addEntryChangeListener(getActionController());
93     getBases().addSequenceChangeListener(getActionController(),
94                                          Bases.MIN_PRIORITY);
95     addEntryGroupChangeListener(getActionController());
96   }
97 
SimpleEntryGroup()98   public SimpleEntryGroup()
99   {
100     addFeatureChangeListener(getActionController());
101     addEntryGroupChangeListener(getActionController());
102   }
103 
104   /**
105    *  Returns true if and only if there are any unsaved changes in any of the
106    *  Entry objects in this EntryGroup.
107    **/
hasUnsavedChanges()108   public boolean hasUnsavedChanges()
109   {
110     final int my_size = size();
111     for(int entry_index = 0; entry_index < my_size;
112         ++entry_index)
113     {
114       if(elementAt(entry_index).hasUnsavedChanges())
115         return true;
116     }
117 
118     return false;
119   }
120 
121   /**
122    *  Return the default Entry for this SimpleEntryGroup.  The "default" is the
123    *  Entry where new features are created.
124    **/
getDefaultEntry()125   public Entry getDefaultEntry()
126   {
127     return default_entry;
128   }
129 
130   /**
131    *  Set the default Entry.  The "default" is the Entry where new features
132    *  are created.
133    *  @param entry The new default entry.  If this Entry is not active this
134    *    method will return immediately.
135    **/
setDefaultEntry(Entry entry)136   public void setDefaultEntry(Entry entry)
137   {
138     if(entry != null && !isActive(entry))
139       return;
140 
141     // do nothing
142     if(default_entry == entry)
143       return;
144     else
145       default_entry = entry;
146 
147     // now inform the listeners that a change has occured
148     final EntryGroupChangeEvent event =
149       new EntryGroupChangeEvent(this, getDefaultEntry(),
150                                 EntryGroupChangeEvent.NEW_DEFAULT_ENTRY);
151 
152     fireEvent(entry_group_listener_list, event);
153   }
154 
155   /**
156    *  Return the index of a feature within this object.  This method treats
157    *  all the features in all the active entries as if they were in one big
158    *  array.  The first feature of the first entry will have index 1, the
159    *  first from the second entry will have index 1 +(the number of features
160    *  in the first entry), etc.
161    *  @param feature The feature to find the index of.
162    *  @return The index of the feature or -1 if the feature isn't in any of
163    *    the entries.  The first index is 0 the last is the total number of
164    *    features in all the entries of this object minus one.
165    **/
indexOf(Feature feature)166   public int indexOf(Feature feature)
167   {
168     int feature_count_of_previous_entries = 0;
169     final int active_entries_size = active_entries.size();
170 
171     for(int entry_index = 0; entry_index < active_entries_size;
172         ++entry_index)
173     {
174       final Entry this_entry  = active_entries.elementAt(entry_index);
175       final int feature_index = this_entry.indexOf(feature);
176 
177       if(feature_index != -1)
178         return feature_index + feature_count_of_previous_entries;
179 
180       feature_count_of_previous_entries += this_entry.getFeatureCount();
181     }
182 
183     return -1;
184   }
185 
186   /**
187    *  Return true if any of the active entries in the group contains the given
188    *  feature.
189    **/
contains(Feature feature)190   public boolean contains(Feature feature)
191   {
192     final int active_entries_size = active_entries.size();
193 
194     for(int i = 0; i < active_entries_size; ++i)
195     {
196       final Entry current_entry = active_entries.elementAt(i);
197 
198       if(current_entry.contains(feature))
199         return true;
200     }
201 
202     return false;
203   }
204 
205   /**
206    *  Return true if the given Entry is active(visible).  The Feature objects
207    *  in an Entry that is not active will be ignored by the methods that deal
208    *  will features: featureAt(), indexOf(), contains(), features(), etc.
209    **/
isActive(Entry entry)210   public boolean isActive(Entry entry)
211   {
212     if(active_entries.contains(entry))
213       return true;
214     else
215       return false;
216   }
217 
218   /**
219    *  Set the "active" setting of the Entry at the given index.  If the index
220    *  refers to the default entry and new_active is false, the default entry
221    *  will be set to the active entry or null if there are no active entries.
222    *  @param index The index of the Entry to change.
223    *  @param active The new active setting.
224    **/
setIsActive(int index, boolean new_active)225   public void setIsActive(int index, boolean new_active)
226   {
227     final Entry entry = elementAt(index);
228 
229     if(new_active)
230     {
231       // no change
232       if(isActive(entry))
233         return;
234       else
235       {
236         // this is slow but it guarantees that the Entry references in the
237         // active_entries vector are in the same order as in the
238         // SimpleEntryGroup
239 
240         final EntryVector new_active_entries = new EntryVector();
241         final int my_size = size();
242 
243         for(int i = 0; i < my_size; ++i)
244         {
245           if(active_entries.contains(elementAt(i)) || index == i)
246             new_active_entries.add(elementAt(i));
247         }
248 
249         active_entries.removeAllElements();
250 
251         final int new_active_entries_size = new_active_entries.size();
252 
253         for(int i = 0; i < new_active_entries_size; ++i)
254           active_entries.add(new_active_entries.elementAt(i));
255 
256         if(active_entries.size() >= 1 && getDefaultEntry() == null)
257         {
258           // there was no default entry before calling addElement() so
259           // make the first non-sequence entry the default entry
260           if(active_entries.elementAt(0) == getSequenceEntry() &&
261              active_entries.size() == 1)
262           {
263             // don't set the default entry to be the sequence entry unless
264             // the user asks for it
265             setDefaultEntry(null);
266           }
267           else
268           {
269             if(active_entries.size() == 1)
270               setDefaultEntry(active_entries.elementAt(0));
271             else
272               setDefaultEntry(active_entries.elementAt(1));
273           }
274         }
275       }
276     }
277     else
278     {
279       // no change
280       if(!isActive(entry))
281         return;
282       else
283       {
284         active_entries.removeElement(entry);
285 
286         if(entry == getDefaultEntry())
287         {
288           if(active_entries.size() > 0)
289           {
290             if(active_entries.elementAt(0) == getSequenceEntry())
291             {
292               // don't set the default entry to be the sequence entry unless
293               // the user asks for it
294               if(active_entries.size() > 1)
295                 setDefaultEntry(active_entries.elementAt(1));
296               else
297                 setDefaultEntry(null);
298             }
299             else
300               setDefaultEntry(active_entries.elementAt(0));
301           }
302           else
303             setDefaultEntry(null);
304         }
305       }
306     }
307 
308     // now inform the listeners that a change has occured
309     final EntryGroupChangeEvent event;
310 
311     // change state
312     if(new_active)        // become active
313       event = new EntryGroupChangeEvent(this, entry,
314                                         EntryGroupChangeEvent.ENTRY_ACTIVE);
315     else                  // become inactive
316       event = new EntryGroupChangeEvent(this, entry,
317                                         EntryGroupChangeEvent.ENTRY_INACTIVE);
318 
319     fireEvent(entry_group_listener_list, event);
320   }
321 
322   /**
323    *  Set the "active" setting of the given Entry.  The Entry is the default
324    *  entry and new_active is false, the default entry will be set to the
325    *  active entry or null if there are no active entries.
326    *  @param entry The Entry to activate or deactivate.
327    *  @param new_active The new active setting.
328    **/
setIsActive(final Entry entry, final boolean new_active)329   public void setIsActive(final Entry entry, final boolean new_active)
330   {
331     setIsActive(indexOf(entry), new_active);
332   }
333 
334 
335   /**
336    *  Return the Entry from this SimpleEntryGroup that contains the sequence
337    *  to view or return null if none of the entries contains a sequence.
338    **/
getSequenceEntry()339   public Entry getSequenceEntry()
340   {
341     if(size() == 0)
342       return null;
343     else
344       return elementAt(0);
345   }
346 
347   /**
348    *  Returns the base length of the sequence of the first Entry in this group
349    *  or 0 if this group is empty.
350    **/
getSequenceLength()351   public int getSequenceLength()
352   {
353     return getBases().getLength();
354   }
355 
356   /**
357    *  Returns the Bases object of the first Entry in this group or null if
358    *  this group is empty.
359    **/
getBases()360   public Bases getBases()
361   {
362     return bases;
363   }
364 
365   /**
366    *  Reverse and complement the sequence and all features in every Entry in
367    *  this SimpleEntryGroup.
368    **/
reverseComplement()369   public void reverseComplement()
370       throws ReadOnlyException
371   {
372     if(isReadOnly())
373       throw new ReadOnlyException();
374 
375     // reverse the sequence
376     getBases().reverseComplement();
377   }
378 
379   /**
380    *  Return true if and only if one or more of the entries or features in
381    *  this SimpleEntryGroup are read-only.
382    **/
isReadOnly()383   public boolean isReadOnly()
384   {
385     final int my_size = size();
386     for(int i = 0; i < my_size; ++i)
387     {
388       final Entry this_entry = elementAt(i);
389 
390       if(this_entry.isReadOnly())
391         return true;
392 
393       final FeatureEnumeration feature_enum = this_entry.features();
394 
395       while(feature_enum.hasMoreFeatures())
396       {
397         if(feature_enum.nextFeature().isReadOnly())
398           return true;
399       }
400     }
401 
402     return false;
403   }
404 
405   /**
406    *  Increment the reference count for this EntryGroup.
407    **/
ref()408   public void ref()
409   {
410     ++reference_count;
411   }
412 
413   /**
414    *  Decrement the reference count for this EntryGroup.  When the reference
415    *  count goes to zero a EntryGroupChangeEvent is sent to all
416    *  EntryGroupChangeListeners with type EntryGroupChangeEvent.DONE_GONE.
417    *  The listeners should then stop using the EntryGroup and release any
418    *  associated resources.
419    **/
unref()420   public void unref()
421   {
422     --reference_count;
423 
424     if(reference_count == 0)
425     {
426       // remove all the entries which will close any edit or view windows
427       while(size() > 0)
428       {
429         final Entry this_entry = elementAt(0);
430         remove(this_entry);
431 
432         this_entry.getEMBLEntry().dispose();
433       }
434 
435       // now inform the listeners that the EntryGroup is no more
436       final EntryGroupChangeEvent event =
437               new EntryGroupChangeEvent(this, null,
438                                         EntryGroupChangeEvent.DONE_GONE);
439 
440       fireEvent(entry_group_listener_list, event);
441     }
442   }
443 
444   /**
445    *  Return the current reference count.
446    **/
refCount()447   public int refCount()
448   {
449     return reference_count;
450   }
451 
452   /**
453    *  Return the Feature at the given index.  This method treats all the
454    *  features in all the entries as if they were in one big array.  See
455    *  the comment on indexOf().
456    *  @param index The index of the required Feature.
457    *  @return The Feature at the given index.  The first index is 0 the last
458    *    is the total number of features in all the entries of this object minus
459    *    one.  If the index is out of range then null will be returned.
460    **/
featureAt(int index)461   public Feature featureAt(int index)
462   {
463     if(index < 0)
464       throw new Error("internal error - index out of range: " + index);
465 
466     final int active_entries_size = active_entries.size();
467 
468     for(int entry_index = 0; entry_index < active_entries_size;
469         ++entry_index)
470     {
471       final Entry this_entry = active_entries.elementAt(entry_index);
472 
473       if(index < this_entry.getFeatureCount())
474         return this_entry.getFeature(index);
475 
476       index -= this_entry.getFeatureCount();
477     }
478 
479     throw new Error("internal error - index out of range: " + index);
480   }
481 
482   /**
483    *  Return a vector containing the references of the Feature objects within
484    *  the given range of indices.
485    *  @param start_index The index of the first feature to return.
486    *  @param end_index The index of the last feature to return.
487    **/
getFeaturesInIndexRange(final int start_index, final int end_index)488   public FeatureVector getFeaturesInIndexRange(final int start_index,
489                                                final int end_index)
490   {
491     final FeatureVector return_vector = new FeatureVector();
492 
493     for(int i = start_index; i <= end_index; ++i)
494       return_vector.add(featureAt(i));
495 
496     return return_vector;
497   }
498 
499   /**
500    *  Return a vector containing the references of the Feature objects within
501    *  the given range for all the active entries in the SimpleEntryGroup.
502    *  @param range Return features that overlap this range - ie the start of
503    *    the feature is less than or equal to the end of the range and the end
504    *    of the feature is greater than or equal to the start of the range.
505    *  @return The non-source key features of this feature table the are within
506    *    the given range.  The returned object is a copy - changes will not
507    *    effect the FeatureTable object itself.
508    **/
getFeaturesInRange(Range range)509   public FeatureVector getFeaturesInRange(Range range)
510       throws OutOfRangeException
511   {
512     final FeatureVector return_vector = new FeatureVector();
513     final int my_size = size();
514 
515     for(int i = 0; i < my_size; ++i)
516     {
517       final Entry this_entry = elementAt(i);
518 
519       if(isActive(this_entry))
520       {
521         final FeatureVector visible_entry_features =
522           elementAt(i).getFeaturesInRange(range);
523 
524         final int visible_entry_features_size = visible_entry_features.size();
525 
526         for(int feature_index = 0; feature_index < visible_entry_features_size;
527             ++feature_index)
528         {
529           final Feature this_feature =
530             visible_entry_features.elementAt(feature_index);
531           return_vector.add(this_feature);
532         }
533       }
534     }
535 
536     return return_vector;
537   }
538 
539   /**
540    *  Return a vector containing the references of the Feature objects from
541    *  all the active entries in the SimpleEntryGroup.
542    *  @return The non-source key features in active entries of this
543    *    SimpleEntryGroup.  The returned object is a copy - changes will not
544    *    effect the SimpleEntryGroup object itself.
545    **/
getAllFeatures()546   public FeatureVector getAllFeatures()
547   {
548     final FeatureVector return_vector = new FeatureVector();
549     final int my_size = size();
550 
551     for(int i = 0; i < my_size; ++i)
552     {
553       final Entry this_entry = elementAt(i);
554 
555       if(isActive(this_entry))
556       {
557         final FeatureVector entry_features = elementAt(i).getAllFeatures();
558         final int entry_features_size = entry_features.size();
559 
560         for(int feature_index = 0; feature_index < entry_features_size;
561             ++feature_index)
562         {
563           final Feature this_feature =
564             entry_features.elementAt(feature_index);
565           return_vector.add(this_feature);
566         }
567       }
568     }
569 
570     return return_vector;
571   }
572 
573   /**
574    *  Return a count of the number of Feature objects from all the active
575    *  entries in the SimpleEntryGroup.
576    *  @return A count of the non-source key features in active entries of this
577    *    SimpleEntryGroup.
578    **/
getAllFeaturesCount()579   public int getAllFeaturesCount()
580   {
581     int return_count = 0;
582     final int my_size = size();
583 
584     for(int i = 0; i < my_size; ++i)
585     {
586       final Entry this_entry = elementAt(i);
587 
588       if(isActive(this_entry))
589         return_count += this_entry.getFeatureCount();
590     }
591 
592     return return_count;
593   }
594 
595   /**
596    *  Add an Entry to this object and then emit the appropriate EntryChange
597    *  events.
598    **/
addElement(Entry entry)599   public void addElement(Entry entry)
600   {
601     super.addElement(entry);
602 
603     // set the default Entry to whichever Entry gets added first
604     if(default_entry == null)
605       default_entry = entry;
606 
607     active_entries.add(entry);
608 
609     // now inform the listeners that an addition has occured
610     final EntryGroupChangeEvent event =
611       new EntryGroupChangeEvent(this, entry,
612                                 EntryGroupChangeEvent.ENTRY_ADDED);
613 
614     fireEvent(entry_group_listener_list, event);
615 
616     // make the new entry the default entry if and only if there was no entry
617     // previously or there was only one entry previously and it contained
618     // sequence but no features
619     if(size() == 1)
620       setDefaultEntry(entry);
621     else
622     {
623       if(size() == 2)
624       {
625         final Entry first_entry = elementAt(0);
626 
627         if(first_entry.getFeatureCount() == 0)
628         {
629           final Bases first_entry_bases = first_entry.getBases();
630 
631           if(first_entry_bases != null &&
632              first_entry_bases.getLength() > 0)
633             setDefaultEntry(entry);
634         }
635       }
636     }
637 
638     entry.addEntryChangeListener(this);
639     entry.addFeatureChangeListener(this);
640   }
641 
642   /**
643    *  A convenience method that does the same as addElement(Entry).
644    **/
add(final Entry entry)645   public void add(final Entry entry)
646   {
647     if(entry.getEMBLEntry() instanceof IndexedGFFDocumentEntry)
648       ((IndexedGFFDocumentEntry)entry.getEMBLEntry()).setEntryGroup(this);
649     else if(entry.getEMBLEntry() instanceof GFFDocumentEntry)
650     {
651       ((GFFDocumentEntry)entry.getEMBLEntry()).adjustCoordinates( getSequenceEntry() );
652       if(!Options.isBlackBeltMode() && size() > 1 &&
653           entry.getEMBLEntry().getSequence() != null )
654       {
655         new MessageDialog (null, "Warning",
656             "Overlaying a GFF with a sequence onto an entry with a sequence.",
657             false);
658       }
659     }
660 
661     addElement(entry);
662   }
663 
664   /**
665    *  Remove an Entry from this object and then emit the appropriate
666    *  EntryGroupChange events.  The first entry in the group can only be
667    *  removed if it is the only Entry because the first Entry contains the
668    *  sequence.
669    *  @return true if the removal succeeded, false if it fails(which can if
670    *    the given Entry isn't in this SimpleEntryGroup or if the user tries to
671    *    remove the first Entry).
672    **/
removeElement(final Entry entry)673   public boolean removeElement(final Entry entry)
674   {
675     // this call will sort out the default entry
676     setIsActive(indexOf(entry), false);
677 
678     entry.dispose();
679 
680     final boolean remove_return = super.removeElement(entry);
681 
682     entry.removeEntryChangeListener(this);
683     entry.removeFeatureChangeListener(this);
684 
685     active_entries.removeElement(entry);
686 
687     // now inform the listeners that a deletion has occured
688     final EntryGroupChangeEvent event =
689       new EntryGroupChangeEvent(this, entry,
690                                 EntryGroupChangeEvent.ENTRY_DELETED);
691 
692     fireEvent(entry_group_listener_list, event);
693 
694     return remove_return;
695   }
696 
697   /**
698    *  A convenience method that does the same as removeElement(Entry).
699    **/
remove(final Entry entry)700   public boolean remove(final Entry entry)
701   {
702     return removeElement(entry);
703   }
704 
705   /**
706    *  Create a new(blank) Feature in the default Entry of this
707    *  SimpleEntryGroup.  See getDefaultEntry() and Entry.createFeature().
708    *  @return The new Feature.
709    **/
createFeature()710   public Feature createFeature() throws ReadOnlyException
711   {
712     final Feature new_feature = getDefaultEntry().createFeature();
713     return new_feature;
714   }
715 
716   /**
717    *  Create a new(empty) Entry in this SimpleEntryGroup.  See
718    *  Entry.newEntry().
719    *  @return The reference of the new Entry.
720    **/
createEntry()721   public Entry createEntry()
722   {
723     Entry new_entry = null;
724     Entry default_entry = getDefaultEntry();
725     if(default_entry != null &&
726        default_entry.getEMBLEntry() != null &&
727        default_entry.getEMBLEntry() instanceof DatabaseDocumentEntry)
728     {
729       DatabaseDocument doc =
730         (DatabaseDocument)((DocumentEntry)default_entry.getEMBLEntry()).getDocument();
731       DatabaseDocument new_doc = doc.createDatabaseDocument();
732 
733       try
734       {
735         DatabaseDocumentEntry new_doc_entry =
736           new DatabaseDocumentEntry();
737         new_doc_entry.setDocument(new_doc);
738         new_entry = new Entry(getBases(), new_doc_entry);
739       }
740       catch(Exception e)
741       {
742         e.printStackTrace();
743       }
744 
745     }
746     else
747       new_entry = Entry.newEntry(getBases());
748 
749     add(new_entry);
750     return new_entry;
751   }
752 
753   /**
754    *  Create a new(empty) Entry in this SimpleEntryGroup.  See
755    *  Entry.newEntry().
756    *  @param name The(file) name of the new Entry.
757    *  @return The reference of the new Entry.
758    **/
createEntry(final String name)759   public Entry createEntry(final String name)
760   {
761     final Entry new_entry = createEntry();
762     new_entry.setName(name);
763     return new_entry;
764   }
765 
766   /**
767    *  Returns an enumeration of the Feature objects in this
768    *  SimpleEntryGroup. The returned FeatureEnumeration object will generate
769    *  all features in this object in turn. The first item generated is the
770    *  item at index 0, then the item at index 1, and so on.
771    **/
features()772   public FeatureEnumeration features()
773   {
774     return new FeatureEnumerator();
775   }
776 
777   /**
778    *  An Enumeration of Feature objects.
779    **/
780   public class FeatureEnumerator implements FeatureEnumeration
781   {
782 
783     /**
784      *  The EntryVector object that we are enumerating.  Set to null when there
785      *  are no more Feature objects.
786      **/
787     private EntryVector active_entries;
788 
789     /**
790      *  The index of the Entry that we will get the next Feature from.
791      **/
792     private int entry_index = -1;
793 
794     /** Enumeration for the current entry */
795     private FeatureEnumeration feature_enumerator;
796 
797     /**
798      *  Create a new FeatureEnumeration that will enumerate the enclosing
799      *  SimpleEntryGroup object.  The SimpleEntryGroup object must not be
800      *  changed while the enumeration is active.
801      **/
FeatureEnumerator()802     public FeatureEnumerator()
803     {
804       active_entries = getActiveEntries();
805 
806       entry_index = 0;
807 
808       if(active_entries.size() > 0)
809         feature_enumerator =
810           active_entries.elementAt(entry_index).features();
811       else
812         feature_enumerator = null;
813     }
814 
815     /**
816      *  See the FeatureEnumeration interface for details.
817      **/
hasMoreFeatures()818     public boolean hasMoreFeatures()
819     {
820       if(feature_enumerator == null)
821         return false;
822 
823       if(feature_enumerator.hasMoreFeatures())
824         return true;
825 
826       ++entry_index;
827 
828       if(entry_index == active_entries.size())
829         return false;
830       else
831       {
832         feature_enumerator =
833           active_entries.elementAt(entry_index).features();
834         return hasMoreFeatures();
835       }
836     }
837 
838     /**
839      *  See the FeatureEnumeration interface for details.
840      **/
nextFeature()841     public Feature nextFeature()
842         throws NoSuchElementException
843     {
844       if(feature_enumerator == null)
845         throw new NoSuchElementException();
846 
847       return feature_enumerator.nextFeature();
848     }
849 
850   }
851 
852   /**
853    *  Implementation of the FeatureChangeListener interface.  We listen for
854    *  changes in every feature of every entry in this group.
855    **/
featureChanged(FeatureChangeEvent event)856   public void featureChanged(FeatureChangeEvent event)
857   {
858     // pass the action straight through
859     fireEvent(feature_listener_list, event);
860   }
861 
862   /**
863    *  Implementation of the EntryChangeListener interface.  We listen for
864    *  changes from every entry in this group and pass the events though to all
865    *  the object listening for EntryChangeEvents for the event from this
866    *  SimpleEntryGroup.
867    **/
entryChanged(EntryChangeEvent event)868   public void entryChanged(EntryChangeEvent event)
869   {
870     // pass the action straight through
871     fireEvent(entry_listener_list, event);
872   }
873 
874   /**
875    *  Send an event to those object listening for it.
876    *  @param listeners A Vector of the objects that the event should be sent
877    *    to.
878    *  @param event The event to send
879    **/
fireEvent(Vector listeners, ChangeEvent event)880   private void fireEvent(Vector listeners, ChangeEvent event)
881   {
882     final Vector targets;
883     // copied from a book - synchronising the whole method might cause a
884     // deadlock
885     synchronized(this)
886     {
887       targets = (Vector)listeners.clone();
888     }
889 
890     //boolean seen_chado_manager = false;
891     final int targets_size = targets.size();
892     for(int i = 0; i < targets_size; ++i)
893     {
894       ChangeListener target =(ChangeListener) targets.elementAt(i);
895 
896       if(event instanceof EntryGroupChangeEvent)
897       {
898         final EntryGroupChangeListener entry_group_change_listener =
899          (EntryGroupChangeListener) target;
900         final EntryGroupChangeEvent group_change_event =
901          (EntryGroupChangeEvent) event;
902         entry_group_change_listener.entryGroupChanged(group_change_event);
903       }
904       else
905       {
906         if(event instanceof EntryChangeEvent)
907         {
908           final EntryChangeListener entry_change_listener =
909                                      (EntryChangeListener) target;
910 
911 //        if(entry_change_listener instanceof ChadoTransactionManager)
912 //        {
913             // just call this listener once
914 //          if(!seen_chado_manager)
915 //          {
916 //            entry_change_listener.entryChanged((EntryChangeEvent) event);
917 //            seen_chado_manager = true;
918 //          }
919 //        }
920 //        else
921             entry_change_listener.entryChanged((EntryChangeEvent) event);
922         }
923         else
924         {
925           final FeatureChangeListener feature_change_listener =
926                                     (FeatureChangeListener) target;
927           feature_change_listener.featureChanged((FeatureChangeEvent) event);
928         }
929 
930       }
931     }
932   }
933 
934   /**
935    *  Adds the specified event listener to receive entry group change events
936    *  from this object.
937    *  @param l the event change listener.
938    **/
addEntryGroupChangeListener(EntryGroupChangeListener l)939   public void addEntryGroupChangeListener(EntryGroupChangeListener l)
940   {
941     entry_group_listener_list.addElement(l);
942   }
943 
944   /**
945    *  Removes the specified event listener so that it no longer receives
946    *  entry group change events from this object.
947    *  @param l the event change listener.
948    **/
removeEntryGroupChangeListener(EntryGroupChangeListener l)949   public void removeEntryGroupChangeListener(EntryGroupChangeListener l)
950   {
951     entry_group_listener_list.removeElement(l);
952   }
953 
954   /**
955    *  Adds the specified event listener to receive entry change events from
956    *  this object.
957    *  @param l the event change listener.
958    **/
addEntryChangeListener(EntryChangeListener l)959   public void addEntryChangeListener(EntryChangeListener l)
960   {
961     entry_listener_list.addElement(l);
962   }
963 
964   /**
965    *  Removes the specified event listener so that it no longer receives
966    *  entry change events from this object.
967    *  @param l the event change listener.
968    **/
removeEntryChangeListener(EntryChangeListener l)969   public void removeEntryChangeListener(EntryChangeListener l)
970   {
971     entry_listener_list.removeElement(l);
972   }
973 
974   /**
975    *  Adds the specified event listener to receive feature change events from
976    *  this object.
977    *  @param l the event change listener.
978    **/
addFeatureChangeListener(FeatureChangeListener l)979   public void addFeatureChangeListener(FeatureChangeListener l)
980   {
981     feature_listener_list.addElement(l);
982   }
983 
984   /**
985    *  Removes the specified event listener so that it no longer receives
986    *  feature change events from this object.
987    *  @param l the event change listener.
988    **/
removeFeatureChangeListener(FeatureChangeListener l)989   public void removeFeatureChangeListener(FeatureChangeListener l)
990   {
991     feature_listener_list.removeElement(l);
992   }
993 
994   /**
995    *  Return the reference of an EntryVector containing the active entries of
996    *  this EntryGroup.
997    **/
getActiveEntries()998   public EntryVector getActiveEntries()
999   {
1000     return(EntryVector) active_entries.clone();
1001   }
1002 
1003   /**
1004    *  This method translates the start and end of every each Range in every
1005    *  Location into another coordinate system.  The Ranges will be truncated
1006    *  if necessary.
1007    *  @param constraint This contains the start and end base of the new
1008    *    coordinate system.  The position given by constraint.getStart() will
1009    *    be at postion/base 1 in the new coordinate system.
1010    *  @return a copy of the EntryGroup which has been translated into the new
1011    *    coordinate system.
1012    **/
truncate(final Range constraint)1013   public EntryGroup truncate(final Range constraint)
1014   {
1015     final Bases new_bases = getBases().truncate(constraint);
1016 
1017     final EntryGroup new_entry_group = new SimpleEntryGroup(new_bases);
1018     final int my_size = size();
1019 
1020     for(int i = 0; i < my_size; ++i)
1021     {
1022       final Entry this_entry = elementAt(i);
1023       final Entry new_entry = this_entry.truncate(new_bases, constraint);
1024       new_entry_group.add(new_entry);
1025     }
1026 
1027     if(size() > 0)
1028     {
1029       final StreamSequence sequence =
1030        (StreamSequence) new_bases.getSequence();
1031       final SimpleDocumentEntry document_entry =
1032        (SimpleDocumentEntry) new_entry_group.elementAt(0).getEMBLEntry();
1033       document_entry.setSequence(sequence);
1034     }
1035 
1036     return new_entry_group;
1037   }
1038 
1039   /**
1040    *  Return the ActionController for this EntryGroup(for undo).
1041    **/
getActionController()1042   public ActionController getActionController()
1043   {
1044     return action_controller;
1045   }
1046 
1047 }
1048