Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • gh-pages
  • build-process-upgrade-merge
  • eb-apollo-generate_names
  • BT5_travis
  • hello_github
  • v18.1.0
  • v18.0.3
  • v18.0.2
  • v18.0.1
  • v18.0.0
  • v18.0.0-RC1
  • v17.0.1
  • v17.0.0
  • v16.0.17
  • v16.0.0
  • v15.0.0
  • v14.0.0
  • v13.2.0
19 results

Entry.java

Blame
  • Entry.java 42.18 KiB
    /* Entry.java
     *
     * created: Sun Oct 11 1998
     *
     * This file is part of Artemis
     *
     * Copyright(C) 1998,1999,2000  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: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Entry.java,v 1.11 2008-06-20 10:00:25 tjc Exp $
     */
    
    package uk.ac.sanger.artemis;
    
    import uk.ac.sanger.artemis.sequence.*;
    
    import uk.ac.sanger.artemis.util.*;
    import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
    import uk.ac.sanger.artemis.io.EmblStreamFeature;
    import uk.ac.sanger.artemis.io.DocumentEntry;
    import uk.ac.sanger.artemis.io.EmblDocumentEntry;
    import uk.ac.sanger.artemis.io.GFFDocumentEntry;
    import uk.ac.sanger.artemis.io.GFFStreamFeature;
    import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
    import uk.ac.sanger.artemis.io.PartialSequence;
    import uk.ac.sanger.artemis.io.Range;
    import uk.ac.sanger.artemis.io.RangeVector;
    import uk.ac.sanger.artemis.io.Key;
    import uk.ac.sanger.artemis.io.Location;
    import uk.ac.sanger.artemis.io.LocationParseException;
    import uk.ac.sanger.artemis.io.QualifierVector;
    import uk.ac.sanger.artemis.io.DocumentEntryFactory;
    import uk.ac.sanger.artemis.io.EntryInformation;
    import uk.ac.sanger.artemis.io.EntryInformationException;
    
    import java.util.NoSuchElementException;
    import java.util.Vector;
    import java.io.IOException;
    import java.io.File;
    
    
    /**
     *  This class is a wrapper for the io.Entry class which contains the state
     *  and handles the events needed for editing one embl entry in Diana.  The
     *  state includes an EMBL.Entry object and a list of EntryChange listeners
     *  and FeatureChange listeners.  Other objects can register as listeners for
     *  changes to the entry.  For that reason, all changes to the embl.Entry
     *  objects should go through this class. (see ChangeEvent for details of the
     *  possible events.)
     *
     *  @author Kim Rutherford
     *  @version $Id: Entry.java,v 1.11 2008-06-20 10:00:25 tjc Exp $
     **/
    
    public class Entry implements FeatureChangeListener, Selectable 
    {
    
      /**
       *  The embl.Entry object that was passed to the constructor
       **/
      private uk.ac.sanger.artemis.io.Entry embl_entry;
    
      /**
       *  A vector of those objects listening for entry change events.
       **/
      final private Vector<ChangeListener> entry_listener_list = new Vector<ChangeListener>();
    
      /**
       *  A vector of those objects listening for feature change events.
       **/
      final private Vector<ChangeListener> feature_listener_list = new Vector<ChangeListener>();
    
      /**
       *  This is the Bases reference that was passed to the constructor.
       **/
      /*final*/ private Bases bases;
    
      /**
       *  Create a new Entry object.
       *  @param bases The Bases object which contains the Strand objects that will
       *    be used by the features of this Entry.
       *  @param embl_entry a reference to an embl.Entry object containing the
       *    underlying data for new object
       *  @exception OutOfRangeException Thrown if one of the features in
       *    embl_entry is out of range of the Bases object.
       **/
      public Entry(final Bases bases,
                   final uk.ac.sanger.artemis.io.Entry embl_entry)
          throws OutOfRangeException 
      {
        this.embl_entry = embl_entry;
        this.bases = bases;
    
        checkLocations();
        createDianaFeatures();
      }
    
      /**
       *  Create a new Entry object from a uk.ac.sanger.artemis.io.Entry
       *  object.  A new Bases object will be created for the new Entry.
       *  @param embl_entry a reference to an embl.Entry object containing the
       *    underlying data for new object
       *  @exception OutOfRangeException Thrown if one of the features in
       *    embl_entry is out of range of the Bases object.
       *  @exception NoSequenceException Thrown if embl_entry contains no sequence.
       **/
      public Entry(final uk.ac.sanger.artemis.io.Entry embl_entry)
          throws OutOfRangeException, NoSequenceException 
      {
        this.embl_entry = embl_entry;
    
        if(embl_entry.getSequence() == null ||
           embl_entry.getSequence().length() == 0) 
          throw new NoSequenceException();
        else 
          this.bases = new Bases(embl_entry.getSequence());
    
        checkLocations();
        createDianaFeatures();
      }
      
      
      /**
       *  Returns true if and only if this entry is read only.
       **/
      public boolean isReadOnly() 
      {
        return getEMBLEntry().isReadOnly();
      }
    
      /**
       *  Save the changes to this Entry back to where the Entry came from.  If
       *  this object is read only a ReadOnlyException will be thrown.
       *  @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
       *    ANY_FORMAT.  If ANY_FORMAT then the Entry will be saved in the
       *    same format it was created, otherwise it will be saved in the given
       *    format.
       *  @exception EntryInformationException Thrown if the destination type
       *    cannot contain the Key, Qualifier or Key/Qualifier combinations of one
       *    of the features in this Entry.
       *  @exception IOException Thrown if an IO error occurs while saving.  One
       *    possibility is ReadOnlyException.
       **/
      public void save(final int destination_type)
          throws IOException, EntryInformationException 
      {
        if(destination_type == DocumentEntryFactory.ANY_FORMAT) 
          getEMBLEntry().save();
        else 
        {
          if(getEMBLEntry() instanceof DocumentEntry) 
            getEMBLEntryAsNewType(getEntryInformation(),
                                  destination_type, false).save();
          else 
            throw new ReadOnlyException("operation cannot be " +
                                        "applied to this entry");
        }
      }
    
      /**
       *  Save the changes to this Entry back to where the Entry came from(like
       *  save()) but without any uk.ac.sanger.artemis extensions.  If this object is read only
       *  or doesn't support this a ReadOnlyException will be thrown.
       *  @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
       *    ANY_FORMAT.  If ANY_FORMAT then the Entry will be saved in the
       *    same format it was created, otherwise it will be saved in the given
       *    format.
       *  @exception EntryInformationException Thrown if the destination type
       *    cannot contain the Key, Qualifier or Key/Qualifier combinations of one
       *    of the features in this Entry.
       **/
      public void saveStandardOnly(final int destination_type)
          throws IOException,EntryInformationException 
      {
        if(destination_type == DocumentEntryFactory.ANY_FORMAT) 
          getEMBLEntry().save();
        else 
        {
          if(getEMBLEntry() instanceof DocumentEntry) 
          {
            final EntryInformation entry_information =
              Options.getDBEntryInformation();
    
            getEMBLEntryAsNewType(entry_information,
                                   destination_type, false).save();
          } 
          else 
            throw new ReadOnlyException("operation cannot be " +
                                        "applied to this entry");
        }
      }
    
      /**
       *  Save the changes to this Entry to the file.  If this object is read only
       *  a ReadOnlyException will be thrown.  This method only works when the
       *  underlying embl.Entry object is a DocumentEntry.
       *  @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
       *    GFF_FORMAT or ANY_FORMAT.  If ANY_FORMAT then the Entry will
       *    be saved in the same format it was created, otherwise it will be saved
       *    in the given format.
       *  @param force If true then invalid qualifiers and any features with
       *    invalid keys will be quietly thrown away when saving.  "Invalid" means
       *    that the key/qualifier is not allowed to occur in the destination type
       *   (probably determined by the default EntryInformation object of the
       *    destination type).  If false an EntryInformationException will be
       *    thrown for invalid keys or qualifiers.
       *  @exception EntryInformationException Thrown if force is false and if the
       *    destination type cannot contain the Key, Qualifier or Key/Qualifier
       *    combination of one of the features in this Entry.
       **/
      public void save(final File file, final int destination_type,
                       final boolean force)
          throws IOException, EntryInformationException 
      {
        final EntryInformation artemis_entry_information;
        
        if((getEMBLEntry() instanceof DatabaseDocumentEntry ||
            getEMBLEntry() instanceof GFFDocumentEntry) &&
           destination_type == DocumentEntryFactory.EMBL_FORMAT)
          artemis_entry_information = Options.getArtemisEntryInformation();
        else
          artemis_entry_information = getEntryInformation();
        
        save(file, destination_type, force, artemis_entry_information);
      }
      
      
      /**
       *  Save the changes to this Entry to the file.  If this object is read only
       *  a ReadOnlyException will be thrown.  This method only works when the
       *  underlying embl.Entry object is a DocumentEntry.
       *  @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
       *    GFF_FORMAT or ANY_FORMAT.  If ANY_FORMAT then the Entry will
       *    be saved in the same format it was created, otherwise it will be saved
       *    in the given format.
       *  @param force If true then invalid qualifiers and any features with
       *    invalid keys will be quietly thrown away when saving.  "Invalid" means
       *    that the key/qualifier is not allowed to occur in the destination type
       *   (probably determined by the default EntryInformation object of the
       *    destination type).  If false an EntryInformationException will be
       *    thrown for invalid keys or qualifiers.
       *  @exception EntryInformationException Thrown if force is false and if the
       *    destination type cannot contain the Key, Qualifier or Key/Qualifier
       *    combination of one of the features in this Entry.
       **/
      public void save(final File file, final int destination_type,
                       final boolean force, final EntryInformation artemis_entry_information)
          throws IOException, EntryInformationException 
      {
        final FileDocument file_document = new FileDocument(file);
        final DocumentEntry document_entry =
          (DocumentEntry)getEMBLEntryAsNewType(artemis_entry_information,
                                               destination_type, force);
        document_entry.save(file_document);
      }
    
      /**
       *  Save the changes to this Entry to the file(like save(Document)) but
       *  without any uk.ac.sanger.artemis extensions.  If this object is read only a
       *  ReadOnlyException will be thrown.  This method only works when the
       *  underlying embl.Entry object is a DocumentEntry.
       *  @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
       *    ANY_FORMAT.  If ANY_FORMAT then the Entry will be saved in the
       *    same format it was created, otherwise it will be saved in the given
       *    format.
       *  @param force If true then invalid qualifiers and any features with
       *    invalid keys will be quietly thrown away when saving.  "Invalid" means
       *    that the key/qualifier is not allowed to occur in the destination type
       *   (probably determined by the default EntryInformation object of the
       *    destination type).  If false an EntryInformationException will be
       *    thrown for invalid keys or qualifiers.
       *  @exception EntryInformationException Thrown if force is false and if the
       *    destination type cannot contain the Key, Qualifier or Key/Qualifier
       *    combination of one of the features in this Entry.
       **/
      public void saveStandardOnly(final File file,
                                   final int destination_type,
                                   final boolean force)
          throws IOException, EntryInformationException 
      {
        final FileDocument file_document = new FileDocument(file);
    
        final DocumentEntry document_entry =
         (DocumentEntry) getEMBLEntryAsNewType(Options.getDBEntryInformation(),
                                               destination_type, force);
        document_entry.save(file_document);
      }
    
      /**
       *  Returns true if and only if there have been some changes to this Entry
       *  since the last save.
       **/
      public boolean hasUnsavedChanges() 
      {
        return getEMBLEntry().hasUnsavedChanges();
      }
    
      /**
       *  Create a new Entry in the given EntryGroup.
       *  @return A reference to the new Entry.
       **/
      public static Entry newEntry(final Bases bases) 
      {
        final uk.ac.sanger.artemis.io.Entry entry =
          new EmblDocumentEntry(Options.getArtemisEntryInformation());
    
        try 
        {
          return new Entry(bases, entry);
        } 
        catch(OutOfRangeException e) 
        {
          // an empty Entry cannot have any out of range features
          throw new Error("internal error - unexpected exception: " + e);
        }
      }
    
      /**
       *  Return the name of this Entry or null if it has no name.
       **/
      public String getName() 
      {
        return getEMBLEntry().getName();
      }
    
      /**
       *  Set the name of this Entry - if possible(the return value will let the
       *  caller know).
       *  @return true if and only if the name was successfully set.  Not all
       *    Entry objects can change there name, so it is up to the calling
       *    function to check the return value.
       **/
      public boolean setName(final String name) 
      {
        final String orig_entry_name = getName();
    
        if(getEMBLEntry().setName(name)) 
        {
          if(orig_entry_name == null || ! name.equals(orig_entry_name)) 
          {
            // now inform the listeners that the name has changed
            final EntryChangeEvent event =
              new EntryChangeEvent(name, this, EntryChangeEvent.NAME_CHANGED);
    
            fireAction(entry_listener_list, event);
          }
          return true;
        } 
        else 
          return false;
      }
    
      /**
       *  Return the text of the EMBL header of this Entry or null if there is no
       *  header.
       **/
      public String getHeaderText() 
      {
        return getEMBLEntry().getHeaderText();
      }
    
      /**
       *  Set the header of this Entry to be the given text.
       *  @return true if and only if the header was successfully set.  Not all
       *    Entry objects can change their header, so it is up to the calling
       *    function to check the return value.
       *  @exception IOException thrown if there is a problem reading the header
       *    from the String - most likely ReadFormatException.
       **/
      public boolean setHeaderText(final String new_header)
          throws IOException 
      {
        if(getEMBLEntry().setHeaderText(new_header)) 
        {
          final EntryChangeEvent event =
            new EntryChangeEvent(new_header,
                                  this,
                                  EntryChangeEvent.HEADER_CHANGED);
    
          fireAction(entry_listener_list, event);
    
          return true;
        }
        else 
          return false;
      }
    
      /**
       *  Return the path of the directory that this entry is in.
       **/
      public Document getRootDocument() 
      {
        if(getEMBLEntry() instanceof DocumentEntry &&  // XXX FIXME:
           ((DocumentEntry) getEMBLEntry()).getDocument() != null) 
        {
          return((DocumentEntry)getEMBLEntry()).getDocument().getParent();
        } 
        else 
          return null;
      }
    
      /**
       *  Return a vector containing the references of the Feature objects within
       *  the given range.
       *  @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_features = new FeatureVector();
        final uk.ac.sanger.artemis.io.FeatureVector embl_features =
          getEMBLEntry().getFeaturesInRange(range);
    
    //    System.err.println("starting getFeaturesInRange()");
        Feature diana_feature;
        for(int i = 0 ; i < embl_features.size() ; ++i)
        {
          diana_feature = getFeatureOf(embl_features.featureAt(i));
    
    //       System.err.println("getFeaturesInRange() - diana_feature:" +
    //                           diana_feature + " " +
    //                           embl_features.elementAt(i));
    
          return_features.add(diana_feature);
        }
    
    //    System.err.println("ending getFeaturesInRange()"); 
    
        return return_features;
      }
    
      /**
       *  Return a vector containing the references of the Feature objects in this
       *  Entry.
       *  @return The non-source key features of this Entry.  The
       *    returned object is a copy - changes will not effect the Entry
       *    object itself.
       **/
      public FeatureVector getAllFeatures() 
      {
        final FeatureVector return_features = new FeatureVector();
        final uk.ac.sanger.artemis.io.FeatureVector embl_features =
          getEMBLEntry().getAllFeatures();
    
        Feature diana_feature;
        for(int i = 0; i < embl_features.size(); ++i) 
        {
          diana_feature = getFeatureOf(embl_features.featureAt(i));
          return_features.add(diana_feature);
        }
    
        return return_features;
      }
      
      public void remove(final Feature feature)
          throws ReadOnlyException 
      {
        remove(feature, false);
      }
    
      /**
       *  Delete a Feature from this object and the underlying embl.Entry object.
       *  @param feature The Feature to delete.
       **/
      public void remove(final Feature feature,
                         final boolean duplicate)
          throws ReadOnlyException 
      {
        synchronized(getBases()) 
        {
          // get the embl.Feature object out of the uk.ac.sanger.artemis.Feature object then
          // remove it
          if(!getEMBLEntry().remove(feature.getEmblFeature())) 
            throw new Error("internal error - remove failed");
    
          // now inform the listeners that a deletion has occured
          final EntryChangeEvent event =
            new EntryChangeEvent(this, feature, duplicate, EntryChangeEvent.FEATURE_DELETED);
    
          fireAction(entry_listener_list, event);
          feature.setEntry(null);
        }
      }
    
      /**
       *  Remove all the features in this Entry.
       **/
      public void removeAllFeatures()
          throws ReadOnlyException 
      {
        // remove the first feature
        while(getFeatureCount() > 0) 
          remove(getFeature(0));
      }
    
      /**
       *  Add a Feature to the Entry.  The new feature will be inserted in order
       *  in the feature vector.  The features in the vector are ordered by the
       *  first base of each feature.
       *  @param new_feature The new feature to add.  It should not be a member
       *    of the Entry object.
       *  @param force If true then invalid qualifiers will be quietly thrown away
       *    and a feature with invalid keys will not be added.  "Invalid" means
       *    that the key/qualifier is non allowed to occur in an Entry of this type
       *   (probably determined by the EntryInformation object of this Entry).
       *    If false an EntryInformationException will be thrown for invalid keys
       *    or qualifiers.
       *  @exception EntryInformationException Thrown force is false if this Entry
       *    cannot contain the Key, Qualifier or Key/Qualifier combination of the
       *    given Feature
       **/
      public void add(final Feature new_feature,
                      final boolean force)
            throws EntryInformationException, OutOfRangeException,
            ReadOnlyException 
      {
        add(new_feature, false, force);
      }
      
      /**
       *  Add a Feature to the Entry.  The new feature will be inserted in order
       *  in the feature vector.  The features in the vector are ordered by the
       *  first base of each feature.
       *  @param new_feature The new feature to add.  It should not be a member
       *    of the Entry object.
       *  @param force If true then invalid qualifiers will be quietly thrown away
       *    and a feature with invalid keys will not be added.  "Invalid" means
       *    that the key/qualifier is non allowed to occur in an Entry of this type
       *   (probably determined by the EntryInformation object of this Entry).
       *    If false an EntryInformationException will be thrown for invalid keys
       *    or qualifiers.
       *  @exception EntryInformationException Thrown force is false if this Entry
       *    cannot contain the Key, Qualifier or Key/Qualifier combination of the
       *    given Feature
       **/
      public void add(final Feature new_feature, final boolean duplicate,
                      final boolean force)
          throws EntryInformationException, OutOfRangeException,
                 ReadOnlyException 
      {
        try
        {
          if(new_feature.getEntry() != null)
            throw new Error("internal error - Feature has a parent");
    
          new_feature.setEntry(this);
    
          final uk.ac.sanger.artemis.io.Feature old_embl_reference =
            new_feature.getEmblFeature();
    
          // this call will add the new_feature in the correct place in the
          // embl.Feature vector of the FeatureTable object
          final uk.ac.sanger.artemis.io.Feature new_embl_reference;
    
          if(force) 
          {
            new_embl_reference =
              getEMBLEntry().forcedAdd(new_feature.getEmblFeature());
          } 
          else 
          {
            new_embl_reference =
              getEMBLEntry().add(new_feature.getEmblFeature());
          }
    
          if(new_embl_reference != old_embl_reference) 
          {
            new_feature.getEmblFeature().setUserData(null);
            new_feature.setEmblFeature(new_embl_reference);
            new_embl_reference.setUserData(new_feature);
          }
    
          // now inform the listeners that a addition has occured
          final EntryChangeEvent event =
            new EntryChangeEvent(this, new_feature, duplicate,
                                 EntryChangeEvent.FEATURE_ADDED);
    
          fireAction(entry_listener_list, event);
    
        } 
        catch(EntryInformationException e) 
        {
          new_feature.setEntry(null);
          throw e;
        }
      }
    
      /**
       *  Forget all the features in this entry and inform all the EntryChange
       *  listeners that all the features have gone away.  The garbage collector
       *  will really dispose of the features later.
       **/
      public void dispose() 
      {
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
    
          // now inform the listeners that a deletion has occured
          final EntryChangeEvent event =
            new EntryChangeEvent(this, current_feature,
                                  EntryChangeEvent.FEATURE_DELETED);
    
          fireAction(entry_listener_list, event);
    
          current_feature.setEntry(null);
        }
      }
    
      /**
       *  Returns the Feature at the given index in the vector of Features.
       **/
      public Feature getFeature(int i) 
      {
        return getFeatureOf(getEMBLEntry().getFeatureAtIndex(i));
      }
    
      /**
       *  Create a new Feature in this Entry with no qualifiers, a default key and
       *  a location of "1..max_base"
       **/
      public Feature createFeature()
          throws ReadOnlyException 
      {
        final Key new_key;
        final Location new_location;
        final QualifierVector new_qualifiers = new QualifierVector();
    
        try 
        {
          new_key = getEntryInformation().getDefaultKey();
          new_location = new Location("1" + ".." + getBases().getLength());
    
          return createFeature(new_key, new_location, new_qualifiers);
        } 
        catch(LocationParseException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
        } 
        catch(EntryInformationException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
        } 
        catch(OutOfRangeException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
        }
      }
    
      /**
       *  Create and return a new Feature in this Entry with the given key,
       *  location and qualifiers.  This method creates a new embl.Feature object
       *  and then wraps it in a uk.ac.sanger.artemis.Feature object and 
       *  then inserts it in thisEntry object.
       *  @param new_key The Key to use for the new Feature.
       *  @param new_location The Location to use for the new Feature.
       *  @param new_qualifiers The a vector containing the Qualifier objects to
       *    use for the new Feature.
       *  @exception EntryInformationException Thrown force is false if this Entry
       *    cannot contain a feature with the given Key, Qualifier or
       *    Key/Qualifier combination.
       **/
      public Feature createFeature(Key new_key,
                                    Location new_location,
                                    QualifierVector new_qualifiers)
          throws EntryInformationException, ReadOnlyException,
          OutOfRangeException
      {
        final uk.ac.sanger.artemis.io.Feature new_embl_feature =
          getEMBLEntry().createFeature(new_key, new_location, new_qualifiers);
    
        final Feature new_feature = getFeatureOf(new_embl_feature);
    
        // now inform the listeners that a addition has occured
        final EntryChangeEvent event =
          new EntryChangeEvent(this, new_feature, EntryChangeEvent.FEATURE_ADDED);
    
        fireAction(entry_listener_list, event);
        return new_feature;
      }
    
      /**
       *  Create and return a new Feature in this Entry with the given key,
       *  location and no qualifiers.  This method creates a new embl.Feature
       *  object and then wraps it in a uk.ac.sanger.artemis.Feature object and then inserts it
       *  in this Entry object.
       *  @param new_key The Key to use for the new Feature.
       *  @param new_location The Location to use for the new Feature.
       **/
      public Feature createFeature(Key new_key,
                                    Location new_location)
          throws EntryInformationException, ReadOnlyException,
          OutOfRangeException 
      {
        final QualifierVector new_qualifiers = new QualifierVector();
        return createFeature(new_key, new_location, new_qualifiers);
      }
    
      /**
       *  Return true if this Entry contains the given Feature.
       **/
      public boolean contains(Feature feature) 
      {
        return getEMBLEntry().contains(feature.getEmblFeature());
      }
    
      /**
       *  Return the index of the given Feature in the feature table of this Entry
       *  or -1 if the feature isn't in this Entry.
       **/
      public int indexOf(Feature feature) 
      {
        return getEMBLEntry().indexOf(feature.getEmblFeature());
      }
    
      /**
       *  Return the index of a feature in the embl feature table of this entry.
       *  The indices start at zero.
       *  @param feature The Feature to lookup.
       *  @return The index of the feature or -1 if the feature isn't in this
       *    Entry.
       **/
      public int getIndexOfFeature(Feature feature) 
      {
        return getEMBLEntry().indexOf(feature.getEmblFeature());
      }
    
      /**
       *  Returns an enumeration of the Feature objects in this Entry. 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 
      {
        /**
         *  Create a new FeatureEnumeration that will enumerate the enclosing
         *  Entry object.  The Entry object must not be changed while the
         *  enumeration is active.
         **/
        public FeatureEnumerator()
        {
          feature_enumerator = getEMBLEntry().features();
        }
    
        /**
         *  See the FeatureEnumeration interface for details.
         **/
        public boolean hasMoreFeatures() 
        {
          return feature_enumerator.hasMoreFeatures();
        }
    
        /**
         *  See the FeatureEnumeration interface for details.
         **/
        public Feature nextFeature()
            throws NoSuchElementException 
        {
    
          final uk.ac.sanger.artemis.io.Feature this_feature =
            feature_enumerator.nextFeature();
    
          return getFeatureOf(this_feature);
        }
    
        /**
         *  The Enumeration for the current entry
         **/
        private uk.ac.sanger.artemis.io.FeatureEnumeration feature_enumerator;
      }
    
      /**
       *  Returns the number of features in this entry.
       **/
      public int getFeatureCount() 
      {
        return getEMBLEntry().getFeatureCount();
      }
    
      /**
       *  Return the Bases object that was passed to the constructor.
       **/
      public Bases getBases() 
      {
        return bases;
      }
    
      /**
       *  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 Entry which has been translated into the new
       *    coordinate system.
       **/
      public Entry truncate(final Bases new_bases, final Range constraint) 
      {
        final uk.ac.sanger.artemis.io.Entry new_embl_entry;
    
        if(getEMBLEntry() instanceof GFFDocumentEntry || 
           getEMBLEntry() instanceof IndexedGFFDocumentEntry)
          new_embl_entry = new GFFDocumentEntry(getEntryInformation());
        else
          new_embl_entry = new EmblDocumentEntry(getEntryInformation());
    
        final Entry new_entry;
    
        try 
        {
          new_entry = new Entry(new_bases, new_embl_entry);
        } 
        catch(OutOfRangeException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
        }
        
        if(getEMBLEntry() instanceof IndexedGFFDocumentEntry)
        {
          ((IndexedGFFDocumentEntry)getEMBLEntry()).truncate(constraint, new_entry);
          return new_entry;
        }
    
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
          final Location current_feature_location = current_feature.getLocation();
    
          final Location new_location =
            current_feature_location.truncate(constraint);
          
          if(new_location != null) 
          {
            final Key current_key = current_feature.getKey();
            final QualifierVector current_qualifiers =
              current_feature.getQualifiers();
            try 
            {
              final uk.ac.sanger.artemis.io.Feature new_feature; 
              if(current_feature.getEmblFeature() instanceof GFFStreamFeature)
                new_feature = new GFFStreamFeature(current_key, new_location,
                                                        current_qualifiers);
              else
                new_feature = new EmblStreamFeature(current_key, new_location,
                                                        current_qualifiers);
    
              new_entry.add(new Feature(new_feature), true);
            }
            catch(EntryInformationException e) 
            {
              throw new Error("internal error - unexpected exception: " + e);
            } 
            catch(OutOfRangeException e) 
            {
              throw new Error("internal error - unexpected exception: " + e);
            } 
            catch(ReadOnlyException e) 
            {
              throw new Error("internal error - unexpected exception: " + e);
            }
          }
        }
    
        return new_entry;
      }
    
      /**
       *  Return a Vector containing those features that don't have valid EMBL
       *  key.
       **/
      public FeatureVector checkForNonEMBLKeys() 
      {
        final FeatureVector non_embl_features = new FeatureVector();
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
    
          if(!current_feature.hasValidEMBLKey()) 
            non_embl_features.add(current_feature);
        }
    
        return non_embl_features;
      }
    
      /**
       *  Returns a vector containing those CDS features that have no /pseudo
       *  qualifier and do not have a valid start codon.  The returned features
       *  may be illegal in EMBL submissions.
       **/
      public FeatureVector checkFeatureStartCodons() 
      {
        // get all the CDS features that do not have a /pseudo or /pseudogene qualifier
        final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
            new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
            new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
            FeaturePredicateConjunction.AND);
    
        final FeatureVector non_embl_features = new FeatureVector();
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
          if(predicate.testPredicate(current_feature)) 
          {
            if(!current_feature.hasValidStartCodon()) 
              non_embl_features.add(current_feature);
          }
        }
        return non_embl_features;
      }
    
      /**
       *  Returns a vector containing those CDS features that have no /pseudo
       *  qualifier and do not have a valid stop codon.  The returned features may
       *  be illegal in EMBL submissions.
       **/
      public FeatureVector checkFeatureStopCodons() 
      {
        // get all the CDS features that do not have a /pseudo or /pseudogene qualifier
        final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
            new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
            new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
            FeaturePredicateConjunction.AND);
    
        final FeatureVector non_embl_features = new FeatureVector();
    
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
    
          if(predicate.testPredicate(current_feature)) 
          {
            if(!current_feature.hasValidStopCodon()) 
              non_embl_features.add(current_feature);
          }
        }
    
        return non_embl_features;
      }
    
      /**
       *  Returns a vector containing those features that have the same key and
       *  location as one or more other features.  These features are not allowed
       *  in EMBL submissions.
       **/
      public FeatureVector checkForEMBLDuplicates() 
      {
        final FeatureVector non_embl_features = new FeatureVector();
        final FeatureEnumeration feature_enum = features();
        Feature last_feature = null;
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
          final Key current_key = current_feature.getKey();
          final Location current_location = current_feature.getLocation();
    
          if(last_feature != null &&
              last_feature.getKey().equals(current_key) &&
              last_feature.getLocation().equals(current_location)) 
          {
            if(! non_embl_features.contains(last_feature)) 
              non_embl_features.add(last_feature);
            
            non_embl_features.add(current_feature);
            continue;
          }
          last_feature = current_feature;
        }
    
        return non_embl_features;
      }
    
      /**
       *  Returns a vector containing those features that have the same key and
       *  location as one or more other features.  These features are not allowed
       *  in EMBL submissions.
       **/
      public FeatureVector checkForOverlappingCDSs() 
      {
        final FeatureVector cds_features = new FeatureVector();
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
    
          if(current_feature.isCDS()) 
            cds_features.add(current_feature);
        }
    
        final FeatureVector return_features = new FeatureVector();
    
        for(int i = 0 ; i + 1 < cds_features.size() ; ++i) 
        {
          final Feature this_feature = cds_features.elementAt(i);
          final Feature next_feature = cds_features.elementAt(i + 1);
    
          final int this_feature_end = this_feature.getRawLastBase();
          final int next_feature_start = next_feature.getRawFirstBase();
    
          if(this_feature_end >= next_feature_start)
            return_features.add(this_feature);
        }
    
        return return_features;
      }
    
      /**
       *  Returns a vector containing those features that have a feature that is
       *  missing a required qualifier.  These features are not allowed in EMBL
       *  submissions.
       **/
      public FeatureVector checkForMissingQualifiers() 
      {
        final FeatureVector non_embl_features = new FeatureVector();
    
        final FeatureEnumeration feature_enum = features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature current_feature = feature_enum.nextFeature();
    
          if(!current_feature.hasRequiredQualifiers()) 
            non_embl_features.add(current_feature);
        }
    
        return non_embl_features;
      }
    
      /**
       *  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);
      }
    
      /**
       *  Implementation of the FeatureChangeListener interface.  We need to
       *  listen to feature change events from the Features in this object.
       *  @param event The change event.
       **/
      public void featureChanged(FeatureChangeEvent event) 
      {
        // pass the action straight through
        fireAction(feature_listener_list, event);
      }
    
      /**
       *  Return the EntryInformation object of this Entry.
       **/
      public EntryInformation getEntryInformation() 
      {
        return getEMBLEntry().getEntryInformation();
      }
    
      /**
       *  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 fireAction(Vector<ChangeListener> listeners, ChangeEvent event) 
      {
        final Vector<ChangeListener> targets;
        // copied from a book - synchronising the whole method might cause a
        // deadlock
        synchronized(this) 
        {
          targets = (Vector) listeners.clone();
        }
    
        for(ChangeListener target: targets) 
        {
          if(event instanceof EntryChangeEvent) 
          {
            final EntryChangeListener entry_change_listener =
              (EntryChangeListener) target;
            entry_change_listener.entryChanged((EntryChangeEvent) event);
          } 
          else
          {
            final FeatureChangeListener feature_change_listener =
              (FeatureChangeListener) target;
            feature_change_listener.featureChanged((FeatureChangeEvent) event);
          }
        }
      }
    
      /**
       *  Return the uk.ac.sanger.artemis.Feature object of the given embl.Feature object.  This
       *  method will create an appropriate uk.ac.sanger.artemis.Feature if none exists.
       **/
      private Feature
        getFeatureOf(uk.ac.sanger.artemis.io.Feature embl_feature) 
      {
        final Feature test_feature =(Feature) embl_feature.getUserData();
     
        if(test_feature == null) 
        {
          final Feature new_feature = new Feature(embl_feature);
          new_feature.setEntry(this);
          // hack to create a FeatureSegment for each exon, which has to be done
          // aftre setting the Entry of the Feature
          new_feature.getSegments();
          return new_feature;
        } 
        else 
          return test_feature;
      }
    
      /**
       *  Check that all features in the embl.Entry object are in range for the
       *  Bases object that was passed to the constructor.
       **/
      private void checkLocations() throws OutOfRangeException
      {
        final uk.ac.sanger.artemis.io.FeatureEnumeration feature_enumerator =
          getEMBLEntry().features();
    
        while(feature_enumerator.hasMoreFeatures()) 
        {
          final uk.ac.sanger.artemis.io.Feature feature =
            feature_enumerator.nextFeature();
    
          final Location location = feature.getLocation();
          final RangeVector ranges = location.getRanges();
    
          for(int i = 0 ; i < ranges.size() ; ++i) 
          {
            if(((Range)ranges.elementAt(i)).getEnd() > getBases().getLength())
            {
              if(!(getBases().getSequence() instanceof PartialSequence))
                throw new OutOfRangeException(location.toString());
            }
          }
        }
      }
    
      /**
       *  Create a uk.ac.sanger.artemis.Feature object for each embl.Feature in embl_entry.
       **/
      private void createDianaFeatures() 
      {
        // enumerating the features will call getFeatureOf() for each
        // embl.Feature, which will in turn create a uk.ac.sanger.artemis.Feature
    
        final FeatureEnumeration feature_enumerator = features();
        while(feature_enumerator.hasMoreFeatures())  
          feature_enumerator.nextFeature();
      }
    
      /**
       *  Return the embl.Entry object that was passed to the constructor.
       **/
      public uk.ac.sanger.artemis.io.Entry getEMBLEntry() 
      {
        return embl_entry;
      }
    
      /**
       *  Return the embl.Entry object that was passed to the constructor.
       *  @param entry_information The EntryInformation object for the new Entry.
       *  @param new_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
       *    ANY_FORMAT.  If ANY_FORMAT then the Entry will be returned as is.
       *  @param force If true then invalid qualifiers and any features with
       *    invalid keys in the new Entry will be quietly thrown away.  "Invalid"
       *    means that the key/qualifier is not allowed to occur in an Entry of
       *    this type(probably determined by the EntryInformation object of this
       *    Entry).  If false an EntryInformationException will be thrown for
       *    invalid keys or qualifiers.
       *  @exception EntryInformationException Thrown if an Entry using the given
       *    EntryInformation object cannot contain the Key, Qualifier or
       *    Key/Qualifier combination of one of the features in the Document.
       **/
      private uk.ac.sanger.artemis.io.Entry
        getEMBLEntryAsNewType(final EntryInformation entry_information,
                               final int new_type, final boolean force)
          throws EntryInformationException 
      {
        return DocumentEntryFactory.makeDocumentEntry(entry_information,
                                                      embl_entry, new_type,
                                                      force);
      }
    
    }