/* SimpleEntryGroup.java
 *
 * created: Wed Nov 11 1998
 *
 * This file is part of Artemis
 *
 * Copyright(C) 1998,1999,2000,2001,2002  Genome Research Limited
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or(at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /cvsroot/pathsoft/artemis/uk/ac/sanger/artemis/SimpleEntryGroup.java,v 1.5 2006/08/07 14:57:10 tjc Exp $
 **/

package uk.ac.sanger.artemis;

import uk.ac.sanger.artemis.sequence.*;
import uk.ac.sanger.artemis.io.EntryInformationException;
import uk.ac.sanger.artemis.io.Range;
import uk.ac.sanger.artemis.io.StreamSequence;
import uk.ac.sanger.artemis.io.SimpleDocumentEntry;
import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
import uk.ac.sanger.artemis.io.DocumentEntry;
import uk.ac.sanger.artemis.util.DatabaseDocument;
import uk.ac.sanger.artemis.util.ReadOnlyException;
import uk.ac.sanger.artemis.util.OutOfRangeException;
import uk.ac.sanger.artemis.chado.ChadoTransactionManager;

import java.io.IOException;
import java.util.Vector;
import java.util.NoSuchElementException;

/**
 *  This class implements a vector of Entry objects, with additional methods
 *  for querying and changing the feature tables of all the entries at
 *  once.  Objects of this class act a bit like single Entry objects.
 *
 *  @author Kim Rutherford
 *  @version $Id: SimpleEntryGroup.java,v 1.5 2006/08/07 14:57:10 tjc Exp $
 **/

