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.biojava.bio.seq.impl; 023 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.List; 027 028import org.biojava.bio.Annotatable; 029import org.biojava.bio.Annotation; 030import org.biojava.bio.BioException; 031import org.biojava.bio.seq.Feature; 032import org.biojava.bio.seq.FeatureFilter; 033import org.biojava.bio.seq.FeatureHolder; 034import org.biojava.bio.seq.FilterUtils; 035import org.biojava.bio.seq.RemoteFeature; 036import org.biojava.bio.seq.Sequence; 037import org.biojava.bio.seq.StrandedFeature; 038import org.biojava.bio.seq.db.IllegalIDException; 039import org.biojava.bio.seq.projection.ProjectedFeatureHolder; 040import org.biojava.bio.seq.projection.Projection; 041import org.biojava.bio.seq.projection.ReparentContext; 042import org.biojava.bio.seq.projection.TranslateFlipContext; 043import org.biojava.bio.symbol.Alphabet; 044import org.biojava.bio.symbol.Edit; 045import org.biojava.bio.symbol.FuzzyLocation; 046import org.biojava.bio.symbol.Location; 047import org.biojava.bio.symbol.LocationTools; 048import org.biojava.bio.symbol.RangeLocation; 049import org.biojava.bio.symbol.Symbol; 050import org.biojava.bio.symbol.SymbolList; 051import org.biojava.ontology.InvalidTermException; 052import org.biojava.ontology.Term; 053import org.biojava.utils.ChangeEvent; 054import org.biojava.utils.ChangeForwarder; 055import org.biojava.utils.ChangeListener; 056import org.biojava.utils.ChangeSupport; 057import org.biojava.utils.ChangeType; 058import org.biojava.utils.ChangeVetoException; 059 060/** 061 * View a sub-section of a given sequence object, including all the 062 * features intersecting that region. 063 * 064 * <p> 065 * All features entirely contained within the region are projected by just 066 * translating their locations. The features that overlap the region are 067 * replaced by RemoteFeature instances with fuzzy locations that are 068 * trunchated to fit inside the sub-section. All features not contained by 069 * the region are not projected. 070 * </p> 071 * 072 * @author Thomas Down 073 * @author Matthew Pocock 074 * @since 1.2 075 */ 076 077public class SubSequence 078implements Sequence, java.io.Serializable 079{ 080 private final Sequence parent; 081 private final SymbolList symbols; 082 private final String name; 083 private final String uri; 084 private final Annotation annotation; 085 private final RangeLocation parentLocation; 086 087 private transient ProjectedFeatureHolder features; 088 private transient ChangeSupport changeSupport; 089 private transient ChangeListener seqListener; 090 protected transient ChangeForwarder annotationForwarder; 091 092 private void allocChangeSupport() { 093 if (seqListener == null) { 094 installSeqListener(); 095 } 096 changeSupport = new ChangeSupport(); 097 } 098 099 private void installSeqListener() { 100 seqListener = new ChangeListener() { 101 public void preChange(ChangeEvent cev) 102 throws ChangeVetoException { 103 if (changeSupport != null) { 104 changeSupport.firePreChangeEvent(makeChainedEvent(cev)); 105 } 106 } 107 108 public void postChange(ChangeEvent cev) { 109 if (changeSupport != null) { 110 changeSupport.firePostChangeEvent(makeChainedEvent(cev)); 111 } 112 } 113 114 private ChangeEvent makeChainedEvent(ChangeEvent cev) { 115 return new ChangeEvent(SubSequence.this, 116 FeatureHolder.FEATURES, 117 null, null, 118 cev); 119 } 120 }; 121 parent.addChangeListener(seqListener, FeatureHolder.FEATURES); 122 } 123 124 /** 125 * Construct a new SubSequence of the specified sequence. 126 * Generally you would use the SequenceTools.subSequence() methods 127 * to get an instance of this class. 128 * @param seq A sequence to view 129 * @param start The start of the range to view 130 * @param end The end of the range to view 131 * @param name Name for the subsequence 132 * @throws java.lang.IndexOutOfBoundsException is the start or end position is illegal. 133 */ 134 public SubSequence(Sequence seq, 135 final int start, 136 final int end, 137 final String name) 138 { 139 this.parent = seq; 140 this.parentLocation = new RangeLocation(start, end); 141 this.symbols = seq.subList(start, end); 142 this.name = name; 143 this.uri = seq.getURN() + "?start=" + start + ";end=" + end; 144 this.annotation = seq.getAnnotation(); 145 } 146 147 /** 148 * Construct a new SubSequence of the specified sequence. Generally 149 * you would use the SequenceTools.subSequence() methods to get an 150 * instance of this class. 151 * 152 * @param seq A sequence to view 153 * @param start The start of the range to view 154 * @param end The end of the range to view 155 * @throws java.lang.IndexOutOfBoundsException if the start or end position is illegal. 156 */ 157 158 public SubSequence(Sequence seq, 159 final int start, 160 final int end) { 161 this(seq, start, end, seq.getName() + " (" + start + " - " + end + ")"); 162 } 163 164 // 165 // SymbolList stuff 166 // 167 168 public Symbol symbolAt(int pos) { 169 return symbols.symbolAt(pos); 170 } 171 172 public Alphabet getAlphabet() { 173 return symbols.getAlphabet(); 174 } 175 176 public SymbolList subList(int start, int end) { 177 return symbols.subList(start, end); 178 } 179 180 public String seqString() { 181 return symbols.seqString(); 182 } 183 184 public String subStr(int start, int end) { 185 return symbols.subStr(start, end); 186 } 187 188 public List toList() { 189 return symbols.toList(); 190 } 191 192 public int length() { 193 return symbols.length(); 194 } 195 196 public Iterator iterator() { 197 return symbols.iterator(); 198 } 199 200 public void edit(Edit edit) 201 throws ChangeVetoException { 202 throw new ChangeVetoException("Can't edit SubSequences"); 203 } 204 205 // 206 // Implements featureholder 207 // 208 209 public int countFeatures() { 210 return getFeatures().countFeatures(); 211 } 212 213 public Iterator features() { 214 return getFeatures().features(); 215 } 216 217 public FeatureHolder filter(FeatureFilter ff, boolean recurse) { 218 return getFeatures().filter(ff, recurse); 219 } 220 221 public FeatureHolder filter(FeatureFilter ff) { 222 return getFeatures().filter(ff); 223 } 224 225 public boolean containsFeature(Feature f) { 226 return getFeatures().containsFeature(f); 227 } 228 229 public Feature createFeature(Feature.Template templ) 230 throws BioException, ChangeVetoException { 231 232 ProjectedFeatureHolder featureHolder = getFeatures(); 233 234 Feature f = null; 235 synchronized (featureHolder) { 236 f = featureHolder.createFeature(templ); 237 } 238 return f; 239 240 } 241 242 public void removeFeature(Feature f) 243 throws ChangeVetoException, BioException { 244 ProjectedFeatureHolder featureHolder = getFeatures(); 245 246 synchronized (featureHolder){ 247 featureHolder.removeFeature(f); 248 } 249 } 250 251 public FeatureFilter getSchema() { 252 return getFeatures().getSchema(); 253 } 254 255 protected ProjectedFeatureHolder getFeatures() { 256 if (features == null) { 257 FeatureHolder clipped = new ProjectedFeatureHolder( 258 new SubProjectedFeatureContext(parent, parentLocation) ); 259 features = new ProjectedFeatureHolder( 260 new TranslateFlipContext(this, 261 clipped, 262 - parentLocation.getMin() + 1) ); 263 } 264 return features; 265 } 266 267 // 268 // Identifiable 269 // 270 271 public String getName() { 272 return name; 273 } 274 275 public String getURN() { 276 return uri; 277 } 278 279 // 280 // Annotatable 281 // 282 283 public Annotation getAnnotation() { 284 return annotation; 285 } 286 287 /** 288 * Return the parent sequence of which this is a partial view 289 * 290 * @since 1.3 291 */ 292 293 public Sequence getSequence() { 294 return this.parent; 295 } 296 297 public int getStart() { 298 return parentLocation.getMin(); 299 } 300 301 public int getEnd() { 302 return parentLocation.getMax(); 303 } 304 305 public void addChangeListener(ChangeListener cl, ChangeType ct) { 306 if (changeSupport == null) { 307 allocChangeSupport(); 308 } 309 310 if (annotationForwarder == null && ct == Annotatable.ANNOTATION) { 311 annotationForwarder = 312 new ChangeForwarder.Retyper(this, changeSupport, Annotation.PROPERTY); 313 getAnnotation().addChangeListener(annotationForwarder, 314 Annotatable.ANNOTATION); 315 } 316 317 changeSupport.addChangeListener(cl, ct); 318 } 319 320 public void addChangeListener(ChangeListener cl) { 321 addChangeListener(cl, ChangeType.UNKNOWN); 322 } 323 324 public void removeChangeListener(ChangeListener cl, ChangeType ct) { 325 if (changeSupport != null) { 326 changeSupport.removeChangeListener(cl, ct); 327 } 328 } 329 330 public void removeChangeListener(ChangeListener cl) { 331 removeChangeListener(cl, ChangeType.UNKNOWN); 332 } 333 334 public boolean isUnchanging(ChangeType ct) { 335 return parent.isUnchanging(ct); 336 } 337 338 /** 339 * TargetContext that implements the mapping between the parent sequence and this 340 * sub-sequence. 341 * 342 * <p> 343 * This context is public because all contexts must be public, and not because 344 * it is usefull to you or part of the API. Don't use it directly. 345 * </p> 346 * 347 * <p> 348 * The context extends TranslateFlipContext so that it can translate all 349 * features within the parent location to being from index 1 in the 350 * projection. It also transforms and reverts locations so that locations 351 * falling outside the parent location are trunchated and replaced by 352 * fuzzy locations. Lastly, features with fuzzy locations are replaced by 353 * RemoteFeature instances. 354 *</p> 355 * 356 * @author Matthew Pocock 357 * @author Thomas Down 358 * @since 1.4 359 */ 360 public static class SubProjectedFeatureContext 361 extends ReparentContext { 362 private static final FeatureFilter REMOTE_FILTER = 363 new FeatureFilter.ByClass(RemoteFeature.class); 364 365 private static RemoteFeature.Resolver resolver 366 = new RemoteFeature.Resolver() { 367 public Feature resolve(RemoteFeature rFeat) throws IllegalIDException, BioException { 368 if(!(rFeat instanceof SubRemote)) { 369 throw new BioException("Unable to resolve feature: " + rFeat); 370 } 371 372 return rFeat.getRemoteFeature(); 373 } 374 }; 375 376 private final RangeLocation parentLocation; 377 private final FeatureFilter remoteLocationFilter; 378 private final FeatureFilter clippingFilter; 379 380 private SubProjectedFeatureContext(FeatureHolder wrapped, 381 RangeLocation parentLocation) { 382 super(FeatureHolder.EMPTY_FEATURE_HOLDER, 383 new LazyFilterFeatureHolder(wrapped, 384 FilterUtils.overlapsLocation( 385 parentLocation))); 386 387 this.parentLocation = parentLocation; 388 this.remoteLocationFilter = new FeatureFilter.Not( 389 FilterUtils.containedByLocation(parentLocation)); 390 this.clippingFilter = FilterUtils.overlapsLocation(parentLocation); 391 } 392 393 public Location projectLocation(Location toTransform) { 394 if(LocationTools.overlaps(parentLocation, toTransform) && 395 !LocationTools.contains(parentLocation, toTransform)) 396 { 397 toTransform = fuzzyize(toTransform); 398 } 399 400 return toTransform; 401 } 402 403 public Feature projectFeature(Feature origFeat) { 404 if(remoteLocationFilter.accept(origFeat)) { 405 Location loc = fuzzyize(origFeat.getLocation()); 406 List regions = makeRegions(origFeat.getLocation(), 407 origFeat.getSequence().getName()); 408 409 origFeat = new SubRemote(origFeat, loc, regions); 410 } 411 412 return super.projectFeature(origFeat); 413 } 414 415 public Feature revertFeature(Feature projFeat) { 416 Feature origFeat = super.revertFeature(projFeat); 417 418 if(!(origFeat instanceof Projection) && 419 (origFeat instanceof SubRemote) ) 420 { 421 origFeat = ((SubRemote) origFeat).getRemoteFeature(); 422 } 423 424 return origFeat; 425 } 426 427 public FeatureHolder projectChildFeatures(Feature f, FeatureHolder parent) { 428 return new LazyFilterFeatureHolder(super.projectChildFeatures(f, parent), 429 clippingFilter); 430 } 431 432 private Location fuzzyize(Location loc) { 433 List locList = new ArrayList(); 434 435 for(Iterator i = loc.blockIterator(); i.hasNext(); ) { 436 Location l = (Location) i.next(); 437 if(LocationTools.overlaps(parentLocation, l)) { 438 boolean fuzzyLeft = l.getMin() < parentLocation.getMin(); 439 boolean fuzzyRight = l.getMax() > parentLocation.getMax(); 440 441 if(fuzzyLeft || fuzzyRight) { 442 locList.add(new FuzzyLocation(Math.min(l.getMin(), parentLocation.getMin()), 443 Math.max(l.getMax(), parentLocation.getMax()), 444 Math.max(l.getMin(), parentLocation.getMin()), 445 Math.min(l.getMax(), parentLocation.getMax()), 446 fuzzyLeft, 447 fuzzyRight, 448 FuzzyLocation.RESOLVE_INNER 449 )); 450 } else { 451 locList.add(l); 452 } 453 } 454 } 455 456 return LocationTools.union(locList); 457 } 458 459 private List makeRegions(Location oldLoc, String seqID) { 460 List regions = new ArrayList(); 461 462 for(Iterator i = oldLoc.blockIterator(); i.hasNext(); ) { 463 Location loc = (Location) i.next(); 464 465 if(!LocationTools.overlaps(loc, parentLocation)) { 466 regions.add(new RemoteFeature.Region(loc, seqID, true)); 467 } else if(LocationTools.contains(parentLocation, loc)) { 468 regions.add(new RemoteFeature.Region(loc, null, false)); 469 } else { 470 // straddles boundary 471 Location remote = LocationTools.subtract(loc, parentLocation); 472 Location local = LocationTools.subtract(parentLocation, loc); 473 RemoteFeature.Region remoteR = new RemoteFeature.Region(remote, seqID, true); 474 RemoteFeature.Region localR = new RemoteFeature.Region(local, null, false); 475 476 if(remote.getMin() < local.getMin()) { 477 regions.add(remoteR); 478 regions.add(localR); 479 } else { 480 regions.add(localR); 481 regions.add(remoteR); 482 } 483 } 484 } 485 486 return regions; 487 } 488 489 protected FilterUtils.FilterTransformer getReverter() { 490 final FilterUtils.FilterTransformer delegate = super.getReverter(); 491 492 return new FilterUtils.FilterTransformer() { 493 public FeatureFilter transform(FeatureFilter filt) { 494 // I may have got this logic wrong 495 if (filt.equals(REMOTE_FILTER)) { 496 filt = remoteLocationFilter; 497 } 498 499 filt = new FeatureFilter.And( 500 filt, 501 FilterUtils.overlapsLocation(parentLocation) 502 ); 503 504 filt = delegate.transform(filt); 505 506 return filt; 507 } 508 }; 509 } 510 511 protected FilterUtils.FilterTransformer getTransformer() { 512 return super.getTransformer(); 513 } 514 515 // fixme: this isn't wired to forward events 516 // however, the context should be skipping these anyhow during 517 // projectFeature and restoreFeature. 518 private class SubRemote 519 implements RemoteFeature { 520 private final Feature wrapped; 521 private final Location loc; 522 private final List regionList; 523 524 public SubRemote(Feature wrapped, Location loc, List regionList) { 525 this.wrapped = wrapped; 526 this.loc = loc; 527 this.regionList = regionList; 528 } 529 530 public Location getLocation() { 531 return loc; 532 } 533 534 public void setLocation(Location loc) 535 throws ChangeVetoException { 536 throw new ChangeVetoException("Can't set location: " + loc + 537 " on " + this); 538 } 539 540 public List getRegions() { 541 return regionList; 542 } 543 544 public RemoteFeature.Resolver getResolver() { 545 return resolver; 546 } 547 548 public Feature getRemoteFeature() { 549 return wrapped; 550 } 551 552 public StrandedFeature.Strand getStrand() { 553 if(wrapped instanceof StrandedFeature) { 554 return ((StrandedFeature) wrapped).getStrand(); 555 } else { 556 return StrandedFeature.UNKNOWN; 557 } 558 } 559 560 public void setStrand(StrandedFeature.Strand strand) 561 throws ChangeVetoException 562 { 563 if(wrapped instanceof StrandedFeature) { 564 ((StrandedFeature) wrapped).setStrand(strand); 565 } else { 566 throw new ChangeVetoException("Can't set strand. The underlying feature is not stranded: " + wrapped); 567 } 568 } 569 570 public String getType() { 571 return wrapped.getType(); 572 } 573 574 public void setType(String type) 575 throws ChangeVetoException { 576 wrapped.setType(type); 577 } 578 579 public Term getTypeTerm() { 580 return wrapped.getTypeTerm(); 581 } 582 583 public void setTypeTerm(Term t) throws ChangeVetoException, InvalidTermException { 584 wrapped.setTypeTerm(t); 585 } 586 587 public Term getSourceTerm() { 588 return wrapped.getSourceTerm(); 589 } 590 591 public void setSourceTerm(Term t) throws ChangeVetoException, InvalidTermException { 592 wrapped.setSourceTerm(t); 593 } 594 595 public String getSource() { 596 return wrapped.getSource(); 597 } 598 599 public void setSource(String source) 600 throws ChangeVetoException { 601 wrapped.setSource(source); 602 } 603 604 public SymbolList getSymbols() { 605 return wrapped.getSymbols(); 606 } 607 608 public FeatureHolder getParent() { 609 return wrapped.getParent(); 610 } 611 612 public Sequence getSequence() { 613 return wrapped.getSequence(); 614 } 615 616 public Feature.Template makeTemplate() { 617 return wrapped.makeTemplate(); 618 } 619 620 public int countFeatures() { 621 return wrapped.countFeatures(); 622 } 623 624 public Iterator features() { 625 return wrapped.features(); 626 } 627 628 public FeatureHolder filter(FeatureFilter fc, boolean recurse) { 629 return wrapped.filter(fc, recurse); 630 } 631 632 public FeatureHolder filter(FeatureFilter filter) { 633 return wrapped.filter(filter); 634 } 635 636 public Feature createFeature(Feature.Template ft) 637 throws BioException, ChangeVetoException { 638 return wrapped.createFeature(ft); 639 } 640 641 public void removeFeature(Feature f) 642 throws ChangeVetoException, BioException { 643 wrapped.removeFeature(f); 644 } 645 646 public boolean containsFeature(Feature f) { 647 return wrapped.containsFeature(f); 648 } 649 650 public FeatureFilter getSchema() { 651 return wrapped.getSchema(); 652 } 653 654 public void addChangeListener(ChangeListener cl) { 655 wrapped.addChangeListener(cl); 656 } 657 658 public void addChangeListener(ChangeListener cl, ChangeType ct) { 659 wrapped.addChangeListener(cl, ct); 660 } 661 662 public void removeChangeListener(ChangeListener cl) { 663 wrapped.removeChangeListener(cl); 664 } 665 666 public void removeChangeListener(ChangeListener cl, ChangeType ct) { 667 wrapped.removeChangeListener(cl, ct); 668 } 669 670 public boolean isUnchanging(ChangeType ct) { 671 return wrapped.isUnchanging(ct); 672 } 673 674 public Annotation getAnnotation() { 675 return wrapped.getAnnotation(); 676 } 677 } 678 } 679}