/* Feature.java * * created: Sun Oct 11 1998 * * This file is part of Artemis * * Copyright (C) 1998,1999,2000 Genome Research Limited * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $ */ package uk.ac.sanger.artemis; import uk.ac.sanger.artemis.util.*; import uk.ac.sanger.artemis.components.genebuilder.GeneUtils; import uk.ac.sanger.artemis.io.OutOfDateException; import uk.ac.sanger.artemis.io.LocationParseException; import uk.ac.sanger.artemis.io.Location; import uk.ac.sanger.artemis.io.Key; import uk.ac.sanger.artemis.io.PartialSequence; import uk.ac.sanger.artemis.io.Qualifier; import uk.ac.sanger.artemis.io.QualifierVector; import uk.ac.sanger.artemis.io.RangeVector; import uk.ac.sanger.artemis.io.Range; import uk.ac.sanger.artemis.io.InvalidRelationException; import uk.ac.sanger.artemis.io.DateStampFeature; import uk.ac.sanger.artemis.io.EntryInformation; import uk.ac.sanger.artemis.io.EntryInformationException; import uk.ac.sanger.artemis.io.EmblStreamFeature; import uk.ac.sanger.artemis.io.GFFStreamFeature; import uk.ac.sanger.artemis.io.FastaStreamSequence; import uk.ac.sanger.artemis.io.StreamFeature; import uk.ac.sanger.artemis.sequence.*; import uk.ac.sanger.artemis.plot.*; import java.awt.Color; import java.util.Vector; import java.io.*; import java.util.Date; import java.util.regex.Pattern; /** * This class extends an embl.Feature with the other information needed by * Diana. It also able to send events to other objects that are interested * in changes to this object. (see FeatureChangeEvent details of the * possible change events.) To make changes to the feature it calls methods * in the Entry class. Changes to this object will update the underlying * embl.Feature and embl.Entry objects. * * @author Kim Rutherford **/ public class Feature implements EntryChangeListener, Selectable, SequenceChangeListener, MarkerChangeListener, OptionChangeListener { /** * The Entry that controls/contains this feature. This is the Entry object * that was passed to the constructor. **/ private Entry entry; /** * This is a reference to the low level Feature object that this class is * providing a wrapper for. This is the embl.Feature object that was * passed to constructor. **/ private uk.ac.sanger.artemis.io.Feature embl_feature; /** * The segment of this Feature. All features have at least one segment. **/ private FeatureSegmentVector segments = null; /** * A vector of those objects listening for feature change events. **/ private final Vector feature_listener_list = new Vector(); /** * The translation of the bases of this feature. **/ private AminoAcidSequence amino_acids = null; /** * The bases of this feature. **/ private String bases = null; /** * The count of the number of amino acids in this feature. (set by * getAACount() and resetCache()). **/ private int aa_count = -1; /** * The count of the bases in this feature (set by getBaseCount() and * resetCache()). **/ private int base_count = -1; /** * This array contains counts of the occurrences of each three bases. The * first index is the first base, the second index is the second base, etc. * The indices are the same as those for Bases.letter_index. * (set by resetCache()). **/ private int [][][] codon_counts = null; /** * This array contains counts of the occurrences of each amino acids in the * translation of the bases of the current feature. * (set by resetCache()). **/ private int [] residue_counts = null; /** * This array contains counts of number of each base that appear in each * codon position. The first index is the codon position and the second is * the base (indexed in the same way as Bases.letter_index). * (set by resetCache()). **/ private int [][] positional_base_counts = null; /** * This array contains the counts of the total number of each base in the * feature. * The indices are the same as those for Bases.letter_index. * (set by resetCache()). **/ private int [] base_counts = null; /** * The current Location reference is saved each time setLocation() is * called so that if the reference changes resetCache() can * be called. **/ private Location old_location = null; /** * Incremented when startListening() is called - decremented when * stopListening() is called. **/ private int listen_count = 0; private Color colour = null; /** * Create a new Feature object. * @param entry The uk.ac.sanger.artemis.Entry object that contains this Feature. * @param embl_feature The embl.Feature object that this class is * providing a wrapper for. **/ public Feature(uk.ac.sanger.artemis.io.Feature embl_feature) { this.embl_feature = embl_feature; embl_feature.setUserData(this); old_location = embl_feature.getLocation(); } /** * Implementation of the EntryChangeListener interface. We listen to * EntryChange events so that if the feature is deleted we can destroy any * objects that use it. * * NOTE: not active currently **/ public void entryChanged(EntryChangeEvent event) { } /** * This method fixes up the location of this Feature when a Marker changes. **/ public void markerChanged(final MarkerChangeEvent event) { try { final Location old_location = getLocation(); updateEMBLFeatureLocation(); locationChanged(old_location); } catch(ReadOnlyException e) {} } /** * This method fixes up the location of this Feature when a sequence * changes. **/ public void sequenceChanged(final SequenceChangeEvent event) { try { // we don't send a FeatureChangeEvent because the logical location // hasn't changed // all the Markers for the ranges of this feature will have changed // before this method is called because the markers are added as // SequenceChangeListener with a higher priority than this Feature if(event.getType() == SequenceChangeEvent.REVERSE_COMPLEMENT) reverseComplement(getEntry().getBases().getLength()); else if(event.getType() == SequenceChangeEvent.CONTIG_REVERSE_COMPLEMENT) { final Location old_location = getLocation(); // if the event is contained within this feature then the feature // sequence may have changed final Range this_feature_range = getMaxRawRange(); boolean feature_changed = false; Range eventRange = event.getRange(); // reverse complement feature if within contig region if(eventRange.getStart() <= this_feature_range.getStart() && eventRange.getEnd() >= this_feature_range.getEnd()) { try { final Location new_location = getLocation().reverseComplement(event.getLength(), eventRange.getStart()); setLocationInternal(new_location); } catch(OutOfRangeException e) { throw new Error("internal error - inconsistent location: " + e); } } } else if(event.getType() == SequenceChangeEvent.CONTIG_REORDER) { final Location old_location = getLocation(); final int new_base_pos = event.getPosition(); final int range_start = event.getRange().getStart(); final int range_end = event.getRange().getEnd(); final Range this_feature_range = getMaxRawRange(); // check if feature is effected if( (this_feature_range.getStart() >= new_base_pos || this_feature_range.getStart() >= range_start) && (this_feature_range.getStart() < new_base_pos || this_feature_range.getStart() < range_end) ) { try { final int diff; if(range_start <= this_feature_range.getStart() && range_end >= this_feature_range.getEnd()) { if(new_base_pos < range_start) diff = new_base_pos-range_start; else diff = new_base_pos-range_end-1; } else { if(this_feature_range.getStart() < range_start) diff = range_end-range_start+1; else diff = range_start-range_end-1; } Location new_location = moveSegments(diff); setLocationInternal(new_location); } catch(OutOfRangeException e) { throw new Error("internal error - inconsistent location: " + e); } } } else { final Location old_location = getLocation(); // if the event is contained within this feature then the feature // sequence may have changed final Range this_feature_range = getMaxRawRange(); boolean feature_changed = false; int eventPosition = event.getPosition(); if(eventPosition >= this_feature_range.getStart() && eventPosition <= this_feature_range.getEnd() + 1) { // check each segment final FeatureSegmentVector segments = getSegments(); int seg_size = segments.size(); for(int i = 0; i < seg_size; ++i) { final FeatureSegment this_segment = segments.elementAt(i); final Range this_segment_range = this_segment.getRawRange(); if(eventPosition >= this_segment_range.getStart() && eventPosition <= this_segment_range.getEnd() + 1) feature_changed = true; } } updateEMBLFeatureLocation(); if(feature_changed) { resetCache(); locationChanged(old_location); } } } catch(ReadOnlyException e) { throw new Error("internal error - unexpected exception: " + e); } } /** * Invoked when an Option is changed. **/ public void optionChanged(OptionChangeEvent event) { // if the eukaryotic mode option changes the sequence may change (ie. the // start codon may need to be translated differently) - see // getTranslation() locationChanged(getLocation()); } /** * Returns the embl feature that was passed to the constructor. **/ public uk.ac.sanger.artemis.io.Feature getEmblFeature() { return embl_feature; } /** * Write this Feature to the given stream in the native format of this * Feature. The output will be in Genbank format if this Feature is a * Genbank feature and EMBL format otherwise. * @param writer The record is written to this Writer. **/ public void writeNative(final Writer writer) throws IOException { if(getEmblFeature() instanceof StreamFeature) ((StreamFeature)getEmblFeature()).writeToStream(writer); else { final EntryInformation entry_info = getEntry().getEntryInformation(); // this is a hack to make the correct EntryInformation object available // to the feature writing code. final uk.ac.sanger.artemis.io.EmblDocumentEntry document_entry = new uk.ac.sanger.artemis.io.EmblDocumentEntry(entry_info); final uk.ac.sanger.artemis.io.Feature returned_feature = document_entry.forcedAdd(new EmblStreamFeature(getEmblFeature())); ((EmblStreamFeature)returned_feature).writeToStream(writer); } } /** * Write a PIR database record of this feature to the given Writer. * @param writer The record is written to this Writer. **/ public void writePIROfFeature(final Writer writer) { final String gene_name = getGeneName(); final String pir_name; if(gene_name == null) { if(getLabel() == null) pir_name = getKey().toString(); else pir_name = getLabel(); } else pir_name = gene_name; final String header_line = ">BL;" + pir_name + ", " + getEntry().getName() + " " + getWriteRange() + " MW:" + (int) getMolecularWeight(); final PrintWriter print_writer = new PrintWriter(writer); print_writer.println(header_line); final String translation_string = getTranslation().toString().toUpperCase(); wrapAndWrite(print_writer, translation_string, 80); print_writer.println("*"); print_writer.flush(); } /** * Write the bases of this feature to the given Writer. * @param writer The bases are written to this Writer. **/ public void writeBasesOfFeature(final Writer writer) throws IOException { final FastaStreamSequence stream_sequence = new FastaStreamSequence(getBases(), getIDString() + ", " + getWriteRange()); stream_sequence.writeToStream(writer); } /** * Write the amino acid symbols of this feature to the given Writer. * @param writer The amino acids are written to this Writer. **/ public void writeAminoAcidsOfFeature(final Writer writer) throws IOException { final StringBuffer header_buffer = new StringBuffer(">"); header_buffer.append(getSystematicName()); header_buffer.append(" "); header_buffer.append(getIDString()); header_buffer.append(" "); final String product = getProductString(); if(product == null) header_buffer.append("undefined product"); else header_buffer.append(product); header_buffer.append(" ").append(getWriteRange()).append(" MW:"); header_buffer.append((int) getMolecularWeight()); final PrintWriter print_writer = new PrintWriter(writer); print_writer.println(header_buffer); final String translation_string = getTranslation().toString().toUpperCase(); wrapAndWrite(print_writer, translation_string, 60); print_writer.flush(); } /** * Return a String containing the bases upstream of this feature. * @param count The number of (immediately) upstream bases to return. **/ public String getUpstreamBases(final int count) { final int feature_start_base = getFirstBase(); final int start_base; final int end_base; if(feature_start_base == 1) { // there are no bases before this feature return ""; } else { end_base = feature_start_base - 1; if(feature_start_base > count) start_base = feature_start_base - count; else start_base = 1; } final String bases_string; try { bases_string = getStrand().getSubSequence(new Range(start_base, end_base)); } catch(OutOfRangeException e) { throw new Error("internal error - unexpected exception: " + e); } return bases_string; } /** * Return a String containing the bases downstream of this feature. * @param count The number of (immediately) downstream bases to return. **/ public String getDownstreamBases(final int count) { final int feature_end_base = getLastBase(); final int start_base; final int end_base; final int sequenceLength = getSequenceLength(); if(feature_end_base == sequenceLength) { // there are no bases after this feature return ""; } else { start_base = feature_end_base + 1; if(sequenceLength - feature_end_base > count) end_base = feature_end_base + count; else end_base = sequenceLength; } final String bases_string; try { bases_string = getStrand().getSubSequence(new Range(start_base, end_base)); } catch(OutOfRangeException e) { throw new Error("internal error - unexpected exception: " + e); } return bases_string; } /** * Helper method for writePIROfFeature(). * @return A string of the form "100:200 reverse" or "1:2222 forward". **/ public String getWriteRange() { String partial = " "; if(isPartial(true)) partial += "partial 5' "; if(isPartial(false)) partial += "partial 3' "; return (isForwardFeature() ? getFirstCodingBaseMarker().getRawPosition() + ":" + getLastBaseMarker().getRawPosition() + partial + "forward" : getLastBaseMarker().getRawPosition() + ":" + getFirstCodingBaseMarker().getRawPosition() + partial + "reverse"); } /** * If lookAt5prime is set to true then only return true if the 5' end is * partial otherwise only return true if the 3' end is partial. * @param lookAt5prime * @return */ private boolean isPartial(final boolean lookAt5prime) { try { boolean isDatabaseFeature = GeneUtils.isDatabaseEntry(getEmblFeature()); if(isDatabaseFeature) { if(lookAt5prime) { if(isForwardFeature()) { if(getQualifierByName("Start_range") != null) return true; } else if(getQualifierByName("End_range") != null) return true; } else { if(isForwardFeature()) { if(getQualifierByName("End_range") != null) return true; } else if(getQualifierByName("Start_range") != null) return true; } } } catch (Exception e) {} return getLocation().isPartial(lookAt5prime); } /** * Write a String object to a PrintWriter object, wrapping it a the given * coloumn. * @param writer The String will be written to this object. * @param string The String to write. * @param wrap_column The lines of output will be no longer than this many * characters. **/ private void wrapAndWrite(final PrintWriter writer, final String string, final int wrap_column) { String remaining_string = string; while(remaining_string.length() > 0) { int last_index = wrap_column; if(wrap_column > remaining_string.length()) last_index = remaining_string.length(); final String write_string = remaining_string.substring(0, last_index); writer.println(write_string); remaining_string = remaining_string.substring(last_index); } } /** * Return this Feature as a EMBL, Genbank or GFF formatted String * (depending on the type of this Feature). **/ public String toString() { final StringWriter string_writer = new StringWriter(); try { writeNative(string_writer); } catch(IOException e) { throw new Error("internal error - unexpected exception: " + e); } return string_writer.toString() ; } /** * Return a Reader object that gives an EMBL format version of this * Feature when read. **/ public Reader toReader() { return new StringReader(toString()); } /** * Return true if and only if this Feature is on the forward strand. **/ public boolean isForwardFeature() { if(getLocation().isComplement()) return false; else return true; } /** * Return the key of this Feature. **/ public Key getKey() { return getEmblFeature().getKey(); } /** * Return true if and only if the key of this feature is protein feature - * one that should be displayed on the translation lines of the * FeatureDisplay component rather than on the forward or backward strand. **/ public boolean isProteinFeature() { if(getKey().toString().startsWith("CDS") || getKey().equals(DatabaseDocument.EXONMODEL) || getKey().equals("exon") || getKey().equals("BLASTCDS") || getKey().equals("polypeptide")) return true; else return false; } /** * Return true if and only if the key of this feature is CDS feature. **/ public boolean isCDS() { if(getKey().equals("CDS")) return true; else return false; } /** * Return true if and only if the key of this feature is CDS feature and * the feature has a /partial qualifier. **/ private boolean isPartialCDS() { try { if(getKey().equals("CDS") && getQualifierByName("partial") != null) return true; else return false; } catch(InvalidRelationException e) { throw new Error("internal error - unexpected exception: " + e); } } /** * Return the Location of this Feature. **/ public Location getLocation() { final Location current_location = getEmblFeature().getLocation(); return current_location; } /** * Return a Vector containing the qualifiers of this Feature. * XXX - FIXME - should return a copy or be private **/ public QualifierVector getQualifiers() { // return the embl.QualifierVector from the underlying embl.Feature object return getEmblFeature().getQualifiers(); } /** * Return the Entry that owns this object. **/ public Entry getEntry() { return entry; } /** * Return the value of the first /codon_start qualifier as an integer or * returns 1 if codon_start doesn't exist or if it makes no sense. **/ public int getCodonStart() { try { final String codon_start_string = getValueOfQualifier("codon_start"); if(codon_start_string == null) { // default value return 1; } if(codon_start_string.equals("2")) return 2; else { if(codon_start_string.equals("3")) return 3; else return 1; // default value } } catch(InvalidRelationException e) { return 1; } } /** * Return the value of the first /score qualifier of this feature as an * integer. * @return The score or -1 if the feature has no /score qualifier. **/ public int getScore() { try { final String score_string = getValueOfQualifier("score"); if(score_string == null) { // default value return -1; } try { final int score_int = Float.valueOf(score_string).intValue(); if(score_int > 100) return 100; if(score_int < 0) return 0; return score_int; } catch(NumberFormatException e) { // assume there is no /score return -1; } } catch(InvalidRelationException e) { throw new Error("internal error - unexpected exception: " + e); } } /** * Set the Entry that owns this object. **/ public void setEntry(final Entry entry) { if(this.entry != null) stopListening(); final Entry old_entry = this.entry; this.entry = entry; if(old_entry == entry) return; else { if(old_entry != null) removeFeatureChangeListener(old_entry); if(entry != null) { // the Entry object acts as a proxy for FeatureChange events, other // objects can can addFeatureChangeListener() once on the Entry object // instead of calling it for every Feature. addFeatureChangeListener(getEntry()); } } if(this.entry != null) startListening(); } /** * Change this Feature and it's underlying embl Feature object to the given * key, location and qualifiers. * @exception InvalidRelationException Thrown if this Feature cannot contain * the given Qualifier. * @exception OutOfRangeException Thrown if the location is out of * range for this Entry. * @exception ReadOnlyException Thrown if this Feature cannot be changed. **/ public void set(Key new_key, Location new_location, QualifierVector new_qualifiers) throws EntryInformationException, OutOfRangeException, ReadOnlyException { try { set((Date)null, new_key, new_location, new_qualifiers); } catch(OutOfDateException e) { throw new Error("internal error - unexpected exception: " + e); } } /** * Change this Feature and it's underlying embl Feature object to the given * key, location and qualifiers. * @exception InvalidRelationException Thrown if this Feature cannot contain * the given Qualifier. * @exception OutOfRangeException Thrown if the location is out of * range for this Entry. * @exception ReadOnlyException Thrown if this Feature cannot be changed. * @exception OutOfDateException If the key has changed in the server since * the time given by datestamp. If datestamp argument is null then this * exception will never be thrown. **/ public void set(final Date datestamp, final Key new_key, final Location new_location, final QualifierVector new_qualifiers) throws EntryInformationException, OutOfRangeException, ReadOnlyException, OutOfDateException { final Key old_key = getKey(); old_location = getLocation(); final QualifierVector old_qualifiers = getQualifiers().copy(); final int sequence_length; if(!(getEntry().getBases().getSequence() instanceof PartialSequence)) { sequence_length = getEntry().getBases().getLength(); if(new_location != null) { final Range span = new_location.getTotalRange(); if(span.getEnd() > sequence_length || span.getStart() < 1) { throw new OutOfRangeException(new_location.toString()); } } } if(datestamp == null || !(getEmblFeature() instanceof DateStampFeature)) { getEmblFeature().set(new_key, new_location, new_qualifiers); } else { ((DateStampFeature)getEmblFeature()).set(datestamp, new_key, new_location, new_qualifiers); } resetCache(); if(new_location != old_location && segments != null) reexamineSegments(); // now inform the listeners that a change has occured final FeatureChangeEvent event = new FeatureChangeEvent(this, this, old_key, old_location, old_qualifiers, FeatureChangeEvent.ALL_CHANGED); fireAction(feature_listener_list, event); } /** * This method will send a FeatureChangeEvent with type LOCATION_CHANGED to * all the FeatureChangeEvent listeners when a marker changes. It also * calls resetCache(), because changing the location will change the * translation and bases of the feature. **/ private void locationChanged(final Location old_location, final QualifierVector qualifiers, int type) { resetCache(); // now inform the listeners that a change has occured final FeatureChangeEvent feature_change_event = new FeatureChangeEvent(this, this, null, old_location, qualifiers, type); this.old_location = getLocation(); fireAction(feature_listener_list, feature_change_event); } private void locationChanged(final Location old_location) { locationChanged(old_location, null, FeatureChangeEvent.LOCATION_CHANGED); } /** * Add the values from the given qualifier to the Qualifier object with the * same name in this Feature or if there is no Qualifier with that name * just add a copy of the argument. * @param qualifier This object contians name and values to add. * @return The Qualifier that was changed or created. **/ public Qualifier addQualifierValues(Qualifier qualifier) throws EntryInformationException, ReadOnlyException { final QualifierVector old_qualifiers = getQualifiers().copy(); final Qualifier return_qualifier; final Qualifier current_qualifier = getEmblFeature().getQualifierByName(qualifier.getName()); if(current_qualifier == null) return_qualifier = qualifier.copy(); else { return_qualifier = current_qualifier.copy(); return_qualifier.addValues(qualifier.getValues()); } setQualifier(return_qualifier); // now inform the listeners that a change has occured // THIS IS ALREADY DONE IN setQualifier() /* final FeatureChangeEvent event = new FeatureChangeEvent(qualifier, this, null, null, old_qualifiers, FeatureChangeEvent.QUALIFIER_CHANGED); fireAction(feature_listener_list, event); */ return return_qualifier; } /** * Add the given Qualifier to this Feature, replacing any exisiting * Qualifier that have the same name. * @param qualifier The Qualifier to add. **/ public void setQualifier(final Qualifier qualifier) throws EntryInformationException, ReadOnlyException { final QualifierVector old_qualifiers = getQualifiers().copy(); getEmblFeature().setQualifier(qualifier); // now inform the listeners that a change has occured final FeatureChangeEvent event = new FeatureChangeEvent(qualifier, this, null, null, old_qualifiers, FeatureChangeEvent.QUALIFIER_CHANGED); if(qualifier.getName().equals("transl_except")) { // discard cache amino_acids = null; } fireAction(feature_listener_list, event); } /** * Remove the Qualifier with the given name. If there is no Qualifier with * that name then return immediately. * @param name The qualifier name to look for. **/ public void removeQualifierByName(final String name) throws EntryInformationException, ReadOnlyException, OutOfDateException { if(getEmblFeature().getQualifierByName(name) == null) { // nothing to remove return; } final QualifierVector old_qualifiers = getQualifiers().copy(); getEmblFeature().removeQualifierByName(name); // now inform the listeners that a change has occured final FeatureChangeEvent event = new FeatureChangeEvent(this, this, null, null, old_qualifiers, FeatureChangeEvent.QUALIFIER_CHANGED); if(name.equals("transl_except")) { // discard cache amino_acids = null; } fireAction(feature_listener_list, event); } /** * Return the time when this feature last changed or null if this Feature * doesn't support datestamps. **/ public Date getDatestamp() { if(getEmblFeature() instanceof DateStampFeature) return ((DateStampFeature)getEmblFeature()).getDatestamp(); else return null; } /** * Return true if and only if any qualifier in this feature contains the * given text string. * @param search_text The text to search for. * @param fold_case If true then the text comparisons will ignore case. * @param match_substring If true then matches to substrings are allowed. * @param qualifier_names If null search all qualifiers, otherwise just * search these names, **/ public boolean containsText(final String search_text, final boolean fold_case, final boolean match_substring, final StringVector qualifier_names) { return findOrReplaceText(search_text, fold_case, match_substring, false, qualifier_names, null); } /** * Return true if and only if any qualifier in this feature contains the * given text string. * @param search_text The text to search for. * @param fold_case If true then the text comparisons will ignore case. * @param match_substring If true then matches to substrings are allowed. * @param qualifier_names If null search all qualifiers, otherwise just * search these names, * @param replaceText text to replace all qualifier value matches. If null * then returns true if text found. **/ public boolean findOrReplaceText(final String search_text, final boolean fold_case, final boolean match_substring, final boolean deleteQualifier, final StringVector qualifier_names, final String replaceText) { final String real_search_text; if(fold_case) real_search_text = search_text.toLowerCase(); else real_search_text = search_text; final QualifierVector qualifiers = getQualifiers(); QualifierVector newQualifiers = null; Vector qualifiersToDelete = null; int qual_size = qualifiers.size(); boolean hasReplacedOrDeletedText = false; for(int i = 0; i < qual_size; ++i) { final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(i); if(qualifier_names != null && !qualifier_names.contains(this_qualifier.getName())) continue; final StringVector values = this_qualifier.getValues(); if(values != null) { StringVector newValues = null; StringVector deleteValues = null; int val_size = values.size(); for(int values_index = 0; values_index < val_size; ++values_index) { String this_value_string = (String)values.elementAt(values_index); if(this_value_string == null) continue; if(fold_case) this_value_string = this_value_string.toLowerCase(); if(! match_substring && this_value_string.equals(real_search_text) || match_substring && this_value_string.indexOf(real_search_text) != -1) { if(deleteQualifier) { if(deleteValues == null) deleteValues = new StringVector(); deleteValues.add(this_value_string); continue; } // found the match & return true if replace function off if(replaceText == null) return true; String new_text = replaceText; if(match_substring) { final String value_string = (String)values.elementAt(values_index); new_text = Pattern.compile(real_search_text, Pattern.CASE_INSENSITIVE) .matcher(value_string).replaceAll(replaceText); } if(newValues == null) newValues = this_qualifier.getValues(); newValues.setElementAt(new_text, values_index); } } if(newValues != null) { if(newQualifiers == null) newQualifiers = new QualifierVector(); newQualifiers.setQualifier( new Qualifier(this_qualifier.getName(),newValues)); hasReplacedOrDeletedText = true; } if(deleteQualifier && deleteValues != null) { newValues = this_qualifier.getValues(); newValues.removeAll(deleteValues); if(newValues.size() < 1) { if(qualifiersToDelete == null) qualifiersToDelete = new Vector(); qualifiersToDelete.add(this_qualifier.getName()); } else { newQualifiers = new QualifierVector(); newQualifiers.setQualifier( new Qualifier(this_qualifier.getName(),newValues)); } hasReplacedOrDeletedText = true; } } } // delete qualifiers if(qualifiersToDelete != null) { try { for (int i = 0; i < qualifiersToDelete.size(); i++) removeQualifierByName(qualifiersToDelete.get(i)); } catch (Exception e) { e.printStackTrace(); } } if(newQualifiers != null) { for(int i=0; i end_of_segment_position - 2 || (!trim_to_next && 1.0 * (current_marker_position - start_of_feature_position) / getTranslationBasesLength() > 0.3)) { // the current_marker is past the end of the first segment or is // more than 30% into the feature return false; } } catch(OutOfRangeException e) { return false; } } try { first_segment.setStartPosition(current_marker.getPosition()); } catch(OutOfRangeException e) { throw new Error("internal error - unexpected exception: " + e); } try { // remove /codon_start (if it exists) since the new location will be in // the correct frame removeQualifierByName("codon_start"); } catch(OutOfDateException ode) { // do nothing } catch(EntryInformationException eie) { // do nothing } updateEMBLFeatureLocation(); locationChanged(old_location); return true; } /** * Return the label (the value of the first /label qualifier) of this * Feature, but return null if there is no label or if the label is "*". **/ public String getLabel() { try { return getValueOfQualifier("label"); } catch(InvalidRelationException e) { return null; } } /** * Return the gene name (the value of the first /gene qualifier) of this * Feature or null if there it has no gene name. **/ public String getGeneName() { try { return getValueOfQualifier("gene"); } catch(InvalidRelationException e) { return null; } } /** * Return the name of this Feature to use in the display. The search order * is /primary_name, /synonym, /systematic_id, /temporary_systematic_id, * /gene, /locus_tag, /label. If none of the qualifiers exists return * the feature key of this feature. **/ public String getIDString() { final String picked_name = pickName(Options.getOptions().getDisplayQualifierNames()); if(picked_name == null) return getKey().toString(); else return picked_name; } /** * Return the systematic name of this feature. **/ public String getSystematicName() { final String picked_name = pickName(Options.getOptions().getSystematicQualifierNames()); if(picked_name == null) return getIDString(); else return picked_name; } /** * Look at the qualifier_names one-by-one and return the first value of the * first qualifier found. **/ private String pickName(final StringVector qualifier_names) { int qn_size = qualifier_names.size(); for(int i = 0; i < qn_size; ++i) { try { final Qualifier qualifier = getQualifierByName((String)qualifier_names.elementAt(i)); if(qualifier != null) { final StringVector values = qualifier.getValues(); if(values != null && values.size() > 0) { for(int j=0; j= 0) { final String location_part = value.substring(START_STRING.length(), comma_pos); final String aa_part = value.substring(comma_pos + COMMA_STRING.length(), value.length() - 1); char aa_part_one_letter_code = AminoAcidSequence.getOneLetterCode(aa_part); // aa_part is probably "OTHER" if(aa_part_one_letter_code == (char)-1) aa_part_one_letter_code = '.'; final Location location = new Location(location_part); int start_base_in_feature; if(isForwardFeature()) { start_base_in_feature = location.getFirstBase() - getRawFirstBase() - (getCodonStart() - 1); } else { start_base_in_feature = getRawLastBase() - location.getLastBase() - (getCodonStart() - 1); } if(start_base_in_feature >= 0 && start_base_in_feature < getBaseCount() - getCodonStart() + 1) { final int start_aa_in_feature = start_base_in_feature / 3; new_amino_acids_string = new_amino_acids_string.substring(0, start_aa_in_feature) + aa_part_one_letter_code + new_amino_acids_string.substring(start_aa_in_feature + 1); } } } } } } } catch(InvalidRelationException e) {} catch(LocationParseException e) {} if(new_amino_acids_string == amino_acids_string) return null; else return new AminoAcidSequence(new_amino_acids_string); } /** * Return the translation of the bases in this feature. **/ public AminoAcidSequence getTranslation() { if(amino_acids == null) { amino_acids = AminoAcidSequence.getTranslation(getTranslationBases(), true); // a very short feature if(amino_acids.length() == 0) return amino_acids; final AminoAcidSequence fixed_amino_acids = fixTranslationExceptions(); if(fixed_amino_acids != null) amino_acids = fixed_amino_acids; if(isCDS() && !isPartialCDS() && hasValidStartCodon()) { if(amino_acids.elementAt(0) != 'm') { // translation should always start with M final String amino_acids_string = amino_acids.toString(); final String new_amino_acids_string = 'M' + amino_acids_string.substring(1); amino_acids = new AminoAcidSequence(new_amino_acids_string); } } } return amino_acids; } /** * Return the number of amino acids in this feature (total of all segments). **/ public int getAACount() { if(aa_count == -1) aa_count = getTranslationBasesLength() / 3; return aa_count; } /** * Return the total molecular weight of the amino acid translation of the * bases of this feature. **/ public float getMolecularWeight() { return getTranslation().getMolecularWeight(); } /** * Return the number of occurrences of the given codon in the frame * starting at the correct frame for this feature (see the * getTranslationBases() method). The first index is the first base, the * second index is the second base, etc. The indices are the same as those * for Bases.letter_index. An example: calling getCodonCount(0, 0, 1) * will return the count of the codon with the sequence t,t,c. **/ public int getCodonCount(final int first, final int second, final int third) { if(codon_counts == null) setArrays(); return codon_counts[first][second][third]; } /** * Return a count of the occurrences of each amino acids in the translation * of the bases of the current feature. The index should be the same as * the index that is returned by AminoAcidSequence.getSymbolIndex(). **/ public int getResidueCount(final int amino_acid_index) { if(residue_counts == null) setArrays(); return residue_counts[amino_acid_index]; } /** * Return the count of each base that appears in each codon position (1, 2 * or 3). The first index is the codon position and the second is the base * (indexed in the same way as Bases.letter_index). (set by resetCache ()). **/ public int getPositionalBaseCount(final int codon_base_position, final int base_index) { if(positional_base_counts == null) setArrays(); return positional_base_counts[codon_base_position][base_index]; } /** * Return the count each base in the feature. The indices are the same as * those for Bases.letter_index. (set by resetCache()). **/ public int getBaseCount(final int base_index) { if(base_counts == null) setArrays(); return base_counts[base_index]; } /** * Return the codon position 1 and 2 correlation score for the bases of * this feature. **/ public double get12CorrelationScore() { final int t1_count = getPositionalBaseCount(0, Bases.getIndexOfBase('t')); final int c1_count = getPositionalBaseCount(0, Bases.getIndexOfBase('c')); final int a1_count = getPositionalBaseCount(0, Bases.getIndexOfBase('a')); final int g1_count = getPositionalBaseCount(0, Bases.getIndexOfBase('g')); final int t2_count = getPositionalBaseCount(1, Bases.getIndexOfBase('t')); final int c2_count = getPositionalBaseCount(1, Bases.getIndexOfBase('c')); final int a2_count = getPositionalBaseCount(1, Bases.getIndexOfBase('a')); final int g2_count = getPositionalBaseCount(1, Bases.getIndexOfBase('g')); final int c3_count = getPositionalBaseCount(2, Bases.getIndexOfBase('c')); final int g3_count = getPositionalBaseCount(2, Bases.getIndexOfBase('g')); final int base_total = getTranslationBases().length(); final double cor1_2_score = 3.0 * t1_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_1[0] + 3.0 * c1_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_1[1] + 3.0 * a1_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_1[2] + 3.0 * g1_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_1[3] + 3.0 * t2_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_2[0] + 3.0 * c2_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_2[1] + 3.0 * a2_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_2[2] + 3.0 * g2_count/base_total * Codon12CorrelationAlgorithm.correlation_score_factors_2[3] + 0.5; // add 0.5 because that is what the old uk.ac.sanger.artemis did return cor1_2_score; } /** * Return the percentage GC content of the bases of this feature. **/ public double getPercentGC() { final String bases = getBases(); if(bases.length() > 0) { int gc_count = 0; final char[] sequence_chars = new char[bases.length()]; bases.getChars(0, bases.length(), sequence_chars, 0); for(int i = 0 ; i < bases.length() ; ++i) { final char this_char = sequence_chars[i]; if(this_char == 'g' || this_char == 'c') ++gc_count; } return 100.0 * gc_count / bases.length(); } else return 0.0; } /** * Return the first (lowest) base number of this Feature. The lowest base * of the Feature is the minimum (with respect to the Feature's Strand) of * the lowest bases of all the segments of this Feature. * @return The first base of this feature or 0 if this feature doesn't know * where it is. **/ public int getFirstBase() { final Marker first_base_marker = getFirstBaseMarker(); if(first_base_marker == null) return 0; else return first_base_marker.getPosition (); } /** * Return the last (highest) base number of this Feature. The highest base * of the Feature is the maximum (with respect to the Feature's Strand) of * the highest bases of all the segments of this Feature. * @return The last base of this feature or 0 if this feature doesn't know * where it is. **/ public int getLastBase() { final Marker last_base_marker = getLastBaseMarker(); if(last_base_marker == null) return 0; else return last_base_marker.getPosition (); } /** * Return true if and only if the first base of this Feature is less than * the argument Feature with respect to the Feature's Strand. **/ public boolean lessThan(Feature other_feature) { if(getFirstBase() < other_feature.getFirstBase()) return true; else return false; } /** * Return true if and only if the first base of this Feature is greater * than the argument Feature with respect to the Feature's Strand. **/ public boolean greaterThan(Feature other_feature) { if(getFirstBase() > other_feature.getFirstBase()) { return true; } else { return false; } } /** * Return the first (lowest) base number of this Feature. The lowest base * of the Feature is the minimum (with respect to the raw sequence) of the * lowest bases of all the segments of this Feature. * @return The first base of this feature or 0 if this feature doesn't know * where it is. **/ public int getRawFirstBase() { final int A_BIG_NUMBER = 0x7fffffff; // largest integer int minimum = A_BIG_NUMBER; for(int i = 0; i < getSegments().size(); ++i) { final int current_minimum; if(isForwardFeature()) { current_minimum = getSegments().elementAt(i).getStart().getRawPosition(); } else { current_minimum = getSegments().elementAt(i).getEnd().getRawPosition(); } if(current_minimum < minimum) minimum = current_minimum; } if(minimum == A_BIG_NUMBER) return 0; else return minimum; } /** * Return the last (highest) base number of this Feature. The highest base * of the Feature is the maximum (with respect to the raw sequence) of the * highest bases of all the segments of this Feature. * @return The last base of this feature or 0 if this feature doesn't know * where it is. **/ public int getRawLastBase() { final int A_SMALL_NUMBER = -1; int maximum = A_SMALL_NUMBER; int seg_size = getSegments().size(); for(int i = 0; i < seg_size; ++i) { final int current_maximum; if(isForwardFeature()) { current_maximum = getSegments().elementAt(i).getEnd().getRawPosition(); } else { current_maximum = getSegments().elementAt(i).getStart().getRawPosition(); } if(current_maximum > maximum) maximum = current_maximum; } if(maximum == A_SMALL_NUMBER) return 0; else return maximum; } /** * Return the maximum extent of this feature. The range returned starts at * getRawFirstBase() and ends at getRawLastBase(). **/ public Range getMaxRawRange() { try { return new Range(getRawFirstBase(), getRawLastBase()); } catch(OutOfRangeException e) { throw new Error("internal error - unexpected exception: " + e); } } /** * Return true if and only if the first base of this Feature is less than * the argument Feature with to the raw sequence. **/ public boolean rawLessThan(Feature other_feature) { if(getRawFirstBase() < other_feature.getRawFirstBase()) return true; else return false; } /** * Return true if and only if the first base of this Feature is greater * than the argument Feature with to the raw sequence. **/ public boolean rawGreaterThan(Feature other_feature) { if(getRawFirstBase() > other_feature.getRawFirstBase()) return true; else return false; } /** * Return the Marker of the first (lowest) base number of this Feature. * The lowest base of the Feature is the minimum of the lowest bases of all * the segments of this Feature. * @return The Marker of the first base of this feature or null if this * feature doesn't know where it is. **/ public Marker getFirstBaseMarker() { final int A_BIG_NUMBER = 0x7fffffff; // largest integer int minimum = A_BIG_NUMBER; Marker minimum_marker = null; for(int i = 0 ; i < getSegments().size() ; ++i) { final Marker current_marker = getSegments().elementAt(i).getStart(); final int current_minimum = current_marker.getPosition(); if(current_minimum < minimum) { minimum = current_minimum; minimum_marker = current_marker; } } return minimum_marker; } /** * Return the Marker of the last (highest) base number of this Feature. * The highest base of the Feature is the maximum of the highest bases of * all the segments of this Feature. * @return The Marker of the last base of this feature or null if this * feature doesn't know where it is. **/ public Marker getLastBaseMarker() { final long A_SMALL_NUMBER = -1; long maximum = A_SMALL_NUMBER; Marker maximum_marker = null; for(int i = 0; i < getSegments().size(); ++i) { final Marker current_marker = getSegments().elementAt(i).getEnd(); final int current_maximum = current_marker.getPosition(); if(current_maximum > maximum) { maximum = current_maximum; maximum_marker = current_marker; } } return maximum_marker; } /** * Return the Marker of the first base of the first (coding) codon of this * feature. This is the minimum of the lowest bases of all the segments of * this Feature plus codon_start minus one. * @return The Marker of the first coding base of this feature or null if * this feature doesn't know where it is. **/ public Marker getFirstCodingBaseMarker() { final Marker first_base_marker = getFirstBaseMarker(); try { return first_base_marker.moveBy(getCodonStart() - 1); } catch(OutOfRangeException e) { // if we are at the end of the sequence then just return the first base // marker return first_base_marker; } } /** * Given a base position in this feature, return the Marker of the * corresponding base on the Strand object. * @param position The position within the feature - not counting introns. * @exception OutOfRangeException Thrown if the new position is less than 1 * or greater than the length of the sequence. **/ public Marker getPositionInSequence (final int position) throws OutOfRangeException { // the position is outside of the feature if(position < 1) throw new OutOfRangeException("position: " + position); // subtract one because positions are numbered from 1 not 0 int bases_remaining = position - 1; for(int i = 0; i < segments.size(); ++i) { final FeatureSegment this_segment = segments.elementAt(i); if(bases_remaining < this_segment.getBaseCount()) return this_segment.getStart().moveBy(bases_remaining); else bases_remaining -= this_segment.getBaseCount(); } // the position is outside of the feature throw new OutOfRangeException("position: " + position); } /** * Return the (base) position in this feature of the given Marker or -1 if * the given Marker does not correspond to any bases in this Feature. **/ public int getFeaturePositionFromMarker(final Marker base_marker) { // counts the bases in the previous segments as we loop over them int bases_so_far = 0; final int marker_position = base_marker.getPosition(); int seg_size = segments.size(); for(int i = 0; i < seg_size; ++i) { final FeatureSegment this_segment = segments.elementAt(i); final int this_segment_start = this_segment.getStart().getPosition(); final int marker_start_diff = marker_position - this_segment_start; if(marker_start_diff >= 0 && marker_start_diff < this_segment.getBaseCount()) { return bases_so_far + marker_start_diff; } bases_so_far += this_segment.getBaseCount(); } return -1; } public void resetColour() { colour = null; } /** * Return the colour (the value of the /colour qualifier) of this Feature * or null if there is no colour qualifier and no default colour. **/ public Color getColour() { if(colour != null) return colour; String colour_qualifier; try { colour_qualifier = getValueOfQualifier("colour"); // it's international "be nice to Americans day": if(colour_qualifier == null) colour_qualifier = getValueOfQualifier("color"); } catch(InvalidRelationException e) { colour_qualifier = null; } // use default colour for this type of feature if(colour_qualifier == null) { colour = Options.getOptions().getDefaultFeatureColour(getKey()); return colour; } final StringVector colours = StringVector.getStrings(colour_qualifier); if(colours.size() < 1) { colour = Options.getOptions().getDefaultFeatureColour(getKey()); return colour; } try { if(colours.size() == 3) { int red = Integer.parseInt((String)colours.elementAt(0)); int green = Integer.parseInt((String)colours.elementAt(1)); int blue = Integer.parseInt((String)colours.elementAt(2)); if(red < 0) red = 0; if(red > 255) red = 255; if(green < 0) green = 0; if(green > 255) green = 255; if(blue < 0) blue = 0; if(blue > 255) blue = 255; colour = new Color(red, green, blue); return colour; } else { final String colour_string = (String)colours.elementAt(0); final int colour_number; colour_number = Integer.parseInt(colour_string); colour = Options.getOptions().getColorFromColourNumber(colour_number); return colour; } } catch(NumberFormatException e) { // use default colour for this type of feature colour = Options.getOptions().getDefaultFeatureColour(getKey()); return colour; } } /** * Return a vector of String objects containing all the values of the * qualifier with the given name. If the Feature has two qualifiers: * /db_xref="PID:e1256466" /db_xref="SPTREMBL:O43016" * then this method will return a vector containing "PID:e1256466" and * "SPTREMBL:O43016". * Returns null if the is no Qualifier with the given name. **/ public StringVector getValuesOfQualifier(String qualifier_name) throws InvalidRelationException { final Qualifier this_qualifier = getQualifierByName(qualifier_name); if(this_qualifier == null) return null; else return this_qualifier.getValues(); } /** * Return the Qualifier in this Feature with the given name or null if * there no such Qualifier. * @param name The qualifier name to look for. **/ public Qualifier getQualifierByName(final String name) throws InvalidRelationException { return getEmblFeature().getQualifierByName(name); } /** * Return the value of the first occurrence of the given qualifier in this * Feature or null if this feature doesn't have this qualifier. If this * feature has the given qualifier but the qualifier doesn't have a value * then it will return "" - a zero length String. * @param qualifier_name The name of the qualifier to search for. **/ public String getValueOfQualifier(String qualifier_name) throws InvalidRelationException { final StringVector values = getValuesOfQualifier(qualifier_name); if(values == null) return null; else { if(values.size() == 0) return ""; else { final String first_element = (String)values.elementAt(0); if(first_element == null) return ""; else return first_element; } } } /** * Remove this Feature from the Entry object that contains it. It will * also be removed from the underlying embl.Entry object. **/ public void removeFromEntry() throws ReadOnlyException { getEntry().remove(this); } /** * Return the vector containing the references of the FeatureSegment * objects of this Feature. **/ public FeatureSegmentVector getSegments() { final Location current_location = getEmblFeature().getLocation(); if(segments == null) createSegments(); else { // see comment on old_location if(current_location != old_location) { reexamineSegments(); if(!old_location.equals(current_location)) { locationChanged(old_location); old_location = current_location; } } } return segments; } /** * Examine each Range in the Location of this Feature. Remove * FeatureSegments that don't correspond to Ranges in the Location. Add * a FeatureSegment for each extra Range that has appeared. This is * necessary because the feature location may change at any time in a * client/server environment. **/ private void reexamineSegments() { stopSegmentsListening(); final Location current_location = getEmblFeature().getLocation(); final RangeVector ranges = current_location.getRanges(); final Vector new_segments = new Vector(); int ranges_size = ranges.size(); new_segments.setSize(ranges_size); FeatureSegmentVector old_segments = (FeatureSegmentVector)segments.clone(); // if the feature has changed strand, give up and recreate all // segments if(current_location.isComplement() != old_location.isComplement()) old_segments.removeAllElements(); // first find exact matches EXACT_SEGMENTS: for(int old_segment_index = old_segments.size() - 1; old_segment_index >= 0; --old_segment_index) { final FeatureSegment old_segment = old_segments.elementAt(old_segment_index); for(int range_index = 0; range_index < ranges_size; ++range_index) { final Range new_range = (Range)ranges.elementAt(range_index); // there is a Range in the new Location that exactly matches a Range // in an old FeatureSegment so reuse the old FeatureSegment if(old_segment.getRawRange().equals(new_range)) { old_segment.setRange(new_range); new_segments.setElementAt(old_segment, range_index); old_segments.removeElementAt(old_segment_index); continue EXACT_SEGMENTS; } } } // find matches where the start or end has changed CHANGED_END: for(int old_segment_index = old_segments.size() - 1; old_segment_index >= 0; --old_segment_index) { final FeatureSegment old_segment = old_segments.elementAt(old_segment_index); for(int range_index = 0; range_index < ranges_size; ++range_index) { final Range new_range = (Range)ranges.elementAt(range_index); if(old_segment.getRawRange().getStart() == new_range.getStart() || old_segment.getRawRange().getEnd() == new_range.getEnd()) { // there is a Range in the new Location that partly matches a Range // in an old FeatureSegment so reuse the old FeatureSegment old_segment.setRange(new_range); new_segments.setElementAt(old_segment, range_index); old_segments.removeElementAt(old_segment_index); continue CHANGED_END; } } } // create a segment for each range that we don't have a segment for for(int new_segment_index = 0; new_segment_index < ranges_size; ++new_segment_index) { final Range missing_range = (Range)ranges.elementAt(new_segment_index); if(new_segments.elementAt(new_segment_index) == null) { // new Range -> create a segment new_segments.setElementAt(makeSegment(missing_range), new_segment_index); } } segments = new FeatureSegmentVector(); for(int i = 0; i < ranges.size(); ++i) segments.addElementAtEnd((FeatureSegment) new_segments.elementAt(i)); startSegmentsListening(); } /** * Return the reference of the Strand that this Feature and it's * FeatureSegment objects is associated with. **/ public Strand getStrand() { // get the strand of the first entry in the entry group if(isForwardFeature()) return entry.getBases().getForwardStrand(); else return entry.getBases().getReverseStrand(); } /** * Return the reference of a new copy of this Feature. This method will * update the underlying embl.Entry and the new Feature will be installed * in the same Entry object as this one. * @return The reference of the new feature. **/ public Feature duplicate() throws ReadOnlyException { return duplicate(false); } /** * Return the reference of a new copy of this Feature. This method will * update the underlying embl.Entry and the new Feature will be installed * in the same Entry object as this one. * @param isDuplicatedInChado if true then create new ID and update in chado * @return * @throws ReadOnlyException */ public Feature duplicate(final boolean isDuplicatedInChado) throws ReadOnlyException { uk.ac.sanger.artemis.io.Feature new_embl_feature; if(getEmblFeature() instanceof GFFStreamFeature) new_embl_feature = new GFFStreamFeature(getEmblFeature(), isDuplicatedInChado); else new_embl_feature = new EmblStreamFeature(getEmblFeature()); final Feature return_feature = new Feature(new_embl_feature); try { getEntry().add(return_feature, !isDuplicatedInChado, false); } catch(EntryInformationException e) { throw new Error("internal error - unexpected exception: " + e); } catch(OutOfRangeException e) { throw new Error("internal error - unexpected exception: " + e); } return return_feature; } /** * Move this Feature to the given Entry. The move is achieved by removing * the feature from it's current Entry and adding to the the * destination_entry. * @param destination_entry The Feature will be moved to this Entry. * @param force If true then invalid qualifiers will be quietly thrown away * when saving. Features with invalid keys won't be moved. "Invalid" * means that the key/qualifier is not allowed to occur in the * destination type (probably determined by the default EntryInformation * object of the destination type). If false an * EntryInformationException will be thrown for invalid keys or * qualifiers. * @exception EntryInformationException Thrown if force is false and if the * destination type cannot contain the Key, Qualifier or Key/Qualifier * combination of the given feature. **/ public void moveTo(final Entry destination_entry, final boolean force) throws EntryInformationException, OutOfRangeException, ReadOnlyException { if(destination_entry.isReadOnly()) throw new ReadOnlyException(); else { // this (hack) causes the calls to stopListening() and startListening() // in setEntry() to return immediately which improves the speed a lot // when many features are loaded startListening(); try { final Entry old_entry = getEntry(); getEntry().remove(this); try { destination_entry.add(this, force); } catch(EntryInformationException e) { // put the feature back where it was old_entry.add(this, true); // re-throw throw e; } } finally { // see note above on startListening() stopListening(); } } } /** * Make a copy of the this Feature and add it to the given Entry. * @return The reference of the new feature. * @exception EntryInformationException Thrown if the destination Entry * cannot contain a Feature with this Key, Qualifier or Key/Qualifier * combination. **/ public Feature copyTo(final Entry destination_entry) throws EntryInformationException, OutOfRangeException, ReadOnlyException { if(destination_entry.isReadOnly()) throw new ReadOnlyException(); uk.ac.sanger.artemis.io.Feature new_embl_feature = new EmblStreamFeature(getEmblFeature()); final Feature return_feature = new Feature(new_embl_feature); destination_entry.add(return_feature, false); return return_feature; } /** * Adds the specified event listener to receive feature change events from * this object. * @param l the change event listener. **/ public void addFeatureChangeListener(FeatureChangeListener l) { feature_listener_list.addElement(l); } /** * Removes the specified event listener so that it no longer receives * feature change events from this object. * @param l the change event listener. **/ public void removeFeatureChangeListener(FeatureChangeListener l) { feature_listener_list.removeElement(l); } /** * Returns true if and only if this feature is read only or is in a read * only entry. **/ public boolean isReadOnly() { return getEmblFeature().isReadOnly(); } /** * This method examines each FeatureSegment and uses that information to * set the location of the underlying embl.Feature object. This must be * called any time a segment position changes, or when there is a change to * the sequence (the sequence as represented by the Strand and Bases * objects). This method does not fire off any FeatureChange events. **/ private void updateEMBLFeatureLocation() throws ReadOnlyException { final boolean complement = getLocation().isComplement(); final RangeVector ranges = new RangeVector(); for(int i = 0; i < segments.size(); ++i) ranges.addElement(segments.elementAt (i).getRawRange ()); try { final Location new_location = new Location(ranges, complement); getEmblFeature().setLocation(new_location); old_location = new_location; } catch(OutOfRangeException e) { throw new Error("internal error - inconsistent location information: " + e.getMessage()); } } /** * Send an event to those object listening for it. * @param listeners A Vector of the objects that the event should be sent * to. * @param event The event to send **/ private void fireAction(Vector listeners, ChangeEvent event) { final Vector targets; // copied from a book - synchronising the whole method might cause a // deadlock synchronized(this) { targets = (Vector)listeners.clone(); } for(int i = 0 ; i < targets.size() ; ++i) { ChangeListener target = (ChangeListener)targets.elementAt(i); final FeatureChangeListener feature_change_listener = (FeatureChangeListener)target; feature_change_listener.featureChanged((FeatureChangeEvent) event); } } /** * Reset these arrays: segments, amino_acids, bases, codon_counts, * residue_counts, positional_base_counts and base_counts. **/ private void resetCache() { amino_acids = null; bases = null; codon_counts = null; residue_counts = null; positional_base_counts = null; base_counts = null; aa_count = -1; base_count = -1; } /** * Update the values stored in residue_counts, codon_counts, base_counts * and positional_base_counts. **/ private void setArrays() { final String translation_bases = getTranslationBases(); final AminoAcidSequence translation = getTranslation(); final String translation_string = translation.toString(); codon_counts = new int[4][4][4]; residue_counts = new int[AminoAcidSequence.symbol_count]; base_counts = new int[4]; positional_base_counts = new int[3][4]; // zero the residue_counts array for(int i = 0; i < residue_counts.length; ++i) residue_counts[i] = 0; int trans_len = translation_string.length(); for(int i = 0; i < trans_len; ++i) { final int symbol_index = AminoAcidSequence.getSymbolIndex(translation_string.charAt(i)); ++residue_counts[symbol_index]; } // zero the codon_counts array for(int first = 0 ; first < 4 ; ++first) { for(int second = 0 ; second < 4 ; ++second) { for(int third = 0 ; third < 4 ; ++third) codon_counts[first][second][third] = 0; } } int base_len = translation_bases.length(); for(int base_index = 0; base_index < base_len; ++base_index) { // this is 0, 1, 2 or 3 final int index_of_base = Bases.getIndexOfBase(translation_bases.charAt(base_index)); if(index_of_base < 4) ++base_counts[index_of_base]; } for(int i = 0; i < translation_bases.length() / 3; ++i) { final int first_base_index = Bases.getIndexOfBase(translation_bases.charAt(i * 3)); final int second_base_index = Bases.getIndexOfBase(translation_bases.charAt(i * 3 + 1)); final int third_base_index = Bases.getIndexOfBase(translation_bases.charAt(i * 3 + 2)); if(first_base_index < 4) ++positional_base_counts[0][first_base_index]; if(second_base_index < 4) ++positional_base_counts[1][second_base_index]; if(third_base_index < 4) ++positional_base_counts[2][third_base_index]; if(first_base_index < 4 && second_base_index < 4 && third_base_index < 4) ++codon_counts[first_base_index][second_base_index][third_base_index]; } } /** * Remove all FeatureSegments from all listener lists. **/ private void stopSegmentsListening() { if(segments != null) { for(int i = 0 ; i < segments.size(); ++i) { segments.elementAt(i).stopListening(); segments.elementAt(i).removeMarkerChangeListener(this); } } } /** * Remove this Feature and its FeatureSegments from all listener lists. **/ private void stopListening() { if(listen_count == 1) { if(getEntry() != null && getEntry().getBases() != null) { final Bases bases = getEntry().getBases(); bases.removeSequenceChangeListener(this); } stopSegmentsListening(); Options.getOptions().removeOptionChangeListener(this); } --listen_count; if(listen_count < 0) throw new Error("Feature.listen_count < 0"); } /** * Add all FeatureSegments to all appropriate listener lists. **/ private void startSegmentsListening() { if(segments != null) { int seg_size = segments.size(); for (int i = 0; i < seg_size; ++i) { segments.elementAt(i).startListening(); segments.elementAt(i).addMarkerChangeListener(this); } } } /** * Add this Feature and its FeatureSegments to the all appropriate listener * lists. **/ private void startListening() { if(listen_count == 0) { if(getEntry() != null && getEntry().getBases() != null) { final Bases bases = getEntry().getBases(); startSegmentsListening(); // it doesn't matter what the priority is as long as it is lower than // the FeatureSegment priority, so that all FeatureSegment objects get // updated before we try to update the location of this feature final int PRIORITY = Marker.LISTENER_PRIORITY - 2; bases.addSequenceChangeListener(this, PRIORITY); } Options.getOptions().addOptionChangeListener(this); } ++listen_count; } /** * Create one FeatureSegment object for each segment/exon of this Feature. **/ private void createSegments() { // this call will remove each segment from the MarkerChangeListener list // of the start and end Marker objects of the segment. If we don't do // this the segments will not be garbage collected. stopSegmentsListening(); segments = new FeatureSegmentVector(); final Location location = getLocation(); final RangeVector ranges = location.getRanges(); int ranges_size = ranges.size(); for(int i = 0; i < ranges_size; ++i) { final Range this_range = (Range)ranges.elementAt(i); segments.add(makeSegment(this_range)); } startSegmentsListening(); } /** * Make a single FeatureSegment. * @param range Provides the start and end positions of the segment. **/ private FeatureSegment makeSegment(final Range range) { return new FeatureSegment(this, range); } /** * Set the Location of this feature and reset old_location and * segments. Don't send any FeatureChangeEvents. **/ private void setLocationInternal(final Location new_location) throws ReadOnlyException, OutOfRangeException { getEmblFeature().setLocation(new_location, getEntry().getEMBLEntry()); if(new_location != old_location) reexamineSegments(); old_location = new_location; resetCache(); } /** * Set the Location of this feature and notify any FeatureChangeListeners. **/ public void setLocation(final Location new_location) throws ReadOnlyException, OutOfRangeException { final Location old_location = getLocation(); setLocationInternal(new_location); locationChanged(old_location); } public void addSegment(final Range range) throws ReadOnlyException { final QualifierVector old_qualifiers = getQualifiers().copy(); addSegment(range, old_qualifiers); } /** * Add a FeatureSegment to this Feature. * @param range Provides the start and end positions of the segment. **/ public void addSegment(final Range range, final QualifierVector old_qualifiers) throws ReadOnlyException { final Location old_location = getLocation(); final Location new_location = old_location.addRange(range); try { setLocationInternal(new_location); } catch(OutOfRangeException e) { throw new Error("internal error - inconsistent location " + "information: " + e); } reexamineSegments(); locationChanged(old_location, old_qualifiers, FeatureChangeEvent.SEGMENT_CHANGED); } private Location moveSegments(final int shift) throws OutOfRangeException, ReadOnlyException { Location location = getLocation(); RangeVector ranges = old_location.getRanges(); for(int i=0; i 1) { QualifierVector qualifiers = getQualifiers().copy(); final Location old_location = getLocation(); final Location new_location = old_location.removeRange(segment.getRawRange()); try { setLocationInternal(new_location); } catch(OutOfRangeException e) { throw new Error("internal error - inconsistent location " + "information: " + e); } reexamineSegments(); locationChanged(old_location, qualifiers, FeatureChangeEvent.SEGMENT_CHANGED); } else throw new LastSegmentException(); } /** * Return a list of all the names of the qualifiers in the given features. **/ public static StringVector getAllQualifierNames(final FeatureVector features) { final StringVector qualifier_names = new StringVector(); int feat_size = features.size(); for(int i = 0; i < feat_size; ++i) { final Feature feature = features.elementAt(i); final QualifierVector qualifiers = feature.getQualifiers(); int qualifiers_size = qualifiers.size(); for(int qualifier_index = 0; qualifier_index < qualifiers_size; ++qualifier_index) { final Qualifier this_qualifier = (Qualifier)qualifiers.elementAt(qualifier_index); final String name = this_qualifier.getName(); if(!qualifier_names.contains(name)) qualifier_names.add(name); } } qualifier_names.sort(); return qualifier_names; } /** * Reverse and complement the location of this feature. * @param sequence_length The length of the sequence that this Feature is * associated with. **/ private void reverseComplement(final int sequence_length) throws ReadOnlyException { try { final Location new_location = getLocation().reverseComplement(sequence_length); setLocationInternal(new_location); } catch(OutOfRangeException e) { throw new Error("internal error - inconsistent location: " + e); } } /** * Return the sequence length of the Strand that this feature is attached * to. **/ private int getSequenceLength() { FeatureSegment first_seg = getSegments().elementAt(0); return first_seg.getStart().getStrand().getSequenceLength(); } /** * Set the embl.Feature reference of this Feature to be the given reference. **/ void setEmblFeature(final uk.ac.sanger.artemis.io.Feature new_embl_feature) { embl_feature = new_embl_feature; } }