001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
009: *
010: * This library is free software; you can redistribute it and/or modify it under the terms
011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
012: * either version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: * See the GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License along with this
019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020: * Boston, MA 02111-1307, USA.
021: *
022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023: * in the United States and other countries.]
024: *
025: * ------------
026: * StrictBounds.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.util.geom;
030:
031: import java.io.Serializable;
032:
033: /**
034: * The StrictBounds class is a replacement for the Rectangle2D classes.
035: * This class uses integer mathematics instead of floating point values
036: * to achive a higher degree of stability.
037: *
038: * @author Thomas Morgner
039: */
040: public class StrictBounds implements Serializable, Cloneable {
041: /** The x-coordinate of the upper left corner. */
042: private long x;
043: /** The y-coordinate of the upper left corner. */
044: private long y;
045: /** The width of this rectangle. */
046: private long width;
047: /** The height of this rectangle. */
048: private long height;
049: /** A flag indicating whether attempts to change this rectangle should trigger Exceptions. */
050: private boolean locked;
051:
052: /**
053: * DefaultConstructor.
054: */
055: public StrictBounds() {
056: }
057:
058: /**
059: * Creates a StrictBounds object with the given coordinates, width
060: * and height.
061: *
062: * @param x the x-coordinate
063: * @param y the y-coordinate
064: * @param width the width of the rectangle
065: * @param height the height of the rectangle
066: */
067: public StrictBounds(final long x, final long y, final long width,
068: final long height) {
069: this .x = x;
070: this .y = y;
071: this .width = width;
072: this .height = height;
073: }
074:
075: /**
076: * Checks, whether this bounds object is locked.
077: *
078: * @return true, if the bounds are locked and therefore immutable, false otherwise.
079: */
080: public boolean isLocked() {
081: return locked;
082: }
083:
084: /**
085: * Returns a copy of this bounds object which cannot be modified anymore.
086: *
087: * @return a locked copy.
088: */
089: public StrictBounds getLockedInstance() {
090: if (locked) {
091: return this ;
092: }
093:
094: final StrictBounds retval = (StrictBounds) clone();
095: retval.locked = true;
096: return retval;
097: }
098:
099: /**
100: * Returns a copy of this bounds object which can be modified later.
101: *
102: * @return an unlocked copy.
103: */
104: public StrictBounds getUnlockedInstance() {
105: final StrictBounds retval = (StrictBounds) clone();
106: retval.locked = false;
107: return retval;
108: }
109:
110: /**
111: * Sets the location and size of this <code>StrictBounds</code> to the specified double
112: * values.
113: *
114: * @param x the coordinates to which to set the location of the upper left corner of
115: * this <code>StrictBounds</code>
116: * @param y the coordinates to which to set the location of the upper left corner of
117: * this <code>StrictBounds</code>
118: * @param w the value to use to set the width of this <code>StrictBounds</code>
119: * @param h the value to use to set the height of this <code>StrictBounds</code>
120: */
121: public void setRect(final long x, final long y, final long w,
122: final long h) {
123: if (locked) {
124: throw new IllegalStateException("This object is locked");
125: }
126: this .x = x;
127: this .y = y;
128: this .width = w;
129: this .height = h;
130: }
131:
132: /**
133: * Returns the height of the framing rectangle in micro points.
134: *
135: * @return the height of the framing rectangle.
136: */
137: public long getHeight() {
138: return height;
139: }
140:
141: /**
142: * Returns the width of the framing rectangle in micro points.
143: *
144: * @return the width of the framing rectangle.
145: */
146: public long getWidth() {
147: return width;
148: }
149:
150: /**
151: * Returns the X coordinate of the upper left corner of the framing
152: * rectangle in micro points.
153: *
154: * @return the x coordinate of the upper left corner of the framing rectangle.
155: */
156: public long getX() {
157: return x;
158: }
159:
160: /**
161: * Returns the Y coordinate of the upper left corner of the framing
162: * rectangle in micro points.
163: *
164: * @return the y coordinate of the upper left corner of the framing rectangle.
165: */
166: public long getY() {
167: return y;
168: }
169:
170: /**
171: * Determines whether the <code>RectangularShape</code> is empty. When the
172: * <code>RectangularShape</code> is empty, it encloses no area.
173: *
174: * @return <code>true</code> if the <code>RectangularShape</code> is empty;
175: * <code>false</code> otherwise.
176: */
177: public boolean isEmpty() {
178: return width == 0 || height == 0;
179: }
180:
181: /**
182: * Returns a copy of this bounds object. This method will never throw a
183: * 'CloneNotSupportedException'.
184: *
185: * @return the cloned instance.
186: */
187: public Object clone() {
188: try {
189: return super .clone();
190: } catch (CloneNotSupportedException e) {
191: throw new InternalError("Clone must always be supported.");
192: }
193: }
194:
195: /**
196: * Checks, whether this rectangle contains the given point.
197: *
198: * @param x the x-coordinate of the point.
199: * @param y the y-coordinate of the point.
200: * @return true, if the point is inside or directly on the border of this
201: * rectangle, false otherwise.
202: */
203: public boolean contains(final long x, final long y) {
204: if (x < this .x) {
205: return false;
206: }
207: if (y < this .y) {
208: return false;
209: }
210: if (x > (this .x + this .width)) {
211: return false;
212: }
213: return y <= (this .y + this .height);
214: }
215:
216: /**
217: * Checks, whether the given rectangle1 fully contains rectangle 2 (even if rectangle 2
218: * has a height or width of zero!).
219: *
220: * @param rect1 the first rectangle.
221: * @param rect2 the second rectangle.
222: * @return true, if the rectangles intersect each other, false otherwise.
223: */
224: public static boolean intersects(final StrictBounds rect1,
225: final StrictBounds rect2) {
226:
227: final double x0 = rect1.getX();
228: final double y0 = rect1.getY();
229:
230: final double x = rect2.getX();
231: final double width = rect2.getWidth();
232: final double y = rect2.getY();
233: final double height = rect2.getHeight();
234: return (x + width >= x0 && y + height >= y0
235: && x <= x0 + rect1.getWidth() && y <= y0
236: + rect1.getHeight());
237: }
238:
239: /**
240: * Adds the given bounds to this bounds instance. The resulting rectangle
241: * will fully contain both rectangles.
242: *
243: * @param bounds the rectangle that should be added.
244: */
245: public void add(final StrictBounds bounds) {
246: if (locked) {
247: throw new IllegalStateException("This object is locked");
248: }
249:
250: final long x1 = Math.min(getX(), bounds.getX());
251: final long y1 = Math.min(getY(), bounds.getY());
252: final long x2 = Math.max(getX() + getWidth(), bounds.getX()
253: + bounds.getWidth());
254: final long y2 = Math.max(getY() + getHeight(), bounds.getY()
255: + bounds.getHeight());
256: setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
257: }
258:
259: /**
260: * Intersects this rectangle with the given bounds. The resulting rectangle
261: * will cover the space, that is occupied by both rectangles at the same time.
262: *
263: * @param bounds the other rectangle.
264: * @return the resulting intersection.
265: */
266: public StrictBounds createIntersection(final StrictBounds bounds) {
267: final long x1 = Math.max(getX(), bounds.getX());
268: final long y1 = Math.max(getY(), bounds.getY());
269: final long x2 = Math.min(getX() + getWidth(), bounds.getX()
270: + bounds.getWidth());
271: final long y2 = Math.min(getY() + getHeight(), bounds.getY()
272: + bounds.getHeight());
273:
274: return new StrictBounds(x1, y1, Math.max(0, x2 - x1), Math.max(
275: 0, y2 - y1));
276: }
277:
278: /**
279: * Checks, whether the given rectangle1 fully contains rectangle 2 (even if rectangle 2
280: * has a height or width of zero!).
281: *
282: * @param rect1 the first rectangle.
283: * @param rect2 the second rectangle.
284: * @return A boolean.
285: */
286: public static boolean contains(final StrictBounds rect1,
287: final StrictBounds rect2) {
288:
289: final long x0 = rect1.getX();
290: final long y0 = rect1.getY();
291: final long x = rect2.getX();
292: final long y = rect2.getY();
293: final long w = rect2.getWidth();
294: final long h = rect2.getHeight();
295:
296: return ((x >= x0) && (y >= y0)
297: && ((x + w) <= (x0 + rect1.getWidth())) && ((y + h) <= (y0 + rect1
298: .getHeight())));
299:
300: }
301:
302: /**
303: * Checks, whether the given object is a StrictBounds instance convering the
304: * same area as these bounds.
305: *
306: * @param o the other object.
307: * @return true, if the other object is equal to this object, false otherwise.
308: */
309: public boolean equals(final Object o) {
310: if (this == o) {
311: return true;
312: }
313: if (!(o instanceof StrictBounds)) {
314: return false;
315: }
316:
317: final StrictBounds strictBounds = (StrictBounds) o;
318:
319: if (height != strictBounds.height) {
320: return false;
321: }
322: if (width != strictBounds.width) {
323: return false;
324: }
325: if (x != strictBounds.x) {
326: return false;
327: }
328: return y == strictBounds.y;
329:
330: }
331:
332: /**
333: * Computes the hashcode for this rectangle.
334: *
335: * @return the computed hashcode.
336: */
337: public int hashCode() {
338: int result = (int) (x ^ (x >>> 32));
339: result = 29 * result + (int) (y ^ (y >>> 32));
340: result = 29 * result + (int) (width ^ (width >>> 32));
341: result = 29 * result + (int) (height ^ (height >>> 32));
342: return result;
343: }
344:
345: /**
346: * Returns a string representation of these bounds.
347: *
348: * @return the string representing this object.
349: */
350: public String toString() {
351: return new StringBuffer().append(
352: "org.jfree.report.util.geom.StrictBounds{")
353: .append("x=").append(x).append(", y=").append(y)
354: .append(", width=").append(width).append(", height=")
355: .append(height).append('}').toString();
356: }
357:
358: /**
359: * Creates a union from this and the given rectangle. This is similiar to
360: * calling 'add'. Calling this method does not modify the original and there
361: * are no guarantees, that the resulting rectangle has a positive width or
362: * height.
363: *
364: * @param bg the other rectangle.
365: * @return the resulting union rectangle.
366: */
367: public StrictBounds createUnion(final StrictBounds bg) {
368: final long x = Math.min(getX(), bg.getX());
369: final long y = Math.min(getY(), bg.getY());
370: final long w = Math.max(getX() + getWidth(), bg.getX()
371: + bg.getWidth())
372: - x;
373: final long h = Math.max(getY() + getHeight(), bg.getY()
374: + bg.getHeight())
375: - y;
376: return new StrictBounds(x, y, w, h);
377: }
378: }
|