Skip to content
Snippets Groups Projects
SimpleDocumentEntry.java 38.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* SimpleDocumentEntry.java
     *
     * created: Tue Feb 15 2000
     *
     * This file is part of Artemis
     *
     * Copyright(C) 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.
     *
    
    tjc's avatar
    tjc committed
     * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/SimpleDocumentEntry.java,v 1.10 2005-04-20 14:54:28 tjc Exp $
    
    tjc's avatar
    tjc committed
     */
    
    package uk.ac.sanger.artemis.io;
    
    import uk.ac.sanger.artemis.util.*;
    
    import java.io.*;
    import java.util.Date;
    import java.util.Vector;
    
    tjc's avatar
    tjc committed
    import java.util.Hashtable;
    import java.util.Enumeration;
    
    tjc's avatar
    tjc committed
    
    /**
     *  This class contains the methods common to all DocumentEntry objects.
     *
     *  @author Kim Rutherford <kmr@sanger.ac.uk>
    
    tjc's avatar
    tjc committed
     *  @version $Id: SimpleDocumentEntry.java,v 1.10 2005-04-20 14:54:28 tjc Exp $
    
    tjc's avatar
    tjc committed
     **/
    
    abstract public class SimpleDocumentEntry
    
    tjc's avatar
    tjc committed
                          implements DocumentEntry 
    
    tjc's avatar
    tjc committed
    {
    
      /**
       *  A vector of ReadOnlyEmblStreamFeature objects - one for each fasta
       *  record in the DocumentEntry.  These won't be written when the
       *  DocumentEntry is written.
       **/
      private FeatureVector fake_fasta_features = new FeatureVector();
     
      /** EntryInformation object that was passed to constructor */
      final private EntryInformation entry_information;
                                                                                                         
      /** collection to send ReadEvents to */
      private Vector listeners = new Vector();
    
      /** 
       *  The Document object that was passed to the constructor.  This should be
       *  the document that this SimpleDocumentEntry was read from.
       **/
      private Document document = null;
                                                                                                                   
      /**
       *  This contains all the lines(stored as LineGroup objects) from the entry
       *  stream that was passed to the constructor.
       **/
      protected LineGroupVector line_groups = new LineGroupVector();
                                                                                                                   
      /**
       *  The DocumentEntryAutosaveThread that is started when the first call is
       *  made to setDirtyFlag().
       **/
      private Thread autosave_thread = null;
                                                                                                                   
      /**
       *  The Date when this Entry last changed or null if this Entry
       *  hasn't changed since the last save.  Set to null by save().
       **/
      private java.util.Date last_change_time = null;
                                                                                                                   
      /**
       *  Set to true in the constructor while features are added.  setDirtyFlag()
       *  will do nothing while this is true.
       **/
      private boolean in_constructor = false;
    
      /**
       *  Create a new SimpleDocumentEntry from the given Document.
       *  @param entry_information The EntryInformation object of the new Entry.
       *  @param document This is the file that we will read from.  This is also
       *    used for saving the entry back to the file it came from and to give
       *    the new object a name.
       *  @exception IOException thrown if there is a problem reading the entry -
       *    most likely ReadFormatException.
       *  @exception EntryInformationException Thrown if force is false and if this
       *    Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
       *    one of the features in the given Entry.
       **/
      public SimpleDocumentEntry(final EntryInformation entry_information,
                                  final Document document,
                                  final ReadListener read_listener)
          throws IOException, EntryInformationException 
      {
        this.document = document;
        this.entry_information = new SimpleEntryInformation(entry_information);
        this.in_constructor    = true;  // flag used by setDirtyFlag()
    
    
    tjc's avatar
    tjc committed
        if(read_listener != null)
          addReadListener(read_listener);
    
    tjc's avatar
    tjc committed
    
    //tjc  final Reader in_file = getDocument().getReader();
    
        final LinePushBackReader pushback_reader =
                  getDocument().getLinePushBackReader();
    
        LineGroup new_line_group;
    
    
    tjc's avatar
    tjc committed
        boolean isGFF = false;
    
    
    tjc's avatar
    tjc committed
        while((new_line_group =
                LineGroup.readNextLineGroup(pushback_reader)) != null) 
        {
    
    tjc's avatar
    tjc committed
    //    if(document instanceof DatabaseDocument)
    //    {
    //      addLineGroup(new_line_group);
    //      System.out.println("SimpleDocumentEntry DatabaseDocument ******************************"); 
    //      if(new_line_group instanceof StreamSequence)
    //        System.out.println("SimpleDocumentEntry StreamSequence  ******************************");
    //    }
    
    tjc's avatar
    tjc committed
          if(new_line_group instanceof SimpleDocumentFeature)
    
    tjc's avatar
    tjc committed
          {
            final SimpleDocumentFeature new_feature =
                       (SimpleDocumentFeature)new_line_group;
    
    
    tjc's avatar
    tjc committed
            if(new_line_group instanceof GFFStreamFeature)
              isGFF = true;
            
    
    tjc's avatar
    tjc committed
            // try several times because adding the Feature may cause more than
            // one exception
            int i;
            final int MAX_LOOP = 9999;
            EntryInformationException saved_error = null;
    
            for(i = 0; i<MAX_LOOP; ++i) 
            {
              try 
              {
                addInternal(new_feature, true);
                break;
              } 
              catch(EntryInformationException e) 
              {
                getEntryInformation().fixException(e);
                saved_error = e;
              }
            }
    
            if(i == MAX_LOOP) 
              throw new Error("internal error - too many exceptions: " +
                               saved_error.getMessage());
          }
          else 
            addLineGroup(new_line_group);
        }
    
        // we added some features above hence:
        last_change_time = null;
    
        final Sequence sequence = getSequence();
    
        if(sequence != null && sequence instanceof FastaStreamSequence) 
        {
          // add a feature for each FASTA record if there are more 
          // than one record in the FASTA sequence
          final FastaStreamSequence fasta_sequence =
                                    (FastaStreamSequence)sequence;
    
          final String[] header_strings = fasta_sequence.getFastaHeaderStrings();
    
          if(header_strings.length > 1) 
          {
            final int[] header_positions =
                          fasta_sequence.getFastaHeaderPositions();
    
            final FeatureTable feature_table = getFeatureTable();
    
            for(int i = 0 ; i < header_strings.length ; ++i) 
            {
              try
              {
                final Range new_range;
    
                if(i == header_strings.length - 1) 
                {
                  if(header_positions[i] == fasta_sequence.length()) 
                    throw new ReadFormatException("empty FASTA record: >" +
                                                   header_strings[i]);
    
                  new_range = new Range(header_positions[i] + 1,
    
    tjc's avatar
    tjc committed
                                        fasta_sequence.length());
    
    tjc's avatar
    tjc committed
                }  
                else
                {
                  if(header_positions[i] == header_positions[i+1]) 
                    throw new ReadFormatException("empty FASTA record: >" +
                                                   header_strings[i]);
    
                  new_range = new Range(header_positions[i] + 1,
    
    tjc's avatar
    tjc committed
                                        header_positions[i+1]);
    
    tjc's avatar
    tjc committed
                }
    
                final QualifierVector qualifiers = new QualifierVector();
    
                qualifiers.setQualifier(new Qualifier("note",
                                                        header_strings[i]));
                qualifiers.setQualifier(new Qualifier("label",
                                                        header_strings[i]));
                if(i % 2 == 0) 
                  qualifiers.setQualifier(new Qualifier("colour", "10"));
                else 
                  qualifiers.setQualifier(new Qualifier("colour", "11"));
    
                final ReadOnlyEmblStreamFeature new_feature =
                  new ReadOnlyEmblStreamFeature(new Key("fasta_record"),
                                                 new Location(new_range),
                                                 qualifiers);
    
                fake_fasta_features.add(new_feature);
    
    tjc's avatar
    tjc committed
    
                // adjust coordinates of features
                if(isGFF)
                {
    
    tjc's avatar
    tjc committed
                  FeatureVector gff_regions = getAllFeatures();
                  Enumeration gff_features  = gff_regions.elements();
    
    tjc's avatar
    tjc committed
                  while(gff_features.hasMoreElements())
                  {
                    GFFStreamFeature feature = (GFFStreamFeature)gff_features.nextElement();
    
    tjc's avatar
    tjc committed
                    Qualifier qualifier = feature.getQualifierByName("gff_seqname");
                        
                    if(qualifier.getValues().contains(header_strings[i]))
    
    tjc's avatar
    tjc committed
                    {
                      try
                      {
                        final Location loc = feature.getLocation();
                        int start = feature.getFirstBase()+new_range.getStart()-1;
                        int end   = feature.getLastBase()+new_range.getStart()-1;
                        final Range feat_range = new Range(start, end);
                        final RangeVector location_ranges = new RangeVector(feat_range);
                        feature.setLocation(new Location(location_ranges, loc.isComplement()));
                      }
                      catch(OutOfRangeException e)
                      {
                        e.printStackTrace();
                      }
                    }
                  }   
                }
    
    tjc's avatar
    tjc committed
              }
              catch(InvalidRelationException e) 
              {
                throw new Error("internal error - unexpected exception: " + e);
              }
              catch(OutOfRangeException e)
              {
                throw new Error("internal error - unexpected exception: " + e);
              }
            }
    
            addFakeFeatures();
          }
        }
    
        this.in_constructor = false;
      }
    
      /**
       *  Create a new SimpleDocumentEntry with no Document associated with it.
       *  @param entry_information The EntryInformation object of the new Entry.
       **/
      public SimpleDocumentEntry(final EntryInformation entry_information)
      {
        this.entry_information = new SimpleEntryInformation(entry_information);
      }
    
      /**
       *  Create a new SimpleDocumentEntry that will be a copy of the given Entry
       *  and has no Document associated with it.  The new SimpleDocumentEntry
       *  cannot be saved to a file with save() unless save(Document) has been
       *  called first.  Some qualifier and location information will be lost if
       *  the argument Entry is a different type to this class.
       *  @param entry_information The EntryInformation object of the Entry that
       *    will contain this Feature.
       *  @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.
       **/
      public SimpleDocumentEntry(final EntryInformation entry_information,
                                  final Entry new_entry, final boolean force)
          throws EntryInformationException
      {
        this.entry_information = new SimpleEntryInformation(entry_information);
    
        if(new_entry.getClass().equals(this.getClass())) 
        {
          try
          {
            setHeaderText(new_entry.getHeaderText());
          } 
          catch(IOException e) 
          {
            System.err.println(e);
            // if it doesn't work just ignore it
          }
        }
    
        final FeatureEnumeration feature_enum = new_entry.features();
    
        while(feature_enum.hasMoreFeatures()) 
        {
          final Feature new_feature = feature_enum.nextFeature();
    
          try
          {
            if(force) 
              forcedAdd(makeNativeFeature(new_feature, true));
            else 
              add(makeNativeFeature(new_feature, true));
          } 
          catch(ReadOnlyException e) 
          {
            throw new Error("internal error - unexpected exception: " + e);
          }
        }
    
        final Sequence new_sequence = new_entry.getSequence();
    
        if(new_sequence != null) 
          setSequence(makeNativeSequence(new_sequence));
      }
    
      /**
       *  Return the text of the header of this Entry or null if there is no
       *  header.
       **/
      public String getHeaderText() 
      {
        final StringBuffer buffer = new StringBuffer();
    
        for(int i = 0 ; i < line_groups.size() ; ++i) 
        {
          final LineGroup current_line_group = line_groups.elementAt(i);
    
          if(!(current_line_group instanceof FeatureTable) &&
             !(current_line_group instanceof Sequence)) 
            buffer.append(current_line_group.toString());
        }
    
        if(buffer.length() > 0) 
          return buffer.toString();
        else 
          return null;
      }
    
      /**
       *  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.  If null the current header will be
       *    removed.
       *  @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 
      {
    
        final LineGroupVector new_line_groups = new LineGroupVector();
    
        if(new_header != null) 
        {
          final Reader reader = new StringReader(new_header);
    
          final LinePushBackReader pushback_reader =
                              new LinePushBackReader(reader);
    
          LineGroup new_line_group;
    
          try 
          {
            while((new_line_group =
                    LineGroup.readNextLineGroup(pushback_reader)) != null)
            {
              if(new_line_group instanceof MiscLineGroup) 
                new_line_groups.addElement(new_line_group);
              else
                throw new ReadFormatException("the header must contain only " +
                                               "header lines");
            }
          } 
          catch(InvalidRelationException e) 
          {
            throw new ReadFormatException("the header must contain only " +
                                           "header lines");
          }
        }
    
        // now remove all the EmblMisc and GenbankMisc LineGroup objects from
        // this Entry
        for(int i = line_groups.size() - 1 ; i >= 0  ; --i) 
        {
          final LineGroup current_line_group = line_groups.elementAt(i);
    
          if(current_line_group instanceof MiscLineGroup) 
            line_groups.removeElementAt(i);
        }
    
        if(new_header != null) 
        {
          // then add the new LineGroup objects
          for(int i = 0 ; i < new_line_groups.size() ; ++i) 
            line_groups.insertElementAt(new_line_groups.elementAt(i), i);
        }
    
        setDirtyFlag();
    
        return true;
      }
    
      /**
       *  Write this Entry to the given stream.
       *  @param writer The stream to write to.
       *  @exception IOException thrown if there is a problem writing the entry.
       **/
      public void writeToStream(final Writer writer)
          throws IOException 
      {
        removeFakeFeatures();
    
        try
        {
          for(int i = 0 ; i < line_groups.size() ; ++i) 
          {
            final LineGroup current_line_group = line_groups.elementAt(i);
            current_line_group.writeToStream(writer);
          }
    
          if(line_groups.size() == 1) 
          {
            // don't write out the "//" end of entry marker if we have only one
            // LineGroup - this makes life easier for external programs that need
            // to read the feature table or sequence
            return;
          }
          else
          {
            if(line_groups.size() == 2) 
            {
              final LineGroup second_line_group = line_groups.elementAt(1);
    
              // don't write out the "//" end of entry marker if this is raw or
              // FASTA sequence
              if(second_line_group instanceof RawStreamSequence ||
                  second_line_group instanceof FastaStreamSequence) 
                return;
            }
          }
    
          if(this instanceof PublicDBDocumentEntry) 
            LineGroup.writeEndOfEMBLEntry(writer);
        } 
        finally
        {
          addFakeFeatures();
        }
      }
    
      /**
       *  Return a count of the number of Feature objects in this Entry.
       **/
      public int getFeatureCount() 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return 0;
        else 
          return feature_table.getFeatureCount();
      }
    
      /**
       *  The method is identical to add() except that it doesn't check the
       *  read_only flag before adding and it doesn't throw ReadOnlyExceptions.
       *  This is used by the constructor to avoid exceptions.
       *
       *  Add the given Feature to this Entry.  If the Feature is already in an
       *  Entry then Entry.remove() should be called on that Entry before calling
       *  Entry.add().  An Error will be thrown otherwise.
       *  @param throw_entry_info_exceptions if true throw
       *    EntryInformationExceptions, otherwise just send a ReadEvent.
       *  @exception EntryInformationException Thrown if this Entry
       *    cannot contain the Key, Qualifier or Key/Qualifier combination of the
       *    given Feature or if a required qualifier is missing.
       *  @return A reference that was passed to add(), if that Feature can be
       *    stored directly in this Entry, otherwise returns a reference to a new
       *    Feature, that is a copy of the argument.  The argument reference
       *    should not be used after the call to add(), unless the return
       *    reference happens to be the same as the argument.
       **/
      private Feature addInternal(final Feature feature,
                                   final boolean throw_entry_info_exceptions)
          throws EntryInformationException 
      {
        if(feature.getEntry() != null) 
          throw new Error("internal error - a feature must have one owner");
    
        final EntryInformation entry_information = getEntryInformation();
    
        if(!entry_information.isValidKey(feature.getKey()))  
        {
          final String message = feature.getKey() + " is not a valid key";
    
          fireEvent(new ReadEvent(this, message));
    
          throw new InvalidKeyException(feature.getKey() + " is not a valid " +
                                         "key for this entry", feature.getKey());
        }
    
        final QualifierVector new_qualifiers = feature.getQualifiers();
    
        final Key new_key = feature.getKey();
    
        // check the qualifiers
        for(int i = 0 ; i < new_qualifiers.size() ; ++i) 
        {
    
    tjc's avatar
    tjc committed
          final Qualifier this_qualifier = (Qualifier)new_qualifiers.elementAt(i);
    
    tjc's avatar
    tjc committed
          final String this_qualifier_name = this_qualifier.getName();
    
          if(!entry_information.isValidQualifier(new_key, this_qualifier_name)) 
          {
            final String message = new_key + " can't have " + this_qualifier_name 
                                           + " as a qualifier";
    
            fireEvent(new ReadEvent(this, message));
    
            if(throw_entry_info_exceptions) 
              throw new InvalidRelationException(message, new_key,
                                                 this_qualifier);
          }
        }
    
        final SimpleDocumentFeature native_feature =
          makeNativeFeature(feature, false);
    
        final FeatureTable feature_table = getFeatureTable();
        feature_table.add(native_feature);
    
        try 
        {
          native_feature.setDocumentEntry(this);
        }
        catch(ReadOnlyException e) 
        {
          // makeNativeFeature() should never return a read only feature
          throw new Error("internal error - unexpected exception: " + e);
        }
    
        setDirtyFlag();
    
        return native_feature;
      }
    
      /**
       *  Add the given Feature to this Entry.  If the Feature is already in an
       *  Entry then Entry.remove() should be called on that Entry before calling
       *  Entry.add().  An Error will be thrown otherwise.
       *  @exception ReadOnlyException If this entry is read only.
       *  @exception EntryInformationException Thrown if this Entry
       *    cannot contain the Key, Qualifier or Key/Qualifier combination of the
       *    given Feature or if a required qualifier is missing.
       *  @return A reference that was passed to add(), if that Feature can be
       *    stored directly in this Entry, otherwise returns a reference to a new
       *    Feature, that is a copy of the argument.  The argument reference
       *    should not be used after the call to add(), unless the return
       *    reference happens to be the same as the argument.
       **/
      public Feature add(final Feature feature)
          throws EntryInformationException, ReadOnlyException 
      {
        if(isReadOnly()) 
          throw new ReadOnlyException();
    
        return addInternal(feature, true);
      }
    
      /**
       *  Add the given Feature to this Entry.  If the Feature is already in an
       *  Entry then Entry.remove() should be called on that Entry before calling
       *  Entry.forcedAdd()(An Error will be thrown otherwise).  Invalid
       *  qualifiers will be quietly thrown away.  Features with invalid keys will
       *  not be added(and null will be returned).  "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).  Any
       *  qualifiers that are required for this Entry will be quietly added(with
       *  a zero-length string as the value).
       *  @exception ReadOnlyException If this entry is read only.
       *  @return A reference that was passed to add(), if that Feature can be
       *    stored directly in this Entry, otherwise returns a reference to a new
       *    Feature, that is a copy of the argument.  The argument reference
       *    should not be used after the call to add(), unless the return
       *    reference happens to be the same as the argument.  Returns null if and
       *    only if the new Feature has a key that is invalid for this Entry.
       **/
      public Feature forcedAdd(final Feature feature)
          throws ReadOnlyException 
      {
        if(isReadOnly()) 
          throw new ReadOnlyException();
    
        if(feature.getEntry() != null) 
          throw new Error("internal error - a feature must have one owner");
    
        final EntryInformation entry_information = getEntryInformation();
    
        if(!entry_information.isValidKey(feature.getKey())) 
          return null;
    
        final QualifierVector feature_qualifiers = feature.getQualifiers();
        final QualifierVector fixed_qualifiers = new QualifierVector();
        final Key new_key = feature.getKey();
    
        // set to true if there is an invalid qualifier
        boolean qualifiers_fixed = false;
    
        // check the qualifiers
        for(int i = 0 ; i < feature_qualifiers.size() ; ++i)
        {
    
    tjc's avatar
    tjc committed
          final Qualifier this_qualifier = (Qualifier)feature_qualifiers.elementAt(i);
    
    tjc's avatar
    tjc committed
    
          final String this_qualifier_name = this_qualifier.getName();
    
          if(entry_information.isValidQualifier(new_key, this_qualifier_name)) 
            fixed_qualifiers.setQualifier(this_qualifier);
          else 
            qualifiers_fixed = true;
        }
    
        final SimpleDocumentFeature native_feature =
          makeNativeFeature(feature, false);
    
        if(qualifiers_fixed) 
        {
          try
          {
            native_feature.setQualifiers(fixed_qualifiers);
          }
          catch(EntryInformationException e) 
          {
            throw new Error("internal error - unexpected exception: " + e);
          }
        } 
        else
        {
          // otherwise use the feature as is
        }
    
        final FeatureTable feature_table = getFeatureTable();
        feature_table.add(native_feature);
        native_feature.setDocumentEntry(this);
        setDirtyFlag();
    
        return native_feature;
      }
    
      /**
       *  The method is identical to remove() except that it doesn't check the
       *  read_only flag before removing and it doesn't throw ReadOnlyExceptions.
       *
       *  Remove the given Feature from this Entry.
       *  @return true if and only if the Feature was in this Entry.
       **/
      protected boolean removeInternal(Feature feature) 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return false;
        else 
        {
          final SimpleDocumentFeature feature_from_table =
           (SimpleDocumentFeature) feature_table.remove(feature);
    
          if(feature_from_table == null) 
            return false;
          else  
          {
            try 
            {
              feature_from_table.setDocumentEntry(null);
            } 
            catch(ReadOnlyException e) 
            {
              throw new Error("internal error - unexpected exception: " + e);
            }
    
            // get rid of the feature table
            if(feature_table.getFeatureCount() == 0) 
              removeLineGroup(feature_table);
    
            setDirtyFlag();
    
            return true;
          }
        }
      }
    
      /**
       *  Remove the given Feature from this Entry.
       *  @return true if and only if the Feature was in this Entry.
       **/
      public boolean remove(Feature feature)
          throws ReadOnlyException 
      {
        if(isReadOnly() || feature.isReadOnly()) 
          throw new ReadOnlyException();
    
        return removeInternal(feature);
      }
    
      /**
       *  Return the ith Feature from this Entry.  This Features are returned in a
       *  consistent order, sorted by the first base of each Feature.
       **/
      public Feature getFeatureAtIndex(int i) 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return null;
        else 
          return feature_table.getFeatureAtIndex(i);
      }
    
      /**
       *  Return the index of the given Feature.  This does the reverse of
       *  getFeatureAtIndex().
       **/
      public int indexOf(final Feature feature) 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return -1;
        else 
          return feature_table.indexOf(feature);
      }
    
      /**
       *  Returns true if and only if this Entry contains the given feature.
       **/
      public boolean contains(final Feature feature) 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return false;
        else
          return feature_table.contains(feature);
      }
    
      /**
       *  Create a new Feature object of an appropriate type in this Entry.
       *  @param key The new feature key
       *  @param location The Location object for the new feature
       *  @param qualifiers The qualifiers for the new feature(can be null if
       *    there are no qualifiers).
       **/
      public Feature createFeature(final Key key,
                                    final Location location,
                                    final QualifierVector qualifiers)
          throws EntryInformationException, ReadOnlyException,
                 OutOfRangeException 
      {
        if(isReadOnly())
          throw new ReadOnlyException();
    
        final Feature new_feature;
    
        if(this instanceof EmblDocumentEntry) 
          new_feature = new EmblStreamFeature(key, location, qualifiers);
    
        else if(this instanceof DatabaseDocumentEntry)
          new_feature = new DatabaseStreamFeature(key, location, qualifiers);
    
    tjc's avatar
    tjc committed
        else
          new_feature = new GenbankStreamFeature(key, location, qualifiers);
    
        add(new_feature);
        setDirtyFlag();
    
        return new_feature;
      }
    
      /**
       *  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 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) 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return new FeatureVector();
        else 
          return feature_table.getFeaturesInRange(range);
      }
    
      /**
       *  Return a vector containing the references of all the Feature objects in
       *  this Entry.
       *  @return The features of this Entry.  The returned object
       *    is a copy - changes will not effect the Entry object itself.
       **/
      public FeatureVector getAllFeatures() 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null) 
          return new FeatureVector();
        else 
          return feature_table.getAllFeatures();
      }
    
      /**
       *  Returns an enumeration of the Feature objects in this
       *  SimpleDocumentEntry.  The returned Enumeration 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() 
      {
        final FeatureTable feature_table = findFeatureTable();
    
        if(feature_table == null)
        {
          return new FeatureEnumeration() {
            public boolean hasMoreFeatures() {
              return false;
            }
    
            public Feature nextFeature() {
              return null;
            }
          };
        }
        else 
          return feature_table.features();
      }
    
      /**
       *  Return the Sequence object from this entry or null if it does not
       *  contain one.
       *  @return a Sequence object for this Entry.  the returned object is
       *    not a copy - changes to it will change the Entry object itself
       **/
      public Sequence getSequence() 
      {
        for(int i = 0 ; i < line_groups.size() ; ++i) 
        {
          final LineGroup current_line_group = line_groups.elementAt(i);
    
          if(current_line_group instanceof Sequence)
            return(Sequence) current_line_group;
        }
    
        return null;
      }
    
      /**
       *  Add a new LineGroup object to this Entry.
       *  @param new_line_group A new LineGroup to add.
       **/
      private void addLineGroup(final LineGroup new_line_group) 
      {
        if(new_line_group instanceof FeatureHeader) 
        {
          // insert FH lines before the Sequence and Features(if any)
          for(int i = 0 ; i < line_groups.size() ; ++i) 
          {
            final LineGroup this_line_group = line_groups.elementAt(i);
    
            if(this_line_group instanceof Feature ||
                this_line_group instanceof FeatureTable ||
                this_line_group instanceof Sequence) 
            {
              line_groups.insertElementAt(new_line_group, i);
              return;
            }
          }
        } 
        else
        {
          if(new_line_group instanceof Feature)
          {
            // Features before the Sequence and FeatureTable(if any)
            for(int i = 0 ; i < line_groups.size() ; ++i) 
            {
              final LineGroup this_line_group = line_groups.elementAt(i);
    
              if(this_line_group instanceof FeatureTable ||
                  this_line_group instanceof Sequence) 
              {
                line_groups.insertElementAt(new_line_group, i);
                return;
              }
            }
          } 
          else
          {
            if(!(new_line_group instanceof Sequence) &&
                !(new_line_group instanceof FeatureTable)) 
            {
              // insert before features and sequence
              for(int i = 0 ; i < line_groups.size() ; ++i) 
              {
                final LineGroup this_line_group = line_groups.elementAt(i);
    
                if(this_line_group instanceof Feature ||
                    this_line_group instanceof FeatureTable ||
                    this_line_group instanceof FeatureHeader ||
                    this_line_group instanceof Sequence)
                {
                  line_groups.insertElementAt(new_line_group, i);
                  return;
                }
              }
            }
            else
            {
              // fall throw
            }
          }
        }
    
        // otherwise add at end
        line_groups.addElement(new_line_group);
      }
    
      /**
       *  Return the name of this Entry or null if it has no name.
       **/
      public String getName() 
      {
        if(getDocument() == null || getDocument().getName() == null) 
          return null;
        else 
          return getDocument().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) 
      {
        if(name == null || name.length() == 0) 
          return false;
    
        setDocument(new FileDocument(new File(name)));
    
        return true;
      }
    
      /**
       *  Return the EntryInformation object for this Entry.
       **/
      public EntryInformation getEntryInformation() 
      {
        return entry_information;
      }
    
      /**
       *  Create and return a new FeatureTable().  The new FeatureTable will be
       *  added to line_groups in the appropriate place.
       **/
      private FeatureTable createFeatureTable() 
      {
        final FeatureTable new_feature_table;