/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codesourcery.jasm16.utils;
import java.util.List;
/**
* A text region.
*
* @author tobias.gierke@code-sourcery.de
*/
public class TextRegion implements ITextRegion
{
private int startingOffset;
private int length;
public TextRegion(List<? extends ITextRegion> ranges)
{
if (ranges == null) {
throw new IllegalArgumentException("ranges must not be NULL");
}
if ( ranges.isEmpty() ) {
throw new IllegalArgumentException("ranges cannot be empty");
}
ITextRegion first = ranges.get(0);
this.startingOffset = first.getStartingOffset();
this.length = first.getLength();
if ( ranges.size() > 1 ) {
merge( ranges.subList( 1 , ranges.size() ) );
}
}
public static int hashCode(ITextRegion range)
{
if ( range == null ) {
return 0;
}
return range.getStartingOffset()*13 + range.getLength();
}
public TextRegion(ITextRegion range) {
this( range.getStartingOffset() , range.getLength() );
}
public TextRegion(int startingOffset, int length)
{
if ( startingOffset < 0 ) {
throw new IllegalArgumentException("startingOffset must not be >= 0");
}
if ( length < 0 ) {
throw new IllegalArgumentException("length must not be >= 0");
}
this.startingOffset = startingOffset;
this.length = length;
}
@Override
public int getStartingOffset()
{
return startingOffset;
}
@Override
public int getLength()
{
return length;
}
@Override
public void merge(ITextRegion other)
{
// order of calculations is IMPORTANT here, otherwise it's yielding wrong results!
final int newEnd = this.getEndOffset() > other.getEndOffset() ? this.getEndOffset() : other.getEndOffset();
this.startingOffset = this.getStartingOffset() < other.getStartingOffset() ? this.getStartingOffset() : other.getStartingOffset();
this.length = newEnd - this.startingOffset;
}
@Override
public void subtract(ITextRegion other)
{
if ( isSame( other ) )
{
this.length = 0;
return;
}
if ( other.getStartingOffset() == this.getStartingOffset() ) {
// both ranges starts share the same starting offset
final int length = this.getLength() - other.getLength();
if ( length < 0 ) {
throw new IllegalArgumentException("Cannot subtract range "+other+" that is longer than "+this);
}
this.startingOffset = other.getEndOffset();
this.length = length;
return;
}
else if ( other.getEndOffset() == this.getEndOffset() )
{
final int length = this.getLength() - other.getLength();
if ( length < 0 ) {
throw new IllegalArgumentException("Cannot subtract range "+other+" that starts before "+this);
}
this.length = length;
return;
}
else if ( ! this.contains( other ) ) {
return;
}
throw new UnsupportedOperationException("Cannot calculate "+this+" MINUS "+other+" , would yield two non-adjactent ranges");
}
@Override
public boolean contains(ITextRegion other)
{
return other.getStartingOffset() >= this.getStartingOffset() && other.getEndOffset() <= this.getEndOffset();
}
@Override
public boolean overlaps(ITextRegion other)
{
return this.contains( other ) || other.contains( this ) ||
( ! this.contains( other.getStartingOffset() ) && this.contains( other.getEndOffset() ) ) ||
( this.contains( other.getStartingOffset() ) && ! this.contains( other.getEndOffset() ) );
}
public void intersect(ITextRegion other) {
/* |-- this --|
* |-other-|
*/
if ( ! contains( other.getStartingOffset() ) && contains( other.getEndOffset()-1 ) ) {
// ORDER of calculations is important here!
this.length = other.getEndOffset() - this.getStartingOffset();
this.startingOffset = other.getStartingOffset();
return;
}
/* |-- this --|
* |-other-|
*/
if ( contains( other.getStartingOffset() ) && contains( other.getEndOffset() ) ) {
this.startingOffset = other.getStartingOffset();
this.length = other.getLength();
return;
}
/* |-- this --|
* |-other-|
*/
if ( contains( other.getStartingOffset() ) && ! contains( other.getEndOffset() ) ) {
this.length = getEndOffset() - other.getStartingOffset();
this.startingOffset = other.getStartingOffset();
return;
}
/* |-- this --|
* |-----other-----|
*/
if ( other.contains( getStartingOffset() ) && other.contains( getEndOffset() ) ) {
// this range already is the intersection
return;
}
throw new IllegalArgumentException( this+" has no intersection with "+other);
}
@Override
public int getEndOffset()
{
return startingOffset+length;
}
@Override
public boolean contains(int offset)
{
return offset >= this.getStartingOffset() && offset < this.getEndOffset();
}
@Override
public boolean isSame(ITextRegion other)
{
return this == other || ( this.getStartingOffset() == other.getStartingOffset() && this.getLength() == other.getLength() );
}
@Override
public String apply(String string)
{
try {
return string.substring( getStartingOffset() , getEndOffset() );
} catch(StringIndexOutOfBoundsException e) {
throw new StringIndexOutOfBoundsException("TextRegion out of bounds, cannot apply "+this+" to "+
" string of length "+string.length());
}
}
@Override
public String toString()
{
return "["+getStartingOffset()+","+getEndOffset()+"[";
}
@Override
public void merge(List<? extends ITextRegion> ranges)
{
if ( ranges.isEmpty() ) {
return;
}
for ( ITextRegion r : ranges ) {
merge( r );
}
}
}