Newer
Older
/* 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.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.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;
/**
* 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
* @version $Id: Feature.java,v 1.35 2009-02-03 11:36:39 tjc Exp $
**/
public class Feature
implements EntryChangeListener, Selectable, SequenceChangeListener,
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* The Entry that controls/contains this feature. This is the Entry object
* that was passed to the constructor.
**/
private Entry entry;
/**
* This is a reference to the low level Feature object that this class is
* providing a wrapper for. This is the embl.Feature object that was
* passed to constructor.
**/
private uk.ac.sanger.artemis.io.Feature embl_feature;
/**
* The segment of this Feature. All features have at least one segment.
**/
private FeatureSegmentVector segments = null;
/**
* A vector of those objects listening for feature change events.
**/
private final Vector feature_listener_list = new Vector();
/**
* The translation of the bases of this feature.
**/
private AminoAcidSequence amino_acids = null;
/**
* The bases of this feature.
**/
private String bases = null;
/**
* The count of the number of amino acids in this feature. (set by
* getAACount() and resetCache()).
**/
private int aa_count = -1;
/**
* The count of the bases in this feature (set by getBaseCount() and
* resetCache()).
**/
private int base_count = -1;
/**
* This array contains counts of the occurrences of each three bases. The
* first index is the first base, the second index is the second base, etc.
* The indices are the same as those for Bases.letter_index.
* (set by resetCache()).
**/
private int [][][] codon_counts = null;
/**
* This array contains counts of the occurrences of each amino acids in the
* translation of the bases of the current feature.
* (set by resetCache()).
**/
private int [] residue_counts = null;
/**
* This array contains counts of number of each base that appear in each
* codon position. The first index is the codon position and the second is
* the base (indexed in the same way as Bases.letter_index).
* (set by resetCache()).
**/
private int [][] positional_base_counts = null;
/**
* This array contains the counts of the total number of each base in the
* feature.
* The indices are the same as those for Bases.letter_index.
* (set by resetCache()).
**/
private int [] base_counts = null;
/**
* The current Location reference is saved each time setLocation() is
* called so that if the reference changes resetCache() can
* be called. This is needed to handle RWCorbaFeature objects which can
* change their Location at arbitrary times.
**/
private Location old_location = null;
/**
* Incremented when startListening() is called - decremented when
* stopListening() is called.
**/
private int listen_count = 0;
/**
* 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.
**/
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
**/
}
/**
* 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);
}
/**
* 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());
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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);
}
}
}
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
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);
}
}
}
// if the event is contained within this feature then the feature
// sequence may have changed
if(eventPosition >= this_feature_range.getStart() &&
eventPosition <= this_feature_range.getEnd() + 1)
if(eventPosition >= this_segment_range.getStart() &&
eventPosition <= this_segment_range.getEnd() + 1)
if(feature_changed)
{
resetCache();
locationChanged(old_location);
}
catch(ReadOnlyException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
}
/**
* Invoked when an Option is changed.
**/
// if the eukaryotic mode option changes the sequence may change (ie. the
// start codon may need to be translated differently) - see
}
/**
* Returns the embl feature that was passed to the constructor.
**/
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)
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 =
}
}
/**
* 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();
if(gene_name == null)
{
if(getLabel() == null)
pir_name = getKey().toString();
else
pir_name = getLabel();
getEntry().getName() + " " +
getWriteRange() +
" MW:" + (int) getMolecularWeight();
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
{
new FastaStreamSequence(getBases(),
getIDString() + ", " +
getWriteRange());
}
/**
* 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(" ");
header_buffer.append(" ").append(getWriteRange()).append(" MW:");
header_buffer.append((int) getMolecularWeight());
}
/**
* 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();
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 sequenceLength = getSequenceLength();
if(feature_end_base == sequenceLength)
getStrand().getSubSequence(new Range(start_base, end_base));
}
catch(OutOfRangeException e)
{
throw new Error("internal error - unexpected exception: " + e);
* @return A string of the form "100:200 reverse" or "1:2222 forward".
**/
String partial = " ";
if(isPartial(true))
partial += "partial 5' ";
if(isPartial(false))
partial += "partial 3' ";
return (isForwardFeature() ?
getFirstCodingBaseMarker().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)
else if(getQualifierByName("End_range") != null)
return true;
}
else
{
if(isForwardFeature())
{
if(getQualifierByName("End_range") != null)
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)
{
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 a Reader object that gives an EMBL format version of this
* Feature when read.
**/
}
/**
* Return true if and only if this Feature is on the forward strand.
**/
public boolean isForwardFeature()
{
if(getLocation().isComplement())
return true;
}
/**
* Return the key of this Feature.
**/
}
/**
* 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("BLASTCDS") ||
getKey().equals("polypeptide"))
return false;
}
/**
* Return true if and only if the key of this feature is CDS feature.
**/
return false;
}
/**
* Return true if and only if the key of this feature is CDS feature and
* the feature has a /partial qualifier.
**/
{
try
{
if(getKey().equals("CDS") && getQualifierByName("partial") != null)
}
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
**/
// return the embl.QualifierVector from the underlying embl.Feature object
return getEmblFeature().getQualifiers();
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");
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");
try
{
final int score_int = Float.valueOf(score_string).intValue();
}
catch(InvalidRelationException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
}
/**
* Set the Entry that owns this object.
**/
final Entry old_entry = this.entry;
this.entry = entry;
else
{
if(old_entry != null)
removeFeatureChangeListener(old_entry);
}
/**
* 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)
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)
ReadOnlyException, OutOfDateException
{
final Key old_key = getKey();
old_location = getLocation();
final QualifierVector old_qualifiers = getQualifiers().copy();
if(!(getEntry().getBases().getSequence() instanceof PartialSequence))
sequence_length = getEntry().getBases().getLength();
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))
{
}
else
{
((DateStampFeature)getEmblFeature()).set(datestamp,
new_key,
new_location,
new_qualifiers);
if(new_location != old_location && segments != null)
// 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);
}
/**
* This method will send a FeatureChangeEvent with type LOCATION_CHANGED to
* all the FeatureChangeEvent listeners when a marker changes. It also
private void locationChanged(final Location old_location,
final QualifierVector qualifiers,
// now inform the listeners that a change has occured
final FeatureChangeEvent feature_change_event =
qualifiers,
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.
**/