Skip to content
Snippets Groups Projects
Feature.java 95.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* Feature.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.
     *
    
    tjc's avatar
    tjc committed
     * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $
    
    tjc's avatar
    tjc committed
     */
    
    package uk.ac.sanger.artemis;
    
    import uk.ac.sanger.artemis.util.*;
    
    tcarver's avatar
    tcarver committed
    import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.OutOfDateException;
    import uk.ac.sanger.artemis.io.LocationParseException;
    import uk.ac.sanger.artemis.io.Location;
    import uk.ac.sanger.artemis.io.Key;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.PartialSequence;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.Qualifier;
    import uk.ac.sanger.artemis.io.QualifierVector;
    import uk.ac.sanger.artemis.io.RangeVector;
    import uk.ac.sanger.artemis.io.Range;
    import uk.ac.sanger.artemis.io.InvalidRelationException;
    import uk.ac.sanger.artemis.io.DateStampFeature;
    import uk.ac.sanger.artemis.io.EntryInformation;
    import uk.ac.sanger.artemis.io.EntryInformationException;
    import uk.ac.sanger.artemis.io.EmblStreamFeature;
    
    import uk.ac.sanger.artemis.io.GFFStreamFeature;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.io.FastaStreamSequence;
    import uk.ac.sanger.artemis.io.StreamFeature;
    import uk.ac.sanger.artemis.sequence.*;
    import uk.ac.sanger.artemis.plot.*;
    
    import java.awt.Color;
    import java.util.Vector;
    import java.io.*;
    import java.util.Date;
    
    import java.util.regex.Pattern;
    
    tjc's avatar
    tjc committed
    
    /**
     *  This class extends an embl.Feature with the other information needed by
     *  Diana.  It also able to send events to other objects that are interested
     *  in changes to this object.  (see FeatureChangeEvent details of the
     *  possible change events.)  To make changes to the feature it calls methods
     *  in the Entry class.  Changes to this object will update the underlying
     *  embl.Feature and embl.Entry objects.
     *
     *  @author Kim Rutherford
    
    tjc's avatar
    tjc committed
     *  @version $Id: Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $
    
    tjc's avatar
    tjc committed
     **/
    
    public class Feature
        implements EntryChangeListener, Selectable, SequenceChangeListener,
    
    tjc's avatar
    tjc committed
                   MarkerChangeListener, OptionChangeListener
    {
    
    tjc's avatar
    tjc committed
    
      /**
       *  The Entry that controls/contains this feature.  This is the Entry object
       *  that was passed to the constructor.
       **/
      private Entry entry;
    
      /**
       *  This is a reference to the low level Feature object that this class is
       *  providing a wrapper for.  This is the embl.Feature object that was
       *  passed to constructor.
       **/
      private uk.ac.sanger.artemis.io.Feature embl_feature;
    
      /**
       *  The segment of this Feature.  All features have at least one segment.
       **/
      private FeatureSegmentVector segments = null;
    
      /**
       *  A vector of those objects listening for feature change events.
       **/
      private final Vector feature_listener_list = new Vector();
    
      /**
       *  The translation of the bases of this feature.
       **/
      private AminoAcidSequence amino_acids = null;
    
      /**
       *  The bases of this feature.
       **/
      private String bases = null;
    
      /**
       *  The count of the number of amino acids in this feature. (set by
       *  getAACount() and resetCache()).
       **/
      private int aa_count = -1;
    
      /**
       *  The count of the bases in this feature (set by getBaseCount() and
       *  resetCache()).
       **/
      private int base_count = -1;
    
      /**
       *  This array contains counts of the occurrences of each three bases.  The
       *  first index is the first base, the second index is the second base, etc.
       *  The indices are the same as those for Bases.letter_index.
       *  (set by resetCache()).
       **/
      private int [][][] codon_counts = null;
    
      /**
       *  This array contains counts of the occurrences of each amino acids in the
       *  translation of the bases of the current feature.
       *  (set by resetCache()).
       **/
      private int [] residue_counts = null;
    
      /**
       *  This array contains counts of number of each base that appear in each
       *  codon position.  The first index is the codon position and the second is
       *  the base (indexed in the same way as Bases.letter_index).
       *  (set by resetCache()).
       **/
      private int [][] positional_base_counts = null;
    
      /**
       *  This array contains the counts of the total number of each base in the
       *  feature.
       *  The indices are the same as those for Bases.letter_index.
       *  (set by resetCache()).
       **/
      private int [] base_counts = null;
    
      /**
       *  The current Location reference is saved each time setLocation() is
       *  called so that if the reference changes resetCache() can
       *  be called.  This is needed to handle RWCorbaFeature objects which can
       *  change their Location at arbitrary times.
       **/
      private Location old_location = null;
    
      /**
       *  Incremented when startListening() is called - decremented when
       *  stopListening() is called.
       **/
      private int listen_count = 0;
    
    tjc's avatar
    tjc committed
     
      private Color colour = null;
     
    
    tjc's avatar
    tjc committed
      /**
       *  Create a new Feature object.
       *  @param entry The uk.ac.sanger.artemis.Entry object that contains this Feature.
       *  @param embl_feature The embl.Feature object that this class is
       *    providing a wrapper for.
       **/
    
    tjc's avatar
    tjc committed
      public Feature(uk.ac.sanger.artemis.io.Feature embl_feature) 
      {
    
    tjc's avatar
    tjc committed
        this.embl_feature = embl_feature;
    
    tjc's avatar
    tjc committed
        embl_feature.setUserData(this);
        old_location = embl_feature.getLocation();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Implementation of the EntryChangeListener interface.  We listen to
       *  EntryChange events so that if the feature is deleted we can destroy any
       *  objects that use it.
       *
       *  NOTE: not active currently
       **/
    
    tjc's avatar
    tjc committed
      public void entryChanged(EntryChangeEvent event) 
      {
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  This method fixes up the location of this Feature when a Marker changes.
       **/
    
    tjc's avatar
    tjc committed
      public void markerChanged(final MarkerChangeEvent event) 
      {
        try 
        {
          final Location old_location = getLocation();
          updateEMBLFeatureLocation();
          locationChanged(old_location);
        }
    
    tjc's avatar
    tjc committed
        catch(ReadOnlyException e) {}
    
    tjc's avatar
    tjc committed
      }
    
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      /**
       *  This method fixes up the location of this Feature when a sequence
       *  changes.
       **/
    
    tjc's avatar
    tjc committed
      public void sequenceChanged(final SequenceChangeEvent event) 
      {
        try 
        {
    
    tjc's avatar
    tjc committed
          // we don't send a FeatureChangeEvent because the logical location
          // hasn't changed
    
          // all the Markers for the ranges of this feature will have changed
          // before this method is called because the markers are added as
          // SequenceChangeListener with a higher priority than this Feature
    
    
    tjc's avatar
    tjc committed
          if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT) 
            reverseComplement(getEntry().getBases().getLength());
    
          else if(event.getType() == SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT)
          {
             final Location old_location = getLocation();
    
            // if the event is contained within this feature then the feature
            // sequence may have changed
            final Range this_feature_range = getMaxRawRange();
    
            boolean feature_changed = false;
            Range eventRange = event.getRange();
            
            // reverse complement feature if within contig region
            if(eventRange.getStart() <= this_feature_range.getStart() &&
               eventRange.getEnd() >= this_feature_range.getEnd())
            {
              try
              {
                final Location new_location =
                   getLocation().reverseComplement(event.getLength(), eventRange.getStart());
         
                setLocationInternal(new_location);
              }
              catch(OutOfRangeException e)
              {
                throw new Error("internal error - inconsistent location: " + e);
              }
            } 
          }
    
    tjc's avatar
    tjc committed
          else if(event.getType() == SequenceChangeEvent.CONTIG_REORDER)
          {
            final Location old_location = getLocation();
    
            final int new_base_pos = event.getPosition();
            final int range_start  = event.getRange().getStart(); 
            final int range_end = event.getRange().getEnd();
            final Range this_feature_range = getMaxRawRange();
            
            // check if feature is effected
            if( (this_feature_range.getStart() >= new_base_pos ||
                 this_feature_range.getStart() >= range_start) &&
                (this_feature_range.getStart() < new_base_pos ||
                 this_feature_range.getStart() < range_end) )
            {
              try
              {
                final int diff;
                
                if(range_start <= this_feature_range.getStart() &&
                   range_end   >= this_feature_range.getEnd())
                {
                  if(new_base_pos < range_start)
                    diff = new_base_pos-range_start;
                  else
                    diff = new_base_pos-range_end-1;
                }
                else
                {
                  if(this_feature_range.getStart() < range_start)
                    diff = range_end-range_start+1;
                  else
                    diff = range_start-range_end-1;
                }
    
                Location new_location = moveSegments(diff);
                setLocationInternal(new_location);
              }
              catch(OutOfRangeException e)
              {
                throw new Error("internal error - inconsistent location: " + e);
              }
            }
          }
    
    tjc's avatar
    tjc committed
          else 
          {
            final Location old_location = getLocation();
    
    tjc's avatar
    tjc committed
    
            // if the event is contained within this feature then the feature
            // sequence may have changed
    
    tjc's avatar
    tjc committed
            final Range this_feature_range = getMaxRawRange();
    
    tjc's avatar
    tjc committed
    
            boolean feature_changed = false;
    
    tjc's avatar
    tjc committed
            int eventPosition = event.getPosition();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(eventPosition >= this_feature_range.getStart() &&
               eventPosition <= this_feature_range.getEnd() + 1) 
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              // check each segment
    
    tjc's avatar
    tjc committed
              final FeatureSegmentVector segments = getSegments();
    
    tjc's avatar
    tjc committed
              int seg_size = segments.size();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              for(int i = 0; i < seg_size; ++i) 
    
    tjc's avatar
    tjc committed
              {
                final FeatureSegment this_segment = segments.elementAt(i);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
                final Range this_segment_range = this_segment.getRawRange();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
                if(eventPosition >= this_segment_range.getStart() &&
                   eventPosition <= this_segment_range.getEnd() + 1) 
    
    tjc's avatar
    tjc committed
                  feature_changed = true;
              }
            }
    
    
    tjc's avatar
    tjc committed
            updateEMBLFeatureLocation();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(feature_changed) 
            {
              resetCache();
              locationChanged(old_location);
    
    tjc's avatar
    tjc committed
            }
          }
    
    tjc's avatar
    tjc committed
        }
        catch(ReadOnlyException e)
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Invoked when an Option is changed.
       **/
    
    tjc's avatar
    tjc committed
      public void optionChanged(OptionChangeEvent event) 
      {
    
    tjc's avatar
    tjc committed
        // if the eukaryotic mode option changes the sequence may change (ie. the
        // start codon may need to be translated differently) - see
    
    tjc's avatar
    tjc committed
        // getTranslation()
    
    tjc's avatar
    tjc committed
        locationChanged(getLocation());
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Returns the embl feature that was passed to the constructor.
       **/
    
    tjc's avatar
    tjc committed
      public uk.ac.sanger.artemis.io.Feature getEmblFeature() 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        return embl_feature;
      }
    
      /**
       *  Write this Feature to the given stream in the native format of this
       *  Feature.  The output will be in Genbank format if this Feature is a
       *  Genbank feature and EMBL format otherwise.
       *  @param writer The record is written to this Writer.
       **/
    
    tjc's avatar
    tjc committed
      public void writeNative(final Writer writer)
          throws IOException 
      {
        if(getEmblFeature() instanceof StreamFeature) 
    
    tjc's avatar
    tjc committed
          ((StreamFeature)getEmblFeature()).writeToStream(writer);
    
    tjc's avatar
    tjc committed
        else 
        {
          final EntryInformation entry_info = getEntry().getEntryInformation();
    
    tjc's avatar
    tjc committed
    
          // this is a hack to make the correct EntryInformation object available
          // to the feature writing code.
          final uk.ac.sanger.artemis.io.EmblDocumentEntry document_entry =
    
    tjc's avatar
    tjc committed
            new uk.ac.sanger.artemis.io.EmblDocumentEntry(entry_info);
    
    tjc's avatar
    tjc committed
    
          final uk.ac.sanger.artemis.io.Feature returned_feature =
    
    tjc's avatar
    tjc committed
            document_entry.forcedAdd(new EmblStreamFeature(getEmblFeature()));
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          ((EmblStreamFeature)returned_feature).writeToStream(writer);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Write a PIR database record of this feature to the given Writer.
       *  @param writer The record is written to this Writer.
       **/
    
    tjc's avatar
    tjc committed
      public void writePIROfFeature(final Writer writer) 
      {
        final String gene_name = getGeneName();
    
    tjc's avatar
    tjc committed
    
        final String pir_name;
    
    
    tjc's avatar
    tjc committed
        if(gene_name == null) 
        {
          if(getLabel() == null) 
            pir_name = getKey().toString();
          else 
            pir_name = getLabel();
    
    tjc's avatar
    tjc committed
        }
    
    tjc's avatar
    tjc committed
        else 
          pir_name = gene_name;
    
    tjc's avatar
    tjc committed
    
        final String header_line =
          ">BL;" + pir_name + ", " +
    
    tjc's avatar
    tjc committed
          getEntry().getName() + " " +
          getWriteRange() +
          " MW:" + (int) getMolecularWeight();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final PrintWriter print_writer = new PrintWriter(writer);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        print_writer.println(header_line);
    
    tjc's avatar
    tjc committed
    
        final String translation_string =
    
    tjc's avatar
    tjc committed
          getTranslation().toString().toUpperCase();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        wrapAndWrite(print_writer, translation_string, 80);
        print_writer.println("*");
        print_writer.flush();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Write the bases of this feature to the given Writer.
       *  @param writer The bases are written to this Writer.
       **/
    
    tjc's avatar
    tjc committed
      public void writeBasesOfFeature(final Writer writer)
          throws IOException 
      {
    
    tjc's avatar
    tjc committed
        final FastaStreamSequence stream_sequence =
    
    tjc's avatar
    tjc committed
          new FastaStreamSequence(getBases(),
                                  getIDString() + ", " +
                                  getWriteRange());
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        stream_sequence.writeToStream(writer);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Write the amino acid symbols of this feature to the given Writer.
       *  @param writer The amino acids are written to this Writer.
       **/
    
    tjc's avatar
    tjc committed
      public void writeAminoAcidsOfFeature(final Writer writer)
          throws IOException 
      {
        final StringBuffer header_buffer = new StringBuffer(">");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        header_buffer.append(getSystematicName());
        header_buffer.append(" ");
        header_buffer.append(getIDString());
        header_buffer.append(" ");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final String product = getProductString();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(product == null)
          header_buffer.append("undefined product");
    
    tjc's avatar
    tjc committed
        else
    
    tjc's avatar
    tjc committed
          header_buffer.append(product);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        header_buffer.append(" ").append(getWriteRange()).append(" MW:");
        header_buffer.append((int) getMolecularWeight());
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final PrintWriter print_writer = new PrintWriter(writer);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        print_writer.println(header_buffer);
    
    tjc's avatar
    tjc committed
    
        final String translation_string =
    
    tjc's avatar
    tjc committed
          getTranslation().toString().toUpperCase();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        wrapAndWrite(print_writer, translation_string, 60);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        print_writer.flush();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return a String containing the bases upstream of this feature.
       *  @param count The number of (immediately) upstream bases to return.
       **/
    
    tjc's avatar
    tjc committed
      public String getUpstreamBases(final int count) 
      {
        final int feature_start_base = getFirstBase();
    
    tjc's avatar
    tjc committed
        final int start_base;
        final int end_base;
    
    
    tjc's avatar
    tjc committed
        if(feature_start_base == 1) 
        {
    
    tjc's avatar
    tjc committed
          // there are no bases before this feature
          return "";
    
    tjc's avatar
    tjc committed
        }
        else
        {
    
    tjc's avatar
    tjc committed
          end_base = feature_start_base - 1;
    
    
    tjc's avatar
    tjc committed
          if(feature_start_base > count) 
    
    tjc's avatar
    tjc committed
            start_base = feature_start_base - count;
    
    tjc's avatar
    tjc committed
          else 
    
    tjc's avatar
    tjc committed
            start_base = 1;
        }
    
        final String bases_string;
    
    
    tjc's avatar
    tjc committed
        try 
        {
    
    tjc's avatar
    tjc committed
          bases_string =
    
    tjc's avatar
    tjc committed
            getStrand().getSubSequence(new Range(start_base, end_base));
        } 
        catch(OutOfRangeException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
    
        return bases_string;
      }
    
      /**
       *  Return a String containing the bases downstream of this feature.
       *  @param count The number of (immediately) downstream bases to return.
       **/
    
    tjc's avatar
    tjc committed
      public String getDownstreamBases(final int count) 
      {
        final int feature_end_base = getLastBase();
    
    tjc's avatar
    tjc committed
        final int start_base;
        final int end_base;
    
    tjc's avatar
    tjc committed
        final int sequenceLength = getSequenceLength();
        
        if(feature_end_base == sequenceLength)
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          // there are no bases after this feature
          return "";
    
    tjc's avatar
    tjc committed
        }
        else 
        {
    
    tjc's avatar
    tjc committed
          start_base = feature_end_base + 1;
    
    
    tjc's avatar
    tjc committed
          if(sequenceLength - feature_end_base > count) 
    
    tjc's avatar
    tjc committed
            end_base = feature_end_base + count;
    
    tjc's avatar
    tjc committed
          else 
    
    tjc's avatar
    tjc committed
            end_base = sequenceLength;
    
    tjc's avatar
    tjc committed
        }
    
        final String bases_string;
    
    
    tjc's avatar
    tjc committed
        try 
        {
    
    tjc's avatar
    tjc committed
          bases_string =
    
    tjc's avatar
    tjc committed
            getStrand().getSubSequence(new Range(start_base, end_base));
        } 
        catch(OutOfRangeException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
    
        return bases_string;
      }
    
      /**
    
    tjc's avatar
    tjc committed
       *  Helper method for writePIROfFeature().
    
    tjc's avatar
    tjc committed
       *  @return A string of the form "100:200 reverse" or "1:2222 forward".
       **/
    
    tjc's avatar
    tjc committed
      public String getWriteRange()
      {
    
    tcarver's avatar
    tcarver committed
        String partial = " ";
        if(isPartial(true))
          partial += "partial 5' ";
        if(isPartial(false))
          partial += "partial 3' ";
    
    
    tjc's avatar
    tjc committed
        return (isForwardFeature() ?
           getFirstCodingBaseMarker().getRawPosition() + ":" +
    
    tcarver's avatar
    tcarver committed
           getLastBaseMarker().getRawPosition() + partial + "forward" :
    
    tjc's avatar
    tjc committed
           getLastBaseMarker().getRawPosition() + ":" +
    
    tcarver's avatar
    tcarver committed
           getFirstCodingBaseMarker().getRawPosition() + partial + "reverse");
      }
      
      /**
       * If lookAt5prime is set to true then only return true if the 5' end is 
       * partial otherwise only return true if the 3' end is partial.
       * @param lookAt5prime
       * @return
       */
      private boolean isPartial(final boolean lookAt5prime)
      {
        try
        {
          boolean isDatabaseFeature = GeneUtils.isDatabaseEntry(getEmblFeature());
          if(isDatabaseFeature)
          {
            if(lookAt5prime)
            {
              if(isForwardFeature())
              {
    
                if(getQualifierByName("Start_range") != null)
    
    tcarver's avatar
    tcarver committed
                  return true;
              }
    
              else if(getQualifierByName("End_range") != null)
    
    tcarver's avatar
    tcarver committed
                  return true;
            }
            else
            {
              if(isForwardFeature())
              {
    
                if(getQualifierByName("End_range") != null)
    
    tcarver's avatar
    tcarver committed
                  return true;
              }
    
              else if(getQualifierByName("Start_range") != null)
    
    tcarver's avatar
    tcarver committed
                  return true;
            }
          }
        } catch (Exception e) {}
        return getLocation().isPartial(lookAt5prime);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Write a String object to a PrintWriter object, wrapping it a the given
       *  coloumn.
       *  @param writer The String will be written to this object.
       *  @param string The String to write.
       *  @param wrap_column The lines of output will be no longer than this many
       *    characters.
       **/
    
    tjc's avatar
    tjc committed
      private void wrapAndWrite(final PrintWriter writer,
                                final String string,
                                final int wrap_column) 
      {
    
    tjc's avatar
    tjc committed
        String remaining_string = string;
    
    
    tjc's avatar
    tjc committed
        while(remaining_string.length() > 0) 
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          int last_index = wrap_column;
    
    
    tjc's avatar
    tjc committed
          if(wrap_column > remaining_string.length()) 
            last_index = remaining_string.length();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          final String write_string = remaining_string.substring(0, last_index);
          writer.println(write_string);
          remaining_string = remaining_string.substring(last_index);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Return this Feature as a EMBL, Genbank or GFF formatted String
       *  (depending on the type of this Feature).
       **/
    
    tjc's avatar
    tjc committed
       public String toString() 
       {
         final StringWriter string_writer = new StringWriter();
    
         try 
         {
           writeNative(string_writer);
         } 
         catch(IOException e) 
         {
           throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
         }
    
    
    tjc's avatar
    tjc committed
         return string_writer.toString() ;
    
    tjc's avatar
    tjc committed
       }
    
      /**
       *  Return a Reader object that gives an EMBL format version of this
       *  Feature when read.
       **/
    
    tjc's avatar
    tjc committed
      public Reader toReader()
      {
        return new StringReader(toString());
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return true if and only if this Feature is on the forward strand.
       **/
    
    tjc's avatar
    tjc committed
      public boolean isForwardFeature() 
      {
        if(getLocation().isComplement()) 
    
    tjc's avatar
    tjc committed
          return false;
    
    tjc's avatar
    tjc committed
        else
    
    tjc's avatar
    tjc committed
          return true;
      }
    
      /**
       *  Return the key of this Feature.
       **/
    
    tjc's avatar
    tjc committed
      public Key getKey()
      {
        return getEmblFeature().getKey();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return true if and only if the key of this feature is protein feature -
       *  one that should be displayed on the translation lines of the
       *  FeatureDisplay component rather than on the forward or backward strand.
       **/
    
    tjc's avatar
    tjc committed
      public boolean isProteinFeature() 
      {
        if(getKey().toString().startsWith("CDS") ||
    
    tjc's avatar
    tjc committed
           getKey().equals(DatabaseDocument.EXONMODEL) ||
    
    tjc's avatar
    tjc committed
           getKey().equals("exon") ||
    
    tjc's avatar
    tjc committed
           getKey().equals("BLASTCDS") || 
           getKey().equals("polypeptide"))
    
    tjc's avatar
    tjc committed
          return true;
    
    tjc's avatar
    tjc committed
        else
    
    tjc's avatar
    tjc committed
          return false;
      }
    
      /**
       *  Return true if and only if the key of this feature is CDS feature.
       **/
    
    tjc's avatar
    tjc committed
      public boolean isCDS() 
      {
        if(getKey().equals("CDS")) 
    
    tjc's avatar
    tjc committed
          return true;
    
    tjc's avatar
    tjc committed
        else
    
    tjc's avatar
    tjc committed
          return false;
      }
    
      /**
       *  Return true if and only if the key of this feature is CDS feature and
       *  the feature has a /partial qualifier.
       **/
    
    tcarver's avatar
    tcarver committed
      private boolean isPartialCDS() 
    
    tjc's avatar
    tjc committed
      {
        try 
        {
          if(getKey().equals("CDS") && getQualifierByName("partial") != null) 
    
    tjc's avatar
    tjc committed
            return true;
    
    tjc's avatar
    tjc committed
          else
    
    tjc's avatar
    tjc committed
            return false;
    
    tjc's avatar
    tjc committed
        }
        catch(InvalidRelationException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Return the Location of this Feature.
       **/
    
    tjc's avatar
    tjc committed
      public Location getLocation()
      {
        final Location current_location = getEmblFeature().getLocation();
    
    tjc's avatar
    tjc committed
        return current_location;
      }
    
      /**
       *  Return a Vector containing the qualifiers of this Feature.
       *  XXX - FIXME - should return a copy or be private
       **/
    
    tjc's avatar
    tjc committed
      public QualifierVector getQualifiers()
      {
    
    tjc's avatar
    tjc committed
        // return the embl.QualifierVector from the underlying embl.Feature object
        return getEmblFeature().getQualifiers();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Return the Entry that owns this object.
       **/
    
    tjc's avatar
    tjc committed
      public Entry getEntry() 
      {
    
    tjc's avatar
    tjc committed
        return entry;
      }
    
      /**
       *  Return the value of the first /codon_start qualifier as an integer or
       *  returns 1 if codon_start doesn't exist or if it makes no sense.
       **/
    
    tjc's avatar
    tjc committed
      public int getCodonStart()
      {
        try 
        {
          final String codon_start_string = getValueOfQualifier("codon_start");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          if(codon_start_string == null) 
          {
    
    tjc's avatar
    tjc committed
            // default value
            return 1;
          }
    
    
    tjc's avatar
    tjc committed
          if(codon_start_string.equals("2")) 
    
    tjc's avatar
    tjc committed
            return 2;
    
    tjc's avatar
    tjc committed
          else 
          {
            if(codon_start_string.equals("3")) 
    
    tjc's avatar
    tjc committed
              return 3;
    
    tjc's avatar
    tjc committed
            else 
              return 1;  // default value
    
    tjc's avatar
    tjc committed
          }
    
    tjc's avatar
    tjc committed
        } 
        catch(InvalidRelationException e) 
        {
    
    tjc's avatar
    tjc committed
          return 1;
        }
      }
    
      /**
       *  Return the value of the first /score qualifier of this feature as an
       *  integer.
       *  @return The score or -1 if the feature has no /score qualifier.
       **/
    
    tjc's avatar
    tjc committed
      public int getScore() 
      {
        try
        {
          final String score_string = getValueOfQualifier("score");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          if(score_string == null) 
          {
    
    tjc's avatar
    tjc committed
            // default value
            return -1;
          }
    
    
    tjc's avatar
    tjc committed
          try 
          {
            final int score_int = Float.valueOf(score_string).intValue();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(score_int > 100) 
    
    tjc's avatar
    tjc committed
              return 100;
    
    
    tjc's avatar
    tjc committed
            if(score_int < 0)
    
    tjc's avatar
    tjc committed
              return 0;
    
            return score_int;
    
    tjc's avatar
    tjc committed
          }  
          catch(NumberFormatException e) 
          {
    
    tjc's avatar
    tjc committed
            // assume there is no /score
    
            return -1;
          }
    
    tjc's avatar
    tjc committed
        } 
        catch(InvalidRelationException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Set the Entry that owns this object.
       **/
    
    tjc's avatar
    tjc committed
      public void setEntry(final Entry entry) 
    
    tjc's avatar
    tjc committed
      {
        if(this.entry != null)
          stopListening();
    
    tjc's avatar
    tjc committed
    
        final Entry old_entry = this.entry;
    
        this.entry = entry;
    
    
    tjc's avatar
    tjc committed
        if(old_entry == entry) 
    
    tjc's avatar
    tjc committed
          return;
    
    tjc's avatar
    tjc committed
        else 
        {
          if(old_entry != null)
            removeFeatureChangeListener(old_entry);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          if(entry != null)
          {
    
    tjc's avatar
    tjc committed
            // the Entry object acts as a proxy for FeatureChange events, other
    
    tjc's avatar
    tjc committed
            // objects can can addFeatureChangeListener() once on the Entry object
    
    tjc's avatar
    tjc committed
            // instead of calling it for every Feature.
    
    tjc's avatar
    tjc committed
            addFeatureChangeListener(getEntry());
    
    tjc's avatar
    tjc committed
          }
        }
    
    
    tjc's avatar
    tjc committed
        if(this.entry != null)
          startListening();
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Change this Feature and it's underlying embl Feature object to the given
       *  key, location and qualifiers.
       *  @exception InvalidRelationException Thrown if this Feature cannot contain
       *    the given Qualifier.
       *  @exception OutOfRangeException Thrown if the location is out of
       *    range for this Entry.
       *  @exception ReadOnlyException Thrown if this Feature cannot be changed.
       **/
    
    tjc's avatar
    tjc committed
      public void set(Key new_key,
                      Location new_location,
                      QualifierVector new_qualifiers)
    
    tjc's avatar
    tjc committed
          throws EntryInformationException, OutOfRangeException,
    
    tjc's avatar
    tjc committed
                 ReadOnlyException
      {
        try
        {
          set((Date)null, new_key, new_location, new_qualifiers);
        } 
        catch(OutOfDateException e) 
        {
          throw new Error("internal error - unexpected exception: " + e);
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Change this Feature and it's underlying embl Feature object to the given
       *  key, location and qualifiers.
       *  @exception InvalidRelationException Thrown if this Feature cannot contain
       *    the given Qualifier.
       *  @exception OutOfRangeException Thrown if the location is out of
       *    range for this Entry.
       *  @exception ReadOnlyException Thrown if this Feature cannot be changed.
       *  @exception OutOfDateException If the key has changed in the server since
       *    the time given by datestamp.  If datestamp argument is null then this
       *    exception will never be thrown.
       **/
    
    tjc's avatar
    tjc committed
      public void set(final Date datestamp,
                      final Key new_key,
                      final Location new_location,
                      final QualifierVector new_qualifiers)
    
    tjc's avatar
    tjc committed
          throws EntryInformationException, OutOfRangeException,
    
    tjc's avatar
    tjc committed
                 ReadOnlyException, OutOfDateException 
      {
        final Key old_key = getKey();
        old_location = getLocation();
        final QualifierVector old_qualifiers = getQualifiers().copy();
    
    
        final int sequence_length;
        
    
    tjc's avatar
    tjc committed
        if(!(getEntry().getBases().getSequence() instanceof PartialSequence))
    
    tjc's avatar
    tjc committed
        {
    
          sequence_length = getEntry().getBases().getLength();
    
    tjc's avatar
    tjc committed
    
    
          if(new_location != null)
    
    tjc's avatar
    tjc committed
          {
    
            final Range span = new_location.getTotalRange();
    
            if(span.getEnd() > sequence_length || span.getStart() < 1)
            {
              throw new OutOfRangeException(new_location.toString());
            }
    
    tjc's avatar
    tjc committed
          }
        }
    
    tjc's avatar
    tjc committed
        if(datestamp == null ||
           !(getEmblFeature() instanceof DateStampFeature)) 
        {
    
    tjc's avatar
    tjc committed
          getEmblFeature().set(new_key, new_location, new_qualifiers);
    
    tjc's avatar
    tjc committed
        } 
        else
        {
          ((DateStampFeature)getEmblFeature()).set(datestamp,
                                                   new_key,
                                                   new_location,
                                                   new_qualifiers);
    
    tjc's avatar
    tjc committed
        }
    
    
    tjc's avatar
    tjc committed
        resetCache();
    
    tjc's avatar
    tjc committed
    
    
        if(new_location != old_location && segments != null) 
    
    tjc's avatar
    tjc committed
          reexamineSegments();
    
    tjc's avatar
    tjc committed
    
        // now inform the listeners that a change has occured
        final FeatureChangeEvent event =
    
    tjc's avatar
    tjc committed
          new FeatureChangeEvent(this,
                                 this,
                                 old_key,
                                 old_location,
                                 old_qualifiers,
                                 FeatureChangeEvent.ALL_CHANGED);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        fireAction(feature_listener_list, event);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  This method will send a FeatureChangeEvent with type LOCATION_CHANGED to
       *  all the FeatureChangeEvent listeners when a marker changes.  It also
    
    tjc's avatar
    tjc committed
       *  calls resetCache(), because changing the location will change the
    
    tjc's avatar
    tjc committed
       *  translation and bases of the feature.
       **/
    
      private void locationChanged(final Location old_location,
    
    tjc's avatar
    tjc committed
      {
        resetCache();
    
    tjc's avatar
    tjc committed
    
        // now inform the listeners that a change has occured
        final FeatureChangeEvent feature_change_event =
    
    tjc's avatar
    tjc committed
          new FeatureChangeEvent(this,
                                 this,
                                 null,
                                 old_location,
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        this.old_location = getLocation();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        fireAction(feature_listener_list, feature_change_event);
    
    tjc's avatar
    tjc committed
      }
    
    
      private void locationChanged(final Location old_location) 
      {
    
                        FeatureChangeEvent.LOCATION_CHANGED);  
      }
      
    
    tjc's avatar
    tjc committed
      /**
       *  Add the values from the given qualifier to the Qualifier object with the
       *  same name in this Feature or if there is no Qualifier with that name
       *  just add a copy of the argument.
       *  @param qualifier This object contians name and values to add.
       *  @return The Qualifier that was changed or created.
       **/
    
    tjc's avatar
    tjc committed
      public Qualifier addQualifierValues(Qualifier qualifier)