public class SimpleEntryGroup extends EntryVector
                              implements EntryGroup
{

  /** vector of those objects listening for entry change events. */
  final private Vector entry_group_listener_list = new Vector();

  /** vector of those objects listening for entry change events. */
  final private Vector entry_listener_list = new Vector();

  /** vector of those objects listening for feature change events. */
  final private Vector feature_listener_list = new Vector();

  /** vector of entries that are currently active (visible). */
  final private EntryVector active_entries = new EntryVector();

  /**
   *  The default Entry for this SimpleEntryGroup.  The "default" is the Entry
   *  where new features are created.
   **/
  private Entry default_entry = null;

  /** Bases object that was passed to the constructor. */
  private Bases bases;

  /** Incremented by ref(), decremented by unref(). */
  private int reference_count = 0;

  /** The ActionController of this EntryGroup (used for undo). */
  final private ActionController action_controller = new ActionController();

  /** 
   *  Create a new empty SimpleEntryGroup object.
   **/
  public SimpleEntryGroup(final Bases bases) 
  {
    this.bases = bases;

    addFeatureChangeListener(getActionController());
    addEntryChangeListener(getActionController());
    getBases().addSequenceChangeListener(getActionController(),
                                         Bases.MIN_PRIORITY);
    addEntryGroupChangeListener(getActionController());
  }

  /**
   *  Returns true if and only if there are any unsaved changes in any of the
   *  Entry objects in this EntryGroup.
   **/
  public boolean hasUnsavedChanges() 
  {
    final int my_size = size();
    for(int entry_index = 0; entry_index < my_size;
        ++entry_index) 
    {
      if(elementAt(entry_index).hasUnsavedChanges()) 
        return true;
    }

    return false;
  }

  /**
   *  Return the default Entry for this SimpleEntryGroup.  The "default" is the
   *  Entry where new features are created.
   **/
  public Entry getDefaultEntry() 
  {
    return default_entry;
  }

  /**
   *  Set the default Entry.  The "default" is the Entry where new features
   *  are created.
   *  @param entry The new default entry.  If this Entry is not active this
   *    method will return immediately.
   **/
  public void setDefaultEntry(Entry entry) 
  {
    if(entry != null && !isActive(entry))
      return;

    // do nothing
    if(default_entry == entry) 
      return;
    else 
      default_entry = entry;

    // now inform the listeners that a change has occured
    final EntryGroupChangeEvent event =
      new EntryGroupChangeEvent(this, getDefaultEntry(),
                                EntryGroupChangeEvent.NEW_DEFAULT_ENTRY);

    fireEvent(entry_group_listener_list, event);
  }

  /**
   *  Return the index of a feature within this object.  This method treats
   *  all the features in all the active entries as if they were in one big
   *  array.  The first feature of the first entry will have index 1, the
   *  first from the second entry will have index 1 +(the number of features
   *  in the first entry), etc.
   *  @param feature The feature to find the index of.
   *  @return The index of the feature or -1 if the feature isn't in any of
   *    the entries.  The first index is 0 the last is the total number of
   *    features in all the entries of this object minus one.
   **/
  public int indexOf(Feature feature) 
  {
    int feature_count_of_previous_entries = 0;
    final int active_entries_size = active_entries.size();

    for(int entry_index = 0; entry_index < active_entries_size;
        ++entry_index) 
    {
      final Entry this_entry  = active_entries.elementAt(entry_index);
      final int feature_index = this_entry.indexOf(feature);

      if(feature_index != -1) 
        return feature_index + feature_count_of_previous_entries;

      feature_count_of_previous_entries += this_entry.getFeatureCount();
    }

    return -1;
  }

  /**
   *  Return true if any of the active entries in the group contains the given
   *  feature.
   **/
  public boolean contains(Feature feature) 
  {
    final int active_entries_size = active_entries.size();

    for(int i = 0; i < active_entries_size; ++i) 
    {
      final Entry current_entry = active_entries.elementAt(i);

      if(current_entry.contains(feature)) 
        return true;
    }

    return false;
  }

  /**
   *  Return true if the given Entry is active(visible).  The Feature objects
   *  in an Entry that is not active will be ignored by the methods that deal
   *  will features: featureAt(), indexOf(), contains(), features(), etc.
   **/
  public boolean isActive(Entry entry) 
  {
    if(active_entries.contains(entry)) 
      return true;
    else 
      return false;
  }

  /**
   *  Set the "active" setting of the Entry at the given index.  If the index
   *  refers to the default entry and new_active is false, the default entry
   *  will be set to the active entry or null if there are no active entries.
   *  @param index The index of the Entry to change.
   *  @param active The new active setting.
   **/
  public void setIsActive(int index, boolean new_active) 
  {
    final Entry entry = elementAt(index);

    if(new_active)
    {
      // no change
      if(isActive(entry)) 
        return;
      else 
      {
        // this is slow but it guarantees that the Entry references in the
        // active_entries vector are in the same order as in the
        // SimpleEntryGroup

        final EntryVector new_active_entries = new EntryVector();
        final int my_size = size();

        for(int i = 0; i < my_size; ++i) 
        {
          if(active_entries.contains(elementAt(i)) || index == i) 
            new_active_entries.add(elementAt(i));
        }

        active_entries.removeAllElements();

        final int new_active_entries_size = new_active_entries.size();

        for(int i = 0; i < new_active_entries_size; ++i) 
          active_entries.add(new_active_entries.elementAt(i));

        if(active_entries.size() >= 1 && getDefaultEntry() == null) 
        {
          // there was no default entry before calling addElement() so
          // make the first non-sequence entry the default entry
          if(active_entries.elementAt(0) == getSequenceEntry() &&
             active_entries.size() == 1)
          {
            // don't set the default entry to be the sequence entry unless
            // the user asks for it
            setDefaultEntry(null);
          }
          else 
          {
            if(active_entries.size() == 1) 
              setDefaultEntry(active_entries.elementAt(0));
            else 
              setDefaultEntry(active_entries.elementAt(1));
          }
        }
      }
    }
    else 
    {
      // no change
      if(!isActive(entry)) 
        return;
      else
      {
        active_entries.removeElement(entry);

        if(entry == getDefaultEntry()) 
        {
          if(active_entries.size() > 0) 
          {
            if(active_entries.elementAt(0) == getSequenceEntry()) 
            {
              // don't set the default entry to be the sequence entry unless
              // the user asks for it
              if(active_entries.size() > 1)
                setDefaultEntry(active_entries.elementAt(1));
              else
                setDefaultEntry(null);
            }
            else 
              setDefaultEntry(active_entries.elementAt(0));
          }
          else 
            setDefaultEntry(null);
        }
      }
    }

    // now inform the listeners that a change has occured
    final EntryGroupChangeEvent event;

    // change state
    if(new_active)        // become active
      event = new EntryGroupChangeEvent(this, entry,
                                        EntryGroupChangeEvent.ENTRY_ACTIVE);
    else                  // become inactive
      event = new EntryGroupChangeEvent(this, entry,
                                        EntryGroupChangeEvent.ENTRY_INACTIVE);

    fireEvent(entry_group_listener_list, event);
  }

  /**
   *  Set the "active" setting of the given Entry.  The Entry is the default
   *  entry and new_active is false, the default entry will be set to the
   *  active entry or null if there are no active entries.
   *  @param entry The Entry to activate or deactivate.
   *  @param new_active The new active setting.
   **/
  public void setIsActive(final Entry entry, final boolean new_active) 
  {
    setIsActive(indexOf(entry), new_active);
  }


  /**
   *  Return the Entry from this SimpleEntryGroup that contains the sequence
   *  to view or return null if none of the entries contains a sequence.
   **/
  public Entry getSequenceEntry() 
  {
    if(size() == 0) 
      return null;
    else 
      return elementAt(0);
  }

  /**
   *  Returns the base length of the sequence of the first Entry in this group
   *  or 0 if this group is empty.
   **/
  public int getSequenceLength() 
  {
    return getBases().getLength();
  }

  /**
   *  Returns the Bases object of the first Entry in this group or null if
   *  this group is empty.
   **/
  public Bases getBases() 
  {
    return bases;
  }

  /**
   *  Reverse and complement the sequence and all features in every Entry in
   *  this SimpleEntryGroup.
   **/
  public void reverseComplement()
      throws ReadOnlyException 
  {
    if(isReadOnly()) 
      throw new ReadOnlyException();

    // reverse the sequence
    getBases().reverseComplement();
  }

  /**
   *  Return true if and only if one or more of the entries or features in
   *  this SimpleEntryGroup are read-only.
   **/
  public boolean isReadOnly() 
  {
    final int my_size = size();
    for(int i = 0; i < my_size; ++i) 
    {
      final Entry this_entry = elementAt(i);

      if(this_entry.isReadOnly()) 
        return true;

      final FeatureEnumeration feature_enum = this_entry.features();

      while(feature_enum.hasMoreFeatures()) 
      {
        if(feature_enum.nextFeature().isReadOnly()) 
          return true;
      }
    }

    return false;
  }

  /**
   *  Increment the reference count for this EntryGroup.
   **/
  public void ref() 
  {
    ++reference_count;
  }

  /**
   *  Decrement the reference count for this EntryGroup.  When the reference
   *  count goes to zero a EntryGroupChangeEvent is sent to all
   *  EntryGroupChangeListeners with type EntryGroupChangeEvent.DONE_GONE.
   *  The listeners should then stop using the EntryGroup and release any
   *  associated resources.
   **/
  public void unref() 
  {
    --reference_count;

    if(reference_count == 0) 
    {
      // remove all the entries which will close any edit or view windows
      while(size() > 0) 
      {
        final Entry this_entry = elementAt(0);
        remove(this_entry);
      }

      // now inform the listeners that the EntryGroup is no more
      final EntryGroupChangeEvent event =
              new EntryGroupChangeEvent(this, null,
                                        EntryGroupChangeEvent.DONE_GONE);

      fireEvent(entry_group_listener_list, event);
    }
  }

  /**
   *  Return the current reference count.
   **/
  public int refCount() 
  {
    return reference_count;
  }

  /**
   *  Return the Feature at the given index.  This method treats all the
   *  features in all the entries as if they were in one big array.  See
   *  the comment on indexOf().
   *  @param index The index of the required Feature.
   *  @return The Feature at the given index.  The first index is 0 the last
   *    is the total number of features in all the entries of this object minus
   *    one.  If the index is out of range then null will be returned.
   **/
  public Feature featureAt(int index) 
  {
    if(index < 0) 
      throw new Error("internal error - index out of range: " + index);

    int feature_count_of_previous_entries = 0;
    final int active_entries_size = active_entries.size();

    for(int entry_index = 0; entry_index < active_entries_size; 
        ++entry_index)
    {
      final Entry this_entry = active_entries.elementAt(entry_index);

      if(index < this_entry.getFeatureCount()) 
        return this_entry.getFeature(index);

      index -= this_entry.getFeatureCount();
    }

    throw new Error("internal error - index out of range: " + index);
  }

  /**
   *  Return a vector containing the references of the Feature objects within
   *  the given range of indices.
   *  @param start_index The index of the first feature to return.
   *  @param end_index The index of the last feature to return.
   **/
  public FeatureVector getFeaturesInIndexRange(final int start_index,
                                               final int end_index) 
  {
    final FeatureVector return_vector = new FeatureVector();

    for(int i = start_index; i <= end_index; ++i) 
      return_vector.add(featureAt(i));

    return return_vector;
  }

  /**
   *  Return a vector containing the references of the Feature objects within
   *  the given range for all the active entries in the SimpleEntryGroup.
   *  @param range Return features that overlap this range - ie the start of
   *    the feature is less than or equal to the end of the range and the end
   *    of the feature is greater than or equal to the start of the range.
   *  @return The non-source key features of this feature table the are within
   *    the given range.  The returned object is a copy - changes will not
   *    effect the FeatureTable object itself.
   **/
  public FeatureVector getFeaturesInRange(Range range)
      throws OutOfRangeException 
  {
    final FeatureVector return_vector = new FeatureVector();
    final int my_size = size();

    for(int i = 0; i < my_size; ++i) 
    {
      final Entry this_entry = elementAt(i);

      if(isActive(this_entry)) 
      {
        final FeatureVector visible_entry_features =
          elementAt(i).getFeaturesInRange(range);

        final int visible_entry_features_size = visible_entry_features.size();

        for(int feature_index = 0; feature_index < visible_entry_features_size;
            ++feature_index) 
        {
          final Feature this_feature =
            visible_entry_features.elementAt(feature_index);
          return_vector.add(this_feature);
        }
      }
    }

    return return_vector;
  }

  /**
   *  Return a vector containing the references of the Feature objects from
   *  all the active entries in the SimpleEntryGroup.
   *  @return The non-source key features in active entries of this
   *    SimpleEntryGroup.  The returned object is a copy - changes will not
   *    effect the SimpleEntryGroup object itself.
   **/
  public FeatureVector getAllFeatures() 
  {
    final FeatureVector return_vector = new FeatureVector();
    final int my_size = size();

    for(int i = 0; i < my_size; ++i) 
    {
      final Entry this_entry = elementAt(i);

      if(isActive(this_entry)) 
      {
        final FeatureVector entry_features = elementAt(i).getAllFeatures();
        final int entry_features_size = entry_features.size();

        for(int feature_index = 0; feature_index < entry_features_size;
            ++feature_index) 
        {
          final Feature this_feature =
            entry_features.elementAt(feature_index);
          return_vector.add(this_feature);
        }
      }
    }

    return return_vector;
  }

  /**
   *  Return a count of the number of Feature objects from all the active
   *  entries in the SimpleEntryGroup.
   *  @return A count of the non-source key features in active entries of this
   *    SimpleEntryGroup.
   **/
  public int getAllFeaturesCount() 
  {
    int return_count = 0;
    final int my_size = size();

    for(int i = 0; i < my_size; ++i) 
    {
      final Entry this_entry = elementAt(i);

      if(isActive(this_entry)) 
        return_count += this_entry.getFeatureCount();
    }

    return return_count;
  }

  /**
   *  Add an Entry to this object and then emit the appropriate EntryChange
   *  events.
   **/
  public void addElement(Entry entry) 
  {
    super.addElement(entry);

    // set the default Entry to whichever Entry gets added first
    if(default_entry == null) 
      default_entry = entry;

    active_entries.add(entry);

    // now inform the listeners that an addition has occured
    final EntryGroupChangeEvent event =
      new EntryGroupChangeEvent(this, entry,
                                EntryGroupChangeEvent.ENTRY_ADDED);

    fireEvent(entry_group_listener_list, event);

    // make the new entry the default entry if and only if there was no entry
    // previously or there was only one entry previously and it contained
    // sequence but no features
    if(size() == 1) 
      setDefaultEntry(entry);
    else 
    {
      if(size() == 2) 
      {
        final Entry first_entry = elementAt(0);

        if(first_entry.getFeatureCount() == 0) 
        {
          final Bases first_entry_bases = first_entry.getBases();

          if(first_entry_bases != null &&
             first_entry_bases.getLength() > 0) 
            setDefaultEntry(entry);
        }
      }
    }

    entry.addEntryChangeListener(this);
    entry.addFeatureChangeListener(this);
  }

  /**
   *  A convenience method that does the same as addElement(Entry).
   **/
  public void add(final Entry entry) 
  {
    addElement(entry);
  }

  /**
   *  Remove an Entry from this object and then emit the appropriate
   *  EntryGroupChange events.  The first entry in the group can only be
   *  removed if it is the only Entry because the first Entry contains the
   *  sequence.
   *  @return true if the removal succeeded, false if it fails(which can if
   *    the given Entry isn't in this SimpleEntryGroup or if the user tries to
   *    remove the first Entry).
   **/
  public boolean removeElement(final Entry entry) 
  {
    // this call will sort out the default entry
    setIsActive(indexOf(entry), false);

    entry.dispose();

    final boolean remove_return = super.removeElement(entry);

    entry.removeEntryChangeListener(this);
    entry.removeFeatureChangeListener(this);

    active_entries.removeElement(entry);

    // now inform the listeners that a deletion has occured
    final EntryGroupChangeEvent event =
      new EntryGroupChangeEvent(this, entry,
                                EntryGroupChangeEvent.ENTRY_DELETED);

    fireEvent(entry_group_listener_list, event);

    return remove_return;
  }

  /**
   *  A convenience method that does the same as removeElement(Entry).
   **/
  public boolean remove(final Entry entry) 
  {
    return removeElement(entry);
  }

  /**
   *  Create a new(blank) Feature in the default Entry of this
   *  SimpleEntryGroup.  See getDefaultEntry() and Entry.createFeature().
   *  @return The new Feature.
   **/
  public Feature createFeature() throws ReadOnlyException
  {
    final Feature new_feature = getDefaultEntry().createFeature();
    return new_feature;
  }

  /**
   *  Create a new(empty) Entry in this SimpleEntryGroup.  See
   *  Entry.newEntry().
   *  @return The reference of the new Entry.
   **/
  public Entry createEntry() 
  {
    Entry new_entry = null;
    uk.ac.sanger.artemis.io.Entry default_entry = 
      getDefaultEntry().getEMBLEntry();
    if(default_entry != null &&
       default_entry instanceof DatabaseDocumentEntry)
    {
      DatabaseDocument doc =
        (DatabaseDocument)((DocumentEntry)getDefaultEntry().getEMBLEntry()).getDocument();
      DatabaseDocument new_doc = doc.createDatabaseDocument();
      
      try
      {
        DatabaseDocumentEntry new_doc_entry = 
          new DatabaseDocumentEntry();
        new_doc_entry.setDocument(new_doc);
        new_entry = new Entry(getBases(), new_doc_entry);
      }
      catch(Exception e)
      {
        e.printStackTrace();
      }
      
    }
    else
      new_entry = Entry.newEntry(getBases());
    
    add(new_entry);
    return new_entry;
  }

  /**
   *  Create a new(empty) Entry in this SimpleEntryGroup.  See
   *  Entry.newEntry().
   *  @param name The(file) name of the new Entry.
   *  @return The reference of the new Entry.
   **/
  public Entry createEntry(final String name) 
  {
    final Entry new_entry = createEntry();
    new_entry.setName(name);
    return new_entry;
  }

  /**
   *  Returns an enumeration of the Feature objects in this
   *  SimpleEntryGroup. The returned FeatureEnumeration object will generate
   *  all features in this object in turn. The first item generated is the
   *  item at index 0, then the item at index 1, and so on.
   **/
  public FeatureEnumeration features() 
  {
    return new FeatureEnumerator();
  }

  /**
   *  An Enumeration of Feature objects.
   **/
  public class FeatureEnumerator implements FeatureEnumeration 
  {

    /**
     *  The EntryVector object that we are enumerating.  Set to null when there
     *  are no more Feature objects.
     **/
    private EntryVector active_entries;

    /**
     *  The index of the Entry that we will get the next Feature from.
     **/
    private int entry_index = -1;

    /** Enumeration for the current entry */
    private FeatureEnumeration feature_enumerator;

    /**
     *  Create a new FeatureEnumeration that will enumerate the enclosing
     *  SimpleEntryGroup object.  The SimpleEntryGroup object must not be
     *  changed while the enumeration is active.
     **/
    public FeatureEnumerator() 
    {
      active_entries = getActiveEntries();

      entry_index = 0;

      if(active_entries.size() > 0) 
        feature_enumerator =
          active_entries.elementAt(entry_index).features();
      else 
        feature_enumerator = null;
    }

    /**
     *  See the FeatureEnumeration interface for details.
     **/
    public boolean hasMoreFeatures() 
    {
      if(feature_enumerator == null) 
        return false;

      if(feature_enumerator.hasMoreFeatures()) 
        return true;

      ++entry_index;

      if(entry_index == active_entries.size()) 
        return false;
      else
      {
        feature_enumerator =
          active_entries.elementAt(entry_index).features();
        return hasMoreFeatures();
      }
    }

    /**
     *  See the FeatureEnumeration interface for details.
     **/
    public Feature nextFeature()
        throws NoSuchElementException 
    {
      if(feature_enumerator == null) 
        throw new NoSuchElementException();

      return feature_enumerator.nextFeature();
    }

  }

  /**
   *  Implementation of the FeatureChangeListener interface.  We listen for
   *  changes in every feature of every entry in this group.
   **/
  public void featureChanged(FeatureChangeEvent event) 
  {
    // pass the action straight through
    fireEvent(feature_listener_list, event);
  }

  /**
   *  Implementation of the EntryChangeListener interface.  We listen for
   *  changes from every entry in this group and pass the events though to all
   *  the object listening for EntryChangeEvents for the event from this
   *  SimpleEntryGroup.
   **/
  public void entryChanged(EntryChangeEvent event) 
  {
    // pass the action straight through
    fireEvent(entry_listener_list, event);
  }

  /**
   *  Send an event to those object listening for it.
   *  @param listeners A Vector of the objects that the event should be sent
   *    to.
   *  @param event The event to send
   **/
  private void fireEvent(Vector listeners, ChangeEvent event) 
  {
    final Vector targets;
    // copied from a book - synchronising the whole method might cause a
    // deadlock
    synchronized(this) 
    {
      targets = (Vector)listeners.clone();
    }

    boolean seen_chado_manager = false;
    final int targets_size = targets.size();
    for(int i = 0; i < targets_size; ++i) 
    {
      ChangeListener target =(ChangeListener) targets.elementAt(i);

      if(event instanceof EntryGroupChangeEvent)
      {
        final EntryGroupChangeListener entry_group_change_listener =
         (EntryGroupChangeListener) target;
        final EntryGroupChangeEvent group_change_event =
         (EntryGroupChangeEvent) event;
        entry_group_change_listener.entryGroupChanged(group_change_event);
      } 
      else
      {
        if(event instanceof EntryChangeEvent) 
        {
          final EntryChangeListener entry_change_listener =
                                     (EntryChangeListener) target;

//        if(entry_change_listener instanceof ChadoTransactionManager)
//        {
            // just call this listener once
//          if(!seen_chado_manager)
//          {
//            entry_change_listener.entryChanged((EntryChangeEvent) event);
//            seen_chado_manager = true;
//          }
//        }
//        else  
            entry_change_listener.entryChanged((EntryChangeEvent) event);
        } 
        else 
        {
          final FeatureChangeListener feature_change_listener =
                                    (FeatureChangeListener) target;
          feature_change_listener.featureChanged((FeatureChangeEvent) event);
        }

      }
    }
  }

  /**
   *  Adds the specified event listener to receive entry group change events
   *  from this object.
   *  @param l the event change listener.
   **/
  public void addEntryGroupChangeListener(EntryGroupChangeListener l) 
  {
    entry_group_listener_list.addElement(l);
  }

  /**
   *  Removes the specified event listener so that it no longer receives
   *  entry group change events from this object.
   *  @param l the event change listener.
   **/
  public void removeEntryGroupChangeListener(EntryGroupChangeListener l) 
  {
    entry_group_listener_list.removeElement(l);
  }

  /**
   *  Adds the specified event listener to receive entry change events from
   *  this object.
   *  @param l the event change listener.
   **/
  public void addEntryChangeListener(EntryChangeListener l) 
  {
    entry_listener_list.addElement(l);
  }

  /**
   *  Removes the specified event listener so that it no longer receives
   *  entry change events from this object.
   *  @param l the event change listener.
   **/
  public void removeEntryChangeListener(EntryChangeListener l) 
  {
    entry_listener_list.removeElement(l);
  }

  /**
   *  Adds the specified event listener to receive feature change events from
   *  this object.
   *  @param l the event change listener.
   **/
  public void addFeatureChangeListener(FeatureChangeListener l) 
  {
    feature_listener_list.addElement(l);
  }

  /**
   *  Removes the specified event listener so that it no longer receives
   *  feature change events from this object.
   *  @param l the event change listener.
   **/
  public void removeFeatureChangeListener(FeatureChangeListener l) 
  {
    feature_listener_list.removeElement(l);
  }

  /**
   *  Return the reference of an EntryVector containing the active entries of
   *  this EntryGroup.
   **/
  public EntryVector getActiveEntries() 
  {
    return(EntryVector) active_entries.clone();
  }

  /**
   *  This method translates the start and end of every each Range in every
   *  Location into another coordinate system.  The Ranges will be truncated
   *  if necessary.
   *  @param constraint This contains the start and end base of the new
   *    coordinate system.  The position given by constraint.getStart() will
   *    be at postion/base 1 in the new coordinate system.
   *  @return a copy of the EntryGroup which has been translated into the new
   *    coordinate system.
   **/
  public EntryGroup truncate(final Range constraint) 
  {
    final Bases new_bases = getBases().truncate(constraint);

    final EntryGroup new_entry_group = new SimpleEntryGroup(new_bases);
    final int my_size = size();

    for(int i = 0; i < my_size; ++i) 
    {
      final Entry this_entry = elementAt(i);
      final Entry new_entry = this_entry.truncate(new_bases, constraint);
      new_entry_group.add(new_entry);
    }

    if(size() > 0) 
    {
      final StreamSequence sequence =
       (StreamSequence) new_bases.getSequence();
      final SimpleDocumentEntry document_entry =
       (SimpleDocumentEntry) new_entry_group.elementAt(0).getEMBLEntry();
      document_entry.setSequence(sequence);
    }

    return new_entry_group;
  }

  /**
   *  Return the ActionController for this EntryGroup(for undo).
   **/
  public ActionController getActionController() 
  {
    return action_controller;
  }
  
}
