/* SimpleDocumentEntry.java * * created: Tue Feb 15 2000 * * This file is part of Artemis * * Copyright(C) 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/io/SimpleDocumentEntry.java,v 1.30 2009-09-03 13:33:18 tjc Exp $ */ package uk.ac.sanger.artemis.io; import uk.ac.sanger.artemis.util.*; import java.io.*; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.Vector; import java.util.Hashtable; import java.util.Map; /** * This class contains the methods common to all DocumentEntry objects. * * @author Kim Rutherford * @version $Id: SimpleDocumentEntry.java,v 1.30 2009-09-03 13:33:18 tjc Exp $ **/ abstract public class SimpleDocumentEntry implements DocumentEntry { /** * A vector of ReadOnlyEmblStreamFeature objects - one for each fasta * record in the DocumentEntry. These won't be written when the * DocumentEntry is written. **/ private FeatureVector fake_fasta_features = new FeatureVector(); /** EntryInformation object that was passed to constructor */ final private EntryInformation entry_information; /** collection to send ReadEvents to */ private Vector listeners = new Vector(); /** * The Document object that was passed to the constructor. This should be * the document that this SimpleDocumentEntry was read from. **/ private Document document = null; /** * This contains all the lines(stored as LineGroup objects) from the entry * stream that was passed to the constructor. **/ protected Vector line_groups = new Vector(); /** * The DocumentEntryAutosaveThread that is started when the first call is * made to setDirtyFlag(). **/ private Thread autosave_thread = null; /** * The Date when this Entry last changed or null if this Entry * hasn't changed since the last save. Set to null by save(). **/ private java.util.Date last_change_time = null; /** * Set to true in the constructor while features are added. setDirtyFlag() * will do nothing while this is true. **/ protected boolean in_constructor = false; protected Hashtable contig_ranges; /** * Create a new SimpleDocumentEntry from the given Document. * @param entry_information The EntryInformation object of the new Entry. * @param document This is the file that we will read from. This is also * used for saving the entry back to the file it came from and to give * the new object a name. * @exception IOException thrown if there is a problem reading the entry - * most likely ReadFormatException. * @exception EntryInformationException Thrown if force is false and if this * Entry cannot contain the Key, Qualifier or Key/Qualifier combination of * one of the features in the given Entry. **/ public SimpleDocumentEntry(final EntryInformation entry_information, final Document document, final ReadListener read_listener) throws IOException, EntryInformationException { this.document = document; this.entry_information = new SimpleEntryInformation(entry_information); this.in_constructor = true; // flag used by setDirtyFlag() if(read_listener != null) addReadListener(read_listener); final LinePushBackReader pushback_reader = getDocument().getLinePushBackReader(); LineGroup new_line_group; final int MAX_LOOP = 9999; while((new_line_group = LineGroup.readNextLineGroup(pushback_reader, this)) != null) { if(new_line_group instanceof SimpleDocumentFeature) { final SimpleDocumentFeature new_feature = (SimpleDocumentFeature)new_line_group; // try several times because adding the Feature may cause more than // one exception int i; EntryInformationException saved_error = null; for(i = 0; i 1) { final int[] header_positions = fasta_sequence.getFastaHeaderPositions(); //final FeatureTable feature_table = getFeatureTable(); for(int i = 0 ; i < header_strings.length ; ++i) { try { final Range new_range; if(i == header_strings.length - 1) { if(header_positions[i] == fasta_sequence.length()) throw new ReadFormatException("empty FASTA record: >" + header_strings[i]); new_range = new Range(header_positions[i] + 1, fasta_sequence.length()); } else { if(header_positions[i] == header_positions[i+1]) throw new ReadFormatException("empty FASTA record: >" + header_strings[i]); new_range = new Range(header_positions[i] + 1, header_positions[i+1]); } String thisHeader[] = header_strings[i].split("\\s"); final QualifierVector qualifiers = new QualifierVector(); qualifiers.setQualifier(new Qualifier("note", header_strings[i])); qualifiers.setQualifier(new Qualifier("label", thisHeader[0])); if(i % 2 == 0) qualifiers.setQualifier(new Qualifier("colour", "10")); else qualifiers.setQualifier(new Qualifier("colour", "11")); //ReadOnlyEmblStreamFeature final EmblStreamFeature new_feature = new EmblStreamFeature(new Key("fasta_record"), new Location(new_range), qualifiers); fake_fasta_features.add(new_feature); // record coordinates to adjust feature coordinates if(contig_ranges == null) contig_ranges = new Hashtable(); // find the sequence id from the header contig_ranges.put(thisHeader[0], new_range); } catch(InvalidRelationException e) { throw new Error("internal error - unexpected exception: " + e); } catch(OutOfRangeException e) { throw new Error("internal error - unexpected exception: " + e); } } addFakeFeatures(); } } this.in_constructor = false; } /** * Create a new SimpleDocumentEntry with no Document associated with it. * @param entry_information The EntryInformation object of the new Entry. **/ public SimpleDocumentEntry(final EntryInformation entry_information) { this.entry_information = new SimpleEntryInformation(entry_information); } /** * Create a new SimpleDocumentEntry that will be a copy of the given Entry * and has no Document associated with it. The new SimpleDocumentEntry * cannot be saved to a file with save() unless save(Document) has been * called first. Some qualifier and location information will be lost if * the argument Entry is a different type to this class. * @param entry_information The EntryInformation object of the Entry that * will contain this Feature. * @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. **/ public SimpleDocumentEntry(final EntryInformation entry_information, final Entry new_entry, final boolean force) throws EntryInformationException { this.entry_information = new SimpleEntryInformation(entry_information); if(new_entry.getClass().equals(this.getClass()) || (this instanceof GFFDocumentEntry && new_entry instanceof DatabaseDocumentEntry)) { try { setHeaderText(new_entry.getHeaderText()); } catch(IOException e) { System.err.println(e); // if it doesn't work just ignore it } } final FeatureEnumeration feature_enum = new_entry.features(); Set failed = null; while(feature_enum.hasMoreFeatures()) { final Feature new_feature = feature_enum.nextFeature(); try { if(force) { Feature f = (SimpleDocumentFeature)makeNativeFeature(new_feature, true); if(f != null) { if(forcedAdd(f) == null) { if(failed == null) failed = new HashSet(); failed.add(new_feature.getKey().getKeyString()); } } } else { final Object docFeature = makeNativeFeature(new_feature, true); if(docFeature instanceof SimpleDocumentFeature[]) { SimpleDocumentFeature[] docFeatures = (SimpleDocumentFeature[])docFeature; for(int i=0; i 0) return buffer.toString(); else return null; } /** * 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. If null the current header will be * removed. * @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 { final Vector new_line_groups = new Vector(); if(new_header != null) { final Reader reader = new StringReader(new_header); final LinePushBackReader pushback_reader = new LinePushBackReader(reader); LineGroup new_line_group; try { while((new_line_group = LineGroup.readNextLineGroup(pushback_reader, this)) != null) { if(new_line_group instanceof MiscLineGroup) new_line_groups.addElement(new_line_group); else throw new ReadFormatException("the header must contain only " + "header lines"); } } catch(InvalidRelationException e) { throw new ReadFormatException("the header must contain only " + "header lines"); } } // now remove all the EmblMisc and GenbankMisc LineGroup objects from // this Entry for(int i = line_groups.size() - 1 ; i >= 0 ; --i) { final LineGroup current_line_group = line_groups.elementAt(i); if(current_line_group instanceof MiscLineGroup) line_groups.removeElementAt(i); } if(new_header != null) { // then add the new LineGroup objects for(int i = 0 ; i < new_line_groups.size() ; ++i) line_groups.insertElementAt(new_line_groups.elementAt(i), i); } setDirtyFlag(); return true; } /** * Write this Entry to the given stream. * @param writer The stream to write to. * @exception IOException thrown if there is a problem writing the entry. **/ public void writeToStream(final Writer writer) throws IOException { removeFakeFeatures(); try { for(int i = 0 ; i < line_groups.size() ; ++i) { final LineGroup current_line_group = line_groups.elementAt(i); if(this instanceof GFFDocumentEntry && current_line_group instanceof FastaStreamSequence) LineGroup.writeStartOfGFFEntry(writer); current_line_group.writeToStream(writer); } if(line_groups.size() == 1) { // don't write out the "//" end of entry marker if we have only one // LineGroup - this makes life easier for external programs that need // to read the feature table or sequence return; } else { if(line_groups.size() == 2) { final LineGroup second_line_group = line_groups.elementAt(1); // don't write out the "//" end of entry marker if this is raw or // FASTA sequence if(second_line_group instanceof RawStreamSequence || second_line_group instanceof FastaStreamSequence) return; } } if(this instanceof PublicDBDocumentEntry) LineGroup.writeEndOfEMBLEntry(writer); } finally { addFakeFeatures(); } } /** * Return a count of the number of Feature objects in this Entry. **/ public int getFeatureCount() { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return 0; else return feature_table.getFeatureCount(); } /** * The method is identical to add() except that it doesn't check the * read_only flag before adding and it doesn't throw ReadOnlyExceptions. * This is used by the constructor to avoid exceptions. * * Add the given Feature to this Entry. If the Feature is already in an * Entry then Entry.remove() should be called on that Entry before calling * Entry.add(). An Error will be thrown otherwise. * @param throw_entry_info_exceptions if true throw * EntryInformationExceptions, otherwise just send a ReadEvent. * @exception EntryInformationException Thrown if this Entry * cannot contain the Key, Qualifier or Key/Qualifier combination of the * given Feature or if a required qualifier is missing. * @return A reference that was passed to add(), if that Feature can be * stored directly in this Entry, otherwise returns a reference to a new * Feature, that is a copy of the argument. The argument reference * should not be used after the call to add(), unless the return * reference happens to be the same as the argument. **/ private Feature addInternal(final Feature feature, final boolean throw_entry_info_exceptions) throws EntryInformationException { if(feature.getEntry() != null) throw new Error("internal error - a feature must have one owner"); final EntryInformation entry_information = getEntryInformation(); if(!entry_information.isValidKey(feature.getKey())) { final String message = feature.getKey() + " is not a valid key"; fireEvent(new ReadEvent(this, message)); throw new InvalidKeyException(feature.getKey() + " is not a valid " + "key for this entry", feature.getKey()); } final QualifierVector new_qualifiers = feature.getQualifiers(); final Key new_key = feature.getKey(); // check the qualifiers for(int i = 0 ; i < new_qualifiers.size() ; ++i) { final Qualifier this_qualifier = (Qualifier)new_qualifiers.elementAt(i); final String this_qualifier_name = this_qualifier.getName(); if(!entry_information.isValidQualifier(new_key, this_qualifier_name)) { final String message = new_key + " can't have " + this_qualifier_name + " as a qualifier"; fireEvent(new ReadEvent(this, message)); if(throw_entry_info_exceptions) throw new InvalidRelationException(message, new_key, this_qualifier); } } final SimpleDocumentFeature native_feature = (SimpleDocumentFeature)makeNativeFeature(feature, false); final FeatureTable feature_table = getFeatureTable(); feature_table.add(native_feature); try { native_feature.setDocumentEntry(this); } catch(ReadOnlyException e) { // makeNativeFeature() should never return a read only feature throw new Error("internal error - unexpected exception: " + e); } setDirtyFlag(); return native_feature; } /** * Add the given Feature to this Entry. If the Feature is already in an * Entry then Entry.remove() should be called on that Entry before calling * Entry.add(). An Error will be thrown otherwise. * @exception ReadOnlyException If this entry is read only. * @exception EntryInformationException Thrown if this Entry * cannot contain the Key, Qualifier or Key/Qualifier combination of the * given Feature or if a required qualifier is missing. * @return A reference that was passed to add(), if that Feature can be * stored directly in this Entry, otherwise returns a reference to a new * Feature, that is a copy of the argument. The argument reference * should not be used after the call to add(), unless the return * reference happens to be the same as the argument. **/ public Feature add(final Feature feature) throws EntryInformationException, ReadOnlyException { if(isReadOnly()) throw new ReadOnlyException(); return addInternal(feature, true); } /** * Add the given Feature to this Entry. If the Feature is already in an * Entry then Entry.remove() should be called on that Entry before calling * Entry.forcedAdd()(An Error will be thrown otherwise). Invalid * qualifiers will be quietly thrown away. Features with invalid keys will * not be added(and null will be returned). "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). Any * qualifiers that are required for this Entry will be quietly added(with * a zero-length string as the value). * @exception ReadOnlyException If this entry is read only. * @return A reference that was passed to add(), if that Feature can be * stored directly in this Entry, otherwise returns a reference to a new * Feature, that is a copy of the argument. The argument reference * should not be used after the call to add(), unless the return * reference happens to be the same as the argument. Returns null if and * only if the new Feature has a key that is invalid for this Entry. **/ public Feature forcedAdd(final Feature feature) throws ReadOnlyException { if(isReadOnly()) throw new ReadOnlyException(); if(feature.getEntry() != null) throw new Error("internal error - a feature must have one owner"); final EntryInformation entry_information = getEntryInformation(); if(!entry_information.isValidKey(feature.getKey())) return null; final QualifierVector feature_qualifiers = feature.getQualifiers(); final QualifierVector fixed_qualifiers = new QualifierVector(); final Key new_key = feature.getKey(); // set to true if there is an invalid qualifier boolean qualifiers_fixed = false; // check the qualifiers for(int i = 0 ; i < feature_qualifiers.size() ; ++i) { final Qualifier this_qualifier = (Qualifier)feature_qualifiers.elementAt(i); final String this_qualifier_name = this_qualifier.getName(); if(entry_information.isValidQualifier(new_key, this_qualifier_name)) fixed_qualifiers.setQualifier(this_qualifier); else qualifiers_fixed = true; } final SimpleDocumentFeature native_feature = (SimpleDocumentFeature)makeNativeFeature(feature, false); if(qualifiers_fixed) { try { native_feature.setQualifiers(fixed_qualifiers); } catch(EntryInformationException e) { throw new Error("internal error - unexpected exception: " + e); } } else { // otherwise use the feature as is } final FeatureTable feature_table = getFeatureTable(); feature_table.add(native_feature); native_feature.setDocumentEntry(this); setDirtyFlag(); return native_feature; } /** * The method is identical to remove() except that it doesn't check the * read_only flag before removing and it doesn't throw ReadOnlyExceptions. * * Remove the given Feature from this Entry. * @return true if and only if the Feature was in this Entry. **/ protected boolean removeInternal(Feature feature) { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return false; else { final SimpleDocumentFeature feature_from_table = (SimpleDocumentFeature) feature_table.remove(feature); if(feature_from_table == null) return false; else { try { feature_from_table.setDocumentEntry(null); } catch(ReadOnlyException e) { throw new Error("internal error - unexpected exception: " + e); } // get rid of the feature table if(feature_table.getFeatureCount() == 0) removeLineGroup(feature_table); setDirtyFlag(); return true; } } } /** * Remove the given Feature from this Entry. * @return true if and only if the Feature was in this Entry. **/ public boolean remove(Feature feature) throws ReadOnlyException { if(isReadOnly() || feature.isReadOnly()) throw new ReadOnlyException(); return removeInternal(feature); } /** * Return the ith Feature from this Entry. This Features are returned in a * consistent order, sorted by the first base of each Feature. **/ public Feature getFeatureAtIndex(int i) { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return null; else return feature_table.getFeatureAtIndex(i); } /** * Return the index of the given Feature. This does the reverse of * getFeatureAtIndex(). **/ public int indexOf(final Feature feature) { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return -1; else return feature_table.indexOf(feature); } /** * Returns true if and only if this Entry contains the given feature. **/ public boolean contains(final Feature feature) { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return false; else return feature_table.contains(feature); } /** * Create a new Feature object of an appropriate type in this Entry. * @param key The new feature key * @param location The Location object for the new feature * @param qualifiers The qualifiers for the new feature(can be null if * there are no qualifiers). **/ public Feature createFeature(final Key key, final Location location, final QualifierVector qualifiers) throws EntryInformationException, ReadOnlyException, OutOfRangeException { if(isReadOnly()) throw new ReadOnlyException(); final Feature new_feature; if(this instanceof EmblDocumentEntry) new_feature = new EmblStreamFeature(key, location, qualifiers); else if(this instanceof DatabaseDocumentEntry) new_feature = new DatabaseStreamFeature(key, location, qualifiers); else if(this instanceof GFFDocumentEntry) new_feature = new GFFStreamFeature(key, location, qualifiers); else new_feature = new GenbankStreamFeature(key, location, qualifiers); add(new_feature); setDirtyFlag(); return new_feature; } /** * 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 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) { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return new FeatureVector(); else return feature_table.getFeaturesInRange(range); } /** * Return a vector containing the references of all the Feature objects in * this Entry. * @return The features of this Entry. The returned object * is a copy - changes will not effect the Entry object itself. **/ public FeatureVector getAllFeatures() { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) return new FeatureVector(); else return feature_table.getAllFeatures(); } /** * Returns an enumeration of the Feature objects in this * SimpleDocumentEntry. The returned Enumeration 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() { final FeatureTable feature_table = findFeatureTable(); if(feature_table == null) { return new FeatureEnumeration() { public boolean hasMoreFeatures() { return false; } public Feature nextFeature() { return null; } }; } else return feature_table.features(); } /** * Return the Sequence object from this entry or null if it does not * contain one. * @return a Sequence object for this Entry. the returned object is * not a copy - changes to it will change the Entry object itself **/ public Sequence getSequence() { for(int i = 0 ; i < line_groups.size() ; ++i) { final LineGroup current_line_group = line_groups.elementAt(i); if(current_line_group instanceof Sequence) return(Sequence) current_line_group; } return null; } /** * Add a new LineGroup object to this Entry. * @param new_line_group A new LineGroup to add. **/ private void addLineGroup(final LineGroup new_line_group) { if(new_line_group instanceof FeatureHeader) { // insert FH lines before the Sequence and Features(if any) for(int i = 0 ; i < line_groups.size() ; ++i) { final LineGroup this_line_group = line_groups.elementAt(i); if(this_line_group instanceof Feature || this_line_group instanceof FeatureTable || this_line_group instanceof Sequence) { line_groups.insertElementAt(new_line_group, i); return; } } } else if(new_line_group instanceof Feature) { // Features before the Sequence and FeatureTable(if any) for(int i = 0 ; i < line_groups.size() ; ++i) { final LineGroup this_line_group = line_groups.elementAt(i); if(this_line_group instanceof FeatureTable || this_line_group instanceof Sequence) { line_groups.insertElementAt(new_line_group, i); return; } } } else if(!(new_line_group instanceof Sequence) && !(new_line_group instanceof FeatureTable)) { if(new_line_group instanceof GFFMisc) { String line = ((GFFMisc)new_line_group).toString(); if(line.indexOf("FASTA") > -1) // ignore return; } // insert before features and sequence for(int i = 0 ; i < line_groups.size() ; ++i) { final LineGroup this_line_group = line_groups.elementAt(i); if(this_line_group instanceof Feature || this_line_group instanceof FeatureTable || this_line_group instanceof FeatureHeader || this_line_group instanceof Sequence) { line_groups.insertElementAt(new_line_group, i); return; } } } // otherwise add at end line_groups.addElement(new_line_group); } /** * Return the name of this Entry or null if it has no name. **/ public String getName() { if(getDocument() == null || getDocument().getName() == null) return null; else return getDocument().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) { if(name == null || name.length() == 0) return false; setDocument(new FileDocument(new File(name))); return true; } /** * Return the EntryInformation object for this Entry. **/ public EntryInformation getEntryInformation() { return entry_information; } /** * Create and return a new FeatureTable(). The new FeatureTable will be * added to line_groups in the appropriate place. **/ private FeatureTable createFeatureTable() { final FeatureTable new_feature_table; new_feature_table = new StreamFeatureTable(); // put feature table just before SEQUENCE LineGroup if(line_groups.size() > 0 && line_groups.lastElement() instanceof Sequence) line_groups.insertElementAt(new_feature_table, line_groups.size() - 1); else // no SEQUENCE so add the feature table at the end line_groups.insertElementAt(new_feature_table, line_groups.size()); return new_feature_table; } /** * Return the FeatureTable of this DocumentEntry or null if there isn't one * yet. **/ private FeatureTable findFeatureTable() { final int line_groups_size = line_groups.size(); for(int i = 0; i < line_groups_size; ++i) { final LineGroup current_line_group = line_groups.elementAt(i); if(current_line_group instanceof FeatureTable) return (FeatureTable)current_line_group; } return null; } /** * Return the FeatureTable object from this entry(which will be created if * this object does not already contain one). This method has package * scope because other package should add, remove and query Features in the * FeatureTable indirectly(through the Entry class). * @return a FeatureTable object for this Entry. the returned object is * not a copy - changes to it will change the Entry object itself **/ private FeatureTable getFeatureTable() { final FeatureTable found_feature_table = findFeatureTable(); if(found_feature_table == null) return createFeatureTable(); else return found_feature_table; } /** * Remove the given LineGroup from this SimpleDocumentEntry. **/ private void removeLineGroup(final LineGroup line_group) { for(int i = 0 ; i < line_groups.size() ; ++i) { final LineGroup current_line_group = line_groups.elementAt(i); if(current_line_group == line_group) { line_groups.removeElementAt(i); setDirtyFlag(); return; } } } /** * Return the File reference that was passed to the constructor or null if * none was passed. **/ public Document getDocument() { return document; } /** * Set the document to use when saving this DocumentEntry. **/ public void setDocument(final Document document) { this.document = document; } /** * Write this Entry to the Document it came from. This method uses the * current Document reference(as given by getDocument()) and will throw a * NullPointerException if the is no current Document ie. if getDocument() * returns null. Use save(Document) to save the current Entry * to a different Document. * @exception IOException thrown if there is an IO problem saving the entry. **/ public void save() throws IOException { save(getDocument()); } /** * Write this Entry to the given Document. * @param document This is the file that we will write to. * @exception IOException thrown if there is an IO problem saving the entry. **/ public void save(final Document document) throws IOException { final Writer out_file; try { out_file = document.getWriter(); } catch(NullPointerException npe) { return; } writeToStream(out_file); out_file.close(); last_change_time = null; } /** * Returns true if and only if this entry is read only. **/ public boolean isReadOnly() { return false; } /** * Returns true if and only if there have been some changes to this Entry * since the last save. **/ public boolean hasUnsavedChanges() { return last_change_time != null; } /** * Set last_change_time so that hasUnsavedChanges() will return true. * last_change_time can be read with getLastChangeTime(). **/ public void setDirtyFlag() { if(in_constructor) { // features are being added, but the entry hasn't really changed } else { if(autosave_thread == null && getName() != null) { // this is the first change so start autosaving autosave_thread = new DocumentEntryAutosaveThread(this); autosave_thread.start(); } final java.util.Calendar calendar = java.util.Calendar.getInstance(); last_change_time = calendar.getTime(); } } /** * Return the Date when this Entry last changed. **/ public Date getLastChangeTime() { return last_change_time; } /** * Add the given Sequence object to this Entry. **/ public void setSequence(final StreamSequence sequence) { final Sequence old_sequence = getSequence(); if(old_sequence != null) removeLineGroup((StreamSequence) old_sequence); addLineGroup(sequence); } /** * If the given feature can be added directly to this Entry, then return * it, otherwise create and return a new feature of the appropriate type * (eg. return an EmblStreamFeature if this Entry is a EmblDocumentEntry). * The feature should not be in an Entry when this method is called. * @param copy if true then always make a new copy of the Feature. **/ protected abstract Object makeNativeFeature(final Feature feature, final boolean copy); /** * If the given Sequence can be added directly to this Entry, then return a * copy of it, otherwise create and return a new feature of the appropriate * type for this Entry. **/ protected abstract StreamSequence makeNativeSequence(final Sequence sequence); /** * Add the elements of fake_fasta_features to the feature table. **/ private void addFakeFeatures() { final FeatureTable feature_table = getFeatureTable(); for(int i = 0 ; i < fake_fasta_features.size() ; ++i) feature_table.add(fake_fasta_features.featureAt(i)); } /** * Remove the elements of fake_fasta_features from the feature table. **/ private void removeFakeFeatures() { final FeatureTable feature_table = getFeatureTable(); for(int i = 0 ; i < fake_fasta_features.size() ; ++i) feature_table.remove(fake_fasta_features.featureAt(i)); } /** * Add an object that will receive ReadEvents. **/ private void addReadListener(final ReadListener listener) { listeners.addElement(listener); } /** * Send the given event to all the listeners. **/ private void fireEvent(final ReadEvent event) { for(int i = 0 ; i < listeners.size() ; ++i) ((ReadListener)listeners.elementAt(i)).notify(event); } public void dispose() { for(int i=0; i