/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.util.geom;
import java.io.Serializable;
/**
* The StrictBounds class is a replacement for the Rectangle2D classes. This class uses integer mathematics instead of
* floating point values to achive a higher degree of stability.
*
* @author Thomas Morgner
*/
public class StrictBounds implements Serializable, Cloneable {
/**
* The x-coordinate of the upper left corner.
*/
private long x;
/**
* The y-coordinate of the upper left corner.
*/
private long y;
/**
* The width of this rectangle.
*/
private long width;
/**
* The height of this rectangle.
*/
private long height;
/**
* A flag indicating whether attempts to change this rectangle should trigger Exceptions.
*/
private boolean locked;
/**
* DefaultConstructor.
*/
public StrictBounds() {
}
/**
* Creates a StrictBounds object with the given coordinates, width and height.
*
* @param x
* the x-coordinate
* @param y
* the y-coordinate
* @param width
* the width of the rectangle
* @param height
* the height of the rectangle
*/
public StrictBounds( final long x, final long y, final long width, final long height ) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Checks whether this bounds object is locked.
*
* @return true, if the bounds are locked and therefore immutable, false otherwise.
*/
public boolean isLocked() {
return locked;
}
/**
* Returns a copy of this bounds object which cannot be modified anymore.
*
* @return a locked copy.
*/
public StrictBounds getLockedInstance() {
if ( locked ) {
return this;
}
final StrictBounds retval = (StrictBounds) clone();
retval.locked = true;
return retval;
}
/**
* Returns a copy of this bounds object which can be modified later.
*
* @return an unlocked copy.
*/
public StrictBounds getUnlockedInstance() {
final StrictBounds retval = (StrictBounds) clone();
retval.locked = false;
return retval;
}
/**
* Sets the location and size of this <code>StrictBounds</code> to the specified double values.
*
* @param bounds
* the bounds from where to copy all properties <code>StrictBounds</code>
*/
public void setRect( final StrictBounds bounds ) {
if ( locked ) {
throw new IllegalStateException( "This object is locked" );
}
this.x = bounds.x;
this.y = bounds.y;
this.width = bounds.width;
this.height = bounds.height;
}
/**
* Sets the location and size of this <code>StrictBounds</code> to the specified double values.
*
* @param x
* the coordinates to which to set the location of the upper left corner of this <code>StrictBounds</code>
* @param y
* the coordinates to which to set the location of the upper left corner of this <code>StrictBounds</code>
* @param w
* the value to use to set the width of this <code>StrictBounds</code>
* @param h
* the value to use to set the height of this <code>StrictBounds</code>
*/
public void setRect( final long x, final long y, final long w, final long h ) {
if ( locked ) {
throw new IllegalStateException( "This object is locked" );
}
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
/**
* Returns the height of the framing rectangle in micro points.
*
* @return the height of the framing rectangle.
*/
public long getHeight() {
return height;
}
/**
* Returns the width of the framing rectangle in micro points.
*
* @return the width of the framing rectangle.
*/
public long getWidth() {
return width;
}
/**
* Returns the X coordinate of the upper left corner of the framing rectangle in micro points.
*
* @return the x coordinate of the upper left corner of the framing rectangle.
*/
public long getX() {
return x;
}
/**
* Returns the Y coordinate of the upper left corner of the framing rectangle in micro points.
*
* @return the y coordinate of the upper left corner of the framing rectangle.
*/
public long getY() {
return y;
}
/**
* Determines whether the <code>RectangularShape</code> is empty. When the <code>RectangularShape</code> is empty, it
* encloses no area.
*
* @return <code>true</code> if the <code>RectangularShape</code> is empty; <code>false</code> otherwise.
*/
public boolean isEmpty() {
return width == 0 || height == 0;
}
/**
* Returns a copy of this bounds object. This method will never throw a 'CloneNotSupportedException'.
*
* @return the cloned instance.
*/
public Object clone() {
try {
return super.clone();
} catch ( CloneNotSupportedException e ) {
throw new InternalError( "Clone must always be supported." );
}
}
/**
* Checks whether this rectangle contains the given point.
*
* @param x
* the x-coordinate of the point.
* @param y
* the y-coordinate of the point.
* @return true, if the point is inside or directly on the border of this rectangle, false otherwise.
*/
public boolean contains( final long x, final long y ) {
if ( x < this.x ) {
return false;
}
if ( y < this.y ) {
return false;
}
if ( x > ( this.x + this.width ) ) {
return false;
}
return y <= ( this.y + this.height );
}
/**
* Checks whether the given rectangle1 fully contains rectangle 2 (even if rectangle 2 has a height or width of
* zero!).
*
* @param rect1
* the first rectangle.
* @param rect2
* the second rectangle.
* @return true, if the rectangles intersect each other, false otherwise.
*/
public static boolean intersects( final StrictBounds rect1, final StrictBounds rect2 ) {
final double x0 = rect1.getX();
final double y0 = rect1.getY();
final double x = rect2.getX();
final double width = rect2.getWidth();
final double y = rect2.getY();
final double height = rect2.getHeight();
return ( x + width >= x0 && y + height >= y0 && x <= x0 + rect1.getWidth() && y <= y0 + rect1.getHeight() );
}
/**
* Adds the given bounds to this bounds instance. The resulting rectangle will fully contain both rectangles.
*
* @param bounds
* the rectangle that should be added.
*/
public void add( final StrictBounds bounds ) {
if ( locked ) {
throw new IllegalStateException( "This object is locked" );
}
final long x1 = Math.min( getX(), bounds.getX() );
final long y1 = Math.min( getY(), bounds.getY() );
final long x2 = Math.max( getX() + getWidth(), bounds.getX() + bounds.getWidth() );
final long y2 = Math.max( getY() + getHeight(), bounds.getY() + bounds.getHeight() );
setRect( x1, y1, Math.max( 0, x2 - x1 ), Math.max( 0, y2 - y1 ) );
}
/**
* Adds the given bounds to this bounds instance. The resulting rectangle will fully contain both rectangles.
*/
public void add( final long x, final long y, final long width, final long height ) {
if ( locked ) {
throw new IllegalStateException( "This object is locked" );
}
final long x1 = Math.min( getX(), x );
final long y1 = Math.min( getY(), y );
final long x2 = Math.max( getX() + getWidth(), x + width );
final long y2 = Math.max( getY() + getHeight(), y + height );
setRect( x1, y1, Math.max( 0, x2 - x1 ), Math.max( 0, y2 - y1 ) );
}
/**
* Intersects this rectangle with the given bounds. The resulting rectangle will cover the space, that is occupied by
* both rectangles at the same time.
*
* @param bounds
* the other rectangle.
* @return the resulting intersection.
*/
public StrictBounds createIntersection( final StrictBounds bounds ) {
final long x1 = Math.max( getX(), bounds.getX() );
final long y1 = Math.max( getY(), bounds.getY() );
final long x2 = Math.min( getX() + getWidth(), bounds.getX() + bounds.getWidth() );
final long y2 = Math.min( getY() + getHeight(), bounds.getY() + bounds.getHeight() );
return new StrictBounds( x1, y1, Math.max( 0, x2 - x1 ), Math.max( 0, y2 - y1 ) );
}
/**
* Checks whether the given rectangle1 fully contains rectangle 2 (even if rectangle 2 has a height or width of
* zero!).
*
* @param rect1
* the first rectangle.
* @param rect2
* the second rectangle.
* @return A boolean.
*/
public static boolean contains( final StrictBounds rect1, final StrictBounds rect2 ) {
final long x0 = rect1.getX();
final long y0 = rect1.getY();
final long x = rect2.getX();
final long y = rect2.getY();
final long w = rect2.getWidth();
final long h = rect2.getHeight();
return ( ( x >= x0 ) && ( y >= y0 ) && ( ( x + w ) <= ( x0 + rect1.getWidth() ) ) && ( ( y + h ) <= ( y0 + rect1
.getHeight() ) ) );
}
/**
* Checks whether the given object is a StrictBounds instance convering the same area as these bounds.
*
* @param o
* the other object.
* @return true, if the other object is equal to this object, false otherwise.
*/
public boolean equals( final Object o ) {
if ( this == o ) {
return true;
}
if ( !( o instanceof StrictBounds ) ) {
return false;
}
final StrictBounds strictBounds = (StrictBounds) o;
if ( height != strictBounds.height ) {
return false;
}
if ( width != strictBounds.width ) {
return false;
}
if ( x != strictBounds.x ) {
return false;
}
return y == strictBounds.y;
}
/**
* Computes the hashcode for this rectangle.
*
* @return the computed hashcode.
*/
public int hashCode() {
int result = (int) ( x ^ ( x >>> 32 ) );
result = 29 * result + (int) ( y ^ ( y >>> 32 ) );
result = 29 * result + (int) ( width ^ ( width >>> 32 ) );
result = 29 * result + (int) ( height ^ ( height >>> 32 ) );
return result;
}
/**
* Returns a string representation of these bounds.
*
* @return the string representing this object.
*/
public String toString() {
return new StringBuffer( 100 ).append( "org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds{" )
.append( "x=" ).append( x ).append( ", y=" ).append( y ).append( ", width=" ).append( width ).append(
", height=" ).append( height ).append( '}' ).toString();
}
/**
* Creates a union from this and the given rectangle. This is similiar to calling 'add'. Calling this method does not
* modify the original and there are no guarantees, that the resulting rectangle has a positive width or height.
*
* @param bg
* the other rectangle.
* @return the resulting union rectangle.
*/
public StrictBounds createUnion( final StrictBounds bg ) {
final long x = Math.min( getX(), bg.getX() );
final long y = Math.min( getY(), bg.getY() );
final long w = Math.max( getX() + getWidth(), bg.getX() + bg.getWidth() ) - x;
final long h = Math.max( getY() + getHeight(), bg.getY() + bg.getHeight() ) - y;
return new StrictBounds( x, y, w, h );
}
}