Select Git revision
Entry.java 42.18 KiB
/* Entry.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/Entry.java,v 1.11 2008-06-20 10:00:25 tjc Exp $
*/
package uk.ac.sanger.artemis;
import uk.ac.sanger.artemis.sequence.*;
import uk.ac.sanger.artemis.util.*;
import uk.ac.sanger.artemis.io.DatabaseDocumentEntry;
import uk.ac.sanger.artemis.io.EmblStreamFeature;
import uk.ac.sanger.artemis.io.DocumentEntry;
import uk.ac.sanger.artemis.io.EmblDocumentEntry;
import uk.ac.sanger.artemis.io.GFFDocumentEntry;
import uk.ac.sanger.artemis.io.GFFStreamFeature;
import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry;
import uk.ac.sanger.artemis.io.PartialSequence;
import uk.ac.sanger.artemis.io.Range;
import uk.ac.sanger.artemis.io.RangeVector;
import uk.ac.sanger.artemis.io.Key;
import uk.ac.sanger.artemis.io.Location;
import uk.ac.sanger.artemis.io.LocationParseException;
import uk.ac.sanger.artemis.io.QualifierVector;
import uk.ac.sanger.artemis.io.DocumentEntryFactory;
import uk.ac.sanger.artemis.io.EntryInformation;
import uk.ac.sanger.artemis.io.EntryInformationException;
import java.util.NoSuchElementException;
import java.util.Vector;
import java.io.IOException;
import java.io.File;
/**
* This class is a wrapper for the io.Entry class which contains the state
* and handles the events needed for editing one embl entry in Diana. The
* state includes an EMBL.Entry object and a list of EntryChange listeners
* and FeatureChange listeners. Other objects can register as listeners for
* changes to the entry. For that reason, all changes to the embl.Entry
* objects should go through this class. (see ChangeEvent for details of the
* possible events.)
*
* @author Kim Rutherford
* @version $Id: Entry.java,v 1.11 2008-06-20 10:00:25 tjc Exp $
**/
public class Entry implements FeatureChangeListener, Selectable
{
/**
* The embl.Entry object that was passed to the constructor
**/
private uk.ac.sanger.artemis.io.Entry embl_entry;
/**
* A vector of those objects listening for entry change events.
**/
final private Vector<ChangeListener> entry_listener_list = new Vector<ChangeListener>();
/**
* A vector of those objects listening for feature change events.
**/
final private Vector<ChangeListener> feature_listener_list = new Vector<ChangeListener>();
/**
* This is the Bases reference that was passed to the constructor.
**/
/*final*/ private Bases bases;
/**
* Create a new Entry object.
* @param bases The Bases object which contains the Strand objects that will
* be used by the features of this Entry.
* @param embl_entry a reference to an embl.Entry object containing the
* underlying data for new object
* @exception OutOfRangeException Thrown if one of the features in
* embl_entry is out of range of the Bases object.
**/
public Entry(final Bases bases,
final uk.ac.sanger.artemis.io.Entry embl_entry)
throws OutOfRangeException
{
this.embl_entry = embl_entry;
this.bases = bases;
checkLocations();
createDianaFeatures();
}
/**
* Create a new Entry object from a uk.ac.sanger.artemis.io.Entry
* object. A new Bases object will be created for the new Entry.
* @param embl_entry a reference to an embl.Entry object containing the
* underlying data for new object
* @exception OutOfRangeException Thrown if one of the features in
* embl_entry is out of range of the Bases object.
* @exception NoSequenceException Thrown if embl_entry contains no sequence.
**/
public Entry(final uk.ac.sanger.artemis.io.Entry embl_entry)
throws OutOfRangeException, NoSequenceException
{
this.embl_entry = embl_entry;
if(embl_entry.getSequence() == null ||
embl_entry.getSequence().length() == 0)
throw new NoSequenceException();
else
this.bases = new Bases(embl_entry.getSequence());
checkLocations();
createDianaFeatures();
}
/**
* Returns true if and only if this entry is read only.
**/
public boolean isReadOnly()
{
return getEMBLEntry().isReadOnly();
}
/**
* Save the changes to this Entry back to where the Entry came from. If
* this object is read only a ReadOnlyException will be thrown.
* @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
* ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
* same format it was created, otherwise it will be saved in the given
* format.
* @exception EntryInformationException Thrown if the destination type
* cannot contain the Key, Qualifier or Key/Qualifier combinations of one
* of the features in this Entry.
* @exception IOException Thrown if an IO error occurs while saving. One
* possibility is ReadOnlyException.
**/
public void save(final int destination_type)
throws IOException, EntryInformationException
{
if(destination_type == DocumentEntryFactory.ANY_FORMAT)
getEMBLEntry().save();
else
{
if(getEMBLEntry() instanceof DocumentEntry)
getEMBLEntryAsNewType(getEntryInformation(),
destination_type, false).save();
else
throw new ReadOnlyException("operation cannot be " +
"applied to this entry");
}
}
/**
* Save the changes to this Entry back to where the Entry came from(like
* save()) but without any uk.ac.sanger.artemis extensions. If this object is read only
* or doesn't support this a ReadOnlyException will be thrown.
* @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
* ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
* same format it was created, otherwise it will be saved in the given
* format.
* @exception EntryInformationException Thrown if the destination type
* cannot contain the Key, Qualifier or Key/Qualifier combinations of one
* of the features in this Entry.
**/
public void saveStandardOnly(final int destination_type)
throws IOException,EntryInformationException
{
if(destination_type == DocumentEntryFactory.ANY_FORMAT)
getEMBLEntry().save();
else
{
if(getEMBLEntry() instanceof DocumentEntry)
{
final EntryInformation entry_information =
Options.getDBEntryInformation();
getEMBLEntryAsNewType(entry_information,
destination_type, false).save();
}
else
throw new ReadOnlyException("operation cannot be " +
"applied to this entry");
}
}
/**
* Save the changes to this Entry to the file. If this object is read only
* a ReadOnlyException will be thrown. This method only works when the
* underlying embl.Entry object is a DocumentEntry.
* @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
* GFF_FORMAT or ANY_FORMAT. If ANY_FORMAT then the Entry will
* be saved in the same format it was created, otherwise it will be saved
* in the given format.
* @param force If true then invalid qualifiers and any features with
* invalid keys will be quietly thrown away when saving. "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 one of the features in this Entry.
**/
public void save(final File file, final int destination_type,
final boolean force)
throws IOException, EntryInformationException
{
final EntryInformation artemis_entry_information;
if((getEMBLEntry() instanceof DatabaseDocumentEntry ||
getEMBLEntry() instanceof GFFDocumentEntry) &&
destination_type == DocumentEntryFactory.EMBL_FORMAT)
artemis_entry_information = Options.getArtemisEntryInformation();
else
artemis_entry_information = getEntryInformation();
save(file, destination_type, force, artemis_entry_information);
}
/**
* Save the changes to this Entry to the file. If this object is read only
* a ReadOnlyException will be thrown. This method only works when the
* underlying embl.Entry object is a DocumentEntry.
* @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT,
* GFF_FORMAT or ANY_FORMAT. If ANY_FORMAT then the Entry will
* be saved in the same format it was created, otherwise it will be saved
* in the given format.
* @param force If true then invalid qualifiers and any features with
* invalid keys will be quietly thrown away when saving. "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 one of the features in this Entry.
**/
public void save(final File file, final int destination_type,
final boolean force, final EntryInformation artemis_entry_information)
throws IOException, EntryInformationException
{
final FileDocument file_document = new FileDocument(file);
final DocumentEntry document_entry =
(DocumentEntry)getEMBLEntryAsNewType(artemis_entry_information,
destination_type, force);
document_entry.save(file_document);
}
/**
* Save the changes to this Entry to the file(like save(Document)) but
* without any uk.ac.sanger.artemis extensions. If this object is read only a
* ReadOnlyException will be thrown. This method only works when the
* underlying embl.Entry object is a DocumentEntry.
* @param destination_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
* ANY_FORMAT. If ANY_FORMAT then the Entry will be saved in the
* same format it was created, otherwise it will be saved in the given
* format.
* @param force If true then invalid qualifiers and any features with
* invalid keys will be quietly thrown away when saving. "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 one of the features in this Entry.
**/
public void saveStandardOnly(final File file,
final int destination_type,
final boolean force)
throws IOException, EntryInformationException
{
final FileDocument file_document = new FileDocument(file);
final DocumentEntry document_entry =
(DocumentEntry) getEMBLEntryAsNewType(Options.getDBEntryInformation(),
destination_type, force);
document_entry.save(file_document);
}
/**
* Returns true if and only if there have been some changes to this Entry
* since the last save.
**/
public boolean hasUnsavedChanges()
{
return getEMBLEntry().hasUnsavedChanges();
}
/**
* Create a new Entry in the given EntryGroup.
* @return A reference to the new Entry.
**/
public static Entry newEntry(final Bases bases)
{
final uk.ac.sanger.artemis.io.Entry entry =
new EmblDocumentEntry(Options.getArtemisEntryInformation());
try
{
return new Entry(bases, entry);
}
catch(OutOfRangeException e)
{
// an empty Entry cannot have any out of range features
throw new Error("internal error - unexpected exception: " + e);
}
}
/**
* Return the name of this Entry or null if it has no name.
**/
public String getName()
{
return getEMBLEntry().getName();
}
/**
* Set the name of this Entry - if possible(the return value will let the
* caller know).
* @return true if and only if the name was successfully set. Not all
* Entry objects can change there name, so it is up to the calling
* function to check the return value.
**/
public boolean setName(final String name)
{
final String orig_entry_name = getName();
if(getEMBLEntry().setName(name))
{
if(orig_entry_name == null || ! name.equals(orig_entry_name))
{
// now inform the listeners that the name has changed
final EntryChangeEvent event =
new EntryChangeEvent(name, this, EntryChangeEvent.NAME_CHANGED);
fireAction(entry_listener_list, event);
}
return true;
}
else
return false;
}
/**
* Return the text of the EMBL header of this Entry or null if there is no
* header.
**/
public String getHeaderText()
{
return getEMBLEntry().getHeaderText();
}
/**
* Set the header of this Entry to be the given text.
* @return true if and only if the header was successfully set. Not all
* Entry objects can change their header, so it is up to the calling
* function to check the return value.
* @exception IOException thrown if there is a problem reading the header
* from the String - most likely ReadFormatException.
**/
public boolean setHeaderText(final String new_header)
throws IOException
{
if(getEMBLEntry().setHeaderText(new_header))
{
final EntryChangeEvent event =
new EntryChangeEvent(new_header,
this,
EntryChangeEvent.HEADER_CHANGED);
fireAction(entry_listener_list, event);
return true;
}
else
return false;
}
/**
* Return the path of the directory that this entry is in.
**/
public Document getRootDocument()
{
if(getEMBLEntry() instanceof DocumentEntry && // XXX FIXME:
((DocumentEntry) getEMBLEntry()).getDocument() != null)
{
return((DocumentEntry)getEMBLEntry()).getDocument().getParent();
}
else
return null;
}
/**
* Return a vector containing the references of the Feature objects within
* the given range.
* @param range Return features that overlap this range - ie the start of
* the feature is less than or equal to the end of the range and the end
* of the feature is greater than or equal to the start of the range.
* @return The non-source key features of this feature table the are within
* the given range. The returned object is a copy - changes will not
* effect the FeatureTable object itself.
**/
public FeatureVector getFeaturesInRange(Range range)
throws OutOfRangeException
{
final FeatureVector return_features = new FeatureVector();
final uk.ac.sanger.artemis.io.FeatureVector embl_features =
getEMBLEntry().getFeaturesInRange(range);
// System.err.println("starting getFeaturesInRange()");
Feature diana_feature;
for(int i = 0 ; i < embl_features.size() ; ++i)
{
diana_feature = getFeatureOf(embl_features.featureAt(i));
// System.err.println("getFeaturesInRange() - diana_feature:" +
// diana_feature + " " +
// embl_features.elementAt(i));
return_features.add(diana_feature);
}
// System.err.println("ending getFeaturesInRange()");
return return_features;
}
/**
* Return a vector containing the references of the Feature objects in this
* Entry.
* @return The non-source key features of this Entry. The
* returned object is a copy - changes will not effect the Entry
* object itself.
**/
public FeatureVector getAllFeatures()
{
final FeatureVector return_features = new FeatureVector();
final uk.ac.sanger.artemis.io.FeatureVector embl_features =
getEMBLEntry().getAllFeatures();
Feature diana_feature;
for(int i = 0; i < embl_features.size(); ++i)
{
diana_feature = getFeatureOf(embl_features.featureAt(i));
return_features.add(diana_feature);
}
return return_features;
}
public void remove(final Feature feature)
throws ReadOnlyException
{
remove(feature, false);
}
/**
* Delete a Feature from this object and the underlying embl.Entry object.
* @param feature The Feature to delete.
**/
public void remove(final Feature feature,
final boolean duplicate)
throws ReadOnlyException
{
synchronized(getBases())
{
// get the embl.Feature object out of the uk.ac.sanger.artemis.Feature object then
// remove it
if(!getEMBLEntry().remove(feature.getEmblFeature()))
throw new Error("internal error - remove failed");
// now inform the listeners that a deletion has occured
final EntryChangeEvent event =
new EntryChangeEvent(this, feature, duplicate, EntryChangeEvent.FEATURE_DELETED);
fireAction(entry_listener_list, event);
feature.setEntry(null);
}
}
/**
* Remove all the features in this Entry.
**/
public void removeAllFeatures()
throws ReadOnlyException
{
// remove the first feature
while(getFeatureCount() > 0)
remove(getFeature(0));
}
/**
* Add a Feature to the Entry. The new feature will be inserted in order
* in the feature vector. The features in the vector are ordered by the
* first base of each feature.
* @param new_feature The new feature to add. It should not be a member
* of the Entry object.
* @param force If true then invalid qualifiers will be quietly thrown away
* and a feature with invalid keys will not be added. "Invalid" means
* that the key/qualifier is non allowed to occur in an Entry of this type
* (probably determined by the EntryInformation object of this Entry).
* If false an EntryInformationException will be thrown for invalid keys
* or qualifiers.
* @exception EntryInformationException Thrown force is false if this Entry
* cannot contain the Key, Qualifier or Key/Qualifier combination of the
* given Feature
**/
public void add(final Feature new_feature,
final boolean force)
throws EntryInformationException, OutOfRangeException,
ReadOnlyException
{
add(new_feature, false, force);
}
/**
* Add a Feature to the Entry. The new feature will be inserted in order
* in the feature vector. The features in the vector are ordered by the
* first base of each feature.
* @param new_feature The new feature to add. It should not be a member
* of the Entry object.
* @param force If true then invalid qualifiers will be quietly thrown away
* and a feature with invalid keys will not be added. "Invalid" means
* that the key/qualifier is non allowed to occur in an Entry of this type
* (probably determined by the EntryInformation object of this Entry).
* If false an EntryInformationException will be thrown for invalid keys
* or qualifiers.
* @exception EntryInformationException Thrown force is false if this Entry
* cannot contain the Key, Qualifier or Key/Qualifier combination of the
* given Feature
**/
public void add(final Feature new_feature, final boolean duplicate,
final boolean force)
throws EntryInformationException, OutOfRangeException,
ReadOnlyException
{
try
{
if(new_feature.getEntry() != null)
throw new Error("internal error - Feature has a parent");
new_feature.setEntry(this);
final uk.ac.sanger.artemis.io.Feature old_embl_reference =
new_feature.getEmblFeature();
// this call will add the new_feature in the correct place in the
// embl.Feature vector of the FeatureTable object
final uk.ac.sanger.artemis.io.Feature new_embl_reference;
if(force)
{
new_embl_reference =
getEMBLEntry().forcedAdd(new_feature.getEmblFeature());
}
else
{
new_embl_reference =
getEMBLEntry().add(new_feature.getEmblFeature());
}
if(new_embl_reference != old_embl_reference)
{
new_feature.getEmblFeature().setUserData(null);
new_feature.setEmblFeature(new_embl_reference);
new_embl_reference.setUserData(new_feature);
}
// now inform the listeners that a addition has occured
final EntryChangeEvent event =
new EntryChangeEvent(this, new_feature, duplicate,
EntryChangeEvent.FEATURE_ADDED);
fireAction(entry_listener_list, event);
}
catch(EntryInformationException e)
{
new_feature.setEntry(null);
throw e;
}
}
/**
* Forget all the features in this entry and inform all the EntryChange
* listeners that all the features have gone away. The garbage collector
* will really dispose of the features later.
**/
public void dispose()
{
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
// now inform the listeners that a deletion has occured
final EntryChangeEvent event =
new EntryChangeEvent(this, current_feature,
EntryChangeEvent.FEATURE_DELETED);
fireAction(entry_listener_list, event);
current_feature.setEntry(null);
}
}
/**
* Returns the Feature at the given index in the vector of Features.
**/
public Feature getFeature(int i)
{
return getFeatureOf(getEMBLEntry().getFeatureAtIndex(i));
}
/**
* Create a new Feature in this Entry with no qualifiers, a default key and
* a location of "1..max_base"
**/
public Feature createFeature()
throws ReadOnlyException
{
final Key new_key;
final Location new_location;
final QualifierVector new_qualifiers = new QualifierVector();
try
{
new_key = getEntryInformation().getDefaultKey();
new_location = new Location("1" + ".." + getBases().getLength());
return createFeature(new_key, new_location, new_qualifiers);
}
catch(LocationParseException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
catch(EntryInformationException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
catch(OutOfRangeException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
}
/**
* Create and return a new Feature in this Entry with the given key,
* location and qualifiers. This method creates a new embl.Feature object
* and then wraps it in a uk.ac.sanger.artemis.Feature object and
* then inserts it in thisEntry object.
* @param new_key The Key to use for the new Feature.
* @param new_location The Location to use for the new Feature.
* @param new_qualifiers The a vector containing the Qualifier objects to
* use for the new Feature.
* @exception EntryInformationException Thrown force is false if this Entry
* cannot contain a feature with the given Key, Qualifier or
* Key/Qualifier combination.
**/
public Feature createFeature(Key new_key,
Location new_location,
QualifierVector new_qualifiers)
throws EntryInformationException, ReadOnlyException,
OutOfRangeException
{
final uk.ac.sanger.artemis.io.Feature new_embl_feature =
getEMBLEntry().createFeature(new_key, new_location, new_qualifiers);
final Feature new_feature = getFeatureOf(new_embl_feature);
// now inform the listeners that a addition has occured
final EntryChangeEvent event =
new EntryChangeEvent(this, new_feature, EntryChangeEvent.FEATURE_ADDED);
fireAction(entry_listener_list, event);
return new_feature;
}
/**
* Create and return a new Feature in this Entry with the given key,
* location and no qualifiers. This method creates a new embl.Feature
* object and then wraps it in a uk.ac.sanger.artemis.Feature object and then inserts it
* in this Entry object.
* @param new_key The Key to use for the new Feature.
* @param new_location The Location to use for the new Feature.
**/
public Feature createFeature(Key new_key,
Location new_location)
throws EntryInformationException, ReadOnlyException,
OutOfRangeException
{
final QualifierVector new_qualifiers = new QualifierVector();
return createFeature(new_key, new_location, new_qualifiers);
}
/**
* Return true if this Entry contains the given Feature.
**/
public boolean contains(Feature feature)
{
return getEMBLEntry().contains(feature.getEmblFeature());
}
/**
* Return the index of the given Feature in the feature table of this Entry
* or -1 if the feature isn't in this Entry.
**/
public int indexOf(Feature feature)
{
return getEMBLEntry().indexOf(feature.getEmblFeature());
}
/**
* Return the index of a feature in the embl feature table of this entry.
* The indices start at zero.
* @param feature The Feature to lookup.
* @return The index of the feature or -1 if the feature isn't in this
* Entry.
**/
public int getIndexOfFeature(Feature feature)
{
return getEMBLEntry().indexOf(feature.getEmblFeature());
}
/**
* Returns an enumeration of the Feature objects in this Entry. The
* returned FeatureEnumeration object will generate all features in this
* object in turn. The first item generated is the item at index 0, then
* the item at index 1, and so on.
**/
public FeatureEnumeration features()
{
return new FeatureEnumerator();
}
/**
* An Enumeration of Feature objects.
**/
public class FeatureEnumerator implements FeatureEnumeration
{
/**
* Create a new FeatureEnumeration that will enumerate the enclosing
* Entry object. The Entry object must not be changed while the
* enumeration is active.
**/
public FeatureEnumerator()
{
feature_enumerator = getEMBLEntry().features();
}
/**
* See the FeatureEnumeration interface for details.
**/
public boolean hasMoreFeatures()
{
return feature_enumerator.hasMoreFeatures();
}
/**
* See the FeatureEnumeration interface for details.
**/
public Feature nextFeature()
throws NoSuchElementException
{
final uk.ac.sanger.artemis.io.Feature this_feature =
feature_enumerator.nextFeature();
return getFeatureOf(this_feature);
}
/**
* The Enumeration for the current entry
**/
private uk.ac.sanger.artemis.io.FeatureEnumeration feature_enumerator;
}
/**
* Returns the number of features in this entry.
**/
public int getFeatureCount()
{
return getEMBLEntry().getFeatureCount();
}
/**
* Return the Bases object that was passed to the constructor.
**/
public Bases getBases()
{
return bases;
}
/**
* This method translates the start and end of every each Range in every
* Location into another coordinate system. The Ranges will be truncated
* if necessary.
* @param constraint This contains the start and end base of the new
* coordinate system. The position given by constraint.getStart() will
* be at postion/base 1 in the new coordinate system.
* @return a copy of the Entry which has been translated into the new
* coordinate system.
**/
public Entry truncate(final Bases new_bases, final Range constraint)
{
final uk.ac.sanger.artemis.io.Entry new_embl_entry;
if(getEMBLEntry() instanceof GFFDocumentEntry ||
getEMBLEntry() instanceof IndexedGFFDocumentEntry)
new_embl_entry = new GFFDocumentEntry(getEntryInformation());
else
new_embl_entry = new EmblDocumentEntry(getEntryInformation());
final Entry new_entry;
try
{
new_entry = new Entry(new_bases, new_embl_entry);
}
catch(OutOfRangeException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
if(getEMBLEntry() instanceof IndexedGFFDocumentEntry)
{
((IndexedGFFDocumentEntry)getEMBLEntry()).truncate(constraint, new_entry);
return new_entry;
}
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
final Location current_feature_location = current_feature.getLocation();
final Location new_location =
current_feature_location.truncate(constraint);
if(new_location != null)
{
final Key current_key = current_feature.getKey();
final QualifierVector current_qualifiers =
current_feature.getQualifiers();
try
{
final uk.ac.sanger.artemis.io.Feature new_feature;
if(current_feature.getEmblFeature() instanceof GFFStreamFeature)
new_feature = new GFFStreamFeature(current_key, new_location,
current_qualifiers);
else
new_feature = new EmblStreamFeature(current_key, new_location,
current_qualifiers);
new_entry.add(new Feature(new_feature), true);
}
catch(EntryInformationException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
catch(OutOfRangeException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
catch(ReadOnlyException e)
{
throw new Error("internal error - unexpected exception: " + e);
}
}
}
return new_entry;
}
/**
* Return a Vector containing those features that don't have valid EMBL
* key.
**/
public FeatureVector checkForNonEMBLKeys()
{
final FeatureVector non_embl_features = new FeatureVector();
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
if(!current_feature.hasValidEMBLKey())
non_embl_features.add(current_feature);
}
return non_embl_features;
}
/**
* Returns a vector containing those CDS features that have no /pseudo
* qualifier and do not have a valid start codon. The returned features
* may be illegal in EMBL submissions.
**/
public FeatureVector checkFeatureStartCodons()
{
// get all the CDS features that do not have a /pseudo or /pseudogene qualifier
final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
FeaturePredicateConjunction.AND);
final FeatureVector non_embl_features = new FeatureVector();
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
if(predicate.testPredicate(current_feature))
{
if(!current_feature.hasValidStartCodon())
non_embl_features.add(current_feature);
}
}
return non_embl_features;
}
/**
* Returns a vector containing those CDS features that have no /pseudo
* qualifier and do not have a valid stop codon. The returned features may
* be illegal in EMBL submissions.
**/
public FeatureVector checkFeatureStopCodons()
{
// get all the CDS features that do not have a /pseudo or /pseudogene qualifier
final FeaturePredicateConjunction predicate = new FeaturePredicateConjunction(
new FeatureKeyQualifierPredicate(Key.CDS, "pseudo", false),
new FeatureKeyQualifierPredicate(Key.CDS, "pseudogene", false),
FeaturePredicateConjunction.AND);
final FeatureVector non_embl_features = new FeatureVector();
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
if(predicate.testPredicate(current_feature))
{
if(!current_feature.hasValidStopCodon())
non_embl_features.add(current_feature);
}
}
return non_embl_features;
}
/**
* Returns a vector containing those features that have the same key and
* location as one or more other features. These features are not allowed
* in EMBL submissions.
**/
public FeatureVector checkForEMBLDuplicates()
{
final FeatureVector non_embl_features = new FeatureVector();
final FeatureEnumeration feature_enum = features();
Feature last_feature = null;
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
final Key current_key = current_feature.getKey();
final Location current_location = current_feature.getLocation();
if(last_feature != null &&
last_feature.getKey().equals(current_key) &&
last_feature.getLocation().equals(current_location))
{
if(! non_embl_features.contains(last_feature))
non_embl_features.add(last_feature);
non_embl_features.add(current_feature);
continue;
}
last_feature = current_feature;
}
return non_embl_features;
}
/**
* Returns a vector containing those features that have the same key and
* location as one or more other features. These features are not allowed
* in EMBL submissions.
**/
public FeatureVector checkForOverlappingCDSs()
{
final FeatureVector cds_features = new FeatureVector();
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
if(current_feature.isCDS())
cds_features.add(current_feature);
}
final FeatureVector return_features = new FeatureVector();
for(int i = 0 ; i + 1 < cds_features.size() ; ++i)
{
final Feature this_feature = cds_features.elementAt(i);
final Feature next_feature = cds_features.elementAt(i + 1);
final int this_feature_end = this_feature.getRawLastBase();
final int next_feature_start = next_feature.getRawFirstBase();
if(this_feature_end >= next_feature_start)
return_features.add(this_feature);
}
return return_features;
}
/**
* Returns a vector containing those features that have a feature that is
* missing a required qualifier. These features are not allowed in EMBL
* submissions.
**/
public FeatureVector checkForMissingQualifiers()
{
final FeatureVector non_embl_features = new FeatureVector();
final FeatureEnumeration feature_enum = features();
while(feature_enum.hasMoreFeatures())
{
final Feature current_feature = feature_enum.nextFeature();
if(!current_feature.hasRequiredQualifiers())
non_embl_features.add(current_feature);
}
return non_embl_features;
}
/**
* Adds the specified event listener to receive entry change events from
* this object.
* @param l the event change listener.
**/
public void addEntryChangeListener(EntryChangeListener l)
{
entry_listener_list.addElement(l);
}
/**
* Removes the specified event listener so that it no longer receives
* entry change events from this object.
* @param l the event change listener.
**/
public void removeEntryChangeListener(EntryChangeListener l)
{
entry_listener_list.removeElement(l);
}
/**
* Adds the specified event listener to receive feature change events from
* this object.
* @param l the event change 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 event change listener.
**/
public void removeFeatureChangeListener(FeatureChangeListener l)
{
feature_listener_list.removeElement(l);
}
/**
* Implementation of the FeatureChangeListener interface. We need to
* listen to feature change events from the Features in this object.
* @param event The change event.
**/
public void featureChanged(FeatureChangeEvent event)
{
// pass the action straight through
fireAction(feature_listener_list, event);
}
/**
* Return the EntryInformation object of this Entry.
**/
public EntryInformation getEntryInformation()
{
return getEMBLEntry().getEntryInformation();
}
/**
* 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<ChangeListener> listeners, ChangeEvent event)
{
final Vector<ChangeListener> targets;
// copied from a book - synchronising the whole method might cause a
// deadlock
synchronized(this)
{
targets = (Vector) listeners.clone();
}
for(ChangeListener target: targets)
{
if(event instanceof EntryChangeEvent)
{
final EntryChangeListener entry_change_listener =
(EntryChangeListener) target;
entry_change_listener.entryChanged((EntryChangeEvent) event);
}
else
{
final FeatureChangeListener feature_change_listener =
(FeatureChangeListener) target;
feature_change_listener.featureChanged((FeatureChangeEvent) event);
}
}
}
/**
* Return the uk.ac.sanger.artemis.Feature object of the given embl.Feature object. This
* method will create an appropriate uk.ac.sanger.artemis.Feature if none exists.
**/
private Feature
getFeatureOf(uk.ac.sanger.artemis.io.Feature embl_feature)
{
final Feature test_feature =(Feature) embl_feature.getUserData();
if(test_feature == null)
{
final Feature new_feature = new Feature(embl_feature);
new_feature.setEntry(this);
// hack to create a FeatureSegment for each exon, which has to be done
// aftre setting the Entry of the Feature
new_feature.getSegments();
return new_feature;
}
else
return test_feature;
}
/**
* Check that all features in the embl.Entry object are in range for the
* Bases object that was passed to the constructor.
**/
private void checkLocations() throws OutOfRangeException
{
final uk.ac.sanger.artemis.io.FeatureEnumeration feature_enumerator =
getEMBLEntry().features();
while(feature_enumerator.hasMoreFeatures())
{
final uk.ac.sanger.artemis.io.Feature feature =
feature_enumerator.nextFeature();
final Location location = feature.getLocation();
final RangeVector ranges = location.getRanges();
for(int i = 0 ; i < ranges.size() ; ++i)
{
if(((Range)ranges.elementAt(i)).getEnd() > getBases().getLength())
{
if(!(getBases().getSequence() instanceof PartialSequence))
throw new OutOfRangeException(location.toString());
}
}
}
}
/**
* Create a uk.ac.sanger.artemis.Feature object for each embl.Feature in embl_entry.
**/
private void createDianaFeatures()
{
// enumerating the features will call getFeatureOf() for each
// embl.Feature, which will in turn create a uk.ac.sanger.artemis.Feature
final FeatureEnumeration feature_enumerator = features();
while(feature_enumerator.hasMoreFeatures())
feature_enumerator.nextFeature();
}
/**
* Return the embl.Entry object that was passed to the constructor.
**/
public uk.ac.sanger.artemis.io.Entry getEMBLEntry()
{
return embl_entry;
}
/**
* Return the embl.Entry object that was passed to the constructor.
* @param entry_information The EntryInformation object for the new Entry.
* @param new_type Should be one of EMBL_FORMAT, GENBANK_FORMAT or
* ANY_FORMAT. If ANY_FORMAT then the Entry will be returned as is.
* @param force If true then invalid qualifiers and any features with
* invalid keys in the new Entry will be quietly thrown away. "Invalid"
* means that the key/qualifier is not allowed to occur in an Entry of
* this type(probably determined by the EntryInformation object of this
* Entry). If false an EntryInformationException will be thrown for
* invalid keys or qualifiers.
* @exception EntryInformationException Thrown if an Entry using the given
* EntryInformation object cannot contain the Key, Qualifier or
* Key/Qualifier combination of one of the features in the Document.
**/
private uk.ac.sanger.artemis.io.Entry
getEMBLEntryAsNewType(final EntryInformation entry_information,
final int new_type, final boolean force)
throws EntryInformationException
{
return DocumentEntryFactory.makeDocumentEntry(entry_information,
embl_entry, new_type,
force);
}
}