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