001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022package org.biojavax.bio.seq; 023 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Iterator; 028import java.util.Set; 029 030import org.biojava.bio.Annotation; 031import org.biojava.bio.seq.DNATools; 032import org.biojava.bio.seq.RNATools; 033import org.biojava.bio.seq.io.ParseException; 034import org.biojava.bio.symbol.Alphabet; 035import org.biojava.bio.symbol.AlphabetManager; 036import org.biojava.bio.symbol.IllegalAlphabetException; 037import org.biojava.bio.symbol.Location; 038import org.biojava.bio.symbol.SymbolList; 039import org.biojava.bio.symbol.SymbolListViews; 040import org.biojava.utils.AbstractChangeable; 041import org.biojava.utils.ChangeEvent; 042import org.biojava.utils.ChangeSupport; 043import org.biojava.utils.ChangeVetoException; 044import org.biojavax.CrossRef; 045import org.biojavax.CrossReferenceResolver; 046import org.biojavax.Namespace; 047import org.biojavax.RichAnnotation; 048import org.biojavax.RichObjectFactory; 049import org.biojavax.SimpleRichAnnotation; 050import org.biojavax.bio.seq.io.GenbankLocationParser; 051import org.biojavax.ontology.ComparableTerm; 052 053/** 054 * A simple implementation of RichLocation. 055 * @author Richard Holland 056 * @author Mark Schreiber 057 * @author George Waldon 058 * @since 1.5 059 */ 060public class SimpleRichLocation extends AbstractChangeable implements RichLocation { 061 062 private CrossRef crossRef; 063 private RichAnnotation notes = new SimpleRichAnnotation(); 064 protected ComparableTerm term; 065 private Position min; 066 private Position max; 067 private PositionResolver pr = RichObjectFactory.getDefaultPositionResolver(); 068 private CrossReferenceResolver crr = RichObjectFactory.getDefaultCrossReferenceResolver(); 069 private Strand strand; 070 private int rank; 071 protected int circularLength = 0; 072 private RichFeature feature; 073 074 /** 075 * Creates a new instance of SimpleRichSequenceLocation that points to a 076 * single position on the positive strand. 077 * @param pos the location position (a point). 078 * @param rank Rank of location. 079 */ 080 public SimpleRichLocation(Position pos, int rank) { 081 this(pos,pos,rank,Strand.POSITIVE_STRAND); 082 } 083 084 /** 085 * Creates a new instance of SimpleRichSequenceLocation that points to a 086 * single position. 087 * @param pos the location position (a point). 088 * @param rank Rank of location. 089 * @param strand The strand of the location 090 */ 091 public SimpleRichLocation(Position pos, int rank, Strand strand) { 092 this(pos,pos,rank,strand,null); 093 } 094 095 /** 096 * Creates a new instance of SimpleRichSequenceLocation that points to a 097 * single position on another sequence. 098 * @param pos the location position (a point). 099 * @param rank Rank of location. 100 * @param strand the strand of the location 101 * @param crossRef a cross reference to another object (null for parent sequence) 102 */ 103 public SimpleRichLocation(Position pos, int rank, Strand strand, CrossRef crossRef) { 104 this(pos,pos,rank,strand,crossRef); 105 } 106 107 /** 108 * Creates a new instance of SimpleRichSequenceLocation that points to a 109 * range position on the positive strand. 110 * @param min the minimum bound of the location 111 * @param max the maximum bound of the location 112 * @param rank Rank of location. 113 * 114 */ 115 public SimpleRichLocation(Position min, Position max, int rank) { 116 this(min,max,rank,Strand.POSITIVE_STRAND); 117 } 118 119 /** 120 * Creates a new instance of SimpleRichSequenceLocation that points to a 121 * range position. 122 * @param min the minimum bound of the location 123 * @param max the maximum bound of the location 124 * @param rank Rank of location. 125 * @param strand the strand of the location 126 */ 127 public SimpleRichLocation(Position min, Position max, int rank, Strand strand) { 128 this(min,max,rank,strand,null); 129 } 130 131 /** 132 * Creates a new instance of SimpleRichSequenceLocation that points to a 133 * range position on another sequence. 134 * @param min the minimum bound of the location 135 * @param max the maximum bound of the location 136 * @param rank Rank of location. 137 * @param strand the strand of the location 138 * @param crossRef a cross reference to another object (null for parent sequence) 139 */ 140 public SimpleRichLocation(Position min, Position max, int rank, Strand strand, CrossRef crossRef) { 141 this.min = min; 142 this.max = max; 143 this.rank = rank; 144 this.strand = strand; 145 this.crossRef = crossRef; 146 this.feature = null; 147 } 148 149 // Hibernate requirement - not for public use. 150 protected SimpleRichLocation() {} 151 152 /** 153 * {@inheritDoc} 154 */ 155 public void sort() {} 156 157 /** 158 * {@inheritDoc} 159 */ 160 public RichFeature getFeature() { return this.feature; } 161 162 /** 163 * {@inheritDoc} 164 */ 165 public void setFeature(RichFeature feature) throws ChangeVetoException { 166 if(!this.hasListeners(RichLocation.FEATURE)) { 167 this.feature = feature; 168 } else { 169 ChangeEvent ce = new ChangeEvent( 170 this, 171 RichLocation.FEATURE, 172 feature, 173 this.feature 174 ); 175 ChangeSupport cs = this.getChangeSupport(RichLocation.FEATURE); 176 synchronized(cs) { 177 cs.firePreChangeEvent(ce); 178 this.feature = feature; 179 cs.firePostChangeEvent(ce); 180 } 181 } 182 } 183 184 /** 185 * {@inheritDoc} 186 */ 187 public CrossRef getCrossRef() { return this.crossRef; } 188 189 190 // Hibernate requirement - not for public use. 191 protected void setCrossRef(CrossRef crossRef) { this.crossRef = crossRef; } 192 193 /** 194 * {@inheritDoc} 195 */ 196 public Annotation getAnnotation() { return getRichAnnotation(); } 197 198 /** 199 * {@inheritDoc} 200 */ 201 public RichAnnotation getRichAnnotation() { return this.notes; } 202 203 /** 204 * {@inheritDoc} 205 * <b>Warning</b> this method gives access to the original 206 * Collection not a copy. This is required by Hibernate. If you 207 * modify the object directly the behaviour may be unpredictable. 208 */ 209 public Set getNoteSet() { return this.notes.getNoteSet(); } 210 211 /** 212 * {@inheritDoc} 213 * <b>Warning</b> this method gives access to the original 214 * Collection not a copy. This is required by Hibernate. If you 215 * modify the object directly the behaviour may be unpredictable. 216 */ 217 public void setNoteSet(Set notes) throws ChangeVetoException { this.notes.setNoteSet(notes); } 218 219 /** 220 * {@inheritDoc} 221 */ 222 public ComparableTerm getTerm() { return this.term; } 223 224 /** 225 * {@inheritDoc} 226 */ 227 public void setTerm(ComparableTerm term) throws ChangeVetoException { 228 if(!this.hasListeners(RichLocation.TERM)) { 229 this.term = term; 230 } else { 231 ChangeEvent ce = new ChangeEvent( 232 this, 233 RichLocation.TERM, 234 term, 235 this.term 236 ); 237 ChangeSupport cs = this.getChangeSupport(RichLocation.TERM); 238 synchronized(cs) { 239 cs.firePreChangeEvent(ce); 240 this.term = term; 241 cs.firePostChangeEvent(ce); 242 } 243 } 244 } 245 246 /** 247 * {@inheritDoc} 248 */ 249 public int getCircularLength() { return this.circularLength; } 250 251 /** 252 * {@inheritDoc} 253 */ 254 public void setCircularLength(int circularLength) throws ChangeVetoException { 255 if(!this.hasListeners(RichLocation.CIRCULAR)) { 256 this.circularLength = circularLength; 257 } else { 258 ChangeEvent ce = new ChangeEvent( 259 this, 260 RichLocation.CIRCULAR, 261 new Integer(circularLength), 262 new Integer(this.circularLength) 263 ); 264 ChangeSupport cs = this.getChangeSupport(RichLocation.CIRCULAR); 265 synchronized(cs) { 266 cs.firePreChangeEvent(ce); 267 this.circularLength = circularLength; 268 cs.firePostChangeEvent(ce); 269 } 270 } 271 } 272 273 /** 274 * {@inheritDoc} 275 */ 276 public Strand getStrand() { return this.strand; } 277 278 // Hibernate requirement - not for public use. 279 protected void setStrand(Strand strand) { this.strand = strand; } 280 281 // Hibernate requirement - not for public use. 282 int getStrandNum() { return this.strand.intValue(); } 283 284 // Hibernate requirement - not for public use. 285 void setStrandNum(int token) { this.strand = Strand.forValue(token); } 286 287 /** 288 * {@inheritDoc} 289 */ 290 public int getRank() { return this.rank; } 291 292 /** 293 * {@inheritDoc} 294 */ 295 public void setRank(int rank) throws ChangeVetoException { 296 if(!this.hasListeners(RichLocation.RANK)) { 297 this.rank = rank; 298 } else { 299 ChangeEvent ce = new ChangeEvent( 300 this, 301 RichLocation.RANK, 302 new Integer(rank), 303 new Integer(this.rank) 304 ); 305 ChangeSupport cs = this.getChangeSupport(RichLocation.RANK); 306 synchronized(cs) { 307 cs.firePreChangeEvent(ce); 308 this.rank = rank; 309 cs.firePostChangeEvent(ce); 310 } 311 } 312 } 313 314 /** 315 * {@inheritDoc} 316 */ 317 public int getMax() { 318 if (this.max.equals(this.min)) return this.getMin(); // because resolver might resolve differently 319 else return this.pr.getMax(this.max); 320 } 321 322 // Hibernate requirement - not for public use. 323 void setMax(int max) { this.max = new SimplePosition(false,false,max); } 324 325 /** 326 * {@inheritDoc} 327 */ 328 public int getMin() { return this.pr.getMin(this.min); } 329 330 // Hibernate requirement - not for public use. 331 void setMin(int min) { this.min = new SimplePosition(false,false,min); } 332 333 /** 334 * {@inheritDoc} 335 */ 336 public Position getMinPosition() { return this.min; } 337 338 // Hibernate requirement - not for public use. 339 protected void setMinPosition(Position min) { this.min = min; } 340 341 /** 342 * {@inheritDoc} 343 */ 344 public Position getMaxPosition() { return this.max; } 345 346 // Hibernate requirement - not for public use. 347 protected void setMaxPosition(Position max) { this.max = max; } 348 349 /** 350 * {@inheritDoc} 351 */ 352 public void setPositionResolver(PositionResolver p) { this.pr = p; } 353 354 /** 355 * {@inheritDoc} 356 */ 357 public Iterator blockIterator() { return Collections.singleton(this).iterator(); } 358 359 /** 360 * {@inheritDoc} 361 */ 362 public boolean isContiguous() { return true; } 363 364 /** 365 * {@inheritDoc} 366 */ 367 public boolean contains(int p) { 368 int modStart = this.getMin(); 369 int modEnd = this.getMax(); 370 if (this.circularLength>0) { 371 // Modulate the point to fall inside our sequence 372 p = RichLocation.Tools.modulateCircularIndex(p, this.circularLength); 373 // Modulate our location to the earliest possible point in our sequence 374 int[] ourModParts = RichLocation.Tools.modulateCircularLocation(modStart, modEnd, this.circularLength); 375 modStart = ourModParts[0]; 376 modEnd = ourModParts[1]; 377 // If we wrap and the point occurs before us, increment point to fall in correct range 378 if (modEnd>this.circularLength && p<modStart) p+=this.circularLength; 379 } 380 return (p>=modStart && p<=modEnd); 381 } 382 383 /** 384 * {@inheritDoc} 385 */ 386 public Location getDecorator(Class decoratorClass) { return null; } 387 388 /** 389 * {@inheritDoc} 390 */ 391 public Location newInstance(Location loc) { return loc; } 392 393 /** 394 * {@inheritDoc} 395 */ 396 public Location translate(int dist) { 397 return new SimpleRichLocation(this.min.translate(dist),this.max.translate(dist),0,this.strand,this.crossRef); 398 } 399 400 /** 401 * {@inheritDoc} 402 * A location contains another location if it overlaps it, and the coordinates 403 * enclose those of the other location at both ends, and they fall on 404 * the same strand. 405 */ 406 public boolean contains(Location l) { 407 if (!(l instanceof RichLocation)) l = RichLocation.Tools.enrich(l); 408 if (l instanceof EmptyRichLocation) return false; 409 else { 410 RichLocation rl = (RichLocation)l; 411 // Simple vs. simple 412 if (!this.overlaps(rl)) return false; // No overlap = no possible contain 413 if (!this.getStrand().equals(rl.getStrand())) return false; // Diff strand = not contained 414 if (this.circularLength>0) { 415 // Modulate to shortest possible equivalent region 416 int parts[] = RichLocation.Tools.modulateCircularLocationPair(this,rl,this.circularLength); 417 int ourModStart = parts[0]; 418 int ourModEnd = parts[1]; 419 int theirModStart = parts[2]; 420 int theirModEnd = parts[3]; 421 return (ourModStart <= theirModStart && ourModEnd >= theirModEnd); 422 } else { 423 return (this.getMin() <= rl.getMin() && this.getMax() >= rl.getMax()); 424 } 425 } 426 } 427 428 /** 429 * {@inheritDoc} 430 * A location overlaps another location if it is on the same sequence, and 431 * the coordinates overlap, and both are of the same circularity. 432 */ 433 public boolean overlaps(Location l) { 434 if (!(l instanceof RichLocation)) l = RichLocation.Tools.enrich(l); 435 if (l instanceof EmptyRichLocation) return false; 436 else if (l instanceof CompoundRichLocation) return l.overlaps(this); 437 else { 438 // Simple vs. simple 439 RichLocation rl = (RichLocation)l; 440 // Mismatch of crossref is no overlap. 441 if (rl.getCrossRef()!=null || this.crossRef!=null) { 442 if (rl.getCrossRef()!=null && this.crossRef!=null) { 443 if (!this.crossRef.equals(rl.getCrossRef())) return false; 444 } else return false; 445 } 446 if (this.circularLength!=rl.getCircularLength()) return false; // Diff circularLength location sizes = not overlapping 447 // Modulate our start/end to shortest possible equivalent region 448 if (this.circularLength>0) { 449 int parts[] = RichLocation.Tools.modulateCircularLocationPair(this,rl,this.circularLength); 450 int ourModStart = parts[0]; 451 int ourModEnd = parts[1]; 452 int theirModStart = parts[2]; 453 int theirModEnd = parts[3]; 454 return (ourModStart<=theirModEnd && ourModEnd>=theirModStart); 455 } else { 456 return (this.getMin()<=rl.getMax() && this.getMax()>=rl.getMin()); 457 } 458 } 459 } 460 461 /** 462 * {@inheritDoc} 463 * A merged SimpleRichLocation is returned if the two locations overlap and are on 464 * the same strand. Otherwise, a CompoundRichLocation is returned containing 465 * the two locations as members. 466 */ 467 public Location union(Location l) { 468 if (!(l instanceof RichLocation)) l = RichLocation.Tools.enrich(l); 469 if (l instanceof EmptyRichLocation) return this; 470 else if (l instanceof CompoundRichLocation) return l.union(this); 471 else { 472 // Simple vs. simple 473 RichLocation rl = (RichLocation)l; 474 if (this.overlaps(rl) && this.getStrand().equals(rl.getStrand())) { 475 // We can do the one-v-one overlapping same-strand union 476 if (this.circularLength>0) { 477 // Union of Overlapping circular locations 478 // Modulate our start/end to shortest possible equivalent region 479 int parts[] = RichLocation.Tools.modulateCircularLocationPair(this,rl,this.circularLength); 480 int ourModStart = parts[0]; 481 int ourModEnd = parts[1]; 482 int theirModStart = parts[2]; 483 int theirModEnd = parts[3]; 484 // Now we can select the minimum and maximum positions using the modded locations 485 Position startPos = (ourModStart<theirModStart)?this.min:rl.getMinPosition(); 486 Position endPos = (ourModEnd>theirModEnd)?this.max:rl.getMaxPosition(); 487 RichLocation newloc = new SimpleRichLocation(startPos,endPos,0,this.strand,this.crossRef); 488 newloc.setCircularLength(this.circularLength); 489 return newloc; 490 } else { 491 // Union of Overlapping non-circular locations 492 return new SimpleRichLocation(this.posmin(this.min,rl.getMinPosition()),this.posmax(this.max,rl.getMaxPosition()),0,this.strand,this.crossRef); 493 } 494 } 495 // We can do the one-v-one non-overlapping or different-strand union too 496 Collection members = new ArrayList(); 497 members.add(this); 498 members.add(l); 499 if (RichLocation.Tools.isMultiSource(members)) return new MultiSourceCompoundRichLocation(members); 500 else return new CompoundRichLocation(members); 501 } 502 } 503 504 /** 505 * {@inheritDoc} 506 * If the locations overlap and are on the same strand, the intersection 507 * is returned. If they overlap but are on different strands, a CompoundRichLocation 508 * of the overlapping portions is returned. If they do not overlap, the empty 509 * sequence is returned. 510 */ 511 public Location intersection(Location l) { 512 RichLocation rl= RichLocation.Tools.enrich(l); 513 if (rl instanceof EmptyRichLocation) return rl; 514 else if (rl instanceof CompoundRichLocation) return rl.intersection(this); 515 else if(circularLength != rl.getCircularLength()) return rl; 516 else { 517 if (this.overlaps(rl)) { 518 if (this.getStrand().equals(rl.getStrand())) { 519 // We can do the one-v-one same-strand overlapping intersection here 520 if (circularLength>0) { 521 // Modulate our start/end to shortest possible equivalent region 522 int parts[] = RichLocation.Tools.modulateCircularLocationPair(this,rl,circularLength); 523 int ourModStart = parts[0]; 524 int ourModEnd = parts[1]; 525 int theirModStart = parts[2]; 526 int theirModEnd = parts[3]; 527 // Now we can select the minimum and maximum positions using the modded locations 528 Position startPos = (ourModStart>theirModStart)?this.min:rl.getMinPosition(); 529 Position endPos = (ourModEnd<theirModEnd)?this.max:rl.getMaxPosition(); 530 RichLocation templ = new SimpleRichLocation(startPos,endPos,0,this.strand,this.crossRef); 531 templ.setCircularLength(circularLength); 532 return templ; 533 } else { 534 return new SimpleRichLocation(this.posmax(this.min,rl.getMinPosition()),this.posmin(this.max,rl.getMaxPosition()),0,this.strand,this.crossRef); 535 } 536 } 537 // We can do the one-v-one different-strand overlapping intersection here 538 Collection members = new ArrayList(); 539 members.add(new SimpleRichLocation(this.posmax(this.min,rl.getMinPosition()),this.posmin(this.max,rl.getMaxPosition()),0,this.strand,this.crossRef)); 540 members.add(new SimpleRichLocation(this.posmax(this.min,rl.getMinPosition()),this.posmin(this.max,rl.getMaxPosition()),0,rl.getStrand(),this.crossRef)); 541 if (RichLocation.Tools.isMultiSource(members)) return new MultiSourceCompoundRichLocation(members); 542 else return new CompoundRichLocation(members); 543 } else { 544 // We can do the one-v-one non-overlapping intersection here 545 return RichLocation.EMPTY_LOCATION; 546 } 547 } 548 } 549 550 // calculates the smaller of the two positions, based on their resolver output 551 protected Position posmin(Position a, Position b) { 552 int ar = this.pr.getMin(a); 553 int br = this.pr.getMin(b); 554 if (ar<=br) return a; 555 else return b; 556 } 557 558 // calculates the smaller of the two positions, based on their resolver output 559 protected Position posmax(Position a, Position b) { 560 int ar = this.pr.getMax(a); 561 int br = this.pr.getMax(b); 562 if (ar>br) return a; 563 else return b; 564 } 565 566 /** 567 * {@inheritDoc} 568 */ 569 public void setCrossRefResolver(CrossReferenceResolver r) { 570 if (r==null) throw new IllegalArgumentException("Resolver cannot be null"); 571 this.crr = r; 572 } 573 574 /** 575 * {@inheritDoc} 576 * If the location is circular but the sequence is not, or they are both 577 * circular but of different circular lengths, an exception is thrown. 578 * The symbol list passed in is the sequence used to obtain symbols 579 * if the cross reference for this location has not been set. If the cross 580 * reference has been set, then the symbol list passed in is only used 581 * if it has the same accession, namespace and version as the cross 582 * reference on this location. Otherwise, the cross referenced symbol list 583 * is looked up and used instead. 584 */ 585 public SymbolList symbols(SymbolList seq) { 586 if (seq==null) throw new IllegalArgumentException("Sequence cannot be null"); 587 588 if (seq instanceof RichSequence) { 589 RichSequence rs = (RichSequence)seq; 590 if (this.getCircularLength()>0) { 591 if (!rs.getCircular()) throw new IllegalArgumentException("Attempt to apply circular location to non-circular sequence"); 592 if (rs.length()!=this.getCircularLength()) throw new IllegalArgumentException("Attempt to apply circular location to circular sequence of different length"); 593 } 594 } 595 596 // Resolve cross-references to remote sequences 597 if (this.getCrossRef()!=null) { 598 CrossRef cr = this.getCrossRef(); 599 if (seq instanceof RichSequence) { 600 RichSequence rs = (RichSequence)seq; 601 String accession = rs.getAccession(); 602 Namespace ns = rs.getNamespace(); 603 String raccession = cr.getAccession(); 604 String rnamespace = cr.getDbname(); 605 if (!(accession.equals(raccession) && ns.getName().equals(rnamespace))) { 606 // It really is remote - the xref doesn't point to the sequence we just got passed 607 seq = this.crr.getRemoteSymbolList(cr, seq.getAlphabet()); 608 } 609 } else { 610 // It's assumed to be remote because we can't tell what the sequence we were passed really is 611 seq = this.crr.getRemoteSymbolList(this.getCrossRef(), seq.getAlphabet()); 612 } 613 } 614 615 616 // Carry on as before 617 seq = seq.subList(this.getMin(),this.getMax()); 618 619 try { 620 if (this.strand==Strand.NEGATIVE_STRAND) { 621 Alphabet a = seq.getAlphabet(); 622 if (a==AlphabetManager.alphabetForName("DNA")) { 623 seq = DNATools.reverseComplement(seq); 624 } else if (a==AlphabetManager.alphabetForName("RNA")) { 625 seq = RNATools.reverseComplement(seq); 626 } else { 627 seq = SymbolListViews.reverse(seq);// no complement as no such thing 628 } 629 } 630 } catch (IllegalAlphabetException e) { 631 IllegalArgumentException ex = 632 new IllegalArgumentException("Could not understand alphabet of passed sequence"); 633 ex.initCause(e); 634 throw ex; 635 } 636 637 return seq; 638 } 639 640 /** 641 * {@inheritDoc} 642 */ 643 public int hashCode() { 644 int code = 17; 645 // Hibernate comparison - we haven't been populated yet 646 if (this.strand==null) return code; 647 // Normal comparison 648 if (this.term!=null) code = 31*code + this.term.hashCode(); 649 code = 31*code + this.getMin(); 650 code = 31*code + this.getMax(); 651 code = 31*code + this.strand.hashCode(); 652 code = 31*code + this.rank; 653 if (this.crossRef!=null) code = 31*code + this.crossRef.hashCode(); 654 return code; 655 } 656 657 /** 658 * {@inheritDoc} 659 * Locations are equal if their term, min, max, strand, and crossref are 660 * the same, and if their rank is the same too. 661 */ 662 public boolean equals(Object o) { 663 if (! (o instanceof RichLocation)) return false; 664 // Hibernate comparison - we haven't been populated yet 665 if (this.strand==null) return false; 666 // Normal comparison 667 RichLocation fo = (RichLocation) o; 668 if (this.term!=null || fo.getTerm()!=null) { 669 if (this.term!=null && fo.getTerm()!=null) { 670 if (!this.term.equals(fo.getTerm())) return false; 671 } else return false; 672 } 673 if (this.getMin()!=fo.getMin()) return false; 674 if (this.getMax()!=fo.getMax()) return false; 675 if (!this.strand.equals(fo.getStrand())) return false; 676 if (this.crossRef!=null || fo.getCrossRef()!=null) { 677 if (this.crossRef!=null && fo.getCrossRef()!=null) { 678 if(!this.crossRef.equals(fo.getCrossRef())) return false; 679 } else return false; 680 } 681 return this.rank==fo.getRank(); 682 } 683 684 /** 685 * {@inheritDoc} 686 * Locations are sorted first by rank, then crossref, then 687 * strand, then term, then min, then max. 688 */ 689 public int compareTo(Object o) { 690 if (o==this) return 0; 691 // Hibernate comparison - we haven't been populated yet 692 if (this.strand==null) return -1; 693 // Check if we can really compare at all 694 if (!(o instanceof RichLocation)) return -1; 695 // Normal comparison 696 RichLocation fo = (RichLocation) o; 697 if (this.rank!=fo.getRank()) return this.rank-fo.getRank(); 698 if (this.crossRef!=null || fo.getCrossRef()!=null) { 699 if (this.crossRef!=null && fo.getCrossRef()!=null) { 700 return this.crossRef.compareTo(fo.getCrossRef()); 701 } else return -1; 702 } 703 if (!this.strand.equals(fo.getStrand())) return this.strand.compareTo(fo.getStrand()); 704 if (this.term!=null || fo.getTerm()!=null) { 705 if (this.term!=null && fo.getTerm()!=null && !this.term.equals(fo.getTerm())) return this.term.compareTo(fo.getTerm()); 706 else return -1; 707 } 708 if(this.strand.equals(RichLocation.Strand.POSITIVE_STRAND) || this.strand.equals(RichLocation.Strand.UNKNOWN_STRAND )) { 709 if (this.getMin()!=fo.getMin()) return this.getMin()-fo.getMin(); 710 return this.getMax()-fo.getMax(); 711 } else { // NEGATIVE STRAND 712 if (this.getMax()!=fo.getMax()) return fo.getMax()-this.getMax(); 713 return fo.getMin()-this.getMin(); 714 } 715 } 716 717 /** 718 * {@inheritDoc} 719 * Form: "start..end" or just "point" for point locations 720 */ 721 public String toString() { 722 if (this.max.equals(this.min)) { 723 return this.min.toString(); 724 } else { 725 return this.min+".."+this.max; 726 } 727 } 728 729 // Internal use only. 730 void setLocationText(final String theLocation) throws ParseException { 731// System.out.println("SimpleRichLocation.setLocationText-theLocation: ["+theLocation+"]"); 732 if (theLocation == null) { 733 setMinPosition(RichLocation.EMPTY_LOCATION.getMinPosition()); 734 setMaxPosition(RichLocation.EMPTY_LOCATION.getMaxPosition()); 735 } else { 736 final RichLocation location = GenbankLocationParser.parseLocation(RichObjectFactory.getDefaultNamespace(), null, theLocation); 737 setMinPosition(location.getMinPosition()); 738 setMaxPosition(location.getMaxPosition()); 739 } 740// System.out.println("SimpleRichLocation.setLocationText-this: ["+this+"]"); 741 } 742 743 // Internal use only. 744 String getLocationText() { 745// System.out.println("SimpleRichLocation.getLocationText-returns: ["+GenbankLocationParser.writeLocation(new SimpleRichLocation(getMinPosition(), getMaxPosition(), getRank()))+"], this: ["+this+"]"); 746 return GenbankLocationParser.writeLocation(new SimpleRichLocation(getMinPosition(), getMaxPosition(), getRank())); 747 } 748 749 // Hibernate requirement - not for public use. 750 private Integer id; 751 752 /** 753 * Gets the Hibernate ID. Should be used with caution. 754 * @return the Hibernate ID, if using Hibernate. 755 */ 756 public Integer getId() { return this.id; } 757 758 /** 759 * Sets the Hibernate ID. Should be used with caution. 760 * @param id the Hibernate ID, if using Hibernate. 761 */ 762 public void setId(Integer id) { this.id = id;} 763} 764