1 /* Feature.java
2  *
3  * created: Sun Oct 11 1998
4  *
5  * This file is part of Artemis
6  *
7  * Copyright (C) 1998,1999,2000  Genome Research Limited
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $
24  */
25 
26 package uk.ac.sanger.artemis;
27 
28 import uk.ac.sanger.artemis.util.*;
29 import uk.ac.sanger.artemis.components.genebuilder.GeneUtils;
30 import uk.ac.sanger.artemis.io.OutOfDateException;
31 import uk.ac.sanger.artemis.io.LocationParseException;
32 import uk.ac.sanger.artemis.io.Location;
33 import uk.ac.sanger.artemis.io.Key;
34 import uk.ac.sanger.artemis.io.PartialSequence;
35 import uk.ac.sanger.artemis.io.Qualifier;
36 import uk.ac.sanger.artemis.io.QualifierVector;
37 import uk.ac.sanger.artemis.io.RangeVector;
38 import uk.ac.sanger.artemis.io.Range;
39 import uk.ac.sanger.artemis.io.InvalidRelationException;
40 import uk.ac.sanger.artemis.io.DateStampFeature;
41 import uk.ac.sanger.artemis.io.EntryInformation;
42 import uk.ac.sanger.artemis.io.EntryInformationException;
43 import uk.ac.sanger.artemis.io.EmblStreamFeature;
44 import uk.ac.sanger.artemis.io.GFFStreamFeature;
45 import uk.ac.sanger.artemis.io.FastaStreamSequence;
46 import uk.ac.sanger.artemis.io.StreamFeature;
47 import uk.ac.sanger.artemis.sequence.*;
48 import uk.ac.sanger.artemis.plot.*;
49 
50 import java.awt.Color;
51 import java.util.Vector;
52 import java.io.*;
53 import java.util.Date;
54 import java.util.regex.Pattern;
55 
56 /**
57  *  This class extends an embl.Feature with the other information needed by
58  *  Diana.  It also able to send events to other objects that are interested
59  *  in changes to this object.  (see FeatureChangeEvent details of the
60  *  possible change events.)  To make changes to the feature it calls methods
61  *  in the Entry class.  Changes to this object will update the underlying
62  *  embl.Feature and embl.Entry objects.
63  *
64  *  @author Kim Rutherford
65  **/
66 
67 public class Feature
68     implements EntryChangeListener, Selectable, SequenceChangeListener,
69                MarkerChangeListener, OptionChangeListener
70 {
71 
72   /**
73    *  The Entry that controls/contains this feature.  This is the Entry object
74    *  that was passed to the constructor.
75    **/
76   private Entry entry;
77 
78   /**
79    *  This is a reference to the low level Feature object that this class is
80    *  providing a wrapper for.  This is the embl.Feature object that was
81    *  passed to constructor.
82    **/
83   private uk.ac.sanger.artemis.io.Feature embl_feature;
84 
85   /**
86    *  The segment of this Feature.  All features have at least one segment.
87    **/
88   private FeatureSegmentVector segments = null;
89 
90   /**
91    *  A vector of those objects listening for feature change events.
92    **/
93   private final Vector feature_listener_list = new Vector();
94 
95   /**
96    *  The translation of the bases of this feature.
97    **/
98   private AminoAcidSequence amino_acids = null;
99 
100   /**
101    *  The bases of this feature.
102    **/
103   private String bases = null;
104 
105   /**
106    *  The count of the number of amino acids in this feature. (set by
107    *  getAACount() and resetCache()).
108    **/
109   private int aa_count = -1;
110 
111   /**
112    *  The count of the bases in this feature (set by getBaseCount() and
113    *  resetCache()).
114    **/
115   private int base_count = -1;
116 
117   /**
118    *  This array contains counts of the occurrences of each three bases.  The
119    *  first index is the first base, the second index is the second base, etc.
120    *  The indices are the same as those for Bases.letter_index.
121    *  (set by resetCache()).
122    **/
123   private int [][][] codon_counts = null;
124 
125   /**
126    *  This array contains counts of the occurrences of each amino acids in the
127    *  translation of the bases of the current feature.
128    *  (set by resetCache()).
129    **/
130   private int [] residue_counts = null;
131 
132   /**
133    *  This array contains counts of number of each base that appear in each
134    *  codon position.  The first index is the codon position and the second is
135    *  the base (indexed in the same way as Bases.letter_index).
136    *  (set by resetCache()).
137    **/
138   private int [][] positional_base_counts = null;
139 
140   /**
141    *  This array contains the counts of the total number of each base in the
142    *  feature.
143    *  The indices are the same as those for Bases.letter_index.
144    *  (set by resetCache()).
145    **/
146   private int [] base_counts = null;
147 
148   /**
149    *  The current Location reference is saved each time setLocation() is
150    *  called so that if the reference changes resetCache() can
151    *  be called.
152    **/
153   private Location old_location = null;
154 
155   /**
156    *  Incremented when startListening() is called - decremented when
157    *  stopListening() is called.
158    **/
159   private int listen_count = 0;
160 
161   private Color colour = null;
162 
163   /**
164    *  Create a new Feature object.
165    *  @param entry The uk.ac.sanger.artemis.Entry object that contains this Feature.
166    *  @param embl_feature The embl.Feature object that this class is
167    *    providing a wrapper for.
168    **/
Feature(uk.ac.sanger.artemis.io.Feature embl_feature)169   public Feature(uk.ac.sanger.artemis.io.Feature embl_feature)
170   {
171     this.embl_feature = embl_feature;
172     embl_feature.setUserData(this);
173     old_location = embl_feature.getLocation();
174   }
175 
176   /**
177    *  Implementation of the EntryChangeListener interface.  We listen to
178    *  EntryChange events so that if the feature is deleted we can destroy any
179    *  objects that use it.
180    *
181    *  NOTE: not active currently
182    **/
entryChanged(EntryChangeEvent event)183   public void entryChanged(EntryChangeEvent event)
184   {
185   }
186 
187   /**
188    *  This method fixes up the location of this Feature when a Marker changes.
189    **/
markerChanged(final MarkerChangeEvent event)190   public void markerChanged(final MarkerChangeEvent event)
191   {
192     try
193     {
194       final Location old_location = getLocation();
195       updateEMBLFeatureLocation();
196       locationChanged(old_location);
197     }
198     catch(ReadOnlyException e) {}
199   }
200 
201 
202   /**
203    *  This method fixes up the location of this Feature when a sequence
204    *  changes.
205    **/
sequenceChanged(final SequenceChangeEvent event)206   public void sequenceChanged(final SequenceChangeEvent event)
207   {
208     try
209     {
210       // we don't send a FeatureChangeEvent because the logical location
211       // hasn't changed
212 
213       // all the Markers for the ranges of this feature will have changed
214       // before this method is called because the markers are added as
215       // SequenceChangeListener with a higher priority than this Feature
216 
217       if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT)
218         reverseComplement(getEntry().getBases().getLength());
219       else if(event.getType() == SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT)
220       {
221          final Location old_location = getLocation();
222 
223         // if the event is contained within this feature then the feature
224         // sequence may have changed
225         final Range this_feature_range = getMaxRawRange();
226 
227         boolean feature_changed = false;
228         Range eventRange = event.getRange();
229 
230         // reverse complement feature if within contig region
231         if(eventRange.getStart() <= this_feature_range.getStart() &&
232            eventRange.getEnd() >= this_feature_range.getEnd())
233         {
234           try
235           {
236             final Location new_location =
237                getLocation().reverseComplement(event.getLength(), eventRange.getStart());
238 
239             setLocationInternal(new_location);
240           }
241           catch(OutOfRangeException e)
242           {
243             throw new Error("internal error - inconsistent location: " + e);
244           }
245         }
246       }
247       else if(event.getType() == SequenceChangeEvent.CONTIG_REORDER)
248       {
249         final Location old_location = getLocation();
250 
251         final int new_base_pos = event.getPosition();
252         final int range_start  = event.getRange().getStart();
253         final int range_end = event.getRange().getEnd();
254         final Range this_feature_range = getMaxRawRange();
255 
256         // check if feature is effected
257         if( (this_feature_range.getStart() >= new_base_pos ||
258              this_feature_range.getStart() >= range_start) &&
259             (this_feature_range.getStart() < new_base_pos ||
260              this_feature_range.getStart() < range_end) )
261         {
262           try
263           {
264             final int diff;
265 
266             if(range_start <= this_feature_range.getStart() &&
267                range_end   >= this_feature_range.getEnd())
268             {
269               if(new_base_pos < range_start)
270                 diff = new_base_pos-range_start;
271               else
272                 diff = new_base_pos-range_end-1;
273             }
274             else
275             {
276               if(this_feature_range.getStart() < range_start)
277                 diff = range_end-range_start+1;
278               else
279                 diff = range_start-range_end-1;
280             }
281 
282             Location new_location = moveSegments(diff);
283             setLocationInternal(new_location);
284           }
285           catch(OutOfRangeException e)
286           {
287             throw new Error("internal error - inconsistent location: " + e);
288           }
289         }
290       }
291       else
292       {
293         final Location old_location = getLocation();
294 
295         // if the event is contained within this feature then the feature
296         // sequence may have changed
297         final Range this_feature_range = getMaxRawRange();
298 
299         boolean feature_changed = false;
300         int eventPosition = event.getPosition();
301 
302         if(eventPosition >= this_feature_range.getStart() &&
303            eventPosition <= this_feature_range.getEnd() + 1)
304         {
305           // check each segment
306           final FeatureSegmentVector segments = getSegments();
307           int seg_size = segments.size();
308 
309           for(int i = 0; i < seg_size; ++i)
310           {
311             final FeatureSegment this_segment = segments.elementAt(i);
312 
313             final Range this_segment_range = this_segment.getRawRange();
314 
315             if(eventPosition >= this_segment_range.getStart() &&
316                eventPosition <= this_segment_range.getEnd() + 1)
317               feature_changed = true;
318           }
319         }
320 
321         updateEMBLFeatureLocation();
322 
323         if(feature_changed)
324         {
325           resetCache();
326           locationChanged(old_location);
327         }
328       }
329     }
330     catch(ReadOnlyException e)
331     {
332       throw new Error("internal error - unexpected exception: " + e);
333     }
334   }
335 
336   /**
337    *  Invoked when an Option is changed.
338    **/
optionChanged(OptionChangeEvent event)339   public void optionChanged(OptionChangeEvent event)
340   {
341     // if the eukaryotic mode option changes the sequence may change (ie. the
342     // start codon may need to be translated differently) - see
343     // getTranslation()
344     locationChanged(getLocation());
345   }
346 
347   /**
348    *  Returns the embl feature that was passed to the constructor.
349    **/
getEmblFeature()350   public uk.ac.sanger.artemis.io.Feature getEmblFeature()
351   {
352     return embl_feature;
353   }
354 
355   /**
356    *  Write this Feature to the given stream in the native format of this
357    *  Feature.  The output will be in Genbank format if this Feature is a
358    *  Genbank feature and EMBL format otherwise.
359    *  @param writer The record is written to this Writer.
360    **/
writeNative(final Writer writer)361   public void writeNative(final Writer writer)
362       throws IOException
363   {
364     if(getEmblFeature() instanceof StreamFeature)
365       ((StreamFeature)getEmblFeature()).writeToStream(writer);
366     else
367     {
368       final EntryInformation entry_info = getEntry().getEntryInformation();
369 
370       // this is a hack to make the correct EntryInformation object available
371       // to the feature writing code.
372       final uk.ac.sanger.artemis.io.EmblDocumentEntry document_entry =
373         new uk.ac.sanger.artemis.io.EmblDocumentEntry(entry_info);
374 
375       final uk.ac.sanger.artemis.io.Feature returned_feature =
376         document_entry.forcedAdd(new EmblStreamFeature(getEmblFeature()));
377 
378       ((EmblStreamFeature)returned_feature).writeToStream(writer);
379     }
380   }
381 
382   /**
383    *  Write a PIR database record of this feature to the given Writer.
384    *  @param writer The record is written to this Writer.
385    **/
writePIROfFeature(final Writer writer)386   public void writePIROfFeature(final Writer writer)
387   {
388     final String gene_name = getGeneName();
389 
390     final String pir_name;
391 
392     if(gene_name == null)
393     {
394       if(getLabel() == null)
395         pir_name = getKey().toString();
396       else
397         pir_name = getLabel();
398     }
399     else
400       pir_name = gene_name;
401 
402     final String header_line =
403       ">BL;" + pir_name + ", " +
404       getEntry().getName() + " " +
405       getWriteRange() +
406       " MW:" + (int) getMolecularWeight();
407 
408     final PrintWriter print_writer = new PrintWriter(writer);
409 
410     print_writer.println(header_line);
411 
412     final String translation_string =
413       getTranslation().toString().toUpperCase();
414 
415     wrapAndWrite(print_writer, translation_string, 80);
416     print_writer.println("*");
417     print_writer.flush();
418   }
419 
420   /**
421    *  Write the bases of this feature to the given Writer.
422    *  @param writer The bases are written to this Writer.
423    **/
writeBasesOfFeature(final Writer writer)424   public void writeBasesOfFeature(final Writer writer)
425       throws IOException
426   {
427     final FastaStreamSequence stream_sequence =
428       new FastaStreamSequence(getBases(),
429                               getIDString() + ", " +
430                               getWriteRange());
431 
432     stream_sequence.writeToStream(writer);
433   }
434 
435   /**
436    *  Write the amino acid symbols of this feature to the given Writer.
437    *  @param writer The amino acids are written to this Writer.
438    **/
writeAminoAcidsOfFeature(final Writer writer)439   public void writeAminoAcidsOfFeature(final Writer writer)
440       throws IOException
441   {
442     final StringBuffer header_buffer = new StringBuffer(">");
443 
444     header_buffer.append(getSystematicName());
445     header_buffer.append(" ");
446     header_buffer.append(getIDString());
447     header_buffer.append(" ");
448 
449     final String product = getProductString();
450 
451     if(product == null)
452       header_buffer.append("undefined product");
453     else
454       header_buffer.append(product);
455 
456     header_buffer.append(" ").append(getWriteRange()).append(" MW:");
457     header_buffer.append((int) getMolecularWeight());
458 
459     final PrintWriter print_writer = new PrintWriter(writer);
460 
461     print_writer.println(header_buffer);
462 
463     final String translation_string =
464       getTranslation().toString().toUpperCase();
465 
466     wrapAndWrite(print_writer, translation_string, 60);
467 
468     print_writer.flush();
469   }
470 
471   /**
472    *  Return a String containing the bases upstream of this feature.
473    *  @param count The number of (immediately) upstream bases to return.
474    **/
getUpstreamBases(final int count)475   public String getUpstreamBases(final int count)
476   {
477     final int feature_start_base = getFirstBase();
478     final int start_base;
479     final int end_base;
480 
481     if(feature_start_base == 1)
482     {
483       // there are no bases before this feature
484       return "";
485     }
486     else
487     {
488       end_base = feature_start_base - 1;
489 
490       if(feature_start_base > count)
491         start_base = feature_start_base - count;
492       else
493         start_base = 1;
494     }
495 
496     final String bases_string;
497 
498     try
499     {
500       bases_string =
501         getStrand().getSubSequence(new Range(start_base, end_base));
502     }
503     catch(OutOfRangeException e)
504     {
505       throw new Error("internal error - unexpected exception: " + e);
506     }
507 
508     return bases_string;
509   }
510 
511   /**
512    *  Return a String containing the bases downstream of this feature.
513    *  @param count The number of (immediately) downstream bases to return.
514    **/
getDownstreamBases(final int count)515   public String getDownstreamBases(final int count)
516   {
517     final int feature_end_base = getLastBase();
518     final int start_base;
519     final int end_base;
520     final int sequenceLength = getSequenceLength();
521 
522     if(feature_end_base == sequenceLength)
523     {
524       // there are no bases after this feature
525       return "";
526     }
527     else
528     {
529       start_base = feature_end_base + 1;
530 
531       if(sequenceLength - feature_end_base > count)
532         end_base = feature_end_base + count;
533       else
534         end_base = sequenceLength;
535     }
536 
537     final String bases_string;
538 
539     try
540     {
541       bases_string =
542         getStrand().getSubSequence(new Range(start_base, end_base));
543     }
544     catch(OutOfRangeException e)
545     {
546       throw new Error("internal error - unexpected exception: " + e);
547     }
548 
549     return bases_string;
550   }
551 
552   /**
553    *  Helper method for writePIROfFeature().
554    *  @return A string of the form "100:200 reverse" or "1:2222 forward".
555    **/
getWriteRange()556   public String getWriteRange()
557   {
558     String partial = " ";
559     if(isPartial(true))
560       partial += "partial 5' ";
561     if(isPartial(false))
562       partial += "partial 3' ";
563 
564     return (isForwardFeature() ?
565        getFirstCodingBaseMarker().getRawPosition() + ":" +
566        getLastBaseMarker().getRawPosition() + partial + "forward" :
567        getLastBaseMarker().getRawPosition() + ":" +
568        getFirstCodingBaseMarker().getRawPosition() + partial + "reverse");
569   }
570 
571   /**
572    * If lookAt5prime is set to true then only return true if the 5' end is
573    * partial otherwise only return true if the 3' end is partial.
574    * @param lookAt5prime
575    * @return
576    */
isPartial(final boolean lookAt5prime)577   private boolean isPartial(final boolean lookAt5prime)
578   {
579     try
580     {
581       boolean isDatabaseFeature = GeneUtils.isDatabaseEntry(getEmblFeature());
582       if(isDatabaseFeature)
583       {
584         if(lookAt5prime)
585         {
586           if(isForwardFeature())
587           {
588             if(getQualifierByName("Start_range") != null)
589               return true;
590           }
591           else if(getQualifierByName("End_range") != null)
592               return true;
593         }
594         else
595         {
596           if(isForwardFeature())
597           {
598             if(getQualifierByName("End_range") != null)
599               return true;
600           }
601           else if(getQualifierByName("Start_range") != null)
602               return true;
603         }
604       }
605     } catch (Exception e) {}
606     return getLocation().isPartial(lookAt5prime);
607   }
608 
609   /**
610    *  Write a String object to a PrintWriter object, wrapping it a the given
611    *  coloumn.
612    *  @param writer The String will be written to this object.
613    *  @param string The String to write.
614    *  @param wrap_column The lines of output will be no longer than this many
615    *    characters.
616    **/
wrapAndWrite(final PrintWriter writer, final String string, final int wrap_column)617   private void wrapAndWrite(final PrintWriter writer,
618                             final String string,
619                             final int wrap_column)
620   {
621     String remaining_string = string;
622 
623     while(remaining_string.length() > 0)
624     {
625       int last_index = wrap_column;
626 
627       if(wrap_column > remaining_string.length())
628         last_index = remaining_string.length();
629 
630       final String write_string = remaining_string.substring(0, last_index);
631       writer.println(write_string);
632       remaining_string = remaining_string.substring(last_index);
633     }
634   }
635 
636   /**
637    *  Return this Feature as a EMBL, Genbank or GFF formatted String
638    *  (depending on the type of this Feature).
639    **/
toString()640    public String toString()
641    {
642      final StringWriter string_writer = new StringWriter();
643 
644      try
645      {
646        writeNative(string_writer);
647      }
648      catch(IOException e)
649      {
650        throw new Error("internal error - unexpected exception: " + e);
651      }
652 
653      return string_writer.toString() ;
654    }
655 
656   /**
657    *  Return a Reader object that gives an EMBL format version of this
658    *  Feature when read.
659    **/
toReader()660   public Reader toReader()
661   {
662     return new StringReader(toString());
663   }
664 
665   /**
666    *  Return true if and only if this Feature is on the forward strand.
667    **/
isForwardFeature()668   public boolean isForwardFeature()
669   {
670     if(getLocation().isComplement())
671       return false;
672     else
673       return true;
674   }
675 
676   /**
677    *  Return the key of this Feature.
678    **/
getKey()679   public Key getKey()
680   {
681     return getEmblFeature().getKey();
682   }
683 
684   /**
685    *  Return true if and only if the key of this feature is protein feature -
686    *  one that should be displayed on the translation lines of the
687    *  FeatureDisplay component rather than on the forward or backward strand.
688    **/
isProteinFeature()689   public boolean isProteinFeature()
690   {
691     if(getKey().toString().startsWith("CDS") ||
692        getKey().equals(DatabaseDocument.EXONMODEL) ||
693        getKey().equals("exon") ||
694        getKey().equals("BLASTCDS") ||
695        getKey().equals("polypeptide"))
696       return true;
697     else
698       return false;
699   }
700 
701   /**
702    *  Return true if and only if the key of this feature is CDS feature.
703    **/
isCDS()704   public boolean isCDS()
705   {
706     if(getKey().equals("CDS"))
707       return true;
708     else
709       return false;
710   }
711 
712   /**
713    *  Return true if and only if the key of this feature is CDS feature and
714    *  the feature has a /partial qualifier.
715    **/
isPartialCDS()716   private boolean isPartialCDS()
717   {
718     try
719     {
720       if(getKey().equals("CDS") && getQualifierByName("partial") != null)
721         return true;
722       else
723         return false;
724     }
725     catch(InvalidRelationException e)
726     {
727       throw new Error("internal error - unexpected exception: " + e);
728     }
729   }
730 
731   /**
732    *  Return the Location of this Feature.
733    **/
getLocation()734   public Location getLocation()
735   {
736     final Location current_location = getEmblFeature().getLocation();
737     return current_location;
738   }
739 
740   /**
741    *  Return a Vector containing the qualifiers of this Feature.
742    *  XXX - FIXME - should return a copy or be private
743    **/
getQualifiers()744   public QualifierVector getQualifiers()
745   {
746     // return the embl.QualifierVector from the underlying embl.Feature object
747     return getEmblFeature().getQualifiers();
748   }
749 
750   /**
751    *  Return the Entry that owns this object.
752    **/
getEntry()753   public Entry getEntry()
754   {
755     return entry;
756   }
757 
758   /**
759    *  Return the value of the first /codon_start qualifier as an integer or
760    *  returns 1 if codon_start doesn't exist or if it makes no sense.
761    **/
getCodonStart()762   public int getCodonStart()
763   {
764     try
765     {
766       final String codon_start_string = getValueOfQualifier("codon_start");
767 
768       if(codon_start_string == null)
769       {
770         // default value
771         return 1;
772       }
773 
774       if(codon_start_string.equals("2"))
775         return 2;
776       else
777       {
778         if(codon_start_string.equals("3"))
779           return 3;
780         else
781           return 1;  // default value
782       }
783     }
784     catch(InvalidRelationException e)
785     {
786       return 1;
787     }
788   }
789 
790   /**
791    *  Return the value of the first /score qualifier of this feature as an
792    *  integer.
793    *  @return The score or -1 if the feature has no /score qualifier.
794    **/
getScore()795   public int getScore()
796   {
797     try
798     {
799       final String score_string = getValueOfQualifier("score");
800 
801       if(score_string == null)
802       {
803         // default value
804         return -1;
805       }
806 
807       try
808       {
809         final int score_int = Float.valueOf(score_string).intValue();
810 
811         if(score_int > 100)
812           return 100;
813 
814         if(score_int < 0)
815           return 0;
816 
817         return score_int;
818       }
819       catch(NumberFormatException e)
820       {
821         // assume there is no /score
822 
823         return -1;
824       }
825     }
826     catch(InvalidRelationException e)
827     {
828       throw new Error("internal error - unexpected exception: " + e);
829     }
830   }
831 
832   /**
833    *  Set the Entry that owns this object.
834    **/
setEntry(final Entry entry)835   public void setEntry(final Entry entry)
836   {
837     if(this.entry != null)
838       stopListening();
839 
840     final Entry old_entry = this.entry;
841 
842     this.entry = entry;
843 
844     if(old_entry == entry)
845       return;
846     else
847     {
848       if(old_entry != null)
849         removeFeatureChangeListener(old_entry);
850 
851       if(entry != null)
852       {
853         // the Entry object acts as a proxy for FeatureChange events, other
854         // objects can can addFeatureChangeListener() once on the Entry object
855         // instead of calling it for every Feature.
856         addFeatureChangeListener(getEntry());
857       }
858     }
859 
860     if(this.entry != null)
861       startListening();
862   }
863 
864   /**
865    *  Change this Feature and it's underlying embl Feature object to the given
866    *  key, location and qualifiers.
867    *  @exception InvalidRelationException Thrown if this Feature cannot contain
868    *    the given Qualifier.
869    *  @exception OutOfRangeException Thrown if the location is out of
870    *    range for this Entry.
871    *  @exception ReadOnlyException Thrown if this Feature cannot be changed.
872    **/
set(Key new_key, Location new_location, QualifierVector new_qualifiers)873   public void set(Key new_key,
874                   Location new_location,
875                   QualifierVector new_qualifiers)
876       throws EntryInformationException, OutOfRangeException,
877              ReadOnlyException
878   {
879     try
880     {
881       set((Date)null, new_key, new_location, new_qualifiers);
882     }
883     catch(OutOfDateException e)
884     {
885       throw new Error("internal error - unexpected exception: " + e);
886     }
887   }
888 
889   /**
890    *  Change this Feature and it's underlying embl Feature object to the given
891    *  key, location and qualifiers.
892    *  @exception InvalidRelationException Thrown if this Feature cannot contain
893    *    the given Qualifier.
894    *  @exception OutOfRangeException Thrown if the location is out of
895    *    range for this Entry.
896    *  @exception ReadOnlyException Thrown if this Feature cannot be changed.
897    *  @exception OutOfDateException If the key has changed in the server since
898    *    the time given by datestamp.  If datestamp argument is null then this
899    *    exception will never be thrown.
900    **/
set(final Date datestamp, final Key new_key, final Location new_location, final QualifierVector new_qualifiers)901   public void set(final Date datestamp,
902                   final Key new_key,
903                   final Location new_location,
904                   final QualifierVector new_qualifiers)
905       throws EntryInformationException, OutOfRangeException,
906              ReadOnlyException, OutOfDateException
907   {
908     final Key old_key = getKey();
909     old_location = getLocation();
910     final QualifierVector old_qualifiers = getQualifiers().copy();
911 
912     final int sequence_length;
913 
914     if(!(getEntry().getBases().getSequence() instanceof PartialSequence))
915     {
916       sequence_length = getEntry().getBases().getLength();
917 
918       if(new_location != null)
919       {
920         final Range span = new_location.getTotalRange();
921 
922         if(span.getEnd() > sequence_length || span.getStart() < 1)
923         {
924           throw new OutOfRangeException(new_location.toString());
925         }
926       }
927     }
928 
929     if(datestamp == null ||
930        !(getEmblFeature() instanceof DateStampFeature))
931     {
932       getEmblFeature().set(new_key, new_location, new_qualifiers);
933     }
934     else
935     {
936       ((DateStampFeature)getEmblFeature()).set(datestamp,
937                                                new_key,
938                                                new_location,
939                                                new_qualifiers);
940     }
941 
942     resetCache();
943 
944     if(new_location != old_location && segments != null)
945       reexamineSegments();
946 
947     // now inform the listeners that a change has occured
948     final FeatureChangeEvent event =
949       new FeatureChangeEvent(this,
950                              this,
951                              old_key,
952                              old_location,
953                              old_qualifiers,
954                              FeatureChangeEvent.ALL_CHANGED);
955 
956     fireAction(feature_listener_list, event);
957   }
958 
959   /**
960    *  This method will send a FeatureChangeEvent with type LOCATION_CHANGED to
961    *  all the FeatureChangeEvent listeners when a marker changes.  It also
962    *  calls resetCache(), because changing the location will change the
963    *  translation and bases of the feature.
964    **/
locationChanged(final Location old_location, final QualifierVector qualifiers, int type)965   private void locationChanged(final Location old_location,
966                                final QualifierVector qualifiers,
967                                int type)
968   {
969     resetCache();
970 
971     // now inform the listeners that a change has occured
972     final FeatureChangeEvent feature_change_event =
973       new FeatureChangeEvent(this,
974                              this,
975                              null,
976                              old_location,
977                              qualifiers,
978                              type);
979 
980     this.old_location = getLocation();
981 
982     fireAction(feature_listener_list, feature_change_event);
983   }
984 
locationChanged(final Location old_location)985   private void locationChanged(final Location old_location)
986   {
987     locationChanged(old_location, null,
988                     FeatureChangeEvent.LOCATION_CHANGED);
989   }
990 
991   /**
992    *  Add the values from the given qualifier to the Qualifier object with the
993    *  same name in this Feature or if there is no Qualifier with that name
994    *  just add a copy of the argument.
995    *  @param qualifier This object contians name and values to add.
996    *  @return The Qualifier that was changed or created.
997    **/
addQualifierValues(Qualifier qualifier)998   public Qualifier addQualifierValues(Qualifier qualifier)
999       throws EntryInformationException, ReadOnlyException
1000   {
1001 
1002     final QualifierVector old_qualifiers = getQualifiers().copy();
1003     final Qualifier return_qualifier;
1004 
1005     final Qualifier current_qualifier =
1006       getEmblFeature().getQualifierByName(qualifier.getName());
1007 
1008     if(current_qualifier == null)
1009       return_qualifier = qualifier.copy();
1010     else
1011     {
1012       return_qualifier = current_qualifier.copy();
1013       return_qualifier.addValues(qualifier.getValues());
1014     }
1015 
1016     setQualifier(return_qualifier);
1017 
1018     // now inform the listeners that a change has occured
1019     // THIS IS ALREADY DONE IN setQualifier()
1020 /*    final FeatureChangeEvent event =
1021       new FeatureChangeEvent(qualifier,
1022                              this,
1023                              null,
1024                              null,
1025                              old_qualifiers,
1026                              FeatureChangeEvent.QUALIFIER_CHANGED);
1027 
1028     fireAction(feature_listener_list, event);
1029 */
1030     return return_qualifier;
1031   }
1032 
1033   /**
1034    *  Add the given Qualifier to this Feature, replacing any exisiting
1035    *  Qualifier that have the same name.
1036    *  @param qualifier The Qualifier to add.
1037    **/
setQualifier(final Qualifier qualifier)1038   public void setQualifier(final Qualifier qualifier)
1039       throws EntryInformationException, ReadOnlyException
1040   {
1041     final QualifierVector old_qualifiers = getQualifiers().copy();
1042 
1043     getEmblFeature().setQualifier(qualifier);
1044 
1045     // now inform the listeners that a change has occured
1046     final FeatureChangeEvent event =
1047       new FeatureChangeEvent(qualifier,
1048                              this,
1049                              null,
1050                              null,
1051                              old_qualifiers,
1052                              FeatureChangeEvent.QUALIFIER_CHANGED);
1053 
1054     if(qualifier.getName().equals("transl_except"))
1055     {
1056       // discard cache
1057       amino_acids = null;
1058     }
1059 
1060     fireAction(feature_listener_list, event);
1061   }
1062 
1063   /**
1064    *  Remove the Qualifier with the given name.  If there is no Qualifier with
1065    *  that name then return immediately.
1066    *  @param name The qualifier name to look for.
1067    **/
removeQualifierByName(final String name)1068   public void removeQualifierByName(final String name)
1069       throws EntryInformationException, ReadOnlyException, OutOfDateException
1070   {
1071     if(getEmblFeature().getQualifierByName(name) == null)
1072     {
1073       // nothing to remove
1074       return;
1075     }
1076 
1077     final QualifierVector old_qualifiers = getQualifiers().copy();
1078 
1079     getEmblFeature().removeQualifierByName(name);
1080 
1081     // now inform the listeners that a change has occured
1082     final FeatureChangeEvent event =
1083       new FeatureChangeEvent(this,
1084                              this,
1085                              null,
1086                              null,
1087                              old_qualifiers,
1088                              FeatureChangeEvent.QUALIFIER_CHANGED);
1089 
1090     if(name.equals("transl_except"))
1091     {
1092       // discard cache
1093       amino_acids = null;
1094     }
1095 
1096     fireAction(feature_listener_list, event);
1097   }
1098 
1099   /**
1100    *  Return the time when this feature last changed or null if this Feature
1101    *  doesn't support datestamps.
1102    **/
getDatestamp()1103   public Date getDatestamp()
1104   {
1105     if(getEmblFeature() instanceof DateStampFeature)
1106       return ((DateStampFeature)getEmblFeature()).getDatestamp();
1107     else
1108       return null;
1109   }
1110 
1111 
1112   /**
1113    *  Return true if and only if any qualifier in this feature contains the
1114    *  given text string.
1115    *  @param search_text The text to search for.
1116    *  @param fold_case If true then the text comparisons will ignore case.
1117    *  @param match_substring If true then matches to substrings are allowed.
1118    *  @param qualifier_names If null search all qualifiers, otherwise just
1119    *    search these names,
1120    **/
containsText(final String search_text, final boolean fold_case, final boolean match_substring, final StringVector qualifier_names)1121   public boolean containsText(final String search_text,
1122                               final boolean fold_case,
1123                               final boolean match_substring,
1124                               final StringVector qualifier_names)
1125   {
1126     return findOrReplaceText(search_text, fold_case, match_substring, false,
1127                              qualifier_names, null);
1128   }
1129 
1130 
1131   /**
1132    *  Return true if and only if any qualifier in this feature contains the
1133    *  given text string.
1134    *  @param search_text The text to search for.
1135    *  @param fold_case If true then the text comparisons will ignore case.
1136    *  @param match_substring If true then matches to substrings are allowed.
1137    *  @param qualifier_names If null search all qualifiers, otherwise just
1138    *    search these names,
1139    *  @param replaceText text to replace all qualifier value matches. If null
1140    *    then returns true if text found.
1141    **/
findOrReplaceText(final String search_text, final boolean fold_case, final boolean match_substring, final boolean deleteQualifier, final StringVector qualifier_names, final String replaceText)1142   public boolean findOrReplaceText(final String search_text,
1143                               final boolean fold_case,
1144                               final boolean match_substring,
1145                               final boolean deleteQualifier,
1146                               final StringVector qualifier_names,
1147                               final String replaceText)
1148   {
1149     final String real_search_text;
1150 
1151     if(fold_case)
1152       real_search_text = search_text.toLowerCase();
1153     else
1154       real_search_text = search_text;
1155 
1156     final QualifierVector qualifiers = getQualifiers();
1157     QualifierVector newQualifiers = null;
1158     Vector<String> qualifiersToDelete = null;
1159     int qual_size =  qualifiers.size();
1160 
1161     boolean hasReplacedOrDeletedText = false;
1162 
1163     for(int i = 0; i  < qual_size; ++i)
1164     {
1165       final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i);
1166 
1167       if(qualifier_names != null &&
1168          !qualifier_names.contains(this_qualifier.getName()))
1169         continue;
1170 
1171       final StringVector values = this_qualifier.getValues();
1172 
1173       if(values != null)
1174       {
1175         StringVector newValues = null;
1176         StringVector deleteValues = null;
1177         int val_size = values.size();
1178 
1179         for(int values_index = 0; values_index < val_size;
1180              ++values_index)
1181         {
1182           String this_value_string = (String)values.elementAt(values_index);
1183 
1184           if(this_value_string == null)
1185             continue;
1186 
1187           if(fold_case)
1188             this_value_string = this_value_string.toLowerCase();
1189 
1190           if(! match_substring &&
1191              this_value_string.equals(real_search_text) ||
1192              match_substring &&
1193              this_value_string.indexOf(real_search_text) != -1)
1194           {
1195             if(deleteQualifier)
1196             {
1197               if(deleteValues == null)
1198                 deleteValues = new StringVector();
1199               deleteValues.add(this_value_string);
1200               continue;
1201             }
1202             // found the match & return true if replace function off
1203             if(replaceText == null)
1204               return true;
1205 
1206             String new_text = replaceText;
1207             if(match_substring)
1208             {
1209               final String value_string = (String)values.elementAt(values_index);
1210               new_text =
1211                   Pattern.compile(real_search_text, Pattern.CASE_INSENSITIVE)
1212                   .matcher(value_string).replaceAll(replaceText);
1213             }
1214             if(newValues == null)
1215               newValues = this_qualifier.getValues();
1216 
1217             newValues.setElementAt(new_text, values_index);
1218           }
1219         }
1220 
1221         if(newValues != null)
1222         {
1223           if(newQualifiers == null)
1224             newQualifiers = new QualifierVector();
1225           newQualifiers.setQualifier(
1226               new Qualifier(this_qualifier.getName(),newValues));
1227           hasReplacedOrDeletedText = true;
1228         }
1229 
1230         if(deleteQualifier && deleteValues != null)
1231         {
1232           newValues = this_qualifier.getValues();
1233           newValues.removeAll(deleteValues);
1234 
1235           if(newValues.size() < 1)
1236           {
1237             if(qualifiersToDelete == null)
1238               qualifiersToDelete = new Vector<String>();
1239             qualifiersToDelete.add(this_qualifier.getName());
1240           }
1241           else
1242           {
1243             newQualifiers = new QualifierVector();
1244             newQualifiers.setQualifier(
1245               new Qualifier(this_qualifier.getName(),newValues));
1246           }
1247           hasReplacedOrDeletedText = true;
1248         }
1249       }
1250     }
1251 
1252     // delete qualifiers
1253     if(qualifiersToDelete != null)
1254     {
1255       try
1256       {
1257         for (int i = 0; i < qualifiersToDelete.size(); i++)
1258           removeQualifierByName(qualifiersToDelete.get(i));
1259       }
1260       catch (Exception e)
1261       {
1262         e.printStackTrace();
1263       }
1264     }
1265 
1266     if(newQualifiers != null)
1267     {
1268       for(int i=0; i<newQualifiers.size(); i++)
1269       {
1270         try
1271         {
1272           setQualifier((Qualifier) newQualifiers.elementAt(i));
1273         }
1274         catch(ReadOnlyException e)
1275         {
1276           e.printStackTrace();
1277         }
1278         catch(EntryInformationException e)
1279         {
1280           e.printStackTrace();
1281         }
1282       }
1283     }
1284 
1285     return hasReplacedOrDeletedText;
1286   }
1287 
hasValidStartCodon()1288   public boolean hasValidStartCodon()
1289   {
1290     return hasValidStartCodon(false);
1291   }
1292 
1293   /**
1294    *  Return true if and only if this feature ends in a valid stop codon.
1295    **/
hasValidStartCodon(final boolean force)1296   public boolean hasValidStartCodon(final boolean force)
1297   {
1298     if(!isCDS() && !force)
1299       return true;
1300 
1301     try
1302     {
1303       if(getQualifierByName("codon_start") != null &&
1304          isPartialCDS() &&
1305          getFirstBase() == 1)
1306         return true;
1307     }
1308     catch(InvalidRelationException e)
1309     {
1310       throw new Error("internal error - unexpected exception: " + e);
1311     }
1312 
1313     final AminoAcidSequence translation = getTranslation();
1314 
1315     if(translation.length() < 1)
1316       return false;
1317 
1318     final String first_codon = getTranslationBases().substring(0, 3);
1319 
1320     final StringVector start_codons = Options.getOptions().getStartCodons();
1321 
1322 //  if(Options.getOptions().isEukaryoticMode())
1323 //    start_codons = Options.getOptions().getEukaryoticStartCodons();
1324 //  else
1325 //    start_codons = Options.getOptions().getProkaryoticStartCodons();
1326 
1327     if(start_codons.contains(first_codon))
1328       return true;
1329     else
1330       return false;
1331   }
1332 
hasValidStopCodon()1333   public boolean hasValidStopCodon()
1334   {
1335     return hasValidStopCodon(false);
1336   }
1337 
1338   /**
1339    *  Return true if and only if this feature ends in a valid stop codon.
1340    **/
hasValidStopCodon(final boolean force)1341   public boolean hasValidStopCodon(final boolean force)
1342   {
1343     if(!isCDS() && !force)
1344       return true;
1345 
1346     if(isPartialCDS() &&
1347        getLastBase() == getEntry().getBases().getLength())
1348       return true;
1349 
1350     final int bases_length = getBaseCount() - getCodonStart() + 1;
1351 
1352     if(bases_length % 3 != 0)
1353       return false;
1354 
1355     if(bases_length < 3)
1356       return false;
1357 
1358     final String codon_string = getBases().substring(getBaseCount() - 3);
1359 
1360     final char last_codon_translation =
1361       AminoAcidSequence.getCodonTranslation(codon_string);
1362 
1363     if(AminoAcidSequence.isStopCodon(last_codon_translation))
1364       return true;
1365     else
1366       return false;
1367   }
1368 
1369   /**
1370    *  Return true if and only if this feature has a valid EMBL key (rather
1371    *  than an Artemis extension).
1372    **/
hasValidEMBLKey()1373   public boolean hasValidEMBLKey()
1374   {
1375     if(getEntry().getEntryInformation().isValidKey(getKey()))
1376       return true;
1377     else
1378       return false;
1379   }
1380 
1381   /**
1382    *  Return true if and only if this feature has all the required EMBL
1383    *  qualifiers (for submission to EMBL).
1384    **/
hasRequiredQualifiers()1385   public boolean hasRequiredQualifiers()
1386   {
1387     final StringVector required_qualifiers =
1388       getEntry().getEntryInformation().getRequiredQualifiers(getKey());
1389 
1390     if(required_qualifiers == null)
1391       return true;
1392 
1393     try
1394     {
1395       int reqd_size = required_qualifiers.size();
1396       for(int i = 0; i < reqd_size; ++i)
1397       {
1398         if(getQualifierByName((String)required_qualifiers.elementAt(i)) == null)
1399           return false;
1400       }
1401     }
1402     catch(InvalidRelationException e)
1403     {
1404       throw new Error("internal error - unexpected exception: " + e);
1405     }
1406 
1407     return true;
1408   }
1409 
1410   /**
1411    *  This method will fix the end location of the feature so that the feature
1412    *  ends with a stop codon.
1413    *  @return true if and only if the feature has a stop codon or if a stop
1414    *    codon was successfully added (by moving the end by three bases).
1415    **/
fixStopCodon()1416   public boolean fixStopCodon()
1417       throws ReadOnlyException
1418   {
1419     final Location old_location = getLocation();
1420     final int codon_start = getCodonStart();
1421 
1422     // there is no possible way to fix this feature
1423     if((getBaseCount() - codon_start + 1) % 3 != 0)
1424       return false;
1425 
1426     final FeatureSegment last_segment = getSegments().lastElement();
1427     final Marker last_base_marker = last_segment.getEnd();
1428     final Marker last_codon_marker;
1429 
1430     try
1431     {
1432       last_codon_marker = last_base_marker.moveBy(-2);
1433     }
1434     catch(OutOfRangeException e)
1435     {
1436       throw new Error("internal error - unexpected exception: " + e);
1437     }
1438 
1439     final Range last_codon_range;
1440 
1441     try
1442     {
1443       last_codon_range = new Range(last_codon_marker.getPosition(),
1444                                    last_codon_marker.getPosition() + 2);
1445     }
1446     catch(OutOfRangeException e)
1447     {
1448       throw new Error("internal error - unexpected exception: " + e);
1449     }
1450 
1451     final String last_codon_string =
1452       last_codon_marker.getStrand().getSubSequence(last_codon_range);
1453 
1454     final char last_amino_acid_char =
1455       AminoAcidSequence.getCodonTranslation(last_codon_string);
1456 
1457     if(AminoAcidSequence.isStopCodon(last_amino_acid_char))
1458       return true;
1459 
1460     final Range next_codon_range;
1461 
1462     try
1463     {
1464       next_codon_range =
1465         new Range(last_codon_marker.getPosition() + 3,
1466                   last_codon_marker.getPosition() + 5);
1467     }
1468     catch(OutOfRangeException e)
1469     {
1470       throw new Error("internal error - unexpected exception: " + e);
1471     }
1472 
1473     final String next_codon_string =
1474       last_codon_marker.getStrand().getSubSequence(next_codon_range);
1475 
1476     final char next_amino_acid_char =
1477       AminoAcidSequence.getCodonTranslation(next_codon_string);
1478 
1479     if(AminoAcidSequence.isStopCodon(next_amino_acid_char))
1480     {
1481       try
1482       {
1483         // move the end marker to the next codon
1484         final Marker new_end_marker = last_base_marker.moveBy(3);
1485 
1486         last_segment.setEndPosition(new_end_marker.getPosition());
1487       }
1488       catch(OutOfRangeException e)
1489       {
1490         throw new Error("internal error - unexpected exception: " + e);
1491       }
1492 
1493       updateEMBLFeatureLocation();
1494       locationChanged(old_location);
1495 
1496       return true;
1497     }
1498 
1499     return false;
1500   }
1501 
1502   /**
1503    *  This method will move the start of the first segment of this feature so
1504    *  that the first codon is a start codon.  If the segment already starts at
1505    *  a start codon it will return true immediately.
1506    *  @param trim_to_any If true then the features will be trimmed to the next
1507    *    codon with the base pattern: ATG, GTG or TTG, otherwise the features
1508    *    will be trimmed to ATG (Met) only.
1509    *  @param trim_to_next If true then the features will be trimmed to the
1510    *    next start codon (dependent on trim_to_any) regardless of whether the
1511    *    feature currently start on a start codon.  If false then the feature
1512    *    will only be trimmed if the feature doesn't start on a start codon.
1513    *  @return true if and only if the trim worked.  It will fail if the first
1514    *    segment is less than 6 bases long or if there is no start codon in the
1515    *    first segment or in the first 30% of the feature.
1516    **/
trimStart(final boolean trim_to_any, final boolean trim_to_next)1517   public boolean trimStart(final boolean trim_to_any,
1518                            final boolean trim_to_next)
1519       throws ReadOnlyException
1520   {
1521     final Location old_location = getLocation();
1522     final FeatureSegment first_segment = getSegments().elementAt(0);
1523 
1524     // too short to trim
1525     if(first_segment.getBaseCount() < 6)
1526       return false;
1527 
1528     final BasePattern search_pattern;
1529 
1530     try
1531     {
1532       if(trim_to_any)
1533         search_pattern = new BasePattern("dtg");
1534       else
1535         search_pattern = new BasePattern("atg");
1536     }
1537     catch(BasePatternFormatException e)
1538     {
1539       throw new Error("internal error - unexpected exception: " + e);
1540     }
1541 
1542     final String first_codon_bases = getTranslationBases().substring(0, 3);
1543 
1544     Marker current_marker = first_segment.getStart();
1545 
1546     try
1547     {
1548       current_marker = current_marker.moveBy(getCodonStart() - 1);
1549     }
1550     catch(OutOfRangeException e)
1551     {
1552       return false;
1553     }
1554 
1555     if(search_pattern.matches(first_codon_bases))
1556     {
1557       // the segment already starts on a start codon
1558       if(trim_to_next)
1559       {
1560         // move passed the current start codon
1561         try
1562         {
1563           current_marker = current_marker.moveBy(3);
1564         }
1565         catch(OutOfRangeException e)
1566         {
1567           return false;
1568         }
1569       }
1570       else
1571         return true;
1572     }
1573 
1574     final int end_of_segment_position =
1575       first_segment.getEnd().getPosition();
1576 
1577     final int start_of_feature_position =
1578       first_segment.getStart().getPosition();
1579 
1580     while(true)
1581     {
1582       try
1583       {
1584         final String current_codon = Strand.getCodonAtMarker(current_marker);
1585 
1586         if(search_pattern.matches(current_codon))
1587           break;
1588 
1589         current_marker = current_marker.moveBy(3);
1590 
1591         final int current_marker_position = current_marker.getPosition();
1592 
1593         if(current_marker_position > end_of_segment_position - 2 ||
1594            (!trim_to_next &&
1595             1.0 * (current_marker_position - start_of_feature_position) /
1596             getTranslationBasesLength() > 0.3))
1597         {
1598           // the current_marker is past the end of the first segment or is
1599           // more than 30% into the feature
1600           return false;
1601         }
1602       }
1603       catch(OutOfRangeException e)
1604       {
1605         return false;
1606       }
1607     }
1608 
1609     try
1610     {
1611       first_segment.setStartPosition(current_marker.getPosition());
1612     }
1613     catch(OutOfRangeException e)
1614     {
1615       throw new Error("internal error - unexpected exception: " + e);
1616     }
1617 
1618     try
1619     {
1620       // remove /codon_start (if it exists) since the new location will be in
1621       // the correct frame
1622       removeQualifierByName("codon_start");
1623     }
1624     catch(OutOfDateException ode)
1625     {
1626       // do nothing
1627     }
1628     catch(EntryInformationException eie)
1629     {
1630       // do nothing
1631     }
1632 
1633     updateEMBLFeatureLocation();
1634     locationChanged(old_location);
1635 
1636     return true;
1637   }
1638 
1639   /**
1640    *  Return the label (the value of the first /label qualifier) of this
1641    *  Feature, but return null if there is no label or if the label is "*".
1642    **/
getLabel()1643   public String getLabel()
1644   {
1645     try
1646     {
1647       return getValueOfQualifier("label");
1648     }
1649     catch(InvalidRelationException e)
1650     {
1651       return null;
1652     }
1653   }
1654 
1655   /**
1656    *  Return the gene name (the value of the first /gene qualifier) of this
1657    *  Feature or null if there it has no gene name.
1658    **/
getGeneName()1659   public String getGeneName()
1660   {
1661     try
1662     {
1663       return getValueOfQualifier("gene");
1664     }
1665     catch(InvalidRelationException e)
1666     {
1667       return null;
1668     }
1669   }
1670 
1671   /**
1672    *  Return the name of this Feature to use in the display.  The search order
1673    *  is /primary_name, /synonym, /systematic_id, /temporary_systematic_id,
1674    *  /gene, /locus_tag, /label.  If none of the qualifiers exists return
1675    *  the feature key of this feature.
1676    **/
getIDString()1677   public String getIDString()
1678   {
1679     final String picked_name =
1680       pickName(Options.getOptions().getDisplayQualifierNames());
1681 
1682     if(picked_name == null)
1683       return getKey().toString();
1684     else
1685       return picked_name;
1686   }
1687 
1688   /**
1689    *  Return the systematic name of this feature.
1690    **/
getSystematicName()1691   public String getSystematicName()
1692   {
1693     final String picked_name =
1694       pickName(Options.getOptions().getSystematicQualifierNames());
1695 
1696     if(picked_name == null)
1697       return getIDString();
1698     else
1699       return picked_name;
1700   }
1701 
1702 
1703   /**
1704    *  Look at the qualifier_names one-by-one and return the first value of the
1705    *  first qualifier found.
1706    **/
pickName(final StringVector qualifier_names)1707   private String pickName(final StringVector qualifier_names)
1708   {
1709     int qn_size = qualifier_names.size();
1710     for(int i = 0; i < qn_size; ++i)
1711     {
1712       try
1713       {
1714         final Qualifier qualifier =
1715           getQualifierByName((String)qualifier_names.elementAt(i));
1716 
1717         if(qualifier != null)
1718         {
1719           final StringVector values = qualifier.getValues();
1720 
1721           if(values != null && values.size() > 0)
1722           {
1723             for(int j=0; j<values.size(); j++)
1724             {
1725               final String value = (String)values.elementAt(j);
1726               if(value != null && !value.endsWith("current=false") && !value.equals(""))
1727                 return value;
1728             }
1729           }
1730         }
1731       }
1732       catch(InvalidRelationException e){}
1733     }
1734 
1735     return null;
1736   }
1737 
1738   /**
1739    *  Return the note (the value of the first /note qualifier) of this
1740    *  Feature or null if there is no /note.
1741    **/
getNote()1742   public String getNote()
1743   {
1744     try
1745     {
1746       return getValueOfQualifier("note");
1747     }
1748     catch(InvalidRelationException e)
1749     {
1750       return null;
1751     }
1752   }
1753 
1754   /**
1755    *  Return the product (the value of the first /product qualifier) of this
1756    *  Feature or null if there is no /product.
1757    **/
getProductString()1758   public String getProductString()
1759   {
1760     try
1761     {
1762       final String product = getValueOfQualifier("product");
1763       if( product != null &&
1764           getEmblFeature() instanceof GFFStreamFeature &&
1765           product.startsWith("term=") )
1766         return product.substring(5);
1767 
1768       return product;
1769     }
1770     catch(InvalidRelationException e)
1771     {
1772       return null;
1773     }
1774   }
1775 
1776   /**
1777    *  Return the number of bases in this feature not inclding introns (total of
1778    *  all segments).
1779    **/
getBaseCount()1780   public int getBaseCount()
1781   {
1782     if(base_count == -1)
1783     {
1784       int new_base_count = 0;
1785       int seg_size = getSegments().size();
1786 
1787       for(int i = 0; i < seg_size; ++i)
1788         new_base_count += getSegments().elementAt(i).getBaseCount();
1789 
1790       base_count = new_base_count;
1791     }
1792 
1793     return base_count;
1794   }
1795 
1796   /**
1797    *  Return the bases in this feature (all bases in all segments).
1798    **/
getBases()1799   public String getBases()
1800   {
1801     if(bases == null)
1802     {
1803       final StringBuffer buffer = new StringBuffer();
1804       int seg_size = getSegments().size();
1805 
1806       for(int i = 0; i < seg_size; ++i)
1807         buffer.append(getSegments().elementAt(i).getBases());
1808 
1809       bases = buffer.toString();
1810     }
1811 
1812     return bases;
1813   }
1814 
1815   /**
1816    *  Return the number of coding bases in this feature, that is, the number
1817    *  of codons divided by three.
1818    **/
getTranslationBasesLength()1819   public int getTranslationBasesLength()
1820   {
1821     final int codon_start = getCodonStart();
1822     final int start_index = codon_start - 1;
1823 
1824     int end_index = getBases().length();
1825 
1826     final int mod_value = (end_index - start_index) % 3;
1827 
1828     // add 1 or 2 if necessary to make the range a multiple of 3
1829     end_index -= mod_value;
1830 
1831     final int new_length = end_index - start_index;
1832 
1833     if(new_length >= 3)
1834     {
1835       // remove the stop codon (if present)
1836 
1837       final String last_codon = getBases().substring(end_index - 3);
1838 
1839       final char amino_acid_char =
1840         AminoAcidSequence.getCodonTranslation(last_codon);
1841 
1842       if(AminoAcidSequence.isStopCodon(amino_acid_char))
1843         return new_length - 3;
1844     }
1845 
1846     return new_length;
1847   }
1848 
1849   /**
1850    *  Return the bases in this feature (all bases in all segments), taking
1851    *  the /start_codon qualifier into consideration.
1852    **/
getTranslationBases()1853   public String getTranslationBases()
1854   {
1855     final int codon_start = getCodonStart();
1856     final int start_index = codon_start - 1;
1857     final int end_index = getTranslationBasesLength() + start_index;
1858 
1859     return getBases().substring(start_index, end_index);
1860   }
1861 
1862   /**
1863    *  Return an AminoAcidSequence containing the translated sequence of the
1864    *  feature with changes from /transl_except qualifiers added.  Return null
1865    *  if there are no valid /transl_except qualifiers (ie. parsable and with
1866    *  coordinates in range).
1867    **/
fixTranslationExceptions()1868   private AminoAcidSequence fixTranslationExceptions()
1869   {
1870     final String amino_acids_string = amino_acids.toString();
1871     String new_amino_acids_string = amino_acids_string;
1872 
1873     try
1874     {
1875       final Qualifier except_qualifier =
1876         getQualifierByName("transl_except");
1877 
1878       if(except_qualifier != null)
1879       {
1880         final StringVector values = except_qualifier.getValues();
1881 
1882         if(values != null)
1883         {
1884           for(int i = 0; i < values.size(); ++i)
1885           {
1886             final String value = (String)values.elementAt(i);
1887 
1888             final String START_STRING = "(pos:";
1889             final String COMMA_STRING = ",aa:";
1890 
1891             if(value.startsWith(START_STRING) && value.endsWith(")"))
1892             {
1893               final int comma_pos = value.lastIndexOf(COMMA_STRING);
1894 
1895               if(comma_pos >= 0)
1896               {
1897                 final String location_part =
1898                   value.substring(START_STRING.length(), comma_pos);
1899 
1900                 final String aa_part =
1901                   value.substring(comma_pos + COMMA_STRING.length(),
1902                                   value.length() - 1);
1903 
1904                 char aa_part_one_letter_code =
1905                   AminoAcidSequence.getOneLetterCode(aa_part);
1906 
1907                 // aa_part is probably "OTHER"
1908                 if(aa_part_one_letter_code == (char)-1)
1909                   aa_part_one_letter_code = '.';
1910 
1911                 final Location location = new Location(location_part);
1912 
1913                 int start_base_in_feature;
1914 
1915                 if(isForwardFeature())
1916                 {
1917                   start_base_in_feature =
1918                     location.getFirstBase() - getRawFirstBase() -
1919                     (getCodonStart() - 1);
1920                 }
1921                 else
1922                 {
1923                   start_base_in_feature =
1924                     getRawLastBase() - location.getLastBase() -
1925                     (getCodonStart() - 1);
1926                 }
1927 
1928                 if(start_base_in_feature >= 0 &&
1929                    start_base_in_feature <
1930                    getBaseCount() - getCodonStart() + 1)
1931                 {
1932                   final int start_aa_in_feature = start_base_in_feature / 3;
1933 
1934                   new_amino_acids_string =
1935                     new_amino_acids_string.substring(0, start_aa_in_feature) +
1936                     aa_part_one_letter_code +
1937                     new_amino_acids_string.substring(start_aa_in_feature + 1);
1938                 }
1939               }
1940             }
1941           }
1942         }
1943       }
1944     }
1945     catch(InvalidRelationException e) {}
1946     catch(LocationParseException e) {}
1947 
1948     if(new_amino_acids_string == amino_acids_string)
1949       return null;
1950     else
1951       return new AminoAcidSequence(new_amino_acids_string);
1952   }
1953 
1954   /**
1955    *  Return the translation of the bases in this feature.
1956    **/
getTranslation()1957   public AminoAcidSequence getTranslation()
1958   {
1959     if(amino_acids == null)
1960     {
1961       amino_acids =
1962         AminoAcidSequence.getTranslation(getTranslationBases(), true);
1963 
1964       // a very short feature
1965       if(amino_acids.length() == 0)
1966         return amino_acids;
1967 
1968       final AminoAcidSequence fixed_amino_acids =
1969         fixTranslationExceptions();
1970 
1971       if(fixed_amino_acids != null)
1972         amino_acids = fixed_amino_acids;
1973 
1974       if(isCDS() && !isPartialCDS() && hasValidStartCodon())
1975       {
1976         if(amino_acids.elementAt(0) != 'm')
1977         {
1978           // translation should always start with M
1979           final String amino_acids_string = amino_acids.toString();
1980 
1981           final String new_amino_acids_string =
1982             'M' + amino_acids_string.substring(1);
1983 
1984           amino_acids = new AminoAcidSequence(new_amino_acids_string);
1985         }
1986       }
1987     }
1988     return amino_acids;
1989   }
1990 
1991   /**
1992    *  Return the number of amino acids in this feature (total of all segments).
1993    **/
getAACount()1994   public int getAACount()
1995   {
1996     if(aa_count == -1)
1997       aa_count = getTranslationBasesLength() / 3;
1998 
1999     return aa_count;
2000   }
2001 
2002   /**
2003    *  Return the total molecular weight of the amino acid translation of the
2004    *  bases of this feature.
2005    **/
getMolecularWeight()2006   public float getMolecularWeight()
2007   {
2008     return getTranslation().getMolecularWeight();
2009   }
2010 
2011   /**
2012    *  Return the number of occurrences of the given codon in the frame
2013    *  starting at the correct frame for this feature (see the
2014    *  getTranslationBases() method).  The first index is the first base, the
2015    *  second index is the second base, etc.  The indices are the same as those
2016    *  for Bases.letter_index.  An example: calling getCodonCount(0, 0, 1)
2017    *  will return the count of the codon with the sequence t,t,c.
2018    **/
getCodonCount(final int first, final int second, final int third)2019   public int getCodonCount(final int first,
2020                            final int second,
2021                            final int third)
2022   {
2023     if(codon_counts == null)
2024       setArrays();
2025 
2026     return codon_counts[first][second][third];
2027   }
2028 
2029   /**
2030    *  Return a count of the occurrences of each amino acids in the translation
2031    *  of the bases of the current feature.  The index should be the same as
2032    *  the index that is returned by AminoAcidSequence.getSymbolIndex().
2033    **/
getResidueCount(final int amino_acid_index)2034   public int getResidueCount(final int amino_acid_index)
2035   {
2036     if(residue_counts == null)
2037       setArrays();
2038 
2039     return residue_counts[amino_acid_index];
2040   }
2041 
2042   /**
2043    *  Return the count of each base that appears in each codon position (1, 2
2044    *  or 3).  The first index is the codon position and the second is the base
2045    *  (indexed in the same way as Bases.letter_index).  (set by resetCache ()).
2046    **/
getPositionalBaseCount(final int codon_base_position, final int base_index)2047   public int getPositionalBaseCount(final int codon_base_position,
2048                                     final int base_index)
2049   {
2050     if(positional_base_counts == null)
2051       setArrays();
2052 
2053     return positional_base_counts[codon_base_position][base_index];
2054   }
2055 
2056   /**
2057    *  Return the count each base in the feature.  The indices are the same as
2058    *  those for Bases.letter_index. (set by resetCache()).
2059    **/
getBaseCount(final int base_index)2060   public int getBaseCount(final int base_index)
2061   {
2062     if(base_counts == null)
2063       setArrays();
2064 
2065     return base_counts[base_index];
2066   }
2067 
2068   /**
2069    *  Return the codon position 1 and 2 correlation score for the bases of
2070    *  this feature.
2071    **/
get12CorrelationScore()2072   public double get12CorrelationScore()
2073   {
2074     final int t1_count =
2075       getPositionalBaseCount(0, Bases.getIndexOfBase('t'));
2076     final int c1_count =
2077       getPositionalBaseCount(0, Bases.getIndexOfBase('c'));
2078     final int a1_count =
2079       getPositionalBaseCount(0, Bases.getIndexOfBase('a'));
2080     final int g1_count =
2081       getPositionalBaseCount(0, Bases.getIndexOfBase('g'));
2082 
2083     final int t2_count =
2084       getPositionalBaseCount(1, Bases.getIndexOfBase('t'));
2085     final int c2_count =
2086       getPositionalBaseCount(1, Bases.getIndexOfBase('c'));
2087     final int a2_count =
2088       getPositionalBaseCount(1, Bases.getIndexOfBase('a'));
2089     final int g2_count =
2090       getPositionalBaseCount(1, Bases.getIndexOfBase('g'));
2091 
2092     final int c3_count =
2093       getPositionalBaseCount(2, Bases.getIndexOfBase('c'));
2094     final int g3_count =
2095       getPositionalBaseCount(2, Bases.getIndexOfBase('g'));
2096 
2097     final int base_total = getTranslationBases().length();
2098 
2099     final double cor1_2_score =
2100       3.0 * t1_count/base_total *
2101       Codon12CorrelationAlgorithm.correlation_score_factors_1[0] +
2102       3.0 * c1_count/base_total *
2103       Codon12CorrelationAlgorithm.correlation_score_factors_1[1] +
2104       3.0 * a1_count/base_total *
2105       Codon12CorrelationAlgorithm.correlation_score_factors_1[2] +
2106       3.0 * g1_count/base_total *
2107       Codon12CorrelationAlgorithm.correlation_score_factors_1[3] +
2108       3.0 * t2_count/base_total *
2109       Codon12CorrelationAlgorithm.correlation_score_factors_2[0] +
2110       3.0 * c2_count/base_total *
2111       Codon12CorrelationAlgorithm.correlation_score_factors_2[1] +
2112       3.0 * a2_count/base_total *
2113       Codon12CorrelationAlgorithm.correlation_score_factors_2[2] +
2114       3.0 * g2_count/base_total *
2115       Codon12CorrelationAlgorithm.correlation_score_factors_2[3] +
2116       0.5;                 // add 0.5 because that is what the old uk.ac.sanger.artemis did
2117 
2118     return cor1_2_score;
2119   }
2120 
2121   /**
2122    *  Return the percentage GC content of the bases of this feature.
2123    **/
getPercentGC()2124   public double getPercentGC()
2125   {
2126     final String bases = getBases();
2127 
2128     if(bases.length() > 0)
2129     {
2130       int gc_count = 0;
2131 
2132       final char[] sequence_chars = new char[bases.length()];
2133 
2134       bases.getChars(0, bases.length(), sequence_chars, 0);
2135 
2136       for(int i = 0 ; i < bases.length() ; ++i)
2137       {
2138         final char this_char = sequence_chars[i];
2139         if(this_char == 'g' || this_char == 'c')
2140           ++gc_count;
2141       }
2142 
2143       return 100.0 * gc_count / bases.length();
2144     }
2145     else
2146       return 0.0;
2147   }
2148 
2149   /**
2150    *  Return the first (lowest) base number of this Feature.  The lowest base
2151    *  of the Feature is the minimum (with respect to the Feature's Strand) of
2152    *  the lowest bases of all the segments of this Feature.
2153    *  @return The first base of this feature or 0 if this feature doesn't know
2154    *    where it is.
2155    **/
getFirstBase()2156   public int getFirstBase()
2157   {
2158     final Marker first_base_marker = getFirstBaseMarker();
2159 
2160     if(first_base_marker == null)
2161       return 0;
2162     else
2163       return first_base_marker.getPosition ();
2164   }
2165 
2166   /**
2167    *  Return the last (highest) base number of this Feature.  The highest base
2168    *  of the Feature is the maximum (with respect to the Feature's Strand) of
2169    *  the highest bases of all the segments of this Feature.
2170    *  @return The last base of this feature or 0 if this feature doesn't know
2171    *    where it is.
2172    **/
getLastBase()2173   public int getLastBase()
2174   {
2175     final Marker last_base_marker = getLastBaseMarker();
2176 
2177     if(last_base_marker == null)
2178       return 0;
2179     else
2180       return last_base_marker.getPosition ();
2181   }
2182 
2183   /**
2184    *  Return true if and only if the first base of this Feature is less than
2185    *  the argument Feature with respect to the Feature's Strand.
2186    **/
lessThan(Feature other_feature)2187   public boolean lessThan(Feature other_feature)
2188   {
2189     if(getFirstBase() < other_feature.getFirstBase())
2190       return true;
2191     else
2192       return false;
2193   }
2194 
2195   /**
2196    *  Return true if and only if the first base of this Feature is greater
2197    *  than the argument Feature with respect to the Feature's Strand.
2198    **/
greaterThan(Feature other_feature)2199   public boolean greaterThan(Feature other_feature)
2200   {
2201     if(getFirstBase() > other_feature.getFirstBase())
2202     {
2203       return true;
2204     }
2205     else
2206     {
2207       return false;
2208     }
2209   }
2210 
2211   /**
2212    *  Return the first (lowest) base number of this Feature.  The lowest base
2213    *  of the Feature is the minimum (with respect to the raw sequence) of the
2214    *  lowest bases of all the segments of this Feature.
2215    *  @return The first base of this feature or 0 if this feature doesn't know
2216    *    where it is.
2217    **/
getRawFirstBase()2218   public int getRawFirstBase()
2219   {
2220     final int A_BIG_NUMBER = 0x7fffffff; // largest integer
2221     int minimum = A_BIG_NUMBER;
2222 
2223     for(int i = 0; i < getSegments().size(); ++i)
2224     {
2225       final int current_minimum;
2226       if(isForwardFeature())
2227       {
2228         current_minimum =
2229           getSegments().elementAt(i).getStart().getRawPosition();
2230       }
2231       else
2232       {
2233         current_minimum =
2234           getSegments().elementAt(i).getEnd().getRawPosition();
2235       }
2236 
2237       if(current_minimum < minimum)
2238         minimum = current_minimum;
2239     }
2240 
2241     if(minimum == A_BIG_NUMBER)
2242       return 0;
2243     else
2244       return minimum;
2245   }
2246 
2247   /**
2248    *  Return the last (highest) base number of this Feature.  The highest base
2249    *  of the Feature is the maximum (with respect to the raw sequence) of the
2250    *  highest bases of all the segments of this Feature.
2251    *  @return The last base of this feature or 0 if this feature doesn't know
2252    *    where it is.
2253    **/
getRawLastBase()2254   public int getRawLastBase()
2255   {
2256     final int A_SMALL_NUMBER = -1;
2257     int maximum = A_SMALL_NUMBER;
2258     int seg_size = getSegments().size();
2259 
2260     for(int i = 0; i < seg_size; ++i)
2261     {
2262       final int current_maximum;
2263       if(isForwardFeature())
2264       {
2265         current_maximum =
2266           getSegments().elementAt(i).getEnd().getRawPosition();
2267       }
2268       else
2269       {
2270         current_maximum =
2271           getSegments().elementAt(i).getStart().getRawPosition();
2272       }
2273 
2274       if(current_maximum > maximum)
2275         maximum = current_maximum;
2276     }
2277 
2278     if(maximum == A_SMALL_NUMBER)
2279       return 0;
2280     else
2281       return maximum;
2282   }
2283 
2284   /**
2285    *  Return the maximum extent of this feature.  The range returned starts at
2286    *  getRawFirstBase() and ends at getRawLastBase().
2287    **/
getMaxRawRange()2288   public Range getMaxRawRange()
2289   {
2290     try
2291     {
2292       return new Range(getRawFirstBase(), getRawLastBase());
2293     }
2294     catch(OutOfRangeException e)
2295     {
2296       throw new Error("internal error - unexpected exception: " + e);
2297     }
2298   }
2299 
2300 
2301   /**
2302    *  Return true if and only if the first base of this Feature is less than
2303    *  the argument Feature with to the raw sequence.
2304    **/
rawLessThan(Feature other_feature)2305   public boolean rawLessThan(Feature other_feature)
2306   {
2307     if(getRawFirstBase() < other_feature.getRawFirstBase())
2308       return true;
2309     else
2310       return false;
2311   }
2312 
2313   /**
2314    *  Return true if and only if the first base of this Feature is greater
2315    *  than the argument Feature with to the raw sequence.
2316    **/
rawGreaterThan(Feature other_feature)2317   public boolean rawGreaterThan(Feature other_feature)
2318   {
2319     if(getRawFirstBase() > other_feature.getRawFirstBase())
2320       return true;
2321     else
2322       return false;
2323   }
2324 
2325   /**
2326    *  Return the Marker of the first (lowest) base number of this Feature.
2327    *  The lowest base of the Feature is the minimum of the lowest bases of all
2328    *  the segments of this Feature.
2329    *  @return The Marker of the first base of this feature or null if this
2330    *    feature doesn't know where it is.
2331    **/
getFirstBaseMarker()2332   public Marker getFirstBaseMarker()
2333   {
2334     final int A_BIG_NUMBER = 0x7fffffff; // largest integer
2335     int minimum = A_BIG_NUMBER;
2336     Marker minimum_marker = null;
2337 
2338     for(int i = 0 ; i < getSegments().size() ; ++i)
2339     {
2340       final Marker current_marker = getSegments().elementAt(i).getStart();
2341       final int current_minimum = current_marker.getPosition();
2342 
2343       if(current_minimum < minimum)
2344       {
2345         minimum = current_minimum;
2346         minimum_marker = current_marker;
2347       }
2348     }
2349 
2350     return minimum_marker;
2351   }
2352 
2353   /**
2354    *  Return the Marker of the last (highest) base number of this Feature.
2355    *  The highest base of the Feature is the maximum of the highest bases of
2356    *  all the segments of this Feature.
2357    *  @return The Marker of the last base of this feature or null if this
2358    *    feature doesn't know where it is.
2359    **/
getLastBaseMarker()2360   public Marker getLastBaseMarker()
2361   {
2362     final long A_SMALL_NUMBER = -1;
2363     long maximum = A_SMALL_NUMBER;
2364     Marker maximum_marker = null;
2365 
2366     for(int i = 0; i < getSegments().size(); ++i)
2367     {
2368       final Marker current_marker = getSegments().elementAt(i).getEnd();
2369       final int current_maximum = current_marker.getPosition();
2370 
2371       if(current_maximum > maximum)
2372       {
2373         maximum = current_maximum;
2374         maximum_marker = current_marker;
2375       }
2376     }
2377 
2378     return maximum_marker;
2379   }
2380 
2381   /**
2382    *  Return the Marker of the first base of the first (coding) codon of this
2383    *  feature.  This is the minimum of the lowest bases of all the segments of
2384    *  this Feature plus codon_start minus one.
2385    *  @return The Marker of the first coding base of this feature or null if
2386    *    this feature doesn't know where it is.
2387    **/
getFirstCodingBaseMarker()2388   public Marker getFirstCodingBaseMarker()
2389   {
2390     final Marker first_base_marker = getFirstBaseMarker();
2391 
2392     try
2393     {
2394       return first_base_marker.moveBy(getCodonStart() - 1);
2395     }
2396     catch(OutOfRangeException e)
2397     {
2398       // if we are at the end of the sequence then just return the first base
2399       // marker
2400       return first_base_marker;
2401     }
2402   }
2403 
2404   /**
2405    *  Given a base position in this feature, return the Marker of the
2406    *  corresponding base on the Strand object.
2407    *  @param position The position within the feature - not counting introns.
2408    *  @exception OutOfRangeException Thrown if the new position is less than 1
2409    *    or greater than the length of the sequence.
2410    **/
getPositionInSequence(final int position)2411   public Marker getPositionInSequence (final int position)
2412       throws OutOfRangeException
2413   {
2414     // the position is outside of the feature
2415     if(position < 1)
2416       throw new OutOfRangeException("position: " + position);
2417 
2418     // subtract one because positions are numbered from 1 not 0
2419     int bases_remaining = position - 1;
2420 
2421     for(int i = 0; i < segments.size(); ++i)
2422     {
2423       final FeatureSegment this_segment = segments.elementAt(i);
2424 
2425       if(bases_remaining < this_segment.getBaseCount())
2426         return this_segment.getStart().moveBy(bases_remaining);
2427       else
2428         bases_remaining -= this_segment.getBaseCount();
2429     }
2430 
2431     // the position is outside of the feature
2432     throw new OutOfRangeException("position: " + position);
2433   }
2434 
2435   /**
2436    *  Return the (base) position in this feature of the given Marker or -1 if
2437    *  the given Marker does not correspond to any bases in this Feature.
2438    **/
getFeaturePositionFromMarker(final Marker base_marker)2439   public int getFeaturePositionFromMarker(final Marker base_marker)
2440   {
2441     // counts the bases in the previous segments as we loop over them
2442     int bases_so_far = 0;
2443     final int marker_position = base_marker.getPosition();
2444     int seg_size = segments.size();
2445 
2446     for(int i = 0; i < seg_size; ++i)
2447     {
2448       final FeatureSegment this_segment = segments.elementAt(i);
2449       final int this_segment_start = this_segment.getStart().getPosition();
2450 
2451       final int marker_start_diff = marker_position - this_segment_start;
2452 
2453       if(marker_start_diff >= 0 &&
2454          marker_start_diff < this_segment.getBaseCount())
2455       {
2456         return bases_so_far + marker_start_diff;
2457       }
2458 
2459       bases_so_far += this_segment.getBaseCount();
2460     }
2461 
2462     return -1;
2463   }
2464 
resetColour()2465   public void resetColour()
2466   {
2467     colour = null;
2468   }
2469 
2470   /**
2471    *  Return the colour (the value of the /colour qualifier) of this Feature
2472    *  or null if there is no colour qualifier and no default colour.
2473    **/
getColour()2474   public Color getColour()
2475   {
2476     if(colour != null)
2477       return colour;
2478 
2479     String colour_qualifier;
2480     try
2481     {
2482       colour_qualifier = getValueOfQualifier("colour");
2483 
2484       // it's international "be nice to Americans day":
2485       if(colour_qualifier == null)
2486         colour_qualifier = getValueOfQualifier("color");
2487     }
2488     catch(InvalidRelationException e)
2489     {
2490       colour_qualifier = null;
2491     }
2492 
2493     // use default colour for this type of feature
2494     if(colour_qualifier == null)
2495     {
2496       colour = Options.getOptions().getDefaultFeatureColour(getKey());
2497       return colour;
2498     }
2499 
2500     final StringVector colours = StringVector.getStrings(colour_qualifier);
2501 
2502     if(colours.size() < 1)
2503     {
2504       colour = Options.getOptions().getDefaultFeatureColour(getKey());
2505       return colour;
2506     }
2507 
2508     try
2509     {
2510       if(colours.size() == 3)
2511       {
2512         int red   = Integer.parseInt((String)colours.elementAt(0));
2513         int green = Integer.parseInt((String)colours.elementAt(1));
2514         int blue  = Integer.parseInt((String)colours.elementAt(2));
2515 
2516         if(red < 0)
2517           red = 0;
2518 
2519         if(red > 255)
2520           red = 255;
2521 
2522         if(green < 0)
2523           green = 0;
2524 
2525         if(green > 255)
2526           green = 255;
2527 
2528         if(blue < 0)
2529           blue = 0;
2530 
2531         if(blue > 255)
2532           blue = 255;
2533 
2534         colour = new Color(red, green, blue);
2535         return colour;
2536       }
2537       else
2538       {
2539         final String colour_string = (String)colours.elementAt(0);
2540 
2541         final int colour_number;
2542 
2543         colour_number = Integer.parseInt(colour_string);
2544         colour = Options.getOptions().getColorFromColourNumber(colour_number);
2545         return colour;
2546       }
2547     }
2548     catch(NumberFormatException e)
2549     {
2550       // use default colour for this type of feature
2551       colour = Options.getOptions().getDefaultFeatureColour(getKey());
2552       return colour;
2553     }
2554   }
2555 
2556   /**
2557    *  Return a vector of String objects containing all the values of the
2558    *  qualifier with the given name.  If the Feature has two qualifiers:
2559    *    /db_xref="PID:e1256466" /db_xref="SPTREMBL:O43016"
2560    *  then this method will return a vector containing "PID:e1256466" and
2561    *  "SPTREMBL:O43016".
2562    *  Returns null if the is no Qualifier with the given name.
2563    **/
getValuesOfQualifier(String qualifier_name)2564   public StringVector getValuesOfQualifier(String qualifier_name)
2565       throws InvalidRelationException
2566   {
2567     final Qualifier this_qualifier = getQualifierByName(qualifier_name);
2568 
2569     if(this_qualifier == null)
2570       return null;
2571     else
2572       return this_qualifier.getValues();
2573   }
2574 
2575   /**
2576    *  Return the Qualifier in this Feature with the given name or null if
2577    *  there no such Qualifier.
2578    *  @param name The qualifier name to look for.
2579    **/
getQualifierByName(final String name)2580   public Qualifier getQualifierByName(final String name)
2581       throws InvalidRelationException
2582   {
2583     return getEmblFeature().getQualifierByName(name);
2584   }
2585 
2586   /**
2587    *  Return the value of the first occurrence of the given qualifier in this
2588    *  Feature or null if this feature doesn't have this qualifier.  If this
2589    *  feature has the given qualifier but the qualifier doesn't have a value
2590    *  then it will return "" - a zero length String.
2591    *  @param qualifier_name The name of the qualifier to search for.
2592    **/
getValueOfQualifier(String qualifier_name)2593   public String getValueOfQualifier(String qualifier_name)
2594       throws InvalidRelationException
2595   {
2596     final StringVector values = getValuesOfQualifier(qualifier_name);
2597 
2598     if(values == null)
2599       return null;
2600     else
2601     {
2602       if(values.size() == 0)
2603         return "";
2604       else
2605       {
2606         final String first_element = (String)values.elementAt(0);
2607 
2608         if(first_element == null)
2609           return "";
2610         else
2611           return first_element;
2612       }
2613     }
2614   }
2615 
2616   /**
2617    *  Remove this Feature from the Entry object that contains it.  It will
2618    *  also be removed from the underlying embl.Entry object.
2619    **/
removeFromEntry()2620   public void removeFromEntry()
2621       throws ReadOnlyException
2622   {
2623     getEntry().remove(this);
2624   }
2625 
2626   /**
2627    *  Return the vector containing the references of the FeatureSegment
2628    *  objects of this Feature.
2629    **/
getSegments()2630   public FeatureSegmentVector getSegments()
2631   {
2632     final Location current_location = getEmblFeature().getLocation();
2633 
2634     if(segments == null)
2635       createSegments();
2636     else
2637     {
2638       // see comment on old_location
2639       if(current_location != old_location)
2640       {
2641         reexamineSegments();
2642         if(!old_location.equals(current_location))
2643         {
2644           locationChanged(old_location);
2645           old_location = current_location;
2646         }
2647       }
2648     }
2649 
2650     return segments;
2651   }
2652 
2653   /**
2654    *  Examine each Range in the Location of this Feature.  Remove
2655    *  FeatureSegments that don't correspond to Ranges in the Location.  Add
2656    *  a FeatureSegment for each extra Range that has appeared.  This is
2657    *  necessary because the feature location may change at any time in a
2658    *  client/server environment.
2659    **/
reexamineSegments()2660   private void reexamineSegments()
2661   {
2662     stopSegmentsListening();
2663 
2664     final Location current_location = getEmblFeature().getLocation();
2665     final RangeVector ranges = current_location.getRanges();
2666     final Vector new_segments = new Vector();
2667 
2668     int ranges_size = ranges.size();
2669     new_segments.setSize(ranges_size);
2670 
2671     FeatureSegmentVector old_segments =
2672       (FeatureSegmentVector)segments.clone();
2673 
2674     // if the feature has changed strand, give up and recreate all
2675     // segments
2676     if(current_location.isComplement() != old_location.isComplement())
2677       old_segments.removeAllElements();
2678 
2679     // first find exact matches
2680 EXACT_SEGMENTS:
2681     for(int old_segment_index = old_segments.size() - 1; old_segment_index >= 0;
2682         --old_segment_index)
2683     {
2684       final FeatureSegment old_segment =
2685         old_segments.elementAt(old_segment_index);
2686 
2687       for(int range_index = 0; range_index < ranges_size;
2688           ++range_index)
2689       {
2690         final Range new_range = (Range)ranges.elementAt(range_index);
2691 
2692         // there is a Range in the new Location that exactly matches a Range
2693         // in an old FeatureSegment so reuse the old FeatureSegment
2694         if(old_segment.getRawRange().equals(new_range))
2695         {
2696           old_segment.setRange(new_range);
2697           new_segments.setElementAt(old_segment, range_index);
2698           old_segments.removeElementAt(old_segment_index);
2699           continue EXACT_SEGMENTS;
2700         }
2701       }
2702     }
2703 
2704     // find matches where the start or end has changed
2705 CHANGED_END:
2706     for(int old_segment_index = old_segments.size() - 1; old_segment_index >= 0;
2707         --old_segment_index)
2708     {
2709       final FeatureSegment old_segment =
2710         old_segments.elementAt(old_segment_index);
2711 
2712       for(int range_index = 0; range_index < ranges_size;
2713           ++range_index)
2714       {
2715         final Range new_range = (Range)ranges.elementAt(range_index);
2716 
2717         if(old_segment.getRawRange().getStart() ==
2718            new_range.getStart() ||
2719            old_segment.getRawRange().getEnd() ==
2720            new_range.getEnd())
2721         {
2722           // there is a Range in the new Location that partly matches a Range
2723           // in an old FeatureSegment so reuse the old FeatureSegment
2724           old_segment.setRange(new_range);
2725           new_segments.setElementAt(old_segment, range_index);
2726           old_segments.removeElementAt(old_segment_index);
2727           continue CHANGED_END;
2728         }
2729       }
2730     }
2731 
2732     // create a segment for each range that we don't have a segment for
2733     for(int new_segment_index = 0; new_segment_index < ranges_size;
2734         ++new_segment_index)
2735     {
2736       final Range missing_range = (Range)ranges.elementAt(new_segment_index);
2737 
2738       if(new_segments.elementAt(new_segment_index) == null)
2739       {
2740         // new Range -> create a segment
2741         new_segments.setElementAt(makeSegment(missing_range),
2742                                    new_segment_index);
2743       }
2744     }
2745 
2746     segments = new FeatureSegmentVector();
2747 
2748     for(int i = 0; i < ranges.size(); ++i)
2749       segments.addElementAtEnd((FeatureSegment) new_segments.elementAt(i));
2750 
2751     startSegmentsListening();
2752   }
2753 
2754   /**
2755    *  Return the reference of the Strand that this Feature and it's
2756    *  FeatureSegment objects is associated with.
2757    **/
getStrand()2758   public Strand getStrand()
2759   {
2760     // get the strand of the first entry in the entry group
2761     if(isForwardFeature())
2762       return entry.getBases().getForwardStrand();
2763     else
2764       return entry.getBases().getReverseStrand();
2765   }
2766 
2767   /**
2768    *  Return the reference of a new copy of this Feature.  This method will
2769    *  update the underlying embl.Entry and the new Feature will be installed
2770    *  in the same Entry object as this one.
2771    *  @return The reference of the new feature.
2772    **/
duplicate()2773   public Feature duplicate()
2774                  throws ReadOnlyException
2775   {
2776     return duplicate(false);
2777   }
2778 
2779   /**
2780    * Return the reference of a new copy of this Feature.  This method will
2781    *  update the underlying embl.Entry and the new Feature will be installed
2782    *  in the same Entry object as this one.
2783    * @param isDuplicatedInChado  if true then create new ID and update in chado
2784    * @return
2785    * @throws ReadOnlyException
2786    */
duplicate(final boolean isDuplicatedInChado)2787   public Feature duplicate(final boolean isDuplicatedInChado)
2788       throws ReadOnlyException
2789   {
2790     uk.ac.sanger.artemis.io.Feature new_embl_feature;
2791 
2792     if(getEmblFeature() instanceof GFFStreamFeature)
2793       new_embl_feature = new GFFStreamFeature(getEmblFeature(), isDuplicatedInChado);
2794     else
2795       new_embl_feature = new EmblStreamFeature(getEmblFeature());
2796 
2797     final Feature return_feature = new Feature(new_embl_feature);
2798 
2799     try
2800     {
2801       getEntry().add(return_feature, !isDuplicatedInChado, false);
2802     }
2803     catch(EntryInformationException e)
2804     {
2805       throw new Error("internal error - unexpected exception: " + e);
2806     }
2807     catch(OutOfRangeException e)
2808     {
2809       throw new Error("internal error - unexpected exception: " + e);
2810     }
2811 
2812     return return_feature;
2813   }
2814 
2815   /**
2816    *  Move this Feature to the given Entry.  The move is achieved by removing
2817    *  the feature from it's current Entry and adding to the the
2818    *  destination_entry.
2819    *  @param destination_entry The Feature will be moved to this Entry.
2820    *  @param force If true then invalid qualifiers will be quietly thrown away
2821    *    when saving.  Features with invalid keys won't be moved.  "Invalid"
2822    *    means that the key/qualifier is not allowed to occur in the
2823    *    destination type (probably determined by the default EntryInformation
2824    *    object of the destination type).  If false an
2825    *    EntryInformationException will be thrown for invalid keys or
2826    *    qualifiers.
2827    *  @exception EntryInformationException Thrown if force is false and if the
2828    *    destination type cannot contain the Key, Qualifier or Key/Qualifier
2829    *    combination of the given feature.
2830    **/
moveTo(final Entry destination_entry, final boolean force)2831   public void moveTo(final Entry destination_entry, final boolean force)
2832       throws EntryInformationException, OutOfRangeException,
2833       ReadOnlyException
2834   {
2835     if(destination_entry.isReadOnly())
2836       throw new ReadOnlyException();
2837     else
2838     {
2839       // this (hack) causes the calls to stopListening() and startListening()
2840       // in setEntry() to return immediately which improves the speed a lot
2841       // when many features are loaded
2842       startListening();
2843 
2844       try
2845       {
2846         final Entry old_entry = getEntry();
2847 
2848         getEntry().remove(this);
2849 
2850         try
2851         {
2852           destination_entry.add(this, force);
2853         }
2854         catch(EntryInformationException e)
2855         {
2856           // put the feature back where it was
2857           old_entry.add(this, true);
2858 
2859           // re-throw
2860           throw e;
2861         }
2862       }
2863       finally
2864       {
2865         // see note above on startListening()
2866         stopListening();
2867       }
2868     }
2869   }
2870 
2871   /**
2872    *  Make a copy of the this Feature and add it to the given Entry.
2873    *  @return The reference of the new feature.
2874    *  @exception EntryInformationException Thrown if the destination Entry
2875    *    cannot contain a Feature with this Key, Qualifier or Key/Qualifier
2876    *    combination.
2877    **/
copyTo(final Entry destination_entry)2878   public Feature copyTo(final Entry destination_entry)
2879       throws EntryInformationException, OutOfRangeException,
2880       ReadOnlyException
2881   {
2882     if(destination_entry.isReadOnly())
2883       throw new ReadOnlyException();
2884 
2885     uk.ac.sanger.artemis.io.Feature new_embl_feature =
2886                          new EmblStreamFeature(getEmblFeature());
2887 
2888     final Feature return_feature = new Feature(new_embl_feature);
2889 
2890     destination_entry.add(return_feature, false);
2891 
2892     return return_feature;
2893   }
2894 
2895   /**
2896    *  Adds the specified event listener to receive feature change events from
2897    *  this object.
2898    *  @param l the change event listener.
2899    **/
addFeatureChangeListener(FeatureChangeListener l)2900   public void addFeatureChangeListener(FeatureChangeListener l)
2901   {
2902     feature_listener_list.addElement(l);
2903   }
2904 
2905   /**
2906    *  Removes the specified event listener so that it no longer receives
2907    *  feature change events from this object.
2908    *  @param l the change event listener.
2909    **/
removeFeatureChangeListener(FeatureChangeListener l)2910   public void removeFeatureChangeListener(FeatureChangeListener l)
2911   {
2912     feature_listener_list.removeElement(l);
2913   }
2914 
2915   /**
2916    *  Returns true if and only if this feature is read only or is in a read
2917    *  only entry.
2918    **/
isReadOnly()2919   public boolean isReadOnly()
2920   {
2921     return getEmblFeature().isReadOnly();
2922   }
2923 
2924   /**
2925    *  This method examines each FeatureSegment and uses that information to
2926    *  set the location of the underlying embl.Feature object.  This must be
2927    *  called any time a segment position changes, or when there is a change to
2928    *  the sequence (the sequence as represented by the Strand and Bases
2929    *  objects).  This method does not fire off any FeatureChange events.
2930    **/
updateEMBLFeatureLocation()2931   private void updateEMBLFeatureLocation()
2932       throws ReadOnlyException
2933   {
2934     final boolean complement = getLocation().isComplement();
2935     final RangeVector ranges = new RangeVector();
2936 
2937     for(int i = 0; i < segments.size(); ++i)
2938       ranges.addElement(segments.elementAt (i).getRawRange ());
2939 
2940     try
2941     {
2942       final Location new_location = new Location(ranges, complement);
2943 
2944       getEmblFeature().setLocation(new_location);
2945       old_location = new_location;
2946     }
2947     catch(OutOfRangeException e)
2948     {
2949       throw new Error("internal error - inconsistent location information: " +
2950                        e.getMessage());
2951     }
2952   }
2953 
2954 
2955   /**
2956    *  Send an event to those object listening for it.
2957    *  @param listeners A Vector of the objects that the event should be sent
2958    *    to.
2959    *  @param event The event to send
2960    **/
fireAction(Vector listeners, ChangeEvent event)2961   private void fireAction(Vector listeners, ChangeEvent event)
2962   {
2963     final Vector targets;
2964     // copied from a book - synchronising the whole method might cause a
2965     // deadlock
2966     synchronized(this)
2967     {
2968       targets = (Vector)listeners.clone();
2969     }
2970 
2971     for(int i = 0 ; i < targets.size() ; ++i)
2972     {
2973       ChangeListener target = (ChangeListener)targets.elementAt(i);
2974 
2975       final FeatureChangeListener feature_change_listener =
2976                                            (FeatureChangeListener)target;
2977       feature_change_listener.featureChanged((FeatureChangeEvent) event);
2978     }
2979   }
2980 
2981   /**
2982    *  Reset these arrays: segments, amino_acids, bases, codon_counts,
2983    *  residue_counts, positional_base_counts and base_counts.
2984    **/
resetCache()2985   private void resetCache()
2986   {
2987     amino_acids = null;
2988     bases = null;
2989     codon_counts = null;
2990     residue_counts = null;
2991     positional_base_counts = null;
2992     base_counts = null;
2993     aa_count = -1;
2994     base_count = -1;
2995   }
2996 
2997   /**
2998    *  Update the values stored in residue_counts, codon_counts, base_counts
2999    *  and positional_base_counts.
3000    **/
setArrays()3001   private void setArrays()
3002   {
3003     final String translation_bases = getTranslationBases();
3004     final AminoAcidSequence translation = getTranslation();
3005 
3006     final String translation_string = translation.toString();
3007 
3008     codon_counts = new int[4][4][4];
3009     residue_counts = new int[AminoAcidSequence.symbol_count];
3010     base_counts = new int[4];
3011     positional_base_counts = new int[3][4];
3012 
3013     // zero the residue_counts array
3014     for(int i = 0; i < residue_counts.length; ++i)
3015       residue_counts[i] = 0;
3016 
3017     int trans_len = translation_string.length();
3018     for(int i = 0; i < trans_len; ++i)
3019     {
3020       final int symbol_index =
3021         AminoAcidSequence.getSymbolIndex(translation_string.charAt(i));
3022       ++residue_counts[symbol_index];
3023     }
3024 
3025     // zero the codon_counts array
3026     for(int first = 0 ; first < 4 ; ++first)
3027     {
3028       for(int second = 0 ; second < 4 ; ++second)
3029       {
3030         for(int third = 0 ; third < 4 ; ++third)
3031           codon_counts[first][second][third] = 0;
3032       }
3033     }
3034 
3035     int base_len = translation_bases.length();
3036     for(int base_index = 0; base_index < base_len;
3037         ++base_index)
3038     {
3039       // this is 0, 1, 2 or 3
3040       final int index_of_base =
3041         Bases.getIndexOfBase(translation_bases.charAt(base_index));
3042 
3043       if(index_of_base < 4)
3044         ++base_counts[index_of_base];
3045     }
3046 
3047     for(int i = 0; i < translation_bases.length() / 3; ++i)
3048     {
3049       final int first_base_index =
3050         Bases.getIndexOfBase(translation_bases.charAt(i * 3));
3051       final int second_base_index =
3052         Bases.getIndexOfBase(translation_bases.charAt(i * 3 + 1));
3053       final int third_base_index =
3054         Bases.getIndexOfBase(translation_bases.charAt(i * 3 + 2));
3055 
3056       if(first_base_index < 4)
3057         ++positional_base_counts[0][first_base_index];
3058 
3059       if(second_base_index < 4)
3060         ++positional_base_counts[1][second_base_index];
3061 
3062       if(third_base_index < 4)
3063         ++positional_base_counts[2][third_base_index];
3064 
3065       if(first_base_index < 4 && second_base_index < 4 && third_base_index < 4)
3066         ++codon_counts[first_base_index][second_base_index][third_base_index];
3067     }
3068   }
3069 
3070   /**
3071    *  Remove all FeatureSegments from all listener lists.
3072    **/
stopSegmentsListening()3073   private void stopSegmentsListening()
3074   {
3075     if(segments != null)
3076     {
3077       for(int i = 0 ; i < segments.size(); ++i)
3078       {
3079         segments.elementAt(i).stopListening();
3080         segments.elementAt(i).removeMarkerChangeListener(this);
3081       }
3082     }
3083   }
3084 
3085   /**
3086    *  Remove this Feature and its FeatureSegments from all listener lists.
3087    **/
stopListening()3088   private void stopListening()
3089   {
3090     if(listen_count == 1)
3091     {
3092       if(getEntry() != null && getEntry().getBases() != null)
3093       {
3094         final Bases bases = getEntry().getBases();
3095         bases.removeSequenceChangeListener(this);
3096       }
3097 
3098       stopSegmentsListening();
3099 
3100       Options.getOptions().removeOptionChangeListener(this);
3101     }
3102 
3103     --listen_count;
3104 
3105     if(listen_count < 0)
3106       throw new Error("Feature.listen_count < 0");
3107   }
3108 
3109   /**
3110    *  Add all FeatureSegments to all appropriate listener lists.
3111    **/
startSegmentsListening()3112   private void startSegmentsListening()
3113   {
3114     if(segments != null)
3115     {
3116       int seg_size = segments.size();
3117       for (int i = 0; i < seg_size; ++i)
3118       {
3119         segments.elementAt(i).startListening();
3120         segments.elementAt(i).addMarkerChangeListener(this);
3121       }
3122     }
3123   }
3124 
3125   /**
3126    *  Add this Feature and its FeatureSegments to the all appropriate listener
3127    *  lists.
3128    **/
startListening()3129   private void startListening()
3130   {
3131     if(listen_count == 0)
3132     {
3133       if(getEntry() != null && getEntry().getBases() != null)
3134       {
3135         final Bases bases = getEntry().getBases();
3136         startSegmentsListening();
3137 
3138         // it doesn't matter what the priority is as long as it is lower than
3139         // the FeatureSegment priority, so that all FeatureSegment objects get
3140         // updated before we try to update the location of this feature
3141         final int PRIORITY = Marker.LISTENER_PRIORITY - 2;
3142         bases.addSequenceChangeListener(this, PRIORITY);
3143       }
3144 
3145       Options.getOptions().addOptionChangeListener(this);
3146     }
3147 
3148     ++listen_count;
3149   }
3150 
3151   /**
3152    *  Create one FeatureSegment object for each segment/exon of this Feature.
3153    **/
createSegments()3154   private void createSegments()
3155   {
3156     // this call will remove each segment from the MarkerChangeListener list
3157     // of the start and end Marker objects of the segment.  If we don't do
3158     // this the segments will not be garbage collected.
3159     stopSegmentsListening();
3160 
3161     segments = new FeatureSegmentVector();
3162     final Location location = getLocation();
3163     final RangeVector ranges = location.getRanges();
3164 
3165     int ranges_size = ranges.size();
3166     for(int i = 0; i < ranges_size; ++i)
3167     {
3168       final Range this_range = (Range)ranges.elementAt(i);
3169       segments.add(makeSegment(this_range));
3170     }
3171 
3172     startSegmentsListening();
3173   }
3174 
3175   /**
3176    *  Make a single FeatureSegment.
3177    *  @param range Provides the start and end positions of the segment.
3178    **/
makeSegment(final Range range)3179   private FeatureSegment makeSegment(final Range range)
3180   {
3181     return new FeatureSegment(this, range);
3182   }
3183 
3184   /**
3185    *  Set the Location of this feature and reset old_location and
3186    *  segments.  Don't send any FeatureChangeEvents.
3187    **/
setLocationInternal(final Location new_location)3188   private void setLocationInternal(final Location new_location)
3189       throws ReadOnlyException, OutOfRangeException
3190   {
3191     getEmblFeature().setLocation(new_location, getEntry().getEMBLEntry());
3192 
3193     if(new_location != old_location)
3194       reexamineSegments();
3195 
3196     old_location = new_location;
3197     resetCache();
3198   }
3199 
3200   /**
3201    *  Set the Location of this feature and notify any FeatureChangeListeners.
3202    **/
setLocation(final Location new_location)3203   public void setLocation(final Location new_location)
3204       throws ReadOnlyException, OutOfRangeException
3205   {
3206     final Location old_location = getLocation();
3207     setLocationInternal(new_location);
3208     locationChanged(old_location);
3209   }
3210 
addSegment(final Range range)3211   public void addSegment(final Range range)
3212          throws ReadOnlyException
3213   {
3214     final QualifierVector old_qualifiers = getQualifiers().copy();
3215     addSegment(range, old_qualifiers);
3216   }
3217 
3218   /**
3219    *  Add a FeatureSegment to this Feature.
3220    *  @param range Provides the start and end positions of the segment.
3221    **/
addSegment(final Range range, final QualifierVector old_qualifiers)3222   public void addSegment(final Range range,
3223                          final QualifierVector old_qualifiers)
3224          throws ReadOnlyException
3225   {
3226     final Location old_location = getLocation();
3227     final Location new_location = old_location.addRange(range);
3228 
3229     try
3230     {
3231       setLocationInternal(new_location);
3232     }
3233     catch(OutOfRangeException e)
3234     {
3235       throw new Error("internal error - inconsistent location " +
3236                       "information: " + e);
3237     }
3238 
3239     reexamineSegments();
3240     locationChanged(old_location, old_qualifiers, FeatureChangeEvent.SEGMENT_CHANGED);
3241   }
3242 
moveSegments(final int shift)3243   private Location moveSegments(final int shift)
3244       throws OutOfRangeException, ReadOnlyException
3245   {
3246     Location location = getLocation();
3247     RangeVector ranges = old_location.getRanges();
3248     for(int i=0; i<ranges.size(); i++)
3249     {
3250       old_location = location;
3251       final Range seg_range = (Range)ranges.elementAt(i);
3252       final Range seg_new_range = new Range(seg_range.getStart() + shift,
3253                                             seg_range.getEnd() + shift);
3254       location = location.changeRange(seg_range, seg_new_range);
3255 
3256 //    System.out.println("DIFF "+ shift+" "+seg_new_range.getStart()+".."+
3257 //                                         seg_new_range.getEnd()+"  was "+
3258 //                                          seg_range.getStart()+".."+
3259 //                                        seg_range.getEnd());
3260     }
3261 
3262     return location;
3263   }
3264 
3265   /**
3266    *  Delete a FeatureSegment from this object and update the location
3267    *  underlying embl.Feature object.
3268    *  @param segment The FeatureSegment to delete.
3269    *  @exception LastSegmentException thrown if an attempt is made to remove
3270    *    the last segment.
3271    **/
removeSegment(FeatureSegment segment)3272   public void removeSegment(FeatureSegment segment)
3273       throws ReadOnlyException, LastSegmentException
3274   {
3275     if(getSegments().size() > 1)
3276     {
3277       QualifierVector qualifiers = getQualifiers().copy();
3278       final Location old_location = getLocation();
3279 
3280       final Location new_location =
3281         old_location.removeRange(segment.getRawRange());
3282 
3283       try
3284       {
3285         setLocationInternal(new_location);
3286       }
3287       catch(OutOfRangeException e)
3288       {
3289         throw new Error("internal error - inconsistent location " +
3290                         "information: " + e);
3291       }
3292 
3293       reexamineSegments();
3294 
3295       locationChanged(old_location, qualifiers, FeatureChangeEvent.SEGMENT_CHANGED);
3296     }
3297     else
3298       throw new LastSegmentException();
3299   }
3300 
3301   /**
3302    *  Return a list of all the names of the qualifiers in the given features.
3303    **/
getAllQualifierNames(final FeatureVector features)3304   public static StringVector getAllQualifierNames(final FeatureVector features)
3305   {
3306     final StringVector qualifier_names = new StringVector();
3307     int feat_size = features.size();
3308 
3309     for(int i = 0; i < feat_size; ++i)
3310     {
3311       final Feature feature = features.elementAt(i);
3312       final QualifierVector qualifiers = feature.getQualifiers();
3313 
3314       int qualifiers_size = qualifiers.size();
3315       for(int qualifier_index = 0; qualifier_index < qualifiers_size;
3316           ++qualifier_index)
3317       {
3318         final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(qualifier_index);
3319         final String name = this_qualifier.getName();
3320 
3321         if(!qualifier_names.contains(name))
3322           qualifier_names.add(name);
3323       }
3324     }
3325 
3326     qualifier_names.sort();
3327 
3328     return qualifier_names;
3329   }
3330 
3331   /**
3332    *  Reverse and complement the location of this feature.
3333    *  @param sequence_length The length of the sequence that this Feature is
3334    *    associated with.
3335    **/
reverseComplement(final int sequence_length)3336   private void reverseComplement(final int sequence_length)
3337       throws ReadOnlyException
3338   {
3339     try
3340     {
3341       final Location new_location =
3342         getLocation().reverseComplement(sequence_length);
3343 
3344       setLocationInternal(new_location);
3345     }
3346     catch(OutOfRangeException e)
3347     {
3348       throw new Error("internal error - inconsistent location: " + e);
3349     }
3350   }
3351 
3352   /**
3353    *  Return the sequence length of the Strand that this feature is attached
3354    *  to.
3355    **/
getSequenceLength()3356   private int getSequenceLength()
3357   {
3358     FeatureSegment first_seg = getSegments().elementAt(0);
3359     return first_seg.getStart().getStrand().getSequenceLength();
3360   }
3361 
3362   /**
3363    *  Set the embl.Feature reference of this Feature to be the given reference.
3364    **/
setEmblFeature(final uk.ac.sanger.artemis.io.Feature new_embl_feature)3365   void setEmblFeature(final uk.ac.sanger.artemis.io.Feature new_embl_feature)
3366   {
3367     embl_feature = new_embl_feature;
3368   }
3369 
3370 }
3371