001: /*
002: * $RCSfile: Range.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:57:57 $
010: * $State: Exp $
011: */package javax.media.jai.util;
012:
013: import java.io.Serializable;
014:
015: /**
016: * A class to represent ranges of values. A range is defined to contain
017: * all the values between the minimum and maximum values, where
018: * the minimum/maximum value can be considered either included or excluded
019: * from the range.
020: *
021: * <p> This example creates a range of <code>Integer</code>s whose minimum
022: * value is 1 and the maximum value is 5. The range is inclusive at both
023: * ends:
024: *
025: * <p><code>
026: * Range intRange = new Range(Integer.class, new Integer(1), new Integer(5));
027: * </code>
028: *
029: * <p> A <code>Range</code> can be unbounded at either or both of its ends.
030: * An unbounded end is specified by passing null for the value of that end.
031: * A <code>Range</code> unbounded at both of its ends represents a range of
032: * all possible values for the <code>Class</code> of elements in that
033: * <code>Range</code>. The <code>isMinIncluded()</code> method will always
034: * return true for a <code>Range</code> unbounded on the minimum side and
035: * correspondingly the <code>isMaxIncluded()</code> method will always
036: * return true for a <code>Range</code> unbounded on the maximum side.
037: *
038: * <p> An empty range is defined as a <code>Range</code> whose minimum value
039: * is greater than it's maximum value if the ends are included, or as a
040: * <code>Range</code> whose minimum value is greater than or equal to it's
041: * maximum value, if the minimum or the maximum value is excluded.
042: *
043: * @since JAI 1.1
044: */
045: public class Range implements Serializable {
046:
047: // The class of the elements in this Range.
048: private Class elementClass;
049:
050: // The minimum and maximum values of the range.
051: private Comparable minValue, maxValue;
052:
053: // The the minimum/maximum value is included in the range.i
054: // The default value is true, that is, included.
055: private boolean isMinIncluded = true, isMaxIncluded = true;
056:
057: /**
058: * Constructs a <code>Range</code> object given the <code>Class</code>
059: * of the elements in the <code>Range</code>, the minimum value and
060: * the maximum value. The minimum and the maximum value are considered
061: * inclusive.
062: *
063: * <p> An unbounded range can be specified by passing in a null for
064: * either of the two values, in which case the <code>Range</code> is
065: * unbounded on one side, or for both, in which case the
066: * <code>Range</code> represents an all inclusive set.
067: *
068: * @param elementClass The <code>Class</code> of the <code>Range</code>
069: * elements.
070: * @param minValue The lowest value included in the <code>Range</code>.
071: * @param maxValue The highest value included in the <code>Range</code>.
072: *
073: * @throws IllegalArgumentException if minValue and maxValue are both null,
074: * and elementClass is not one of the
075: * subclasses of <code>Comparable</code>.
076: * @throws IllegalArgumentException if minValue is not the same
077: * <code>Class</code> as elementClass.
078: * @throws IllegalArgumentException if maxValue is not the same
079: * <code>Class</code> as elementClass.
080: */
081: public Range(Class elementClass, Comparable minValue,
082: Comparable maxValue) {
083:
084: // If both minValue and maxValue are null, check whether elementClass
085: // is an instanceof Comparable.
086: if ((minValue == null) && (maxValue == null)) {
087: Class c = null;
088: try {
089: c = Class.forName("java.lang.Comparable");
090: } catch (ClassNotFoundException e) {
091: }
092:
093: if (!c.isAssignableFrom(elementClass))
094: throw new IllegalArgumentException(JaiI18N
095: .getString("Range0"));
096: }
097:
098: this .elementClass = elementClass;
099:
100: if (minValue != null
101: && minValue.getClass() != this .elementClass) {
102: throw new IllegalArgumentException(JaiI18N
103: .getString("Range1"));
104: }
105:
106: this .minValue = minValue;
107:
108: if (maxValue != null
109: && maxValue.getClass() != this .elementClass) {
110: throw new IllegalArgumentException(JaiI18N
111: .getString("Range2"));
112: }
113:
114: this .maxValue = maxValue;
115: }
116:
117: /**
118: * Constructs a <code>Range</code> object given the <code>Class</code>
119: * of the elements in the <code>Range</code>, the minimum value and
120: * the maximum value. Whether the minimum value and the maximum value
121: * are considered inclusive is specified via the
122: * <code>isMinIncluded</code> and <code>isMaxIncluded</code> variables.
123: *
124: * <p> An unbounded range can be specified by passing in a null for
125: * either of the two values, in which case the <code>Range</code> is
126: * unbounded at one end, or for both, in which case the
127: * <code>Range</code> represents an all inclusive set. If null is passed
128: * in for either variable, the <code>boolean</code> variables have
129: * no effect.
130: *
131: * @param elementClass The <code>Class</code> of the <code>Range</code>
132: * elements.
133: * @param minValue The lowest value for the <code>Range</code>.
134: * @param isMinIncluded A boolean that defines whether the minimum value is
135: * included in the <code>Range</code>.
136: * @param maxValue The highest value for the <code>Range</code>.
137: * @param isMaxIncluded A boolean that defines whether the maximum value is
138: * included in the <code>Range</code>.
139: *
140: * @throws IllegalArgumentException if minValue and maxValue are both null,
141: * and elementClass is not one of the
142: * subclasses of <code>Comparable</code>.
143: * @throws IllegalArgumentException if minValue is not the same
144: * <code>Class</code> as elementClass.
145: * @throws IllegalArgumentException if maxValue is not the same
146: * <code>Class</code> as elementClass.
147: */
148: public Range(Class elementClass, Comparable minValue,
149: boolean isMinIncluded, Comparable maxValue,
150: boolean isMaxIncluded) {
151: this (elementClass, minValue, maxValue);
152: this .isMinIncluded = isMinIncluded;
153: this .isMaxIncluded = isMaxIncluded;
154: }
155:
156: /**
157: * Returns true if the minimum value is included within this
158: * <code>Range</code>. If the range is unbounded at this end, this
159: * method will return true.
160: */
161: public boolean isMinIncluded() {
162: if (this .minValue == null)
163: return true;
164:
165: return isMinIncluded;
166: }
167:
168: /**
169: * Returns true if the maximum value is included within this
170: * <code>Range</code>. If the range is unbounded at this end, this
171: * method will return true.
172: */
173: public boolean isMaxIncluded() {
174: if (this .maxValue == null)
175: return true;
176:
177: return isMaxIncluded;
178: }
179:
180: /**
181: * Returns the <code>Class</code> of the elements of this <code>Range</code>.
182: */
183: public Class getElementClass() {
184: return elementClass;
185: }
186:
187: /**
188: * Returns the minimum value of this <code>Range</code>.
189: * Returns null if the <code>Range</code> is unbounded at this end.
190: */
191: public Comparable getMinValue() {
192: return minValue;
193: }
194:
195: /**
196: * Returns the maximum value of this <code>Range</code>.
197: * Returns null if the <code>Range</code> is unbounded at this end.
198: */
199: public Comparable getMaxValue() {
200: return maxValue;
201: }
202:
203: /**
204: * Returns true if the specified value is within this <code>Range</code>,
205: * i.e. is either equal to or greater than the minimum value of this
206: * <code>Range</code> and is either lesser than or equal to the maximum
207: * value of this <code>Range</code>.
208: *
209: * @param value The value to be checked for being within this
210: * <code>Range</code>.
211: * @throws IllegalArgumentException if the <code>Class</code> of the value
212: * parameter is not the same as the elementClass of this <code>Range</code>.
213: */
214: public boolean contains(Comparable value) {
215:
216: if (value != null && value.getClass() != elementClass) {
217: throw new IllegalArgumentException(JaiI18N
218: .getString("Range3"));
219: }
220:
221: // First check if the Range is empty
222: if (isEmpty() == true)
223: return false;
224:
225: // check both bounds.
226: return isUnderUpperBound(value) && isOverLowerBound(value);
227: }
228:
229: /**
230: * Return true if the specific value is smaller than the maximum of
231: * this range. If this range is unbounded at the maximum end, return true;
232: * if the specific value is null and the maximum end is bounded, return
233: * false, that is, suppose this null is the "positive infinite".
234: */
235: private boolean isUnderUpperBound(Comparable value) {
236: // if the maximum side is unbounded, return true
237: if (this .maxValue == null)
238: return true;
239:
240: // if the object passed in is null, return false: suppose it is
241: // the "positive infinite". So be care when use this method.
242: if (value == null)
243: return false;
244:
245: if (isMaxIncluded)
246: return maxValue.compareTo(value) >= 0;
247: return maxValue.compareTo(value) > 0;
248: }
249:
250: /** Return true if the specific value is larger than the minimum of
251: * this range. If this range is unbounded at the minimum end, return true;
252: * if the specific value is null, return false;
253: */
254: private boolean isOverLowerBound(Comparable value) {
255: // if the minimum side is unbounded, return true
256: if (this .minValue == null)
257: return true;
258:
259: // if the object passed in is null, return false: suppose it is
260: // the "negative infinite". So be care when use this method.
261: if (value == null)
262: return false;
263:
264: if (isMinIncluded)
265: return minValue.compareTo(value) <= 0;
266: else
267: return minValue.compareTo(value) < 0;
268: }
269:
270: /**
271: * Returns true if the supplied <code>Range</code> is fully contained
272: * within this <code>Range</code>. Fully contained is defined as having
273: * the minimum and maximum values of the fully contained range lie
274: * within the range of values of the containing <code>Range</code>.
275: *
276: * @throws IllegalArgumentException if the <code>Class</code> of the
277: * elements of the given <code>Range</code> is not the same as the
278: * <code>Class</code> of the elements of this <code>Range</code>.
279: * @throws IllegalArgumentException if the given <code>Range</code> is null
280: */
281: public boolean contains(Range range) {
282:
283: if (range == null)
284: throw new IllegalArgumentException(JaiI18N
285: .getString("Range5"));
286: if (elementClass != range.getElementClass())
287: throw new IllegalArgumentException(JaiI18N
288: .getString("Range4"));
289:
290: if (range.isEmpty())
291: return true;
292:
293: Comparable min = range.getMinValue();
294: Comparable max = range.getMaxValue();
295: boolean maxSide, minSide;
296:
297: if (max == null)
298: maxSide = (maxValue == null);
299: else
300: maxSide = isUnderUpperBound(max)
301: || (isMaxIncluded == range.isMaxIncluded() && max
302: .equals(maxValue));
303:
304: if (min == null)
305: minSide = (minValue == null);
306: else
307: minSide = isOverLowerBound(min)
308: || (isMinIncluded == range.isMinIncluded() && min
309: .equals(minValue));
310: return minSide && maxSide;
311: }
312:
313: /**
314: * Returns true if this <code>Range</code> intersects the
315: * given <code>Range</code>.
316: *
317: * @throws IllegalArgumentException if the <code>Class</code> of the
318: * elements of the given <code>Range</code> is not the same as the
319: * <code>Class</code> of the elements of this <code>Range</code>.
320: * @throws IllegalArgumentException if the given <code>Range</code> is null
321: */
322: public boolean intersects(Range range) {
323: if (range == null)
324: throw new IllegalArgumentException(JaiI18N
325: .getString("Range5"));
326: if (elementClass != range.getElementClass())
327: throw new IllegalArgumentException(JaiI18N
328: .getString("Range4"));
329:
330: return !intersect(range).isEmpty();
331: }
332:
333: /**
334: * Returns the union of this <code>Range</code> with the given
335: * <code>Range</code>. If this <code>Range</code> and the given
336: * <code>Range</code> are disjoint, the <code>Range</code> returned
337: * as a result of the union will have a minimum value set to the
338: * minimum of the two disjoint range's minimum values, and the maximum
339: * set to the maximum of the two disjoint range's maximum values, thus
340: * including the disjoint range within it.
341: *
342: * @throws IllegalArgumentException if the <code>Class</code> of the
343: * elements of the given <code>Range</code> is not the same as the
344: * <code>Class</code> of the elements of this <code>Range</code>.
345: * @throws IllegalArgumentException if the given <code>Range</code> is null
346: */
347: public Range union(Range range) {
348:
349: if (range == null)
350: throw new IllegalArgumentException(JaiI18N
351: .getString("Range5"));
352: if (elementClass != range.getElementClass())
353: throw new IllegalArgumentException(JaiI18N
354: .getString("Range4"));
355:
356: if (this .isEmpty())
357: return new Range(elementClass, range.getMinValue(), range
358: .isMinIncluded(), range.getMaxValue(), range
359: .isMaxIncluded());
360:
361: if (range.isEmpty())
362: return new Range(elementClass, this .minValue,
363: this .isMinIncluded, this .maxValue,
364: this .isMaxIncluded);
365:
366: boolean containMin = !isOverLowerBound(range.getMinValue());
367: boolean containMax = !isUnderUpperBound(range.getMaxValue());
368:
369: // If the minimum of this range is contained in the given range, the
370: // minimum of the union is the minimum of the given range; otherwise
371: // it is the minimum of this range. So does the boolean isMinIncluded.
372: // Similar for the maximum end
373: Comparable minValue = containMin ? range.getMinValue()
374: : this .minValue;
375: Comparable maxValue = containMax ? range.getMaxValue()
376: : this .maxValue;
377: boolean isMinIncluded = containMin ? range.isMinIncluded()
378: : this .isMinIncluded;
379: boolean isMaxIncluded = containMax ? range.isMaxIncluded()
380: : this .isMaxIncluded;
381: return new Range(elementClass, minValue, isMinIncluded,
382: maxValue, isMaxIncluded);
383: }
384:
385: /**
386: * Returns the intersection of this <code>Range</code> with the
387: * given <code>Range</code>.
388: *
389: * @throws IllegalArgumentException if the <code>Class</code> of the
390: * elements of the given <code>Range</code> is not the same as the
391: * <code>Class</code> of the elements of this <code>Range</code>.
392: * @throws IllegalArgumentException if the given <code>Range</code> is null
393: */
394: public Range intersect(Range range) {
395:
396: if (range == null)
397: throw new IllegalArgumentException(JaiI18N
398: .getString("Range5"));
399: if (elementClass != range.getElementClass())
400: throw new IllegalArgumentException(JaiI18N
401: .getString("Range4"));
402:
403: if (this .isEmpty()) {
404: Comparable temp = this .minValue;
405: if (temp == null)
406: temp = this .maxValue;
407:
408: // get a non-null object to create an empty Range
409: // because the range is empty, so temp will not be
410: // null
411: return new Range(elementClass, temp, false, temp, false);
412: }
413:
414: if (range.isEmpty()) {
415: Comparable temp = range.getMinValue();
416: if (temp == null)
417: temp = range.getMaxValue();
418:
419: // get a non-null object to create an empty Range
420: // because the range is empty, so temp will not be
421: // null
422: return new Range(elementClass, temp, false, temp, false);
423: }
424:
425: boolean containMin = !isOverLowerBound(range.getMinValue());
426: boolean containMax = !isUnderUpperBound(range.getMaxValue());
427:
428: // If the minimum of this range is contained in the given range, the
429: // minimum of the intersect range should be the one of this range;
430: // similarly, we can get the maximum and the booleans.
431: Comparable minValue = containMin ? this .minValue : range
432: .getMinValue();
433: Comparable maxValue = containMax ? this .maxValue : range
434: .getMaxValue();
435: boolean isMinIncluded = containMin ? this .isMinIncluded : range
436: .isMinIncluded();
437: boolean isMaxIncluded = containMax ? this .isMaxIncluded : range
438: .isMaxIncluded();
439: return new Range(elementClass, minValue, isMinIncluded,
440: maxValue, isMaxIncluded);
441: }
442:
443: /**
444: * Returns the <code>Range</code> of values that are in this
445: * <code>Range</code> but not in the given <code>Range</code>. If
446: * the subtraction results in two disjoint <code>Range</code>s, they
447: * will be returned as two elements of a <code>Range</code> array,
448: * otherwise the resultant <code>Range</code> will be returned as the
449: * first element of a one element array.
450: *
451: * When this <code>Range</code> and the given <code>Range</code> are
452: * both unbounded at both the ends (i.e both the <code>Range</code>s
453: * are all inclusive), this method will return null as the first
454: * element of one element array, as a result of the subtraction.
455: *
456: * When this <code>Range</code> is completely contained in the
457: * given <code>Range</code>, an empty <code>Range</code> is returned.
458: *
459: * @throws IllegalArgumentException if the <code>Class</code> of the
460: * elements of the given <code>Range</code> is not the same as the
461: * <code>Class</code> of the elements of this <code>Range</code>.
462: */
463: public Range[] subtract(Range range) {
464:
465: if (range == null)
466: throw new IllegalArgumentException(JaiI18N
467: .getString("Range5"));
468: if (elementClass != range.getElementClass())
469: throw new IllegalArgumentException(JaiI18N
470: .getString("Range4"));
471:
472: // if this range is empty, return an empty range by copying this range;
473: // if the given range is empty, return this range
474: if (this .isEmpty() || range.isEmpty()) {
475: Range[] ra = { new Range(elementClass, this .minValue,
476: this .isMinIncluded, this .maxValue,
477: this .isMaxIncluded) };
478: return ra;
479: }
480:
481: Comparable min = range.getMinValue();
482: Comparable max = range.getMaxValue();
483: boolean minIn = range.isMinIncluded();
484: boolean maxIn = range.isMaxIncluded();
485:
486: if (this .minValue == null && this .maxValue == null
487: && min == null && max == null) {
488: Range[] ra = { null };
489: return ra;
490: }
491:
492: boolean containMin = this .contains(min);
493: boolean containMax = this .contains(max);
494:
495: // this range may be a full range [null, null]
496: if (containMin && containMax) {
497: Range r1 = new Range(elementClass, this .minValue,
498: this .isMinIncluded, min, !minIn);
499: Range r2 = new Range(elementClass, max, !maxIn,
500: this .maxValue, this .isMaxIncluded);
501:
502: // if r1 is empty , return the second section only;
503: // or if this range and the given range are all unbounded
504: // at the minimum end, r1 is [null, null] but
505: // should be empty. so we need to treat it as a special case;
506: // otherwise, a full range is returned
507: if (r1.isEmpty() || (this .minValue == null && min == null)) {
508: Range[] ra = { r2 };
509: return ra;
510: }
511: // similar to above
512: if (r2.isEmpty() || (this .maxValue == null && max == null)) {
513: Range[] ra = { r1 };
514: return ra;
515: }
516: Range[] ra = { r1, r2 };
517: return ra;
518: }
519: // if the max of the given range is in this range, return the range
520: // from max of given range to the max of this range
521: else if (containMax) {
522: Range[] ra = { new Range(elementClass, max, !maxIn,
523: this .maxValue, this .isMaxIncluded) };
524: return ra;
525: }
526: // if the min of the given range is in this range, return the range
527: // from the min of this range to the min of the given range
528: else if (containMin) {
529: Range[] ra = { new Range(elementClass, this .minValue,
530: this .isMinIncluded, min, !minIn) };
531: return ra;
532: }
533:
534: // no overlap, just copy this range
535: if ((min != null && !isUnderUpperBound(min))
536: || (max != null && !isOverLowerBound(max))) {
537: Range[] ra = { new Range(elementClass, this .minValue,
538: this .isMinIncluded, this .maxValue,
539: this .isMaxIncluded) };
540: return ra;
541: }
542:
543: // this range is contained in the given range, return an empty range
544: min = (this .minValue == null) ? this .maxValue : this .minValue;
545: Range[] ra = { new Range(elementClass, min, false, min, false) };
546: return ra;
547: }
548:
549: /**
550: * Compute a hash code value for this <code>Range</code> object.
551: *
552: * @return a hash code value for this <code>Range</code> object.
553: */
554:
555: public int hashCode() {
556:
557: int code = this .elementClass.hashCode();
558:
559: if (isEmpty())
560: return code;
561:
562: code ^= Integer.MAX_VALUE;
563:
564: if (this .minValue != null) {
565: code ^= this .minValue.hashCode();
566: if (this .isMinIncluded)
567: code ^= 0xFFFF0000;
568: }
569:
570: if (this .maxValue != null) {
571: code ^= this .maxValue.hashCode() * 31;
572: if (this .isMaxIncluded)
573: code ^= 0xFFFF;
574: }
575:
576: return code;
577: }
578:
579: /**
580: * Returns true if this <code>Range</code> and the given
581: * <code>Range</code> both have elements of the same <code>Class</code>,
582: * their minimum and maximum values are the same, and their isMinIncluded()
583: * and isMaxIncluded() methods return the same values.
584: *
585: * If this <code>Range</code> and the given <code>Range</code> are both
586: * empty and the <code>Class</code> of their elements is the same, they
587: * will be found to be equal and true will be returned.
588: */
589: public boolean equals(Object other) {
590:
591: // this range is not null, so if the given object is null,
592: // return false
593: if (other == null)
594: return false;
595:
596: // if the given object is not a range, return false
597: if (!(other instanceof Range))
598: return false;
599:
600: Range r = (Range) other;
601:
602: // if the element class is not same, return false
603: if (this .elementClass != r.getElementClass())
604: return false;
605:
606: // two empty ranges are equal
607: if (this .isEmpty() && r.isEmpty())
608: return true;
609:
610: Comparable min = r.getMinValue();
611: // if the minimum is not null, compare both minValue and the boolean
612: if (this .minValue != null) {
613: if (!this .minValue.equals(min))
614: return false;
615:
616: if (this .isMinIncluded != r.isMinIncluded())
617: return false;
618: }
619: // if the minimum is unbounded, just check the given range is bounded
620: // or not
621: else if (min != null)
622: return false;
623:
624: Comparable max = r.getMaxValue();
625: // if the maximum is not null, compare both maxValue and the boolean
626: if (this .maxValue != null) {
627: if (!this .maxValue.equals(max))
628: return false;
629:
630: if (this .isMaxIncluded != r.isMaxIncluded())
631: return false;
632: }
633: // if the maximum is unbounded, just check the given range is bounded
634: // or not
635: else if (max != null)
636: return false;
637:
638: return true;
639: }
640:
641: /**
642: * Returns true if this <code>Range</code> is empty, i.e. if the minimum
643: * value is greater than the maximum value, if both are included, or if
644: * the minimum value is greater than equal to the maximum value if either
645: * the minimum or maximum value is excluded.
646: */
647: public boolean isEmpty() {
648:
649: // an unbounded range is not empty
650: if (minValue == null || maxValue == null)
651: return false;
652:
653: int cmp = this .minValue.compareTo(this .maxValue);
654:
655: // if the minimum is larger than the maximum, this range is empty
656: if (cmp > 0)
657: return true;
658:
659: // if the minimum equals to the maximum and one side is not
660: // inclusive, then it is empty
661: if (cmp == 0)
662: return !(isMinIncluded & isMaxIncluded);
663:
664: // otherwise, not empty
665: return false;
666: }
667:
668: /**
669: * Returns a <code>String</code> representation of this <code>Range</code>.
670: */
671: public String toString() {
672:
673: // if inclusive, display '[' otherwise display '('
674: char c1 = isMinIncluded ? '[' : '(';
675: char c2 = isMaxIncluded ? ']' : ')';
676:
677: // if both ends are bounded
678: if (minValue != null && maxValue != null)
679: return new String(c1 + this .minValue.toString() + ", "
680: + this .maxValue.toString() + c2);
681:
682: // if the maximum end is bounded
683: if (maxValue != null)
684: return new String(c1 + "---, " + this .maxValue.toString()
685: + c2);
686:
687: // if the minimum end is bounded
688: if (minValue != null)
689: return new String(c1 + this .minValue.toString() + ", "
690: + "---" + c2);
691:
692: // both ends are unbounded
693: return new String(c1 + "---, ---" + c2);
694: }
695: }
|