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.symbol; 023 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Iterator; 028import java.util.List; 029import java.util.ListIterator; 030 031import org.biojava.bio.BioError; 032import org.biojava.bio.BioException; 033 034/** 035 * Tools class containing a number of operators for working with <code>Location</code> objects. 036 * 037 * <p> 038 * Most of the methods in this class are simple set-wise binary operators: for example, 039 * calculate the intersection of two locations. 040 * </p> 041 * 042 * This class provides helpful methods for set-wise manipulation of Location objects. 043 * 044 * @author Matthew Pocock 045 * @author Greg Cox 046 * @author Thomas Down 047 * @author Mark Schreiber 048 * @author Francois Pepin 049 * @since 1.2 050 */ 051final public class LocationTools { 052 /** 053 * Nobody needs one of these. 054 */ 055 056 private LocationTools() { 057 } 058 059 /** 060 * Return the union of two locations. 061 * 062 * <p> 063 * The union will be a Location instance that contains every index contained 064 * by either locA or locB. 065 * </p> 066 * 067 * @param locA the first Location 068 * @param locB the second Location 069 * @return a Location that is the union of locA and locB 070 */ 071 public static Location union(Location locA, Location locB) { 072 if(isDecorated(locA) || isDecorated(locB)) 073 { 074 handleDecorations(locA, locB); 075 if(locA instanceof CircularLocation && locB instanceof CircularLocation){ 076 return CircularLocationTools.union((CircularLocation)locA,(CircularLocation)locB); 077 } 078 if(BetweenLocationTools.isBetween(locA) || (BetweenLocationTools.isBetween(locB))) 079 { 080 return BetweenLocationTools.union(locA, locB); 081 } 082 } 083 084 if( 085 locA.isContiguous() && 086 locB.isContiguous() && 087 locA.overlaps(locB) 088 ) { 089 // the simple case 090 Location mloc = null; 091 try { 092 mloc = MergeLocation.mergeLocations(locA, locB); 093 } 094 catch (BioException ex) { 095 //this shouldn't happen as conditions have been checked above 096 throw new BioError("Assertion Error, cannot build MergeLocation",ex); 097 } 098 return mloc; 099 100 } else { 101 // either may be compound. They may not overlap. We must build the 102 // complete list of blocks, merge overlapping blocks and then create the 103 // appropriate implementation of Location for the resulting list. 104 105 // list of all blocks 106 List locList = new ArrayList(); 107 108 // add all blocks in locA 109 for(Iterator i = locA.blockIterator(); i.hasNext(); ) { 110 locList.add(i.next()); 111 } 112 113 // add all blocks in locB 114 for(Iterator i = locB.blockIterator(); i.hasNext(); ) { 115 locList.add(i.next()); 116 } 117 118 return _union(locList); 119 } 120 } 121 122 /** 123 * Return the intersection of two locations. 124 * <p> 125 * The intersection will be a Location instance that contains every index 126 * contained by both locA and locB. 127 * </p> 128 * 129 * @param locA the first Location 130 * @param locB the second Location 131 * @return a Location that is the intersection of locA and locB 132 */ 133 public static Location intersection(Location locA, Location locB) { 134 135 if(isDecorated(locA) || isDecorated(locB)) 136 { 137 handleDecorations(locA, locB); 138 if(BetweenLocationTools.isBetween(locA) || (BetweenLocationTools.isBetween(locB))) 139 { 140 return BetweenLocationTools.intersection(locA, locB); 141 } 142 if (CircularLocationTools.isCircular(locA) || CircularLocationTools.isCircular(locB)) { 143 return CircularLocationTools.intersection(locA, locB); 144 } 145 } 146 147 if(locA.isContiguous() && locB.isContiguous()) { 148 // handle easy case of solid locations 149 // Ought to make this bit more efficient --THOMASD 150 if (LocationTools.contains(locA, locB)) { 151 return locB; 152 } else if (LocationTools.contains(locB, locA)) { 153 return locA; 154 } if(LocationTools.overlaps(locA, locB)) { 155 int min = Math.max(locA.getMin(), locB.getMin()); 156 int max = Math.min(locA.getMax(), locB.getMax()); 157 return makeLocation(min, max); 158 } else { 159 return Location.empty; 160 } 161 } else { 162 // One or other of the locations is compound. Build a list of all 163 // locations created by finding intersection of all pairwise combinations 164 // of blocks in locA and locB. Ignore all Location.empty. Create the 165 // appropriate Location instance. 166 List locList = new ArrayList(); 167 168 List blA = getBlockList(locA); 169 { 170 int minBlock = blockContainingOrFollowingPoint(blA, locB.getMin()); 171 int maxBlock = blockContainingOrPreceedingPoint(blA, locB.getMax()); 172 if (minBlock == -1 || maxBlock == -1) { 173 return Location.empty; 174 } 175 blA = blA.subList(minBlock, maxBlock + 1); 176 } 177 List blB = getBlockList(locB); 178 { 179 int minBlock = blockContainingOrFollowingPoint(blB, locA.getMin()); 180 int maxBlock = blockContainingOrPreceedingPoint(blB, locA.getMax()); 181 if (minBlock == -1 || maxBlock == -1) { 182 return Location.empty; 183 } 184 blB = blB.subList(minBlock, maxBlock + 1); 185 } 186 187 if (blA.size() > blB.size()) { 188 List temp = blA; 189 blA = blB; 190 blB = temp; 191 } 192 193 for (Iterator i = blA.iterator(); i.hasNext(); ) { 194 Location blocA = (Location) i.next(); 195 int minHitIndex = blockContainingOrFollowingPoint(blB, blocA.getMin()); 196 int maxHitIndex = blockContainingOrPreceedingPoint(blB, blocA.getMax()); 197 for (int hitIndex = minHitIndex; hitIndex <= maxHitIndex; ++hitIndex) { 198 Location blocB = (Location) blB.get(hitIndex); 199 locList.add(LocationTools.intersection(blocA, blocB)); 200 } 201 } 202 203 return buildLoc(locList); 204 } 205 } 206 207 /** 208 * Return an ordered list of non-overlapping blocks of a location 209 * (singleton list for contiguous locations, empty list for empty locations) 210 * 211 * Note that from this point of view, contiguous circular locations 212 * aren't necessarily contiguous :-(. 213 * 214 * @param l the <code>Location</code> to get the blocks from 215 * @return and ordered <code>List</code> of <code>Locations</code> 216 */ 217 218 private static List getBlockList(Location l) { 219 if (l == Location.empty) { 220 return Collections.EMPTY_LIST; 221 } else if (l instanceof CompoundLocation) { 222 return ((CompoundLocation )l).getBlockList(); 223 } else if (l.isContiguous() && !CircularLocationTools.isCircular(l)) { 224 return Collections.nCopies(1, l); 225 } else { 226 List bl = new ArrayList(); 227 for (Iterator bi = l.blockIterator(); bi.hasNext(); ) { 228 bl.add(bi.next()); 229 } 230 Collections.sort(bl, Location.naturalOrder); 231 return bl; 232 } 233 } 234 235 /** 236 * Return the index into <code>bl</code> of a block containing 237 * <code>point</code>, or -1 if it is not found. 238 */ 239 240 private static int blockContainingPoint(List bl, int point) { 241 int start = 0; 242 int end = bl.size() - 1; 243 244 while (start <= end) { 245 int mid = (start + end) / 2; 246 Location block = (Location) bl.get(mid); 247 if (block.contains(point)) { 248 return mid; 249 } else if (point < block.getMin()) { 250 end = mid - 1; 251 } else if (point > block.getMax()) { 252 start = mid + 1; 253 } else { 254 throw new BioError("Assertion failed: overrun in binary search"); 255 } 256 } 257 return -1; 258 } 259 260 /** 261 * Return the index of the block containing <code>point</code>, if it exists, 262 * or the block preceeding it. 263 */ 264 265 private static int blockContainingOrPreceedingPoint(List bl, int point) { 266 // System.err.println("COPP"); 267 int start = 0; 268 int length = bl.size(); 269 int end = length - 1; 270 271 while (start <= end) { 272 int mid = (start + end) / 2; 273 // System.err.println("Start=" + start + " mid=" + mid + " end=" + end); 274 Location block = (Location) bl.get(mid); 275 if (block.contains(point)) { 276 return mid; 277 } else if (point < block.getMin()) { 278 end = mid - 1; 279 } else if (point > block.getMax()) { 280 start = mid + 1; 281 } else { 282 throw new BioError("Assertion failed: overrun in binary search"); 283 } 284 } 285 286 if (end < length) { 287 return end; 288 } else { 289 return -1; 290 } 291 } 292 293 /** 294 * Return the index of the block containing <code>point</code>, if it exists, 295 * or the block preceeding it. 296 */ 297 298 private static int blockContainingOrFollowingPoint(List bl, int point) { 299 // System.err.println("COFP"); 300 int start = 0; 301 int length = bl.size(); 302 int end = length - 1; 303 304 while (start <= end) { 305 int mid = (start + end) / 2; 306 // System.err.println("Start=" + start + " mid=" + mid + " end=" + end); 307 Location block = (Location) bl.get(mid); 308 if (block.contains(point)) { 309 return mid; 310 } else if (point < block.getMin()) { 311 end = mid - 1; 312 } else if (point > block.getMax()) { 313 start = mid + 1; 314 } else { 315 throw new BioError("Assertion failed: overrun in binary search"); 316 } 317 } 318 if (start >= 0) { 319 return start; 320 } else { 321 return -1; 322 } 323 } 324 325 /** 326 * Determines whether the locations are touching or not (if they could be 327 * merged in a single Location. 328 * <p> 329 * Two locations can merge if they contain at least one index of one 330 * beside one index of the other. 331 * </p> 332 * 333 * @param locA the first Location 334 * @param locB the second Location 335 * @return <code>true</code> if they can merge, <code>false</code> otherwise 336 */ 337 public static boolean canMerge(Location locA, Location locB){ 338 if (overlaps(locA, locB)|| 339 overlaps(locA.translate(1), locB)|| 340 overlaps(locA.translate(-1),locB)) 341 return true; 342 return false; 343 } 344 345 346 347 /** 348 * Determines whether the locations overlap or not. 349 * <p> 350 * Two locations overlap if they contain at least one index in common. 351 * </p> 352 * 353 * @param locA the first Location 354 * @param locB the second Location 355 * @return <code>true</code> if they overlap, <code>false</code> otherwise 356 */ 357 358 public static boolean overlaps(Location locA, Location locB) { 359 if(isDecorated(locA) || isDecorated(locB)) 360 { 361 handleDecorations(locA, locB); 362 if(BetweenLocationTools.isBetween(locA) || (BetweenLocationTools.isBetween(locB))) 363 { 364 return BetweenLocationTools.overlaps(locA, locB); 365 } 366 } 367 368 if(locA.isContiguous() && locB.isContiguous()) { 369 // if they are both solid, return whether the extents overlap 370 return !( 371 (locA.getMax() < locB.getMin()) || 372 (locA.getMin() > locB.getMax()) 373 ); 374 } else { 375 // System.err.println("Doing complex overlap stuff"); 376 377 List blA = getBlockList(locA); 378 { 379 // System.err.println("In A restriction"); 380 int minBlock = blockContainingOrFollowingPoint(blA, locB.getMin()); 381 int maxBlock = blockContainingOrPreceedingPoint(blA, locB.getMax()); 382 if (minBlock == -1 || maxBlock == -1) { 383 // System.err.println("blA empty: minBlock=" + minBlock +", maxBlock=" + maxBlock); 384 return false; 385 } 386 blA = blA.subList(minBlock, maxBlock + 1); 387 } 388 List blB = getBlockList(locB); 389 { 390 // System.err.println("In B restriction"); 391 int minBlock = blockContainingOrFollowingPoint(blB, locA.getMin()); 392 int maxBlock = blockContainingOrPreceedingPoint(blB, locA.getMax()); 393 if (minBlock == -1 || maxBlock == -1) { 394 // System.err.println("blB empty: minBlock=" + minBlock +", maxBlock=" + maxBlock); 395 return false; 396 } 397 blB = blB.subList(minBlock, maxBlock + 1); 398 } 399 400 // System.err.println("Restricted lists"); 401 402 for(Iterator aI = blA.iterator(); aI.hasNext(); ) { 403 Location a = (Location) aI.next(); 404 for(Iterator bI = blB.iterator(); bI.hasNext(); ) { 405 Location b = (Location) bI.next(); 406 if(LocationTools.overlaps(a, b)) { 407 return true; 408 } 409 } 410 } 411 412 return false; 413 } 414 } 415 416 /** 417 * Return <code>true</code> iff all indices in <code>locB</code> are also contained 418 * by <code>locA</code>. 419 * 420 * @param locA The containing location 421 * @param locB The contained location 422 * @return <code>true</code> is locA contains locB 423 */ 424 425 public static boolean contains(Location locA, Location locB) { 426 if(isDecorated(locA) || isDecorated(locB)) 427 { 428 handleDecorations(locA, locB); 429 if(BetweenLocationTools.isBetween(locA) || (BetweenLocationTools.isBetween(locB))) 430 { 431 return BetweenLocationTools.contains(locA, locB); 432 } 433 } 434 435 if (locA.getMin() <= locB.getMin() && locA.getMax() >= locB.getMax()) { 436 if (locA.isContiguous()) { 437 return true; 438 } else { 439 List blA = getBlockList(locA); 440 for (Iterator biB = locB.blockIterator(); biB.hasNext(); ) { 441 Location bloc = (Location) biB.next(); 442 int hitIndex = blockContainingPoint(blA, bloc.getMin()); 443 if (hitIndex < 0) { 444 return false; 445 } else { 446 Location hitBloc = (Location) blA.get(hitIndex); 447 if (bloc.getMax() > hitBloc.getMax()) { 448 return false; 449 } 450 } 451 } 452 return true; 453 } 454 } else { 455 return false; 456 } 457 } 458 459 /** 460 * Return whether two locations are equal. 461 * <p> 462 * They are equal if both a contains b and b contains a. Equivalently, they 463 * are equal if for every point p, locA.contains(p) == locB.contains(p). 464 * </p> 465 * 466 * @param locA the first Location 467 * @param locB the second Location 468 * @return true if they are equivalent, false otherwise 469 */ 470 public static boolean areEqual(Location locA, Location locB) { 471 if(isDecorated(locA) || isDecorated(locB)) 472 { 473 handleDecorations(locA, locB); 474 if(BetweenLocationTools.isBetween(locA) || (BetweenLocationTools.isBetween(locB))) 475 { 476 return BetweenLocationTools.areEqual(locA, locB); 477 } 478 } 479 // simple check - if one is broken and the other isn't, they aren't equal. 480 if(locA.isContiguous() != locB.isContiguous()) { 481 return false; 482 } 483 484 // both contiguous if one is - check extent only 485 if(locA.isContiguous()) { 486 return 487 (locA.getMin() == locB.getMin()) && 488 (locA.getMax() == locB.getMax()); 489 } 490 491 // ok - both compound. The blocks returned from blockIterator should each be 492 // equivalent. 493 Iterator i1 = locA.blockIterator(); 494 Iterator i2 = locB.blockIterator(); 495 496 // while there are more pairs to check... 497 while(i1.hasNext() && i2.hasNext()) { 498 // check that this pair is equivalent 499 Location l1 = (Location) i1.next(); 500 Location l2 = (Location) i2.next(); 501 502 if( 503 (l1.getMin() != l2.getMin()) || 504 (l1.getMax() != l2.getMax()) 505 ) { 506 // not equivalent blocks so not equal 507 return false; 508 } 509 } 510 511 // One of the locations had more blocks than the other 512 if(i1.hasNext() || i2.hasNext()) { 513 return false; 514 } 515 516 // Same number of blocks, all equivalent. Must be equal. 517 return true; 518 } 519 520 /** 521 * Create a Location instance from the list of contiguous locations in 522 * locList. 523 * <p> 524 * If the list is empty then Location.empty will be produced. If it is just 525 * one element long, then this will be returned as-is. If there are multiple 526 * locations then they will be sorted and then used to construct a 527 * CompoundLocation. 528 * 529 * @param locList a List<Location>, where each element is a contiguous location. 530 * @return a new Location instance 531 */ 532 static Location buildLoc(List locList) { 533 if(locList.size() == 0) { 534 return Location.empty; 535 } else if(locList.size() == 1) { 536 return (Location) locList.get(0); 537 } else { 538 Collections.sort(locList, Location.naturalOrder); 539 return new CompoundLocation(locList); 540 } 541 } 542 543 /** 544 * Create a Location instance from the sorted list of contiguous locations in 545 * locList. 546 * <p> 547 * If the list is empty then Location.empty will be produced. If it is just 548 * one element long, then this will be returned as-is. If there are multiple 549 * locations then they will be sorted and then used to construct a 550 * CompoundLocation. 551 * 552 * @param locList a List<Location>, where each element is a contiguous location. 553 * @return a new Location instance 554 */ 555 556 static Location buildLocSorted(List locList) { 557 if(locList.size() == 0) { 558 return Location.empty; 559 } else if(locList.size() == 1) { 560 return (Location) locList.get(0); 561 } else { 562 return new CompoundLocation(locList); 563 } 564 } 565 566 /** 567 * The n-way union of a Collection of locations. Returns a Location 568 * which covers every point covered by at least one of the locations 569 * in <code>locs</code> 570 * 571 * @param locs A collection of locations. 572 * @return A union location 573 * @throws ClassCastException if the collection contains non-Location objects. 574 */ 575 576 public static Location union(Collection locs) { 577 boolean circular = false; 578 List locList = new ArrayList(); 579 for (Iterator li = locs.iterator(); li.hasNext(); ) { 580 Location loc = (Location) li.next(); 581 if((loc instanceof CircularLocation)){ 582 circular = true; 583 locList.add(loc); 584 }else{ 585 for (Iterator bi = loc.blockIterator(); bi.hasNext(); ) { 586 locList.add(bi.next()); 587 } 588 } 589 } 590 if(circular){ 591 //need to add these one at a time 592 ListIterator li = locList.listIterator(); 593 CircularLocation loc = (CircularLocation)li.next(); 594 while(li.hasNext()){ 595 loc = (CircularLocation)union(loc, (Location)li.next()); 596 } 597 return loc; 598 }else{ 599 return _union(locList); 600 } 601 } 602 603 604 static Location _union(List locList) { 605 // sort these blocks 606 Collections.sort(locList, Location.naturalOrder); 607 608 // merge into this list... 609 List joinList = new ArrayList(); 610 611 // start iterating over sorted list. 612 // last is used as loop variable. We must be careful about zero lengthed 613 // lists and also careful to merge overlaps before adding to joinList. 614 Iterator i = locList.iterator(); 615 Location last = Location.empty; 616 617 618 // prime last 619 if(i.hasNext()) { 620 last = (Location) i.next(); 621 } 622 623 // merge or add last with next location 624 while(i.hasNext()) { 625 Location cur = (Location) i.next(); 626 if(canMerge(last,cur)) { 627 try { 628 last = MergeLocation.mergeLocations(last,cur); 629 } 630 catch (BioException ex) { 631 throw new BioError("Cannot make MergeLocation",ex); 632 } 633 } else { 634 joinList.add(last); 635 last = cur; 636 } 637 } 638 639 // handle the end of the loop 640 if(last == Location.empty) { 641 return Location.empty; 642 } else { 643 joinList.add(last); 644 } 645 646 // now make the appropriate Location instance 647 return buildLocSorted(joinList); 648 } 649 650 /** 651 * Return a contiguous Location from min to max. 652 * <p> 653 * If min == max then a PointLocation will be made, otherwise, a RangeLocation 654 * will be returned. 655 * 656 * @param min the Location min value 657 * @param max the Location max value 658 * @return a new Location from min to max 659 */ 660 public static Location makeLocation(int min, int max) { 661 if(min == max) { 662 return new PointLocation(min); 663 } else { 664 return new RangeLocation(min, max); 665 } 666 } 667 668 /** 669 * A simple method to generate a RangeLocation wrapped 670 * in a CircularLocation. The method will cope with situtations 671 * where the min is greater than the max. Either of min or max can 672 * be negative, or greater than the underlying sequence length. If min and 673 * max are equal a wrapped point location will be made. 674 * 675 * @param min the "left" end of the location 676 * @param max the "right" end of the location 677 * @param seqLength the lenght of the sequence that the location will 678 * be applied to (for purposes of determining origin). 679 * @return the new <code>CircularLocation</code> 680 */ 681 public static CircularLocation makeCircularLocation(int min, int max, int seqLength){ 682 return CircularLocationTools.makeCircLoc(min,max,seqLength); 683 } 684 685 /** 686 * Checks if the location has a decorator. 687 * 688 * @param theLocation The location to test for decorators 689 * @return True if the location has a decorator and false otherwise 690 */ 691 static boolean isDecorated(Location theLocation) 692 { 693 return (theLocation instanceof AbstractLocationDecorator); 694 } 695 696 /** 697 * Currently CircularLocations are handled if and only if 698 * CircularLocationTools.safeOperation returns true. For this to be true 699 * the locations must have the same sequence length. 700 * 701 * BetweenLocations are handled in all cases. Overlap cases, such as 702 * between, circular locations have indeterminate behavior. 703 * 704 * The default behavior is to not handle decorators. Any new decorators 705 * will have to re-visit this method 706 */ 707 private static void handleDecorations(Location locA, Location locB){ 708 if(CircularLocationTools.isCircular(locA)|| CircularLocationTools.isCircular(locB)){ 709 if(CircularLocationTools.safeOperation(locA, locB) == false){ 710 throw new UnsupportedOperationException( 711 "Binary operations between Circular and"+ 712 " Non-Circular locations, or CircularLocations"+ 713 " from sequences of differing length are currently unsupported."); 714 } 715 } 716 else if(BetweenLocationTools.isBetween(locA) || (BetweenLocationTools.isBetween(locB))) 717 { 718 // Intentionally blank 719 } 720 else{ 721 throw new ClassCastException("Decorated locations are not handled in this version: " + locA.getClass().getName() + ", " + locB.getClass().getName()); 722 } 723 } 724 725 /** 726 * Flips a location relative to a length. 727 * 728 * <p>It is very common in biological sequences to represent locations on a sequence and then reverse that 729 * sequence. This method allows locations in the original coordinate space to be transformed int 730 * locations in the reverse one.</p> 731 * 732 * @param loc the Location to flip 733 * @param len the length of the region to flip within 734 * @return a flipped view of the location 735 */ 736 public static Location flip(Location loc, int len) { 737 if(loc instanceof PointLocation) { 738 return new PointLocation(len - loc.getMin() + 1); 739 } else if(loc instanceof RangeLocation) { 740 return new RangeLocation( 741 len - loc.getMax() + 1, 742 len - loc.getMin() + 1 743 ); 744 } else { 745 Iterator bi = loc.blockIterator(); 746 Location res = (Location) bi.next(); 747 while(bi.hasNext()) { 748 res = LocationTools.union(res, (Location) bi.next()); 749 } 750 return res; 751 } 752 } 753 754 /** 755 * Subtract one location from another. This methods calculates the set of 756 * points which are contains in location <code>keep</code> but not in 757 * <code>remove</code>. 758 * 759 * @param keep A location 760 * @param remove A location 761 * @return a location containing all points which are in x but not y 762 * @since 1.3 763 */ 764 765 public static Location subtract(Location keep, Location remove) { 766 // fixme: documentation hack - I should re-factor this method 767 Location x = keep; 768 Location y = remove; 769 770 if (isDecorated(x) || isDecorated(y)) { 771 handleDecorations(x, y); 772 } 773 774 List spans = new ArrayList(); 775 for (Iterator i = x.blockIterator(); i.hasNext(); ) { 776 Location xb = (Location) i.next(); 777 Location yb = LocationTools.intersection(xb, y); 778 int pos = xb.getMin(); 779 for (Iterator j = yb.blockIterator(); j.hasNext(); ) { 780 Location sb = (Location) j.next(); 781 if (sb.getMin() > pos) { 782 spans.add(new RangeLocation(pos, sb.getMin() - 1)); 783 } 784 pos = sb.getMax() + 1; 785 } 786 if (pos <= xb.getMax()) { 787 spans.add(new RangeLocation(pos, xb.getMax())); 788 } 789 } 790 return LocationTools.union(spans); 791 } 792 793 /** 794 * Return the number of positions which are covered by a <code>Location</code> 795 * 796 * @param loc A location 797 * @return the number of distinct points contained by that location 798 * @since 1.4 799 */ 800 801 public static int coverage(Location loc) { 802 int cov = 0; 803 for (Iterator i = loc.blockIterator(); i.hasNext(); ) { 804 Location bloc = (Location) i.next(); 805 cov += (bloc.getMax() - bloc.getMin() + 1); 806 } 807 return cov; 808 } 809 810 /** 811 * Return a contiguous location running from the minimum to the maximum points of 812 * the specified location. 813 * 814 * @param loc a location 815 * @return a corresponding contiguous location 816 */ 817 818 public static Location shadow(Location loc) { 819 if (loc.isContiguous()) { 820 return loc; 821 } else { 822 return new RangeLocation(loc.getMin(), loc.getMax()); 823 } 824 } 825 826 /** 827 * Return the number of contiguous blocks in a location. 828 * 829 * @param loc a location 830 * @return the number of blocks 831 * @since 1.4 832 */ 833 834 public static int blockCount(Location loc) { 835 if (loc.isContiguous()) { 836 if (loc instanceof EmptyLocation) { 837 return 0; 838 } else { 839 return 1; 840 } 841 } else { 842 int cnt = 0; 843 for (Iterator bi = loc.blockIterator(); bi.hasNext(); ) { 844 bi.next(); ++cnt; 845 } 846 return cnt; 847 } 848 } 849}