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}