001/**
002 * Copyright (C) 2010-2015 The Roslin Institute <contact andy.law@roslin.ed.ac.uk>
003 *
004 * This file is part of JEnsembl: a Java API to Ensembl data sources developed by the
005 * Bioinformatics Group at The Roslin Institute, The Royal (Dick) School of
006 * Veterinary Studies, University of Edinburgh.
007 *
008 * Project hosted at: http://jensembl.sourceforge.net
009 *
010 * This is free software: you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License (version 3) as published by
012 * the Free Software Foundation.
013 *
014 * This software is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * in this software distribution. If not, see: http://opensource.org/licenses/gpl-3.0.html
021 */
022package uk.ac.roslin.ensembl.datasourceaware.core;
023
024import java.util.Date;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Set;
028import org.biojava3.core.sequence.RNASequence;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031import uk.ac.roslin.ensembl.config.EnsemblCoordSystemType;
032import uk.ac.roslin.ensembl.config.EnsemblDBType;
033import uk.ac.roslin.ensembl.dao.factory.DAOCoreFactory;
034import uk.ac.roslin.ensembl.datasourceaware.DAXRef;
035import uk.ac.roslin.ensembl.exception.DAOException;
036import uk.ac.roslin.ensembl.exception.NonUniqueException;
037import uk.ac.roslin.ensembl.exception.RangeException;
038import uk.ac.roslin.ensembl.model.Coordinate;
039import uk.ac.roslin.ensembl.model.Coordinate.Strand;
040import uk.ac.roslin.ensembl.model.Mapping;
041import uk.ac.roslin.ensembl.model.MappingSet;
042import uk.ac.roslin.ensembl.model.ObjectType;
043import uk.ac.roslin.ensembl.model.core.Chromosome;
044import uk.ac.roslin.ensembl.model.core.CoordinateSystem;
045import uk.ac.roslin.ensembl.model.core.Feature;
046import uk.ac.roslin.ensembl.model.database.CollectionCoreDatabase;
047import uk.ac.roslin.ensembl.model.database.SingleSpeciesCoreDatabase;
048
049/**
050 *
051 * @author tpaterso
052 */
053public abstract class DAFeature extends DACoreObject implements Feature {
054
055    protected MappingSet mappings = new MappingSet();
056    protected HashMap<ObjectType, MappingSet> objectTypeMappings = new HashMap<ObjectType, MappingSet>();
057    protected Set<ObjectType> mappedObjectTypes = new HashSet<ObjectType>();
058    protected String description = null;
059    protected DAXRef displayXRef = null;
060    //protected List<DAXRef> xrefs = new ArrayList<DAXRef>();
061    protected String displayName = null;
062    protected Status status = Status.UNKNOWN;
063    protected Boolean current = null;
064    
065    Coordinate topLevelTargetCoordinates = null;
066    DADNASequence topLevelTargetSequence = null;
067    protected DADNASequence thisSequence = null;
068    CoordinateSystem topLevelCS = null;
069        
070    final static Logger LOGGER = LoggerFactory.getLogger(DAFeature.class);
071    protected Integer length;
072    protected boolean initialized = false;
073
074
075    public DAFeature() {
076        super();
077    }
078
079    public DAFeature(DAOCoreFactory factory) {
080        super(factory);
081    }
082
083    @Override
084    public MappingSet getLoadedMappings() {
085        //we haven't got the lazy load working here yet
086        return this.mappings;
087    }
088
089    //Should Be Private?? and call by named object type methods ??
090    @Override
091    public MappingSet getLoadedMappings(ObjectType targetType) {
092
093        if (this.objectTypeMappings.containsKey(targetType)) {
094            return this.objectTypeMappings.get(targetType);
095        } else if (this.isObjectTypeMapped(targetType)) {
096            this.objectTypeMappings.put(targetType, new MappingSet());
097            return this.objectTypeMappings.get(targetType);
098        } else {
099            //we haven't got the lazy load working here yet
100            //so far implemented the reinitialize method in: DATranscript, DAGene, DAExon
101            this.reinitialize();
102            return this.objectTypeMappings.get(targetType);
103        }
104    }
105
106    @Override
107    public Boolean addMapping(Mapping mapping) {
108
109//        //check we haven't already added a mapping for an object with this id!
110//        if (mapping.getTarget() != null) {
111//
112//            for (Mapping m : this.mappings) {
113//                if (m.getTarget() != null && m.getTarget().getId().equals(mapping.getTarget().getId())) {
114//                    return;
115//                }
116//            }
117//        }
118
119        //if we fail to add the mapping this will be false
120        if (this.mappings.add((Mapping) mapping)) {
121
122            ObjectType t = mapping.getTargetType();
123
124            if (t != null) {
125                if (!this.objectTypeMappings.containsKey(t)) {
126                    this.objectTypeMappings.put(t, new MappingSet());
127                }
128
129                this.objectTypeMappings.get(t).add((Mapping) mapping);
130            }
131            return true;
132        }
133
134        return false;
135
136    }
137
138    @Override
139    public void addMappedObjectType(ObjectType mappedType) {
140        mappedObjectTypes.add(mappedType);
141    }
142
143    @Override
144    public Boolean isObjectTypeMapped(ObjectType mappedType) {
145        return mappedObjectTypes.contains(mappedType);
146    }
147
148    // can there be more than one of these?
149    public MappingSet getTopLevelMappings() throws DAOException {
150        
151        if (this.topLevelCS!=null) {
152            return this.getLoadedMappings(topLevelCS.getType());
153        } else {
154            this.inititializeTopLevel();
155        }
156
157        MappingSet out = new MappingSet();
158
159        if (this.getDaoFactory()==null) {
160            throw new DAOException("No DAOFactory Set on this DAFeature");
161        }
162
163        try {
164                if (this.getDaoFactory().getDBType().equals(EnsemblDBType.core)) {
165
166                    this.topLevelCS = ((SingleSpeciesCoreDatabase) this.getDaoFactory().getDatabase()).getTopLevelCoordSystem();
167
168                } else if (this.getDaoFactory().getDBType().equals(EnsemblDBType.collection_core)) {
169                    this.topLevelCS = ((CollectionCoreDatabase) this.getDaoFactory().getDatabase()).getTopLevelCS(this.getSpecies());
170
171                }
172            } catch (DAOException dex) {
173                LOGGER.warn("Failed to get the top level coordinate System for a Feature "
174                        +this.getClass().getSimpleName()+ " "+this.getId(), dex);
175                throw dex;
176            } catch (Exception ex) {
177                LOGGER.warn("Failed to get the top level coordinate System for a Feature "
178                        +this.getClass().getSimpleName()+ " "+this.getId(), ex);
179                throw new DAOException("Failed to retrieve top level CoordinateSystem for this CoreDatabase", ex);
180            }
181
182        if (this.topLevelCS != null) {
183
184            out = this.getLoadedMappings(this.topLevelCS.getType());
185            this.addMappedObjectType(this.topLevelCS.getType());
186        } else {
187            return null;
188        } 
189
190
191            return out;
192    }
193
194    /**
195     * Utility method to pull back a single mapping of this Feature on a Given chromosome.
196     * This should be the mapping stored at initialisation.
197     * If the Feature has implemented a reinitialize method this may be called.
198     * @param chr
199     * @return a single Mapping
200     * @throws NonUniqueException if more than one mapping for the chromosome
201     */
202    @Override
203    public Mapping getChromosomeMapping(Chromosome chr) throws NonUniqueException{
204        
205        if (chr==null) {
206            return null;
207        }
208        
209        Mapping uniqueMapping = null;
210        
211        MappingSet s = this.getLoadedMappings(EnsemblCoordSystemType.chromosome);
212        
213        if (s==null || s.isEmpty()) {
214            return null;
215        }
216        
217        boolean found = false;
218        
219        for (Mapping m : s) {
220            if (m.getTarget()==chr) {
221                if (found) {
222                    throw new NonUniqueException();
223                }
224                uniqueMapping = m;
225                found = true;
226            }
227        }
228        return uniqueMapping;
229    }
230    
231    /**
232     * Utility method to pull back a unique chromosomal mapping of this Feature.
233     * This should be the mapping stored at initialisation.
234     * If the Feature has implemented a reinitialize method this may be called.
235     * @return a single Mapping
236     * @throws NonUniqueException if more than one mapping for the chromosome
237     */
238    @Override
239    public Mapping getChromosomeMapping() throws NonUniqueException{
240        
241        Mapping uniqueMapping = null;
242        MappingSet s = this.getLoadedMappings(EnsemblCoordSystemType.chromosome);
243        
244        if (s==null || s.isEmpty()) {
245            return null;
246        }
247        
248        if (s.size()>1) {
249          throw new NonUniqueException();
250        }
251        
252        return s.first();
253    }
254    
255    /**
256     * Method to return all chromosomal mappings for this feature.
257     * In most circumstances there should be only one mapping.
258     * If the Feature has implemented a re-initialize method this may be called.
259     * @return MappingSet ordered set of chromosomal mappings
260     */
261    @Override
262    public MappingSet getChromosomeMappings() {
263        
264        return this.getLoadedMappings(EnsemblCoordSystemType.chromosome);
265    }
266    
267    public MappingSet getAnnotationLevelMappings() throws DAOException  {
268        
269        if (this.getDaoFactory()==null) {
270            throw new DAOException("No DAOFactory Set on this DAFeature");
271        }
272
273        Set<? extends CoordinateSystem> annotCS = null;
274
275        try {
276            if (this.getDaoFactory().getDBType().equals(EnsemblDBType.core)) {
277
278                annotCS = ((SingleSpeciesCoreDatabase) this.getDaoFactory().getDatabase()).getCSForFeature(this.getType());
279
280            } else if (this.getDaoFactory().getDBType().equals(EnsemblDBType.collection_core)) {
281                annotCS = ((CollectionCoreDatabase) this.getDaoFactory().getDatabase())
282                        .getCSForFeature(this.getSpecies(),this.getType());
283            }
284        } catch (DAOException dex) {
285                LOGGER.warn("Failed to get the annotation level coordinate System for a Feature "
286                        +this.getClass().getSimpleName()+ " "+this.getId(), dex);
287                throw dex;
288            } catch (Exception ex) {
289                LOGGER.warn("Failed to get the annotation level coordinate System for a Feature "
290                        +this.getClass().getSimpleName()+ " "+this.getId(), ex);
291                throw new DAOException("Failed to retrieve annotation level CoordinateSystem for this CoreDatabase", ex);
292            }
293
294        if (annotCS==null || annotCS.isEmpty()) {
295            LOGGER.warn("Failed to get the annotation level coordinate System for a Feature "
296                    +this.getClass().getSimpleName()+ " "+this.getId());
297            return null;
298        }
299
300        MappingSet out = new MappingSet();
301
302        for (CoordinateSystem cs : annotCS ) {
303
304            MappingSet t = this.getLoadedMappings(cs.getType());
305            this.addMappedObjectType(cs.getType());
306            if (t != null) {
307               out.addAll(t);
308            }
309
310        }
311
312        return out;
313    }
314
315    public MappingSet getBuildLevelMappings() throws DAOException{
316
317        if (this.getDaoFactory()==null) {
318            throw new DAOException("No DAOFactory Set on this DAFeature");
319        }
320
321        MappingSet out = new MappingSet();
322        CoordinateSystem buildCS = null;
323        
324        try {
325            if (this.getDaoFactory().getDBType().equals(EnsemblDBType.core)) {
326
327                buildCS = ((SingleSpeciesCoreDatabase) this.getDaoFactory().getDatabase()).getBuildCoordSystem(this.getType().toString());
328
329            } else if (this.getDaoFactory().getDBType().equals(EnsemblDBType.collection_core)) {
330                buildCS = ((CollectionCoreDatabase) this.getDaoFactory().getDatabase()).getBuildCoordSystem(this.getSpecies(),this.getType().toString() );
331
332            }
333        } catch (DAOException dex) {
334                LOGGER.warn("Failed to get the build level coordinate system for this database "
335                        +this.getClass().getSimpleName()+ " "+this.getId(), dex);
336                throw dex;
337            } catch (Exception ex) {
338                LOGGER.warn("Failed to get the build level coordinate system for this database "
339                        +this.getClass().getSimpleName()+ " "+this.getId(), ex);
340                throw new DAOException("Failed to retrieve build level CoordinateSystem for this CoreDatabase", ex);
341            }
342
343        if (buildCS != null) {
344
345            out = this.getLoadedMappings(buildCS.getType());
346            this.addMappedObjectType(buildCS.getType());
347        } else {
348            return null;
349        }
350
351        return out;
352    }
353
354    @Override
355    public void clearAllMappings() {
356            mappings.clear();
357            mappedObjectTypes.clear();
358            objectTypeMappings.clear();
359    }
360
361    public String getDescription()  {
362        return description;
363    }
364
365    public void setDescription(String description) {
366        this.description = description;
367    }
368
369    abstract void reinitialize() ; //throws DAOException ;
370
371    public boolean isInitialized() {
372        return initialized;
373    }
374    
375    public void setInitialized(boolean init) {
376        this.initialized = init;
377    }
378    
379    public static enum Status {
380        KNOWN,
381        NOVEL,
382        PUTATIVE,
383        PREDICTED,
384        KNOWN_BY_PROJECTION,
385        UNKNOWN;
386    }
387
388    public String getStatus() {
389        return this.status.toString();
390    }
391
392    public void setStatus(String status) {
393        try {
394            this.status = Status.valueOf(status);
395        } catch (IllegalArgumentException e) {
396            this.status = Status.UNKNOWN;
397        }
398    }
399
400    public String getDisplayName() {
401        return displayName ;
402    }
403
404    public void setDisplayName(String displayName) {
405        this.displayName = displayName;
406    }
407
408    @Override
409    public Boolean isCurrent() {
410        return current;
411    }
412
413    public void setCurrent(Boolean current) {
414        this.current = current;
415    }
416    
417    /**
418     * This method is used to initialize  the 'topLevelCS', 
419     * the 'topLevelTargetCoordinates' and the 'topLevelTargetSequence' fields.
420     * @throws DAOException 
421     */
422    protected void inititializeTopLevel() throws DAOException {
423        
424        //make sure the gene is intialized
425        reinitialize();
426
427        if (this.topLevelCS != null) {
428            return;
429        }
430
431        if (this.getDaoFactory() == null) {
432            throw new DAOException("No DAOFactory Set on this DAFeature");
433        }
434
435        try {
436            if (this.getDaoFactory().getDBType().equals(EnsemblDBType.core)) {
437
438                this.topLevelCS = ((SingleSpeciesCoreDatabase) this.getDaoFactory().getDatabase()).getTopLevelCoordSystem();
439
440            } else if (this.getDaoFactory().getDBType().equals(EnsemblDBType.collection_core)) {
441                this.topLevelCS = ((CollectionCoreDatabase) this.getDaoFactory().getDatabase()).getTopLevelCS(this.getSpecies());
442
443            }
444        } catch (DAOException dex) {
445            LOGGER.warn("Failed to get the top level coordinate System for a Feature "
446                    + this.getClass().getSimpleName() + " " + this.getId(), dex);
447            throw dex;
448        } catch (Exception ex) {
449            LOGGER.warn("Failed to get the top level coordinate System for a Feature "
450                    + this.getClass().getSimpleName() + " " + this.getId(), ex);
451            throw new DAOException("Failed to retrieve top level CoordinateSystem for this CoreDatabase", ex);
452        }
453
454        if (this.topLevelCS != null) {
455
456            this.addMappedObjectType(this.topLevelCS.getType());
457            Mapping m = null;
458            
459            
460            if (this.getLoadedMappings(this.topLevelCS.getType())!=null && 
461                    !this.getLoadedMappings(this.topLevelCS.getType()).isEmpty()) {
462                m= this.getLoadedMappings(this.topLevelCS.getType()).first() ;
463            }
464
465            if (m == null) {
466                return;
467            }
468
469            topLevelTargetCoordinates = m.getTargetCoordinates();
470            if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null
471                    || topLevelTargetCoordinates.getEnd() == null) {
472                return;
473            }
474
475            topLevelTargetSequence = (DADNASequence) m.getTarget();
476        }
477    }
478
479    /**
480     * Returns the sequence covered by this feature (i.e. a new DADNASequence
481     * object representing this region of the genome, it might be a GAPSequence
482     * if no sequence info is available).
483     * @return DADNASequence
484     */
485    public DADNASequence getSequence()  {
486
487        if (thisSequence != null) {
488            return thisSequence;
489        }
490
491        try {
492            this.inititializeTopLevel();
493        } catch (DAOException dAOException) {
494        }
495               
496        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
497                || topLevelTargetCoordinates.getEnd() == null) {
498            return null;
499        }
500       
501        // according to the ensembl api we should catch null sequences here and 
502        //convert them to the length of the exon - however, i think this is more
503        //to catch empty sequences
504        if (topLevelTargetSequence == null) {
505            Integer length = topLevelTargetCoordinates.getLength();
506            thisSequence = GapSequence.makeGap(length);
507        } else if (Strand.REVERSE_STRAND.equals(topLevelTargetCoordinates.getStrand())) {
508            thisSequence = new DADNASequence(topLevelTargetSequence.getReverseComplementSequenceAsString(topLevelTargetCoordinates.getStart(), topLevelTargetCoordinates.getEnd()));
509        } else {
510            thisSequence = new DADNASequence(topLevelTargetSequence.getSequenceAsString(topLevelTargetCoordinates.getStart(), topLevelTargetCoordinates.getEnd()));
511        }
512
513        return thisSequence;
514    }
515
516    /**
517     * Returns the (genomic) sequence that this feature is annotated upon (at
518     * 'top level').
519     *
520     * @return DADNASequence
521     */
522    public DADNASequence getTargetSequence() {
523        try {
524            this.inititializeTopLevel();
525        } catch (DAOException dAOException) {
526        }
527        return topLevelTargetSequence;
528    }
529
530    /**
531     * Retrieves the String representation of the target (genomic) sequence that
532     * this feature is annotated upon, for the given range. The range arguments
533     * are relative to the start of the annotation, and therefore can be used to
534     * get Flanking Sequences etc. The range arguments are transparently
535     * converted to coordinates on the target sequence.      
536     * Arguments outwith the extent of the annotation (i.e greater than its length, or less than the start site of 1) 
537     * will throw a (Runtime) RangeException.
538     * @param start Integer
539     * @param stop Integer
540     * @return String
541     * @throws RangeException
542     */
543    public String getFlankingTargetSequenceAsString(Integer start, Integer stop) throws RangeException {
544        try {
545            this.inititializeTopLevel();
546        } catch (DAOException dAOException) {
547        }
548               
549        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
550                || topLevelTargetCoordinates.getEnd() == null || topLevelTargetSequence == null) {
551            return "";
552        }
553        
554        //any illegal arguments are thrown as runtime exceptions by the method
555        Coordinate targetCoordinate = this.convertToTargetCoordinate(start, stop);
556
557        if (Strand.REVERSE_STRAND.equals(targetCoordinate.getStrand())) {
558            //any illegal arguments are thrown as runtime exceptions by the  method
559            return topLevelTargetSequence.getReverseComplementSequenceAsString(targetCoordinate.getStart(), targetCoordinate.getEnd());
560        } else {
561            //any illegal arguments are thrown as runtime exceptions by  the method
562            return topLevelTargetSequence.getSequenceAsString(targetCoordinate.getStart(), targetCoordinate.getEnd());
563        }
564    }
565    
566    /**
567     * Retrieves the String representation of the target (genomic) sequence that
568     * this feature is annotated upon, for the given range. The range arguments
569     * are relative to the start of the annotation, and therefore can be used to
570     * get Flanking Sequences etc. The range arguments are transparently
571     * converted to coordinates on the target sequence.
572     * Arguments outwith the extent of the target sequence (i.e greater than its length, or less than the start site of 1) 
573     * will cause the returned sequence to be padded appropriately.     
574     * @param start Integer
575     * @param stop Integer
576     * @return String
577     */
578    public String getPaddedFlankingTargetSequenceAsString(Integer start, Integer stop) {
579        try {
580            this.inititializeTopLevel();
581        } catch (DAOException dAOException) {
582        }
583               
584        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
585                || topLevelTargetCoordinates.getEnd() == null || topLevelTargetSequence == null) {
586            return "";
587        }
588        //any illegal arguments are thrown as runtime exceptions by the wrapped method
589        Coordinate targetCoordinate = this.convertToTargetCoordinate(start, stop);
590
591        if (Strand.REVERSE_STRAND.equals(targetCoordinate.getStrand())) {
592            return topLevelTargetSequence.getPaddedReverseComplementSequenceAsString(targetCoordinate.getStart(), targetCoordinate.getEnd());
593        } else {
594            return topLevelTargetSequence.getPaddedSequenceAsString(targetCoordinate.getStart(), targetCoordinate.getEnd());
595        }
596    }
597
598    /**
599     * Returns the string representation of the (genomic) sequence that this
600     * feature is annotated upon (at 'top level').
601     *
602     * @return String
603     */
604    public String getSequenceAsString() {
605        
606       //original method caused a sequence object to be created..
607       // return this.getSequence() != null ? this.getSequence().getSequenceAsString() : "";
608        
609        if (thisSequence != null) {
610            return thisSequence.getSequenceAsString();
611        }
612        
613        try {
614            this.inititializeTopLevel();
615        } catch (DAOException dAOException) {
616        }
617               
618        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
619                || topLevelTargetCoordinates.getEnd() == null) {
620            return "";
621        }
622       
623        // according to the ensembl api we should catch null sequences here and 
624        //convert them to the length of the exon - however, i think this is more
625        //to catch empty sequences
626        Integer length = topLevelTargetCoordinates.getLength();
627        if (topLevelTargetSequence == null) {
628            return  GapSequence.getGapString(length);
629        } else {
630            return getFlankingTargetSequenceAsString(1,length);
631        }
632
633    }
634
635    /**
636     * Returns the string representation of the (genomic) sequence that this
637     * feature is annotated upon (at 'top level'), for the specified range. The
638     * range arguments are relative to the start of the annotation, i.e. on the correct strand, from '1' to 'length of annotation'.
639     * Arguments outwith the extent of the annotation (i.e greater than its length, or less than the start site of 1) 
640     * will throw a (Runtime) RangeException. There is no Padding function available on Features.
641     * @param start Integer
642     * @param stop Integer
643     * @return String
644     */
645    public String getSequenceAsString(Integer start, Integer stop) throws RangeException {
646        //any illegal arguments are thrown as runtime exceptions by the wrapped method
647
648        //original method caused a sequence object to be created..
649        //return this.getSequence() != null ? ((DADNASequence) this.getSequence()).getSequenceAsString(start, stop) : "";
650        
651        if (thisSequence != null) {
652            return thisSequence.getSequenceAsString(start, stop);
653        }
654        
655        try {
656            this.inititializeTopLevel();
657        } catch (DAOException dAOException) {
658        }
659               
660        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
661                || topLevelTargetCoordinates.getEnd() == null) {
662            return "";
663        }
664       
665        // according to the ensembl api we should catch null sequences here and 
666        //convert them to the length of the exon - however, i think this is more
667        //to catch empty sequences
668
669        if (topLevelTargetSequence == null) {
670            Coordinate c = new Coordinate(start,stop);
671            return  GapSequence.getGapString(c.getLength());
672        } else  {
673            return getFlankingTargetSequenceAsString(start,stop);
674        }
675        
676    }
677      
678    /**
679     * Converts an Integer position on the TopLevel-annotated Target (typically the Chromosome) 
680     * to the position on this feature. The TopLevel Target should only have  a 
681     * positive coordinate system. If the TopLevel Target coordinates are found to extend below 1, 
682     * a range exception is thrown rather than try to handle this.
683     * @TODO maybe rename 'convertTopLevelPositionToFeature'
684     * @param chromosomePosition Integer
685     * @return Integer
686     */    
687    public Integer convertChromosomePositionToFeature(Integer chromosomePosition) {
688        
689        Integer result = null;
690        
691        if (chromosomePosition==null|| chromosomePosition==0 ) {
692            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
693                    +" Use -1 for one base upstream or +1 for the first base.");            
694        }
695        if (chromosomePosition<0) {
696            throw new RangeException("A chromosome has no coordinates lower than 0.");            
697        }
698        
699        try {
700            this.inititializeTopLevel();
701        } catch (DAOException dAOException) {
702        } 
703        
704        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
705                || topLevelTargetCoordinates.getEnd() == null) {
706            return null;
707        }       
708        
709        //these coordinates should be positive, with End>Start
710        Integer featureChrStart = topLevelTargetCoordinates.getStart();
711        Integer featureChrEnd = topLevelTargetCoordinates.getEnd();
712        
713        if (featureChrStart<1 || featureChrEnd<1) {
714            throw new RangeException("Bad (negative) chromosome coordinates for this Feature");
715        }
716        
717        if ( Strand.REVERSE_STRAND.equals(topLevelTargetCoordinates.getStrand())) {
718            result = 1 + (featureChrEnd-chromosomePosition);
719            //if we have gone over 'ZERO' we need a correction
720            if (result<1 ){
721                result--;
722            }
723            
724            
725        } else {
726            result =  chromosomePosition-featureChrStart+1;
727            //if we have gone over 'ZERO' we need a correction
728            if (result<1)
729                {
730                    result--;
731                }
732        }
733
734        return result;
735    }
736    
737    /**
738     * Converts an Integer relative to the annotation start site to the position on the 
739     * annotated TopLevel target (genomic) sequence. Whilst the TopLevel Target should only have  a 
740     * positive coordinate system (and if not a RangeException will be thrown here), 
741     * the query Integer is allowed to be outwith the bounds of the Feature,
742     * and may possibly return a value outwith the bounds of the Chromosome.
743     * @param query Integer
744     * @return Integer
745     */
746    public Integer convertToTargetPosition (Integer query) {
747        
748        //there is no zero in Ensembl
749        //if we are asked for 0 we could perhaps return the mapping for 1 rater than a runtime exception
750        //if we are asked for negative (upstream) positions, we need a +1 correction
751        
752        if (query ==null || query==0) {
753            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
754                    +" Use -1 for one base upstream or +1 for the first base.");
755        }
756        
757        try {
758            this.inititializeTopLevel();
759        } catch (DAOException dAOException) {
760        }
761             
762        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
763                || topLevelTargetCoordinates.getEnd() == null) {
764            return null;
765        }
766        
767        //these coordinates should be positive, with End>Start
768        Integer featureChrStart = topLevelTargetCoordinates.getStart();
769        Integer featureChrEnd = topLevelTargetCoordinates.getEnd();
770        
771        if (featureChrStart<1 || featureChrEnd<1) {
772            throw new RangeException("Bad (negative) chromosome coordinates for this Feature");
773        }
774        
775        
776        Integer temp = null;
777        
778        if (Strand.REVERSE_STRAND.equals(topLevelTargetCoordinates.getStrand())) {          
779            if (query<0) {
780                temp =  featureChrEnd-query;              
781            } else {
782                temp =  featureChrEnd-query+1;
783                //if moved from positive to negative, subtract one
784                if (temp<1){
785                    temp--;
786                }
787            }
788            
789        } else {
790            if (query>0) {
791                temp =  featureChrStart+query-1;           
792            } else {
793                temp = featureChrStart+query;
794                //if moved from positive to negative, subtract one
795                if (temp<1){
796                    temp--;
797                }
798            }
799        }
800     
801        //allow this now and deal with it higher up - as we may allow padding if the extent is not covered
802//        if (temp>this.topLevelTargetSequence.getLength() || temp<1) {
803//            throw new IllegalArgumentException("Coordinates fall outside the extent of the DNA molecule");
804//        }
805
806        return temp;
807        
808        
809    }
810
811    /**
812     * Converts a given range (relative to the annotation start site) to the Coordinates on the 
813     * annotated target TopLevel (genomic) sequence. The TopLevel Target should only have  a 
814     * positive coordinate system (and if not a RangeException will be thrown here, 
815     * originating in the call to 'convertToTargetPosition').
816     * The returned Coordinate is given  on the same strand as the feature.
817     * @param start Integer
818     * @param stop Integer
819     * @return Coordinate
820     */
821    public Coordinate convertToTargetCoordinate (Integer start, Integer stop)  {
822        if (start==null || start==0 || stop==null || stop==0) {
823            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
824                    +" Use -1 for one base upstream or +1 for the first base.");
825        }
826        
827        try {
828            this.inititializeTopLevel();
829        } catch (DAOException dAOException) {}
830        
831        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
832                || topLevelTargetCoordinates.getEnd() == null) {
833            return null;
834        }
835        
836        
837        if (stop<start) {
838               Integer temp = start;
839               start = stop;
840               stop = temp;
841        } 
842        
843        if (Strand.REVERSE_STRAND.equals(topLevelTargetCoordinates.getStrand())) {
844            return new Coordinate(convertToTargetPosition(stop), convertToTargetPosition(start), 
845                   Strand.REVERSE_STRAND) ;
846        } else {
847            return new Coordinate(convertToTargetPosition(start), convertToTargetPosition(stop),
848                    Strand.FORWARD_STRAND);
849        }
850        
851    }    
852
853    /**
854     * Returns an RNASequence object trancribed from the DNASequence representing 
855     * the extent of this annotation. The TranscriptionEngine used is as specified by
856     * the genomic target (annotated) sequence.
857     * @return RNASequence
858     */
859    public RNASequence getRNASequence() {
860        if (this.getSequence()==null || topLevelTargetSequence==null) {
861            return null;
862        } else {
863           Integer id = topLevelTargetSequence.getCodonTableID();
864           return thisSequence.getRNASequence(this.getRegistry().getTranscriptionEngine(id));
865        }
866
867    }
868    
869    /**
870     * Returns a string representation of the RNASequence object trancribed from the DNASequence representing 
871     * the extent of this annotation. The TranscriptionEngine used is as specified by
872     * the genomic target (annotated) sequence.
873     * @return String
874     */    
875    public String getRNASequenceAsString() {
876        if (this.getRNASequence()==null) {
877            return "";
878        } else {
879           return this.getRNASequence().getSequenceAsString();
880        }
881    }
882    
883    /**
884     * Returns a string representation for the given range of the RNASequence object 
885     * trancribed from the DNASequence representing  the extent of this annotation. 
886     * The TranscriptionEngine used is as specified by the genomic target (annotated) sequence.
887     * Arguments outwith the extent of the annotation (i.e greater than its length, or less than the start site of 1) 
888     * will throw a (Runtime) RangeException.
889     * @param start
890     * @param stop
891     * @return String
892     * @throws RangeException 
893     */
894    public String getRNASequenceAsString(Integer start, Integer stop) throws RangeException {
895        if (this.getRNASequence()==null) {
896            return "";
897        } else {   
898           if (stop<start) {
899               Integer temp = start;
900               start = stop;
901               stop = temp;
902           } 
903           if (start<1 ) {
904               throw new RangeException("An RNA start position of less than 1 is not allowed");
905           }
906           if (stop>this.getRNASequence().getLength() ) {
907               throw new RangeException("An RNA cannot be longer than its length");
908           }               
909           return this.getRNASequence().getSequenceAsString(start, stop, null);
910        }
911    }
912    
913    /**
914     * Returns the length of this feature, as calculated from its mapped 
915     * coordinates on the top level annotated sequence (typically the chromosome).
916     */
917    public Integer getLength() {
918
919        if (this.length!=null) {
920            return this.length;
921        }
922        
923        try {
924            this.inititializeTopLevel();
925        } catch (DAOException dAOException) {
926        }
927               
928        if (topLevelTargetCoordinates == null || topLevelTargetCoordinates.getStart() == null 
929                || topLevelTargetCoordinates.getEnd() == null) {
930            return null;
931        }
932        
933        this.length = topLevelTargetCoordinates.getLength();
934        
935        return this.length;
936    }
937    
938    public void setLength(Integer length) {
939        this.length = length;
940    }
941
942    public Coordinate getTopLevelTargetCoordinates() throws DAOException {
943        if (topLevelTargetCoordinates!=null) {
944            return topLevelTargetCoordinates;
945        }    
946        this.inititializeTopLevel();
947        return topLevelTargetCoordinates;
948    }
949
950    public DADNASequence getTopLevelTargetSequence() throws DAOException {
951        if (topLevelTargetSequence!=null) {
952            return topLevelTargetSequence;
953        } 
954        this.inititializeTopLevel();
955        return topLevelTargetSequence;
956    }
957
958    @Override
959    public Integer getId() {
960        if (this.id==null||this.id==0) {
961            reinitialize();
962        }
963        return id;
964    }
965       
966    @Override
967    public Integer getVersion() {
968        if (version==null) {
969            this.reinitialize();
970        }
971        return version;
972    }
973    
974    @Override
975    public Date getModificationDate() {
976        if (modificationDate==null) {
977            this.reinitialize();
978        }
979        return modificationDate;
980    }
981    
982    @Override
983    public Date getCreationDate() {
984        if (creationDate==null) {
985            this.reinitialize();
986        }
987        return creationDate;
988    }
989
990
991}