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