/*
* CATMA Computer Aided Text Markup and Analysis
*
* Copyright (C) 2009 University Of Hamburg
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.catma.document;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
/**
* A range of text with a startPoint and an endPoint. This class is immutable.
* A range is a left to right range because it works together with {@link String}.
*
* @author Marco Petris
*
*/
public class Range implements Comparable<Range> {
private int startPoint;
private int endPoint;
private int hashCode;
/**
* @param startPoint the point before the first character.
* @param endPoint the point after the last character.
*/
public Range( int startPoint, int endPoint ) {
this.startPoint = Math.min(startPoint, endPoint);
this.endPoint = Math.max(startPoint, endPoint);
this.hashCode =
(String.valueOf(this.startPoint )
+ "-"
+ String.valueOf(this.endPoint)).hashCode();
}
/**
* @return the start point
*/
public int getStartPoint() {
return startPoint;
}
/**
* @return the end point
*/
public int getEndPoint() {
return endPoint;
}
/**
* Equality is tested with start- and endpoint.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj ) {
if( ( obj == null ) || !( obj instanceof Range ) ) {
return false;
}
else {
return (( this.startPoint == ((Range)obj).startPoint)
&& ( this.endPoint == ((Range)obj).endPoint) );
}
}
/**
* @return hashcode computed with start- and endpoint
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return this.hashCode;
}
/**
* Tests whether the given point comes after (>) this start point.
* @param point the point to test
* @return true if the given point comes after (>) this start point, else false
*/
public boolean startsAfter( long point ) {
return this.startPoint > point;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Range["+startPoint+","+endPoint+"]";
}
/**
* @return true, if startPoint == endPoint
*/
public boolean isSinglePoint() {
return (this.startPoint == this.endPoint);
}
/**
* Compares Ranges via startPoint and endPoint.
* @param o tha range to compare to
* @return the distance between the this startpoint and the given startpoint or (if there is no such
* distance) the distance between this endpoint and the given endpoint
*/
public int compareTo(Range o) {
if (this.startPoint==o.startPoint) {
if (this.endPoint==o.endPoint) {
return 0;
}
else if (this.endPoint<o.endPoint) {
return -1;
}
else {
return 1;
}
}
else {
if (this.startPoint==o.startPoint) {
return 0;
}
else if (this.startPoint<o.startPoint) {
return -1;
}
else {
return 1;
}
}
}
/**
* @param range
* @return true if the range is in between the range of this pointer (can occupy the edges).
*/
public boolean isInBetween( Range range ) {
if( ( this.getStartPoint() >= range.getStartPoint() )
&& ( this.getEndPoint() <= range.getEndPoint() ) ) {
return true;
}
return false;
}
/**
* Tests whether the given point is in between the range of this pointer but
* is not one of the range's edges.
* @param point the point to test
* @return true if point > startpoint and point < endpoint, else false
*/
private boolean isInBetweenExclusiveEdge( long point ) {
return ( (point > this.getStartPoint())
&& (point < this.getEndPoint()) );
}
/**
* Tests whether the given point is in between the range of this pointer.
* @param point the point to test
* @return true if point >= startpoint and point <= endpoint, else false
*/
private boolean isInBetweenInclusiveEdge( long point ) {
return ( (point >= this.getStartPoint())
&& (point <= this.getEndPoint()) );
}
/**
* Tests whether the given point is after the endpoint
* @param point the point to test
* @return true if the given point is after the endpoint else false
*/
private boolean isAfter( long point ) {
return (this.getEndPoint() < point);
}
/**
* @param rangeToTest the range to test
* @return a list of ranges which are the non overlapping parts of the range
* to test and the range of this pointer
*/
public List<Range> getDisjointRanges( Range rangeToTest ) {
List<Range> result = new ArrayList<Range>();
if( isInBetweenExclusiveEdge( rangeToTest.getStartPoint()) ) {
result.add(
new Range(
this.getStartPoint(),
rangeToTest.getStartPoint() ) );
if( isInBetweenExclusiveEdge( rangeToTest.getEndPoint() ) ) {
result.add(
new Range(
rangeToTest.getEndPoint(),
this.getEndPoint() ) );
}
}
else if( !isAfter( rangeToTest.getEndPoint() ) ) {
result.add(
new Range( rangeToTest.getEndPoint(), this.getEndPoint() ) );
}
return result;
}
/**
* @param rangeToTest the range to test
* @return the overlapping range of the range to test and the range of this pointer
* or null if they do not overlap
*/
public Range getOverlappingRange(
Range rangeToTest ) {
if( ( rangeToTest.getStartPoint() == getEndPoint() )
|| ( getStartPoint() == rangeToTest.getEndPoint() ) ) {
return null;
}
if( isInBetweenInclusiveEdge( rangeToTest.getStartPoint()) ) {
if( isInBetweenInclusiveEdge( rangeToTest.getEndPoint() ) ) {
return new Range(
rangeToTest.getStartPoint(),
rangeToTest.getEndPoint() );
}
else if( isAfter( rangeToTest.getEndPoint() ) ) {
return new Range(
rangeToTest.getStartPoint(),
this.getEndPoint() );
}
}
else if( !isAfter( rangeToTest.getStartPoint() ) ) {
if( isInBetweenInclusiveEdge( rangeToTest.getEndPoint() ) ) {
return new Range(
this.getStartPoint(),
rangeToTest.getEndPoint() );
}
else if( isAfter( rangeToTest.getEndPoint() ) ) {
return new Range(
this.getStartPoint(),
this.getEndPoint() );
}
}
// no overlap
return null;
}
/**
* @return the size of this range
*/
public long getSize() {
return getEndPoint()-getStartPoint();
}
/**
* @param range the range to test
* @return true if the given range and this range overlap in any point
*/
public boolean hasOverlappingRange(Range range) {
return (getOverlappingRange(range) != null);
}
/**
* @param rangeToTest
* @return true if the given range and this range are adjacent to each other
*/
public boolean isAdjacentTo(Range rangeToTest) {
return ((getStartPoint() == rangeToTest.getEndPoint())
|| (getEndPoint() == rangeToTest.getStartPoint()));
}
/**
* Merges the contiguous ranges of the given set.
* @param sortedRanges the ranges to merge
* @return the merged ranges.
*/
public static List<Range> mergeRanges(SortedSet<Range> sortedRanges) {
List<Range> result = new ArrayList<Range>();
Range curRange = null;
Iterator<Range> rangeIterator = sortedRanges.iterator();
if (rangeIterator.hasNext()) {
curRange = rangeIterator.next();
while (rangeIterator.hasNext()) {
Range range = rangeIterator.next();
if (curRange.getEndPoint() == range.getStartPoint()) { // merge
curRange = new Range(curRange.getStartPoint(), range.getEndPoint());
}
else {
result.add(curRange);
curRange = range;
}
}
result.add(curRange);
}
return result;
}
/**
* @param ranges
* @return a range that encloses all given ranges
*/
public static Range getEnclosingRange(List<Range> ranges) {
int startPoint = Integer.MAX_VALUE;
int endPoint = 0;
for (Range range : ranges) {
startPoint = Math.min(range.getStartPoint(), startPoint);
endPoint = Math.max(range.getEndPoint(), endPoint);
}
return new Range(startPoint, endPoint);
}
}