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; 023import java.util.Collection; 024import java.util.Iterator; 025import java.util.Set; 026import java.util.TreeSet; 027 028import org.biojava.bio.Annotation; 029import org.biojava.bio.BioException; 030import org.biojava.bio.seq.Feature; 031import org.biojava.bio.seq.FeatureFilter; 032import org.biojava.bio.seq.FeatureHolder; 033import org.biojava.bio.seq.FilterUtils; 034import org.biojava.bio.seq.Sequence; 035import org.biojava.bio.seq.SimpleFeatureHolder; 036import org.biojava.bio.seq.StrandedFeature; 037import org.biojava.bio.symbol.Location; 038import org.biojava.bio.symbol.SymbolList; 039import org.biojava.ontology.InvalidTermException; 040import org.biojava.ontology.Term; 041import org.biojava.utils.AbstractChangeable; 042import org.biojava.utils.ChangeEvent; 043import org.biojava.utils.ChangeSupport; 044import org.biojava.utils.ChangeVetoException; 045import org.biojavax.RankedCrossRef; 046import org.biojavax.RichAnnotation; 047import org.biojavax.RichObjectFactory; 048import org.biojavax.SimpleRichAnnotation; 049import org.biojavax.ontology.ComparableTerm; 050 051/** 052 * A simple implementation of RichFeature. 053 * @author Richard Holland 054 * @author Mark Schreiber 055 * @author Bubba Puryear 056 * @author George Waldon 057 * @since 1.5 058 */ 059public class SimpleRichFeature extends AbstractChangeable implements RichFeature { 060 061 private static int nextRank = 0; 062 063 private RichAnnotation notes = new SimpleRichAnnotation(); 064 private ComparableTerm typeTerm; 065 private ComparableTerm sourceTerm; 066 private FeatureHolder parent; 067 private RichLocation location = RichLocation.EMPTY_LOCATION; 068 private Set<RankedCrossRef> crossrefs = new TreeSet<RankedCrossRef>(); 069 private Set<RichFeatureRelationship> relations = new TreeSet<RichFeatureRelationship>(); 070 private String name; 071 private int rank = SimpleRichFeature.nextRank++; // Auto-rank! 072 073 /** 074 * Creates a new instance of SimpleRichFeature based on a template. 075 * @param parent The parent feature holder. 076 * @param templ The template to construct the feature from. 077 * @throws ChangeVetoException if we don't want to be like the template. 078 * @throws InvalidTermException if any of the template terms are bad. 079 */ 080 public SimpleRichFeature(FeatureHolder parent, Feature.Template templ) throws ChangeVetoException, InvalidTermException { 081 if (parent==null) throw new IllegalArgumentException("Parent cannot be null"); 082 if (templ==null) throw new IllegalArgumentException("Template cannot be null"); 083 if (templ.type==null && templ.typeTerm==null) throw new IllegalArgumentException("Template type cannot be null"); 084 if (templ.source==null && templ.sourceTerm==null) throw new IllegalArgumentException("Template source cannot be null"); 085 if (templ.location==null) throw new IllegalArgumentException("Template location cannot be null"); 086 087 this.setParent(parent); 088 this.setLocation(templ.location); 089 090 if (templ.typeTerm!=null) this.setTypeTerm(templ.typeTerm); 091 else this.setType(templ.type); 092 if (templ.sourceTerm!=null) this.setSourceTerm(templ.sourceTerm); 093 else this.setSource(templ.source); 094 095 if (templ.annotation instanceof RichAnnotation) { 096 this.notes.setNoteSet(((RichAnnotation)templ.annotation).getNoteSet()); 097 } else { 098 this.notes = new SimpleRichAnnotation(); 099 for (Iterator i = templ.annotation.keys().iterator(); i.hasNext(); ) { 100 Object key = i.next(); 101 this.notes.setProperty(key, templ.annotation.getProperty(key)); 102 } 103 } 104 105 if (templ instanceof RichFeature.Template) { 106 this.setRankedCrossRefs(((RichFeature.Template)templ).rankedCrossRefs); 107 this.setFeatureRelationshipSet(((RichFeature.Template)templ).featureRelationshipSet); 108 } 109 } 110 111 // Hibernate requirement - not for public use. 112 protected SimpleRichFeature() {} 113 114 /** 115 * {@inheritDoc} 116 */ 117 public Feature.Template makeTemplate() { 118 RichFeature.Template templ = new RichFeature.Template(); 119 templ.annotation = this.notes; 120 templ.featureRelationshipSet = this.relations; 121 templ.rankedCrossRefs = this.crossrefs; 122 templ.location = this.location; 123 templ.sourceTerm = this.sourceTerm; 124 templ.source = this.sourceTerm.getName(); 125 templ.typeTerm = this.typeTerm; 126 templ.type = this.typeTerm.getName(); 127 return templ; 128 } 129 130 /** 131 * {@inheritDoc} 132 */ 133 public Annotation getAnnotation() { return getRichAnnotation(); } 134 135 /** 136 * {@inheritDoc} 137 */ 138 public RichAnnotation getRichAnnotation() { return this.notes; } 139 140 /** 141 * {@inheritDoc} 142 * <b>Warning</b> this method gives access to the original 143 * Collection not a copy. This is required by Hibernate. If you 144 * modify the object directly the behaviour may be unpredictable. 145 */ 146 public Set getNoteSet() { return this.notes.getNoteSet(); } 147 148 /** 149 * {@inheritDoc} 150 * <b>Warning</b> this method gives access to the original 151 * Collection not a copy. This is required by Hibernate. If you 152 * modify the object directly the behaviour may be unpredictable. 153 */ 154 public void setNoteSet(Set notes) throws ChangeVetoException { this.notes.setNoteSet(notes); } 155 156 // Hibernate use only 157 Set getLocationSet() { 158 // Convert the location into a set of BioSQL-compatible simple locations 159// System.out.println("SimpleRichFeature.getLocationSet-featureId:"+featureId+", locsSet:"+locsSet+", getLocation:"+getLocation()); 160 setTerm(locsSet, null); 161 Collection newlocs = RichLocation.Tools.flatten(this.location); 162 this.locsSet.retainAll(newlocs); // clear out forgotten ones 163 this.locsSet.addAll(newlocs); // add in new ones 164 setTerm(locsSet, ((RichLocation) getLocation()).getTerm()); 165// System.out.println("SimpleRichFeature.getLocationSet-featureId:"+featureId+", locsSet:"+locsSet+", this:"+this+", getLocation:"+getLocation()); 166 return this.locsSet; // original for Hibernate purposes 167 } 168 169 170 private final static void setTerm(final Collection theCollection, final ComparableTerm theTerm) { 171 final Iterator l = theCollection.iterator(); 172 while(l.hasNext()) { 173 final RichLocation location = (RichLocation) l.next(); 174 try { 175 location.setTerm(theTerm); 176 } catch (Exception e) { 177 throw new RuntimeException("SimpleRichFeature.setTerm-unable to set term <"+theTerm+"> in location <"+location+">"+e); 178 } 179 } 180 } 181 182 // Hibernate use only 183 void setLocationSet(Set locs) throws ChangeVetoException { 184 this.locsSet = locs; // original kept for Hibernate purposes 185 // Construct a nice BioJavaX location from the set of BioSQL-compatible simple ones 186 this.location = RichLocation.Tools.construct(RichLocation.Tools.merge(locs)); 187 if(locs.size() > 0)((RichLocation) location).setTerm(((RichLocation)locs.iterator().next()).getTerm()); 188// System.out.println("SimpleRichFeature.SETLocationSet-featureId:"+featureId+", locs:"+locs+", location:"+location); 189 } 190 private Set locsSet = new TreeSet(); 191 192 /** 193 * {@inheritDoc} 194 */ 195 public void setName(String name) throws ChangeVetoException { 196 if(!this.hasListeners(RichFeature.NAME)) { 197 this.name = name; 198 } else { 199 ChangeEvent ce = new ChangeEvent( 200 this, 201 RichFeature.NAME, 202 name, 203 this.name 204 ); 205 ChangeSupport cs = this.getChangeSupport(RichFeature.NAME); 206 synchronized(cs) { 207 cs.firePreChangeEvent(ce); 208 this.name = name; 209 cs.firePostChangeEvent(ce); 210 } 211 } 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 public String getName() { return this.name; } 218 219 /** 220 * {@inheritDoc} 221 */ 222 public void setRank(int rank) throws ChangeVetoException { 223 if(!this.hasListeners(RichFeature.RANK)) { 224 this.rank = rank; 225 } else { 226 ChangeEvent ce = new ChangeEvent( 227 this, 228 RichFeature.RANK, 229 new Integer(rank), 230 new Integer(this.rank) 231 ); 232 ChangeSupport cs = this.getChangeSupport(RichFeature.RANK); 233 synchronized(cs) { 234 cs.firePreChangeEvent(ce); 235 this.rank = rank; 236 cs.firePostChangeEvent(ce); 237 } 238 } 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 public int getRank() { return this.rank; } 245 246 /** 247 * {@inheritDoc} 248 */ 249 public Sequence getSequence() { 250 FeatureHolder p = this.parent; 251 while (p instanceof Feature) p = ((Feature)p).getParent(); 252 return (Sequence)p; 253 } 254 255 /** 256 * {@inheritDoc} 257 */ 258 public String getSource() { return this.sourceTerm.getName(); } 259 260 /** 261 * {@inheritDoc} 262 */ 263 public void setSource(String source) throws ChangeVetoException { 264 try { 265 this.setSourceTerm(RichObjectFactory.getDefaultOntology().getOrCreateTerm(source)); 266 } catch (InvalidTermException e) { 267 throw new ChangeVetoException("Source term was rejected by the default ontology",e); 268 } 269 } 270 271 /** 272 * {@inheritDoc} 273 */ 274 public Term getSourceTerm() { return this.sourceTerm; } 275 276 /** 277 * {@inheritDoc} 278 */ 279 public void setSourceTerm(Term t) throws ChangeVetoException, InvalidTermException { 280 if (t==null) throw new IllegalArgumentException("Term cannot be null"); 281 ComparableTerm comparableT; 282 if (t instanceof ComparableTerm) { 283 comparableT = (ComparableTerm) t; 284 } else { 285 comparableT = RichObjectFactory.getDefaultOntology().getOrImportTerm(t); 286 } 287 if(!this.hasListeners(RichFeature.SOURCETERM)) { 288 this.sourceTerm = comparableT; 289 } else { 290 ChangeEvent ce = new ChangeEvent( 291 this, 292 RichFeature.SOURCETERM, 293 comparableT, 294 this.sourceTerm 295 ); 296 ChangeSupport cs = this.getChangeSupport(RichFeature.SOURCETERM); 297 synchronized(cs) { 298 cs.firePreChangeEvent(ce); 299 this.sourceTerm = comparableT; 300 cs.firePostChangeEvent(ce); 301 } 302 } 303 } 304 305 /** 306 * {@inheritDoc} 307 */ 308 public String getType() { return this.typeTerm.getName(); } 309 310 /** 311 * {@inheritDoc} 312 */ 313 public void setType(String type) throws ChangeVetoException { 314 try { 315 this.setTypeTerm(RichObjectFactory.getDefaultOntology().getOrCreateTerm(type)); 316 } catch (InvalidTermException e) { 317 throw new ChangeVetoException("Type term was rejected by the default ontology",e); 318 } 319 } 320 321 /** 322 * {@inheritDoc} 323 */ 324 public Term getTypeTerm() { return this.typeTerm; } 325 326 /** 327 * {@inheritDoc} 328 */ 329 public void setTypeTerm(Term t) throws ChangeVetoException, InvalidTermException { 330 if (t==null) throw new IllegalArgumentException("Term cannot be null"); 331 ComparableTerm comparableT; 332 if (t instanceof ComparableTerm) { 333 comparableT = (ComparableTerm) t; 334 } else { 335 comparableT = RichObjectFactory.getDefaultOntology().getOrImportTerm(t); 336 } 337 if(!this.hasListeners(RichFeature.TYPETERM)) { 338 this.typeTerm = comparableT; 339 } else { 340 ChangeEvent ce = new ChangeEvent( 341 this, 342 RichFeature.TYPETERM, 343 comparableT, 344 this.typeTerm 345 ); 346 ChangeSupport cs = this.getChangeSupport(RichFeature.TYPETERM); 347 synchronized(cs) { 348 cs.firePreChangeEvent(ce); 349 this.typeTerm = comparableT; 350 cs.firePostChangeEvent(ce); 351 } 352 } 353 } 354 355 /** 356 * {@inheritDoc} 357 */ 358 public SymbolList getSymbols() { return this.location.symbols(this.getSequence()); } 359 360 /** 361 * {@inheritDoc} 362 */ 363 public Location getLocation() { return this.location; } 364 365 /** 366 * {@inheritDoc} 367 */ 368 public void setLocation(Location loc) throws ChangeVetoException { 369// System.out.println("SimpleRichFeature.setLocation-featureId:"+featureId+", loc:"+loc); 370 if (loc==null) throw new IllegalArgumentException("Location cannot be null"); 371 RichLocation richLoc = RichLocation.Tools.enrich(loc); 372 if(!this.hasListeners(RichFeature.LOCATION)) { 373 this.location = richLoc; 374 } else { 375 ChangeEvent ce = new ChangeEvent( 376 this, 377 RichFeature.LOCATION, 378 richLoc, 379 this.location 380 ); 381 ChangeSupport cs = this.getChangeSupport(RichFeature.LOCATION); 382 synchronized(cs) { 383 cs.firePreChangeEvent(ce); 384 this.location = richLoc; 385 cs.firePostChangeEvent(ce); 386 } 387 } 388 this.location.setFeature(this); 389// System.out.println("SimpleRichFeature.setLocation-location:"+location+(location instanceof RichLocation?", term:"+((RichLocation) location).getTerm():"")); 390 } 391 392 /** 393 * {@inheritDoc} 394 */ 395 public FeatureHolder getParent() { return this.parent; } 396 397 /** 398 * {@inheritDoc} 399 */ 400 public void setParent(FeatureHolder parent) throws ChangeVetoException { 401 if (parent==null) throw new IllegalArgumentException("Parent cannot be null"); 402 if(!this.hasListeners(RichFeature.PARENT)) { 403 this.parent = parent; 404 } else { 405 ChangeEvent ce = new ChangeEvent( 406 this, 407 RichFeature.PARENT, 408 parent, 409 this.parent 410 ); 411 ChangeSupport cs = this.getChangeSupport(RichFeature.PARENT); 412 synchronized(cs) { 413 cs.firePreChangeEvent(ce); 414 this.parent = parent; 415 cs.firePostChangeEvent(ce); 416 } 417 } 418 } 419 420 /** 421 * {@inheritDoc} 422 * <b>Warning</b> this method gives access to the original 423 * Collection not a copy. This is required by Hibernate. If you 424 * modify the object directly the behaviour may be unpredictable. 425 */ 426 public Set<RankedCrossRef> getRankedCrossRefs() { return this.crossrefs; } 427 428 /** 429 * {@inheritDoc} 430 * <b>Warning</b> this method gives access to the original 431 * Collection not a copy. This is required by Hibernate. If you 432 * modify the object directly the behaviour may be unpredictable. 433 */ 434 public void setRankedCrossRefs(Set crossrefs) throws ChangeVetoException { 435 this.crossrefs = crossrefs; // original for Hibernate 436 } 437 438 /** 439 * {@inheritDoc} 440 */ 441 public void addRankedCrossRef(RankedCrossRef crossref) throws ChangeVetoException { 442 if (crossref==null) throw new IllegalArgumentException("Crossref cannot be null"); 443 if(!this.hasListeners(RichFeature.CROSSREF)) { 444 this.crossrefs.add(crossref); 445 } else { 446 ChangeEvent ce = new ChangeEvent( 447 this, 448 RichFeature.CROSSREF, 449 crossref, 450 null 451 ); 452 ChangeSupport cs = this.getChangeSupport(RichFeature.CROSSREF); 453 synchronized(cs) { 454 cs.firePreChangeEvent(ce); 455 this.crossrefs.add(crossref); 456 cs.firePostChangeEvent(ce); 457 } 458 } 459 } 460 461 /** 462 * {@inheritDoc} 463 */ 464 public void removeRankedCrossRef(RankedCrossRef crossref) throws ChangeVetoException { 465 if (crossref==null) throw new IllegalArgumentException("Crossref cannot be null"); 466 if(!this.hasListeners(RichFeature.CROSSREF)) { 467 this.crossrefs.remove(crossref); 468 } else { 469 ChangeEvent ce = new ChangeEvent( 470 this, 471 RichFeature.CROSSREF, 472 null, 473 crossref 474 ); 475 ChangeSupport cs = this.getChangeSupport(RichFeature.CROSSREF); 476 synchronized(cs) { 477 cs.firePreChangeEvent(ce); 478 this.crossrefs.remove(crossref); 479 cs.firePostChangeEvent(ce); 480 } 481 } 482 } 483 484 /** 485 * {@inheritDoc} 486 * <b>Warning</b> this method gives access to the original 487 * Collection not a copy. This is required by Hibernate. If you 488 * modify the object directly the behaviour may be unpredictable. 489 */ 490 public Set<RichFeatureRelationship> getFeatureRelationshipSet() { return this.relations; } // must be original for Hibernate 491 492 /** 493 * {@inheritDoc} 494 * <b>Warning</b> this method gives access to the original 495 * Collection not a copy. This is required by Hibernate. If you 496 * modify the object directly the behaviour may be unpredictable. 497 */ 498 public void setFeatureRelationshipSet(Set<RichFeatureRelationship> relationships) throws ChangeVetoException { 499 this.relations = relationships; // must be original for Hibernate 500 } 501 502 /** 503 * {@inheritDoc} 504 */ 505 public void addFeatureRelationship(RichFeatureRelationship relationship) throws ChangeVetoException { 506 if (relationship==null) throw new IllegalArgumentException("Relationship cannot be null"); 507 if(!this.hasListeners(RichFeature.RELATION)) { 508 this.relations.add(relationship); 509 } else { 510 ChangeEvent ce = new ChangeEvent( 511 this, 512 RichFeature.RELATION, 513 relationship, 514 null 515 ); 516 ChangeSupport cs = this.getChangeSupport(RichFeature.RELATION); 517 synchronized(cs) { 518 cs.firePreChangeEvent(ce); 519 this.relations.add(relationship); 520 cs.firePostChangeEvent(ce); 521 } 522 } 523 } 524 525 /** 526 * {@inheritDoc} 527 */ 528 public void removeFeatureRelationship(RichFeatureRelationship relationship) throws ChangeVetoException { 529 if (relationship==null) throw new IllegalArgumentException("Relationship cannot be null"); 530 if(!this.hasListeners(RichFeature.RELATION)) { 531 this.relations.remove(relationship); 532 } else { 533 ChangeEvent ce = new ChangeEvent( 534 this, 535 RichFeature.RELATION, 536 null, 537 relationship 538 ); 539 ChangeSupport cs = this.getChangeSupport(RichFeature.RELATION); 540 synchronized(cs) { 541 cs.firePreChangeEvent(ce); 542 this.relations.remove(relationship); 543 cs.firePostChangeEvent(ce); 544 } 545 } 546 } 547 548 // Converts relations into a set of child feature objects 549 private Set relationsToFeatureSet() { 550 Set features = new TreeSet(); 551 for (Iterator i = this.relations.iterator(); i.hasNext(); ) { 552 RichFeatureRelationship r = (RichFeatureRelationship)i.next(); 553 features.add(r.getSubject()); 554 } 555 return features; 556 } 557 558 /** 559 * {@inheritDoc} 560 */ 561 public boolean containsFeature(Feature f) { return this.relationsToFeatureSet().contains(f); } 562 563 /** 564 * {@inheritDoc} 565 */ 566 public int countFeatures() { return this.relationsToFeatureSet().size(); } 567 568 /** 569 * {@inheritDoc} 570 */ 571 public Feature createFeature(Feature.Template ft) throws BioException, ChangeVetoException { 572 if (ft==null) throw new IllegalArgumentException("Template cannot be null"); 573 RichFeature f; 574 try { 575 f = new SimpleRichFeature(this.parent, ft); 576 } catch (InvalidTermException e) { 577 throw new ChangeVetoException("Term was not accepted",e); 578 } 579 this.addFeatureRelationship( 580 new SimpleRichFeatureRelationship(this, f, SimpleRichFeatureRelationship.getContainsTerm(), 0) 581 ); 582 return f; 583 } 584 585 /** 586 * {@inheritDoc} 587 */ 588 public Iterator features() { return this.relationsToFeatureSet().iterator(); } 589 590 /** 591 * {@inheritDoc} 592 */ 593 public FeatureHolder filter(FeatureFilter filter) { 594 boolean recurse = !FilterUtils.areProperSubset(filter, FeatureFilter.top_level); 595 return this.filter(filter, recurse); 596 } 597 598 /** 599 * {@inheritDoc} 600 */ 601 public FeatureHolder filter(FeatureFilter fc, boolean recurse) { 602 SimpleFeatureHolder fh = new SimpleFeatureHolder(); 603 for (Iterator i = this.features(); i.hasNext(); ) { 604 Feature f = (RichFeature)i.next(); 605 try { 606 if (fc.accept(f)) fh.addFeature(f); 607 } catch (ChangeVetoException e) { 608 throw new RuntimeException("Aaargh! Our feature was rejected!",e); 609 } 610 } 611 return fh; 612 } 613 614 /** 615 * {@inheritDoc} 616 */ 617 public FeatureFilter getSchema() { return FeatureFilter.all; } 618 619 /** 620 * {@inheritDoc} 621 */ 622 public void removeFeature(Feature f) throws ChangeVetoException, BioException { 623 for (Iterator i = this.relations.iterator(); i.hasNext(); ) { 624 RichFeatureRelationship r = (RichFeatureRelationship)i.next(); 625 if (r.getSubject().equals(f)) i.remove(); 626 } 627 } 628 629 /** 630 * {@inheritDoc} 631 * NOT IMPLEMENTED. 632 */ 633 public void setStrand(StrandedFeature.Strand strand) throws ChangeVetoException { 634 throw new ChangeVetoException("The strand is immutable on RichFeature objects."); 635 } 636 637 /** 638 * {@inheritDoc} 639 */ 640 public StrandedFeature.Strand getStrand() { 641 RichLocation.Strand s = this.location.getStrand(); 642 if (s.equals(RichLocation.Strand.NEGATIVE_STRAND)) return StrandedFeature.NEGATIVE; 643 if (s.equals(RichLocation.Strand.POSITIVE_STRAND)) return StrandedFeature.POSITIVE; 644 else return StrandedFeature.UNKNOWN; 645 } 646 647 /** 648 * {@inheritDoc} 649 */ 650 public int hashCode() { 651 int code = 17; 652 // Hibernate comparison - we haven't been populated yet 653 if (this.parent==null) return code; 654 // Normal comparison 655 code = 31*code + this.rank; 656 code = 31*code + this.parent.hashCode(); 657 code = 31*code + this.sourceTerm.hashCode(); 658 code = 31*code + this.typeTerm.hashCode(); 659 return code; 660 } 661 662 /** 663 * {@inheritDoc} 664 * Features are equal when they have the same rank, parent, type, and source. 665 * Features which are not instance of RichFeature are given a 666 * rank of zero. 667 */ 668 public boolean equals(Object o) { 669 if (! (o instanceof Feature)) return false; 670 // Hibernate comparison - we haven't been populated yet 671 if (this.parent==null) return false; 672 // Normal comparison 673 Feature fo = (Feature) o; 674 int ourRank = this.getRank(); 675 int theirRank = fo instanceof RichFeature? ((RichFeature)fo).getRank() : 0; 676 if ( ourRank!=theirRank) return false; 677 if (! this.parent.equals(fo.getParent())) return false; 678 if (! this.typeTerm.equals(fo.getTypeTerm())) return false; 679 if (! this.sourceTerm.equals(fo.getSourceTerm())) return false; 680 return true; 681 } 682 683 /** 684 * {@inheritDoc} 685 * Features are sorted first by rank, then parent, type, and source. 686 * If both parents are not comparable then this part of the sorting 687 * is skipped. Features which are not instance of RichFeature are 688 * given a rank of zero. 689 */ 690 public int compareTo(Object o) { 691 if (o==this) return 0; 692 // Hibernate comparison - we haven't been populated yet 693 if (this.parent==null) return -1; 694 // Normal comparison 695 Feature them = (Feature)o; 696 int ourRank = this.getRank(); 697 int theirRank = them instanceof RichFeature? ((RichFeature)them).getRank():0; 698 if (ourRank!=theirRank) return ourRank-theirRank; 699 if (this.parent instanceof Comparable && 700 them.getParent() instanceof Comparable && 701 !this.parent.equals(them.getParent())) 702 return ((Comparable)this.parent).compareTo(them.getParent()); 703 if (! this.typeTerm.equals(them.getTypeTerm())) 704 return this.typeTerm.compareTo(them.getTypeTerm()); 705 if (! this.sourceTerm.equals(them.getSourceTerm())) 706 return this.sourceTerm.compareTo(them.getSourceTerm()); 707 if(this.parent.equals(them.getParent())) 708 return 0; // equality on non-comparable parents 709 else 710 return -1; 711 } 712 713 /** 714 * {@inheritDoc} 715 * Form: "(#rank) parent:type,source(location)" 716 */ 717 public String toString() { 718 return "(#"+this.rank+") "+this.parent+":"+this.getType()+","+this.getSource()+"("+this.location+")"; 719 } 720 721 // Hibernate requirement - not for public use. 722 private Integer id; 723 724 /** 725 * Gets the Hibernate ID. Should be used with caution. 726 * @return the Hibernate ID, if using Hibernate. 727 */ 728 public Integer getId() { return this.id; } 729 730 /** 731 * Sets the Hibernate ID. Should be used with caution. 732 * @param id the Hibernate ID, if using Hibernate. 733 */ 734 public void setId(Integer id) { this.id = id;} 735 736} 737