1 /* SimpleDocumentEntry.java
2  *
3  * created: Tue Feb 15 2000
4  *
5  * This file is part of Artemis
6  *
7  * Copyright(C) 2000  Genome Research Limited
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or(at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/io/SimpleDocumentEntry.java,v 1.30 2009-09-03 13:33:18 tjc Exp $
24  */
25 
26 package uk.ac.sanger.artemis.io;
27 
28 import uk.ac.sanger.artemis.util.*;
29 
30 import java.io.*;
31 import java.util.Collections;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Set;
36 import java.util.Vector;
37 import java.util.Hashtable;
38 import java.util.Map;
39 
40 /**
41  *  This class contains the methods common to all DocumentEntry objects.
42  *
43  *  @author Kim Rutherford <kmr@sanger.ac.uk>
44  *  @version $Id: SimpleDocumentEntry.java,v 1.30 2009-09-03 13:33:18 tjc Exp $
45  **/
46 
47 abstract public class SimpleDocumentEntry
48                       implements DocumentEntry
49 {
50 
51   /**
52    *  A vector of ReadOnlyEmblStreamFeature objects - one for each fasta
53    *  record in the DocumentEntry.  These won't be written when the
54    *  DocumentEntry is written.
55    **/
56   private FeatureVector fake_fasta_features = new FeatureVector();
57 
58   /** EntryInformation object that was passed to constructor */
59   final private EntryInformation entry_information;
60 
61   /** collection to send ReadEvents to */
62   private Vector<ReadListener> listeners = new Vector<ReadListener>();
63 
64   /**
65    *  The Document object that was passed to the constructor.  This should be
66    *  the document that this SimpleDocumentEntry was read from.
67    **/
68   private Document document = null;
69 
70   /**
71    *  This contains all the lines(stored as LineGroup objects) from the entry
72    *  stream that was passed to the constructor.
73    **/
74   protected Vector<LineGroup> line_groups = new Vector<LineGroup>();
75 
76   /**
77    *  The DocumentEntryAutosaveThread that is started when the first call is
78    *  made to setDirtyFlag().
79    **/
80   private Thread autosave_thread = null;
81 
82   /**
83    *  The Date when this Entry last changed or null if this Entry
84    *  hasn't changed since the last save.  Set to null by save().
85    **/
86   private java.util.Date last_change_time = null;
87 
88   /**
89    *  Set to true in the constructor while features are added.  setDirtyFlag()
90    *  will do nothing while this is true.
91    **/
92   protected boolean in_constructor = false;
93 
94   protected Hashtable<String, Range> contig_ranges;
95 
96   /**
97    *  Create a new SimpleDocumentEntry from the given Document.
98    *  @param entry_information The EntryInformation object of the new Entry.
99    *  @param document This is the file that we will read from.  This is also
100    *    used for saving the entry back to the file it came from and to give
101    *    the new object a name.
102    *  @exception IOException thrown if there is a problem reading the entry -
103    *    most likely ReadFormatException.
104    *  @exception EntryInformationException Thrown if force is false and if this
105    *    Entry cannot contain the Key, Qualifier or Key/Qualifier combination of
106    *    one of the features in the given Entry.
107    **/
SimpleDocumentEntry(final EntryInformation entry_information, final Document document, final ReadListener read_listener)108   public SimpleDocumentEntry(final EntryInformation entry_information,
109                               final Document document,
110                               final ReadListener read_listener)
111       throws IOException, EntryInformationException
112   {
113     this.document = document;
114     this.entry_information = new SimpleEntryInformation(entry_information);
115     this.in_constructor    = true;  // flag used by setDirtyFlag()
116 
117     if(read_listener != null)
118       addReadListener(read_listener);
119 
120     final LinePushBackReader pushback_reader =
121               getDocument().getLinePushBackReader();
122 
123     LineGroup new_line_group;
124 
125     final int MAX_LOOP = 9999;
126 
127     while((new_line_group =
128             LineGroup.readNextLineGroup(pushback_reader, this)) != null)
129     {
130       if(new_line_group instanceof SimpleDocumentFeature)
131       {
132         final SimpleDocumentFeature new_feature =
133                    (SimpleDocumentFeature)new_line_group;
134 
135         // try several times because adding the Feature may cause more than
136         // one exception
137         int i;
138         EntryInformationException saved_error = null;
139 
140         for(i = 0; i<MAX_LOOP; ++i)
141         {
142           try
143           {
144             addInternal(new_feature, true);
145             break;
146           }
147           catch(EntryInformationException e)
148           {
149             getEntryInformation().fixException(e);
150             saved_error = e;
151           }
152         }
153 
154         if(i == MAX_LOOP)
155           throw new Error("internal error - too many exceptions: " +
156                            saved_error.getMessage());
157       }
158       else
159         addLineGroup(new_line_group);
160 
161       if(new_line_group instanceof IndexFastaStream)
162         break;
163     }
164 
165     pushback_reader.close();
166 
167     // we added some features above hence:
168     last_change_time = null;
169 
170     final Sequence sequence = getSequence();
171 
172     if(sequence != null && sequence instanceof FastaStreamSequence)
173     {
174       // add a feature for each FASTA record if there are more
175       // than one record in the FASTA sequence
176       final FastaStreamSequence fasta_sequence =
177                                 (FastaStreamSequence)sequence;
178 
179       final String[] header_strings = fasta_sequence.getFastaHeaderStrings();
180 
181       if(header_strings.length > 1)
182       {
183         final int[] header_positions =
184                       fasta_sequence.getFastaHeaderPositions();
185 
186         //final FeatureTable feature_table =
187         getFeatureTable();
188 
189         for(int i = 0 ; i < header_strings.length ; ++i)
190         {
191           try
192           {
193             final Range new_range;
194 
195             if(i == header_strings.length - 1)
196             {
197               if(header_positions[i] == fasta_sequence.length())
198                 throw new ReadFormatException("empty FASTA record: >" +
199                                                header_strings[i]);
200 
201               new_range = new Range(header_positions[i] + 1,
202                                     fasta_sequence.length());
203             }
204             else
205             {
206               if(header_positions[i] == header_positions[i+1])
207                 throw new ReadFormatException("empty FASTA record: >" +
208                                                header_strings[i]);
209 
210               new_range = new Range(header_positions[i] + 1,
211                                     header_positions[i+1]);
212             }
213 
214             String thisHeader[] = header_strings[i].split("\\s");
215             final QualifierVector qualifiers = new QualifierVector();
216 
217             qualifiers.setQualifier(new Qualifier("note",
218                                                     header_strings[i]));
219             qualifiers.setQualifier(new Qualifier("label", thisHeader[0]));
220             if(i % 2 == 0)
221               qualifiers.setQualifier(new Qualifier("colour", "10"));
222             else
223               qualifiers.setQualifier(new Qualifier("colour", "11"));
224 
225             //ReadOnlyEmblStreamFeature
226             final EmblStreamFeature new_feature =
227               new EmblStreamFeature(new Key("fasta_record"),
228                                              new Location(new_range),
229                                              qualifiers);
230 
231             fake_fasta_features.add(new_feature);
232 
233             // record coordinates to adjust feature coordinates
234             if(contig_ranges == null)
235               contig_ranges = new Hashtable<String, Range>();
236 
237             // find the sequence id from the header
238             contig_ranges.put(thisHeader[0], new_range);
239           }
240           catch(InvalidRelationException e)
241           {
242             throw new Error("internal error - unexpected exception: " + e);
243           }
244           catch(OutOfRangeException e)
245           {
246             throw new Error("internal error - unexpected exception: " + e);
247           }
248         }
249         addFakeFeatures();
250       }
251     }
252 
253     this.in_constructor = false;
254   }
255 
256   /**
257    *  Create a new SimpleDocumentEntry with no Document associated with it.
258    *  @param entry_information The EntryInformation object of the new Entry.
259    **/
SimpleDocumentEntry(final EntryInformation entry_information)260   public SimpleDocumentEntry(final EntryInformation entry_information)
261   {
262     this.entry_information = new SimpleEntryInformation(entry_information);
263   }
264 
265   /**
266    *  Create a new SimpleDocumentEntry that will be a copy of the given Entry
267    *  and has no Document associated with it.  The new SimpleDocumentEntry
268    *  cannot be saved to a file with save() unless save(Document) has been
269    *  called first.  Some qualifier and location information will be lost if
270    *  the argument Entry is a different type to this class.
271    *  @param entry_information The EntryInformation object of the Entry that
272    *    will contain this Feature.
273    *  @param force If true then invalid qualifiers and any features with
274    *    invalid keys in the new Entry will be quietly thrown away.  "Invalid"
275    *    means that the key/qualifier is not allowed to occur in an Entry of
276    *    this type(probably determined by the EntryInformation object of this
277    *    Entry).  If false an EntryInformationException will be thrown for
278    *    invalid keys or qualifiers.
279    **/
SimpleDocumentEntry(final EntryInformation entry_information, final Entry new_entry, final boolean force)280   public SimpleDocumentEntry(final EntryInformation entry_information,
281                               final Entry new_entry, final boolean force)
282       throws EntryInformationException
283   {
284     this.entry_information = new SimpleEntryInformation(entry_information);
285 
286     if(new_entry.getClass().equals(this.getClass()) ||
287         (this instanceof GFFDocumentEntry && new_entry instanceof DatabaseDocumentEntry))
288     {
289       try
290       {
291         setHeaderText(new_entry.getHeaderText());
292       }
293       catch(IOException e)
294       {
295         System.err.println(e);
296         // if it doesn't work just ignore it
297       }
298     }
299 
300     final FeatureEnumeration feature_enum = new_entry.features();
301 
302     Set<String> failed = null;
303     while(feature_enum.hasMoreFeatures())
304     {
305       final Feature new_feature = feature_enum.nextFeature();
306 
307       try
308       {
309         if(force)
310         {
311           Feature f = (SimpleDocumentFeature)makeNativeFeature(new_feature, true);
312 
313           if(f != null)
314           {
315             if(forcedAdd(f) == null)
316             {
317               if(failed == null)
318                 failed = new HashSet<String>();
319               failed.add(new_feature.getKey().getKeyString());
320             }
321           }
322         }
323         else
324         {
325           final Object docFeature = makeNativeFeature(new_feature, true);
326           if(docFeature instanceof SimpleDocumentFeature[])
327           {
328             SimpleDocumentFeature[] docFeatures = (SimpleDocumentFeature[])docFeature;
329             for(int i=0; i<docFeatures.length; i++)
330               add((SimpleDocumentFeature)docFeatures[i]);
331           }
332           else if(docFeature != null)
333             add((SimpleDocumentFeature)docFeature);
334         }
335       }
336       catch(ReadOnlyException e)
337       {
338         throw new Error("internal error - unexpected exception: " + e);
339       }
340     }
341 
342     if(failed != null)
343       UI.warn("Failed to use the following keys\n"+failed.toString(), "Warning - unknown keys");
344 
345     final Sequence new_sequence = new_entry.getSequence();
346 
347     if(new_sequence != null)
348       setSequence(makeNativeSequence(new_sequence));
349   }
350 
351   /**
352    *  Return the text of the header of this Entry or null if there is no
353    *  header.
354    **/
getHeaderText()355   public String getHeaderText()
356   {
357     final StringBuffer buffer = new StringBuffer();
358 
359     for(int i = 0 ; i < line_groups.size() ; ++i)
360     {
361       final LineGroup current_line_group = line_groups.elementAt(i);
362 
363       if(!(current_line_group instanceof FeatureTable) &&
364          !(current_line_group instanceof Sequence))
365         buffer.append(current_line_group.toString());
366     }
367 
368     if(buffer.length() > 0)
369       return buffer.toString();
370     else
371       return null;
372   }
373 
374   /**
375    *  Set the header of this Entry to be the given text.
376    *  @return true if and only if the header was successfully set.  Not all
377    *    Entry objects can change their header, so it is up to the calling
378    *    function to check the return value.  If null the current header will be
379    *    removed.
380    *  @exception IOException thrown if there is a problem reading the header
381    *    from the String - most likely ReadFormatException.
382    **/
setHeaderText(final String new_header)383   public boolean setHeaderText(final String new_header)
384       throws IOException
385   {
386 
387     final Vector<LineGroup> new_line_groups = new Vector<LineGroup>();
388 
389     if(new_header != null)
390     {
391       final Reader reader = new StringReader(new_header);
392 
393       final LinePushBackReader pushback_reader =
394                           new LinePushBackReader(reader);
395 
396       LineGroup new_line_group;
397 
398       try
399       {
400         while((new_line_group =
401                 LineGroup.readNextLineGroup(pushback_reader, this)) != null)
402         {
403           if(new_line_group instanceof MiscLineGroup)
404             new_line_groups.addElement(new_line_group);
405           else
406             throw new ReadFormatException("the header must contain only " +
407                                            "header lines");
408         }
409       }
410       catch(InvalidRelationException e)
411       {
412         throw new ReadFormatException("the header must contain only " +
413                                        "header lines");
414       }
415     }
416 
417     // now remove all the EmblMisc and GenbankMisc LineGroup objects from
418     // this Entry
419     for(int i = line_groups.size() - 1 ; i >= 0  ; --i)
420     {
421       final LineGroup current_line_group = line_groups.elementAt(i);
422 
423       if(current_line_group instanceof MiscLineGroup)
424         line_groups.removeElementAt(i);
425     }
426 
427     if(new_header != null)
428     {
429       // then add the new LineGroup objects
430       for(int i = 0 ; i < new_line_groups.size() ; ++i)
431         line_groups.insertElementAt(new_line_groups.elementAt(i), i);
432     }
433 
434     setDirtyFlag();
435 
436     return true;
437   }
438 
439   /**
440    *  Write this Entry to the given stream.
441    *  @param writer The stream to write to.
442    *  @exception IOException thrown if there is a problem writing the entry.
443    **/
writeToStream(final Writer writer)444   public void writeToStream(final Writer writer)
445       throws IOException
446   {
447     removeFakeFeatures();
448 
449     try
450     {
451       for(int i = 0 ; i < line_groups.size() ; ++i)
452       {
453         final LineGroup current_line_group = line_groups.elementAt(i);
454         if(this instanceof GFFDocumentEntry &&
455            current_line_group instanceof FastaStreamSequence)
456           LineGroup.writeStartOfGFFEntry(writer);
457         current_line_group.writeToStream(writer);
458       }
459 
460       if(line_groups.size() == 1)
461       {
462         // don't write out the "//" end of entry marker if we have only one
463         // LineGroup - this makes life easier for external programs that need
464         // to read the feature table or sequence
465         return;
466       }
467       else
468       {
469         if(line_groups.size() == 2)
470         {
471           final LineGroup second_line_group = line_groups.elementAt(1);
472 
473           // don't write out the "//" end of entry marker if this is raw or
474           // FASTA sequence
475           if(second_line_group instanceof RawStreamSequence ||
476              second_line_group instanceof FastaStreamSequence)
477             return;
478         }
479       }
480 
481       if(this instanceof PublicDBDocumentEntry)
482         LineGroup.writeEndOfEMBLEntry(writer);
483 
484     }
485     finally
486     {
487       addFakeFeatures();
488     }
489   }
490 
491   /**
492    *  Return a count of the number of Feature objects in this Entry.
493    **/
getFeatureCount()494   public int getFeatureCount()
495   {
496     final FeatureTable feature_table = findFeatureTable();
497 
498     if(feature_table == null)
499       return 0;
500     else
501       return feature_table.getFeatureCount();
502   }
503 
504   /**
505    *  The method is identical to add() except that it doesn't check the
506    *  read_only flag before adding and it doesn't throw ReadOnlyExceptions.
507    *  This is used by the constructor to avoid exceptions.
508    *
509    *  Add the given Feature to this Entry.  If the Feature is already in an
510    *  Entry then Entry.remove() should be called on that Entry before calling
511    *  Entry.add().  An Error will be thrown otherwise.
512    *  @param throw_entry_info_exceptions if true throw
513    *    EntryInformationExceptions, otherwise just send a ReadEvent.
514    *  @exception EntryInformationException Thrown if this Entry
515    *    cannot contain the Key, Qualifier or Key/Qualifier combination of the
516    *    given Feature or if a required qualifier is missing.
517    *  @return A reference that was passed to add(), if that Feature can be
518    *    stored directly in this Entry, otherwise returns a reference to a new
519    *    Feature, that is a copy of the argument.  The argument reference
520    *    should not be used after the call to add(), unless the return
521    *    reference happens to be the same as the argument.
522    **/
addInternal(final Feature feature, final boolean throw_entry_info_exceptions)523   private Feature addInternal(final Feature feature,
524                                final boolean throw_entry_info_exceptions)
525       throws EntryInformationException
526   {
527     if(feature.getEntry() != null)
528       throw new Error("internal error - a feature must have one owner");
529 
530     final EntryInformation entry_information = getEntryInformation();
531 
532     if(!entry_information.isValidKey(feature.getKey()))
533     {
534       final String message = feature.getKey() + " is not a valid key";
535 
536       fireEvent(new ReadEvent(this, message));
537 
538       throw new InvalidKeyException(feature.getKey() + " is not a valid " +
539                                      "key for this entry", feature.getKey());
540     }
541 
542     final QualifierVector new_qualifiers = feature.getQualifiers();
543 
544     final Key new_key = feature.getKey();
545 
546     // check the qualifiers
547     for(int i = 0 ; i < new_qualifiers.size() ; ++i)
548     {
549       final Qualifier this_qualifier = (Qualifier)new_qualifiers.elementAt(i);
550       final String this_qualifier_name = this_qualifier.getName();
551 
552       if(!entry_information.isValidQualifier(new_key, this_qualifier_name))
553       {
554         final String message = new_key + " can't have " + this_qualifier_name
555                                        + " as a qualifier";
556 
557         fireEvent(new ReadEvent(this, message));
558 
559         if(throw_entry_info_exceptions)
560           throw new InvalidRelationException(message, new_key,
561                                              this_qualifier);
562       }
563     }
564 
565     final SimpleDocumentFeature native_feature =
566       (SimpleDocumentFeature)makeNativeFeature(feature, false);
567 
568     final FeatureTable feature_table = getFeatureTable();
569     feature_table.add(native_feature);
570 
571     try
572     {
573       native_feature.setDocumentEntry(this);
574     }
575     catch(ReadOnlyException e)
576     {
577       // makeNativeFeature() should never return a read only feature
578       throw new Error("internal error - unexpected exception: " + e);
579     }
580 
581     setDirtyFlag();
582 
583     return native_feature;
584   }
585 
586   /**
587    *  Add the given Feature to this Entry.  If the Feature is already in an
588    *  Entry then Entry.remove() should be called on that Entry before calling
589    *  Entry.add().  An Error will be thrown otherwise.
590    *  @exception ReadOnlyException If this entry is read only.
591    *  @exception EntryInformationException Thrown if this Entry
592    *    cannot contain the Key, Qualifier or Key/Qualifier combination of the
593    *    given Feature or if a required qualifier is missing.
594    *  @return A reference that was passed to add(), if that Feature can be
595    *    stored directly in this Entry, otherwise returns a reference to a new
596    *    Feature, that is a copy of the argument.  The argument reference
597    *    should not be used after the call to add(), unless the return
598    *    reference happens to be the same as the argument.
599    **/
add(final Feature feature)600   public Feature add(final Feature feature)
601       throws EntryInformationException, ReadOnlyException
602   {
603     if(isReadOnly())
604       throw new ReadOnlyException();
605 
606     return addInternal(feature, true);
607   }
608 
609   /**
610    *  Add the given Feature to this Entry.  If the Feature is already in an
611    *  Entry then Entry.remove() should be called on that Entry before calling
612    *  Entry.forcedAdd()(An Error will be thrown otherwise).  Invalid
613    *  qualifiers will be quietly thrown away.  Features with invalid keys will
614    *  not be added(and null will be returned).  "Invalid" means that the
615    *  key/qualifier is non allowed to occur in an Entry of this type(probably
616    *  determined by the EntryInformation object of this Entry).  Any
617    *  qualifiers that are required for this Entry will be quietly added(with
618    *  a zero-length string as the value).
619    *  @exception ReadOnlyException If this entry is read only.
620    *  @return A reference that was passed to add(), if that Feature can be
621    *    stored directly in this Entry, otherwise returns a reference to a new
622    *    Feature, that is a copy of the argument.  The argument reference
623    *    should not be used after the call to add(), unless the return
624    *    reference happens to be the same as the argument.  Returns null if and
625    *    only if the new Feature has a key that is invalid for this Entry.
626    **/
forcedAdd(final Feature feature)627   public Feature forcedAdd(final Feature feature)
628       throws ReadOnlyException
629   {
630     if(isReadOnly())
631       throw new ReadOnlyException();
632 
633     if(feature.getEntry() != null)
634       throw new Error("internal error - a feature must have one owner");
635 
636     final EntryInformation entry_information = getEntryInformation();
637 
638     if(!entry_information.isValidKey(feature.getKey()))
639       return null;
640 
641     final QualifierVector feature_qualifiers = feature.getQualifiers();
642     final QualifierVector fixed_qualifiers = new QualifierVector();
643     final Key new_key = feature.getKey();
644 
645     // set to true if there is an invalid qualifier
646     boolean qualifiers_fixed = false;
647 
648     // check the qualifiers
649     for(int i = 0 ; i < feature_qualifiers.size() ; ++i)
650     {
651       final Qualifier this_qualifier = (Qualifier)feature_qualifiers.elementAt(i);
652 
653       final String this_qualifier_name = this_qualifier.getName();
654 
655       if(entry_information.isValidQualifier(new_key, this_qualifier_name))
656         fixed_qualifiers.setQualifier(this_qualifier);
657       else
658         qualifiers_fixed = true;
659     }
660 
661     final SimpleDocumentFeature native_feature =
662       (SimpleDocumentFeature)makeNativeFeature(feature, false);
663 
664     if(qualifiers_fixed)
665     {
666       try
667       {
668         native_feature.setQualifiers(fixed_qualifiers);
669       }
670       catch(EntryInformationException e)
671       {
672         throw new Error("internal error - unexpected exception: " + e);
673       }
674     }
675     else
676     {
677       // otherwise use the feature as is
678     }
679 
680     final FeatureTable feature_table = getFeatureTable();
681     feature_table.add(native_feature);
682     native_feature.setDocumentEntry(this);
683     setDirtyFlag();
684 
685     return native_feature;
686   }
687 
688   /**
689    *  The method is identical to remove() except that it doesn't check the
690    *  read_only flag before removing and it doesn't throw ReadOnlyExceptions.
691    *
692    *  Remove the given Feature from this Entry.
693    *  @return true if and only if the Feature was in this Entry.
694    **/
removeInternal(Feature feature)695   protected boolean removeInternal(Feature feature)
696   {
697     final FeatureTable feature_table = findFeatureTable();
698 
699     if(feature_table == null)
700       return false;
701     else
702     {
703       final SimpleDocumentFeature feature_from_table =
704        (SimpleDocumentFeature) feature_table.remove(feature);
705 
706       if(feature_from_table == null)
707         return false;
708       else
709       {
710         try
711         {
712           feature_from_table.setDocumentEntry(null);
713         }
714         catch(ReadOnlyException e)
715         {
716           throw new Error("internal error - unexpected exception: " + e);
717         }
718 
719         // get rid of the feature table
720         if(feature_table.getFeatureCount() == 0)
721           removeLineGroup(feature_table);
722 
723         setDirtyFlag();
724 
725         return true;
726       }
727     }
728   }
729 
730   /**
731    *  Remove the given Feature from this Entry.
732    *  @return true if and only if the Feature was in this Entry.
733    **/
remove(Feature feature)734   public boolean remove(Feature feature)
735       throws ReadOnlyException
736   {
737     if(isReadOnly() || feature.isReadOnly())
738       throw new ReadOnlyException();
739 
740     return removeInternal(feature);
741   }
742 
743   /**
744    *  Return the ith Feature from this Entry.  This Features are returned in a
745    *  consistent order, sorted by the first base of each Feature.
746    **/
getFeatureAtIndex(int i)747   public Feature getFeatureAtIndex(int i)
748   {
749     final FeatureTable feature_table = findFeatureTable();
750 
751     if(feature_table == null)
752       return null;
753     else
754       return feature_table.getFeatureAtIndex(i);
755   }
756 
757   /**
758    *  Return the index of the given Feature.  This does the reverse of
759    *  getFeatureAtIndex().
760    **/
indexOf(final Feature feature)761   public int indexOf(final Feature feature)
762   {
763     final FeatureTable feature_table = findFeatureTable();
764 
765     if(feature_table == null)
766       return -1;
767     else
768       return feature_table.indexOf(feature);
769   }
770 
771   /**
772    *  Returns true if and only if this Entry contains the given feature.
773    **/
contains(final Feature feature)774   public boolean contains(final Feature feature)
775   {
776     final FeatureTable feature_table = findFeatureTable();
777 
778     if(feature_table == null)
779       return false;
780     else
781       return feature_table.contains(feature);
782   }
783 
784   /**
785    *  Create a new Feature object of an appropriate type in this Entry.
786    *  @param key The new feature key
787    *  @param location The Location object for the new feature
788    *  @param qualifiers The qualifiers for the new feature(can be null if
789    *    there are no qualifiers).
790    **/
createFeature(final Key key, final Location location, final QualifierVector qualifiers)791   public Feature createFeature(final Key key,
792                                 final Location location,
793                                 final QualifierVector qualifiers)
794       throws EntryInformationException, ReadOnlyException,
795              OutOfRangeException
796   {
797     if(isReadOnly())
798       throw new ReadOnlyException();
799 
800     final Feature new_feature;
801 
802     if(this instanceof EmblDocumentEntry)
803       new_feature = new EmblStreamFeature(key, location, qualifiers);
804     else if(this instanceof DatabaseDocumentEntry)
805       new_feature = new DatabaseStreamFeature(key, location, qualifiers);
806     else if(this instanceof GFFDocumentEntry)
807       new_feature = new GFFStreamFeature(key, location, qualifiers);
808     else
809       new_feature = new GenbankStreamFeature(key, location, qualifiers);
810 
811     add(new_feature);
812     setDirtyFlag();
813 
814     return new_feature;
815   }
816 
817   /**
818    *  Return a vector containing the references of the Feature objects within
819    *  the given range.
820    *  @param range Return features that overlap this range - ie the start of
821    *    the feature is less than or equal to the end of the range and the end
822    *    of the feature is greater than or equal to the start of the range.
823    *  @return The features of this feature table the are within
824    *    the given range.  The returned object is a copy - changes will not
825    *    effect the FeatureTable object itself.
826    **/
getFeaturesInRange(Range range)827   public FeatureVector getFeaturesInRange(Range range)
828   {
829     final FeatureTable feature_table = findFeatureTable();
830 
831     if(feature_table == null)
832       return new FeatureVector();
833     else
834       return feature_table.getFeaturesInRange(range);
835   }
836 
837   /**
838    *  Return a vector containing the references of all the Feature objects in
839    *  this Entry.
840    *  @return The features of this Entry.  The returned object
841    *    is a copy - changes will not effect the Entry object itself.
842    **/
getAllFeatures()843   public FeatureVector getAllFeatures()
844   {
845     final FeatureTable feature_table = findFeatureTable();
846 
847     if(feature_table == null)
848       return new FeatureVector();
849     else
850       return feature_table.getAllFeatures();
851   }
852 
853   /**
854    *  Returns an enumeration of the Feature objects in this
855    *  SimpleDocumentEntry.  The returned Enumeration object will generate
856    *  all features in this object in turn. The first item generated is the
857    *  item at index 0, then the item at index 1, and so on.
858    **/
features()859   public FeatureEnumeration features()
860   {
861     final FeatureTable feature_table = findFeatureTable();
862 
863     if(feature_table == null)
864     {
865       return new FeatureEnumeration() {
866         public boolean hasMoreFeatures() {
867           return false;
868         }
869 
870         public Feature nextFeature() {
871           return null;
872         }
873       };
874     }
875     else
876       return feature_table.features();
877   }
878 
879   /**
880    *  Return the Sequence object from this entry or null if it does not
881    *  contain one.
882    *  @return a Sequence object for this Entry.  the returned object is
883    *    not a copy - changes to it will change the Entry object itself
884    **/
885   public Sequence getSequence()
886   {
887     for(int i = 0 ; i < line_groups.size() ; ++i)
888     {
889       final LineGroup current_line_group = line_groups.elementAt(i);
890 
891       if(current_line_group instanceof Sequence)
892         return(Sequence) current_line_group;
893     }
894 
895     return null;
896   }
897 
898   /**
899    *  Add a new LineGroup object to this Entry.
900    *  @param new_line_group A new LineGroup to add.
901    **/
902   private void addLineGroup(final LineGroup new_line_group)
903   {
904     if(new_line_group instanceof FeatureHeader)
905     {
906       // insert FH lines before the Sequence and Features(if any)
907       for(int i = 0 ; i < line_groups.size() ; ++i)
908       {
909         final LineGroup this_line_group = line_groups.elementAt(i);
910 
911         if(this_line_group instanceof Feature ||
912             this_line_group instanceof FeatureTable ||
913             this_line_group instanceof Sequence)
914         {
915           line_groups.insertElementAt(new_line_group, i);
916           return;
917         }
918       }
919     }
920     else if(new_line_group instanceof Feature)
921     {
922       // Features before the Sequence and FeatureTable(if any)
923       for(int i = 0 ; i < line_groups.size() ; ++i)
924       {
925         final LineGroup this_line_group = line_groups.elementAt(i);
926 
927         if(this_line_group instanceof FeatureTable ||
928             this_line_group instanceof Sequence)
929         {
930           line_groups.insertElementAt(new_line_group, i);
931           return;
932         }
933       }
934     }
935     else if(!(new_line_group instanceof Sequence) &&
936             !(new_line_group instanceof FeatureTable))
937     {
938       if(new_line_group instanceof GFFMisc)
939       {
940         String line = ((GFFMisc)new_line_group).toString();
941         if(line.indexOf("FASTA") > -1)  // ignore
942           return;
943       }
944 
945       // insert before features and sequence
946       for(int i = 0 ; i < line_groups.size() ; ++i)
947       {
948         final LineGroup this_line_group = line_groups.elementAt(i);
949 
950         if(this_line_group instanceof Feature ||
951            this_line_group instanceof FeatureTable ||
952            this_line_group instanceof FeatureHeader ||
953            this_line_group instanceof Sequence)
954         {
955           line_groups.insertElementAt(new_line_group, i);
956           return;
957         }
958       }
959     }
960 
961     // otherwise add at end
962     line_groups.addElement(new_line_group);
963   }
964 
965   /**
966    *  Return the name of this Entry or null if it has no name.
967    **/
968   public String getName()
969   {
970     if(getDocument() == null || getDocument().getName() == null)
971       return null;
972     else
973       return getDocument().getName();
974   }
975 
976   /**
977    *  Set the name of this Entry - if possible(the return value will let the
978    *  caller know).
979    *  @return true if and only if the name was successfully set.  Not all
980    *    Entry objects can change there name, so it is up to the calling
981    *    function to check the return value.
982    **/
983   public boolean setName(final String name)
984   {
985     if(name == null || name.length() == 0)
986       return false;
987 
988     setDocument(new FileDocument(new File(name)));
989 
990     return true;
991   }
992 
993   /**
994    *  Return the EntryInformation object for this Entry.
995    **/
996   public EntryInformation getEntryInformation()
997   {
998     return entry_information;
999   }
1000 
1001   /**
1002    *  Create and return a new FeatureTable().  The new FeatureTable will be
1003    *  added to line_groups in the appropriate place.
1004    **/
1005   private FeatureTable createFeatureTable()
1006   {
1007     final FeatureTable new_feature_table;
1008 
1009     new_feature_table = new StreamFeatureTable();
1010 
1011     // put feature table just before SEQUENCE LineGroup
1012     if(line_groups.size() > 0 &&
1013         line_groups.lastElement() instanceof Sequence)
1014       line_groups.insertElementAt(new_feature_table,
1015                                    line_groups.size() - 1);
1016     else  // no SEQUENCE so add the feature table at the end
1017       line_groups.insertElementAt(new_feature_table, line_groups.size());
1018 
1019     return new_feature_table;
1020   }
1021 
1022   /**
1023    *  Return the FeatureTable of this DocumentEntry or null if there isn't one
1024    *  yet.
1025    **/
1026   private FeatureTable findFeatureTable()
1027   {
1028     final int line_groups_size = line_groups.size();
1029 
1030     for(int i = 0; i < line_groups_size; ++i)
1031     {
1032       final LineGroup current_line_group = line_groups.elementAt(i);
1033 
1034       if(current_line_group instanceof FeatureTable)
1035         return (FeatureTable)current_line_group;
1036     }
1037 
1038     return null;
1039   }
1040 
1041 
1042   /**
1043    *  Return the FeatureTable object from this entry(which will be created if
1044    *  this object does not already contain one).  This method has package
1045    *  scope because other package should add, remove and query Features in the
1046    *  FeatureTable indirectly(through the Entry class).
1047    *  @return a FeatureTable object for this Entry.  the returned object is
1048    *    not a copy - changes to it will change the Entry object itself
1049    **/
1050   private FeatureTable getFeatureTable()
1051   {
1052     final FeatureTable found_feature_table = findFeatureTable();
1053 
1054     if(found_feature_table == null)
1055       return createFeatureTable();
1056     else
1057       return found_feature_table;
1058   }
1059 
1060   /**
1061    *  Remove the given LineGroup from this SimpleDocumentEntry.
1062    **/
1063   private void removeLineGroup(final LineGroup line_group)
1064   {
1065     for(int i = 0 ; i < line_groups.size() ; ++i)
1066     {
1067       final LineGroup current_line_group = line_groups.elementAt(i);
1068 
1069       if(current_line_group == line_group)
1070       {
1071         line_groups.removeElementAt(i);
1072         setDirtyFlag();
1073         return;
1074       }
1075     }
1076   }
1077 
1078   /**
1079    *  Return the File reference that was passed to the constructor or null if
1080    *  none was passed.
1081    **/
1082   public Document getDocument()
1083   {
1084     return document;
1085   }
1086 
1087 
1088   /**
1089    *  Set the document to use when saving this DocumentEntry.
1090    **/
1091   public void setDocument(final Document document)
1092   {
1093     this.document = document;
1094   }
1095 
1096 
1097   /**
1098    *  Write this Entry to the Document it came from.  This method uses the
1099    *  current Document reference(as given by getDocument()) and will throw a
1100    *  NullPointerException if the is no current Document ie. if getDocument()
1101    *  returns null.  Use save(Document) to save the current Entry
1102    *  to a different Document.
1103    *  @exception IOException thrown if there is an IO problem saving the entry.
1104    **/
1105   public void save()
1106       throws IOException
1107   {
1108     save(getDocument());
1109   }
1110 
1111 
1112   /**
1113    *  Write this Entry to the given Document.
1114    *  @param document This is the file that we will write to.
1115    *  @exception IOException thrown if there is an IO problem saving the entry.
1116    **/
1117   public void save(final Document document)
1118       throws IOException
1119   {
1120     final Writer out_file;
1121     try
1122     {
1123       out_file = document.getWriter();
1124     }
1125     catch(NullPointerException npe)
1126     {
1127       return;
1128     }
1129 
1130     writeToStream(out_file);
1131     out_file.close();
1132 
1133     last_change_time = null;
1134   }
1135 
1136 
1137   /**
1138    *  Returns true if and only if this entry is read only.
1139    **/
1140   public boolean isReadOnly()
1141   {
1142     return false;
1143   }
1144 
1145 
1146   /**
1147    *  Returns true if and only if there have been some changes to this Entry
1148    *  since the last save.
1149    **/
1150   public boolean hasUnsavedChanges()
1151   {
1152     return last_change_time != null;
1153   }
1154 
1155 
1156   /**
1157    *  Set last_change_time so that hasUnsavedChanges() will return true.
1158    *  last_change_time can be read with getLastChangeTime().
1159    **/
1160   public void setDirtyFlag()
1161   {
1162     if(in_constructor)
1163     {
1164       // features are being added, but the entry hasn't really changed
1165     }
1166     else
1167     {
1168        if(autosave_thread == null && getName() != null)
1169        {
1170          // this is the first change so start autosaving
1171          autosave_thread = new DocumentEntryAutosaveThread(this);
1172          autosave_thread.start();
1173        }
1174 
1175       final java.util.Calendar calendar = java.util.Calendar.getInstance();
1176       last_change_time = calendar.getTime();
1177     }
1178   }
1179 
1180 
1181   /**
1182    *  Return the Date when this Entry last changed.
1183    **/
1184   public Date getLastChangeTime()
1185   {
1186     return last_change_time;
1187   }
1188 
1189 
1190   /**
1191    *  Add the given Sequence object to this Entry.
1192    **/
1193   public void setSequence(final StreamSequence sequence)
1194   {
1195     final Sequence old_sequence = getSequence();
1196 
1197     if(old_sequence != null)
1198       removeLineGroup((StreamSequence) old_sequence);
1199 
1200     addLineGroup(sequence);
1201   }
1202 
1203 
1204   /**
1205    *  If the given feature can be added directly to this Entry, then return
1206    *  it, otherwise create and return a new feature of the appropriate type
1207    * (eg. return an EmblStreamFeature if this Entry is a EmblDocumentEntry).
1208    *  The feature should not be in an Entry when this method is called.
1209    *  @param copy if true then always make a new copy of the Feature.
1210    **/
1211   protected abstract Object
1212     makeNativeFeature(final Feature feature,
1213                        final boolean copy);
1214 
1215   /**
1216    *  If the given Sequence can be added directly to this Entry, then return a
1217    *  copy of it, otherwise create and return a new feature of the appropriate
1218    *  type for this Entry.
1219    **/
1220   protected abstract StreamSequence
1221     makeNativeSequence(final Sequence sequence);
1222 
1223   /**
1224    *  Add the elements of fake_fasta_features to the feature table.
1225    **/
1226   private void addFakeFeatures()
1227   {
1228     final FeatureTable feature_table = getFeatureTable();
1229 
1230     for(int i = 0 ; i < fake_fasta_features.size() ; ++i)
1231       feature_table.add(fake_fasta_features.featureAt(i));
1232   }
1233 
1234   /**
1235    *  Remove the elements of fake_fasta_features from the feature table.
1236    **/
1237   private void removeFakeFeatures()
1238   {
1239     final FeatureTable feature_table = getFeatureTable();
1240 
1241     for(int i = 0 ; i < fake_fasta_features.size() ; ++i)
1242       feature_table.remove(fake_fasta_features.featureAt(i));
1243   }
1244 
1245   /**
1246    *  Add an object that will receive ReadEvents.
1247    **/
1248   private void addReadListener(final ReadListener listener)
1249   {
1250     listeners.addElement(listener);
1251   }
1252 
1253   /**
1254    *  Send the given event to all the listeners.
1255    **/
1256   private void fireEvent(final ReadEvent event)
1257   {
1258     for(int i = 0 ; i < listeners.size() ; ++i)
1259      ((ReadListener)listeners.elementAt(i)).notify(event);
1260   }
1261 
1262   public void dispose()
1263   {
1264     for(int i=0; i<line_groups.size(); i++)
1265       line_groups.removeElementAt(i);
1266     line_groups = null;
1267   }
1268 }
1269