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