/* * Copyright (c) 2012, grossmann * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the jo-widgets.org nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL jo-widgets.org BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.jowidgets.util; import java.util.Iterator; public final class Interval<NUMBER_TYPE extends Number> { private final NUMBER_TYPE leftBoundary; private final boolean leftOpen; private final NUMBER_TYPE rightBoundary; private final boolean rightOpen; private String stringRepresentation; public Interval(final Interval<NUMBER_TYPE> original) { this( Assert.getParamNotNull(original, "original").getLeftBoundary(), original.isLeftOpen(), original.getRightBoundary(), original.isRightOpen()); } public Interval(final NUMBER_TYPE leftBoundary, final NUMBER_TYPE rightBoundary) { this(leftBoundary, false, rightBoundary, false); } public Interval( final NUMBER_TYPE leftBoundary, final boolean leftOpen, final NUMBER_TYPE rightBoundary, final boolean rightOpen) { this.leftBoundary = leftBoundary; this.leftOpen = leftOpen; this.rightBoundary = rightBoundary; this.rightOpen = rightOpen; } public Interval<NUMBER_TYPE> createCopy() { return new Interval<NUMBER_TYPE>(this); } public NUMBER_TYPE getLeftBoundary() { return leftBoundary; } public boolean isLeftOpen() { return leftOpen; } public NUMBER_TYPE getRightBoundary() { return rightBoundary; } public boolean isRightOpen() { return rightOpen; } /** * Calculates the intersection of this interval and the given interval * * If the intersection is the empty interval, null will be returned * * @param interval The interval to intersect with * * @return The intersection interval or null, if the intersection is empty */ public Interval<NUMBER_TYPE> intersect(final Interval<NUMBER_TYPE> interval) { Assert.paramNotNull(interval, "interval"); final NUMBER_TYPE min = NumberUtils.max(leftBoundary, interval.getLeftBoundary()); final NUMBER_TYPE max = NumberUtils.min(rightBoundary, interval.getRightBoundary()); if (min != null && max != null && NumberUtils.compareTo(min, max) <= 0) { return new Interval<NUMBER_TYPE>(min, max); } else { return null; } } /** * Calculates the intersection of this interval and the given intervas * * If the intersection is the empty interval, null will be returned * * @param interval The interval to intersect with * * @return The intersection interval or null, if the intersection is empty */ public Interval<NUMBER_TYPE> intersect(final Iterable<Interval<NUMBER_TYPE>> intervals) { if (EmptyCheck.isEmpty(intervals)) { return this; } Interval<NUMBER_TYPE> result = this; final Iterator<Interval<NUMBER_TYPE>> iterator = intervals.iterator(); while (iterator.hasNext()) { result = result.intersect(iterator.next()); if (result == null) { return null; } } return result; } /** * Calculates the closure of this interval and the given interval * * @param interval The interval to calculate the closure with * * @return The closure interval, never null */ public Interval<NUMBER_TYPE> closure(final Interval<NUMBER_TYPE> interval) { if (interval == null) { return this; } else { final NUMBER_TYPE min = NumberUtils.min(getLeftBoundary(), interval.getLeftBoundary()); final NUMBER_TYPE max = NumberUtils.max(getRightBoundary(), interval.getRightBoundary()); return new Interval<NUMBER_TYPE>(min, max); } } /** * Calculates the closure of this interval and the given intervals * * @param intervals The intervals to calculate the closure with * * @return The closure interval, never null */ public Interval<NUMBER_TYPE> closure(final Iterable<Interval<NUMBER_TYPE>> intervals) { if (EmptyCheck.isEmpty(intervals)) { return this; } Interval<NUMBER_TYPE> result; final Iterator<Interval<NUMBER_TYPE>> iterator = intervals.iterator(); result = this; while (iterator.hasNext()) { result = result.closure(iterator.next()); } return result; } /** * Calculates the closure of the given intervals * * @param interval1 The given iterval1, may be null * @param interval2 The given iterval12, may be null * * @return The closure or null if the given intervals are both null */ public static <T extends Number> Interval<T> closureOf(final Interval<T> interval1, final Interval<T> interval2) { if (interval1 == null && interval2 == null) { return null; } else if (interval1 == null) { return interval2; } else if (interval2 == null) { return interval1; } else { return interval1.closure(interval2); } } /** * Calculates the closure of the given intervals * * @param intervals The given itervals, may be null or empty * * @return The closure or null if intervals is empty */ public static <T extends Number> Interval<T> closureOf(final Iterable<Interval<T>> intervals) { if (EmptyCheck.isEmpty(intervals)) { return null; } final Iterator<Interval<T>> iterator = intervals.iterator(); final Interval<T> first = iterator.next(); //this iterable only works until the implemenation of closure() will now be changed final Iterable<Interval<T>> iterable = new Iterable<Interval<T>>() { @Override public Iterator<Interval<T>> iterator() { return iterator; } }; return first.closure(iterable); } /** * Calculates the union of this interval and the given interval * * If the union can not be calculated, e.g. its not a single interval, null will be returned * * @param interval The interval to calculate the union with * * @return The union interval or null */ public Interval<NUMBER_TYPE> union(final Interval<NUMBER_TYPE> interval) { Assert.paramNotNull(interval, "interval"); if (intersect(interval) == null) { return null; } final NUMBER_TYPE min = NumberUtils.min(leftBoundary, interval.getLeftBoundary()); final NUMBER_TYPE max = NumberUtils.max(rightBoundary, interval.getRightBoundary()); if (min != null && max != null) { return new Interval<NUMBER_TYPE>(min, max); } else { return null; } } /** * Checks if a number is contained in an interval * * @param number The number to check * * @return True if contained, false otherwise */ public boolean contains(final NUMBER_TYPE number) { return NumberUtils.compareTo(number, leftBoundary) >= 0 && NumberUtils.compareTo(number, rightBoundary) <= 0; } /** * Checks if the given interval is completely contained in this interval. * * This is true if the left and the right boundary is conatined in this interval. * * @param other The interval to check if contained * * @return True if contained, false otherwise */ public boolean contains(final Interval<NUMBER_TYPE> other) { Assert.paramNotNull(other, "other"); return contains(other.getLeftBoundary()) && contains(other.getRightBoundary()); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((leftBoundary == null) ? 0 : leftBoundary.hashCode()); result = prime * result + (leftOpen ? 1231 : 1237); result = prime * result + ((rightBoundary == null) ? 0 : rightBoundary.hashCode()); result = prime * result + (rightOpen ? 1231 : 1237); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Interval)) { return false; } final Interval<?> other = (Interval<?>) obj; if (leftBoundary == null) { if (other.leftBoundary != null) { return false; } } else if (!leftBoundary.equals(other.leftBoundary)) { return false; } if (leftOpen != other.leftOpen) { return false; } if (rightBoundary == null) { if (other.rightBoundary != null) { return false; } } else if (!rightBoundary.equals(other.rightBoundary)) { return false; } if (rightOpen != other.rightOpen) { return false; } return true; } @Override public String toString() { if (stringRepresentation == null) { final StringBuilder builder = new StringBuilder(); if (leftOpen) { builder.append("("); } else { builder.append("["); } if (leftBoundary != null) { builder.append(leftBoundary); } else { builder.append("UNDEFINED"); } builder.append(","); if (rightBoundary != null) { builder.append(rightBoundary); } else { builder.append("UNDEFINED"); } if (rightOpen) { builder.append(")"); } else { builder.append("]"); } stringRepresentation = builder.toString(); } return stringRepresentation; } }