/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* 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 it.geosolutions.jaiext.range;
import java.awt.image.DataBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Abstract class used for checking if a selected value is inside the selected Range. All the methods throw an {@link UnsupportedOperationException}
* but for every subclass one of these methods is overridden with the correct functionality. These 6 methods are different only for the data type
* used. In this way it is possible to reach a better performance by using primitive variables than generic. All the subclasses can contain a Range
* composed by a minimum and a maximum or a single-point Range. For Double and Float data type the NaN data can be used only with a single-point
* Range.
*/
public abstract class Range {
private static final Logger LOGGER = Logger.getLogger(Range.class.toString());
private static final int PRIME_NUMBER = 37;
public enum DataType {
BYTE(Byte.class, DataBuffer.TYPE_BYTE), USHORT(Short.class, DataBuffer.TYPE_USHORT), SHORT(
Short.class, DataBuffer.TYPE_SHORT), INTEGER(Integer.class, DataBuffer.TYPE_INT), FLOAT(
Float.class, DataBuffer.TYPE_FLOAT), DOUBLE(Double.class, DataBuffer.TYPE_DOUBLE),
// FIXME LONG VALUE CAN BE CHANGED IF LONG DATA TYPE TAKES ANOTHER VALUE
LONG(Long.class, DataBuffer.TYPE_DOUBLE + 1);
private Class<? extends Number> classType;
private int dataType;
private DataType(Class<? extends Number> classType, int dataType) {
this.classType = classType;
this.dataType = dataType;
}
public Class<? extends Number> getClassValue() {
return classType;
}
public int getDataType() {
return dataType;
}
public static int dataTypeFromClass(Class<?> classType) {
if (classType == BYTE.getClassValue()) {
return BYTE.getDataType();
} else if (classType == SHORT.getClassValue()) {
return SHORT.getDataType();
} else if (classType == INTEGER.getClassValue()) {
return INTEGER.getDataType();
} else if (classType == FLOAT.getClassValue()) {
return FLOAT.getDataType();
} else if (classType == DOUBLE.getClassValue()) {
return DOUBLE.getDataType();
} else if (classType == LONG.getClassValue()) {
return LONG.getDataType();
} else {
throw new IllegalArgumentException(
"This class does not belong to the already existing classes");
}
}
public static Class<? extends Number> classFromType(int dataType) {
switch (dataType) {
case DataBuffer.TYPE_BYTE:
return Byte.class;
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
return Short.class;
case DataBuffer.TYPE_INT:
return Integer.class;
case DataBuffer.TYPE_FLOAT:
return Float.class;
case DataBuffer.TYPE_DOUBLE:
return Double.class;
case DataBuffer.TYPE_DOUBLE + 1:
return Long.class;
default:
return null;
}
}
}
protected boolean isMinIncluded;
protected boolean isMaxIncluded;
Range(boolean isMinIncluded, boolean isMaxIncluded) {
this.isMaxIncluded = isMaxIncluded;
this.isMinIncluded = isMinIncluded;
}
/** Method for checking if a byte value is contained inside the Range */
public boolean contains(byte value) {
return containsN(value);
}
/**
* Method for checking if a short/ushort value is contained inside the Range
*/
public boolean contains(short value) {
return containsN(value);
}
/** Method for checking if an integer value is contained inside the Range */
public boolean contains(int value) {
return containsN(value);
}
/** Method for checking if a float value is contained inside the Range */
public boolean contains(float value) {
return containsN(value);
}
/** Method for checking if a double value is contained inside the Range */
public boolean contains(double value) {
return containsN(value);
}
/** Method for checking if a long value is contained inside the Range */
public boolean contains(long value) {
return containsN(value);
}
/** Method for checking if a Generic value is contained inside the Range */
public <T extends Number> boolean containsN(T value) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Wrong data type tested: Input: " + value.getClass()
+ ", output: " + this.getDataType().getClassValue());
}
int dataType = this.getDataType().getDataType();
switch (dataType) {
case DataBuffer.TYPE_BYTE:
return contains(value.byteValue());
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
return contains(value.shortValue());
case DataBuffer.TYPE_INT:
return contains(value.intValue());
case DataBuffer.TYPE_FLOAT:
return contains(value.floatValue());
case DataBuffer.TYPE_DOUBLE:
return contains(value.doubleValue());
case DataBuffer.TYPE_DOUBLE + 1:
return contains(value.longValue());
default:
throw new IllegalArgumentException("Wrong data type");
}
}
public boolean contains(Range other) {
// NaN checks
if (this.isNaN() && !other.isNaN()) {
return false;
}
if (other.isNaN()) {
if (this.isNanIncluded()) {
return true;
} else {
return false;
}
}
double min1 = this.getMin().doubleValue();
double max1 = this.getMax().doubleValue();
double min2 = other.getMin().doubleValue();
double max2 = other.getMax().doubleValue();
// Simple check
boolean minContains = min1 < min2;
boolean maxContains = max1 > max2;
if (minContains && maxContains) {
return true;
}
boolean minEquals = min1 == min2;
boolean maxEquals = max1 == max2;
// Check on equal bounds
if (!minContains && minEquals) {
minContains = this.isMinIncluded() && other.isMinIncluded();
}
if (!maxContains && maxEquals) {
maxContains = this.isMaxIncluded() && other.isMaxIncluded();
}
if (minContains && maxContains) {
return true;
}
return false;
}
public boolean intersects(Range other) {
// Check if one of them is contained into the other
if (this.contains(other) || other.contains(this)) {
return true;
}
double min1 = this.getMin().doubleValue();
double max1 = this.getMax().doubleValue();
double min2 = other.getMin().doubleValue();
double max2 = other.getMax().doubleValue();
// Check the bounds
boolean minCheck = this.isMinIncluded() && other.isMaxIncluded() ? min1 <= max2
: min1 < max2;
boolean maxCheck = this.isMaxIncluded() && other.isMinIncluded() ? max1 >= min2
: max1 > min2;
return minCheck && maxCheck;
}
public abstract Range intersection(Range other);
public abstract Range union(Range other);
/** Returns the Range data Type */
public abstract DataType getDataType();
/** Indicates if the Range is a degenerated single point Range or not */
public abstract boolean isPoint();
/** Returns the maximum bound of the Range */
public abstract Number getMax();
/** Returns the minimum bound of the Range */
public abstract Number getMin();
/**
* Returns the maximum bound of the Range or the nearest one if not included
*/
public abstract Number getMax(boolean isMaxIncluded);
/**
* Returns the minimum bound of the Range or the nearest one if not included
*/
public abstract Number getMin(boolean isMinIncluded);
/** Returns true if the current Range accepts NaN values */
public boolean isNanIncluded() {
return true;
}
/**
* Returns true if and only if the current Range is a point representing NaN
*/
public boolean isNaN() {
return false;
}
/** Returns the a boolean indicating if the Maximum bound is included */
public boolean isMaxIncluded() {
return isMaxIncluded;
}
/** Returns the a boolean indicating if the Minimum bound is included */
public boolean isMinIncluded() {
return isMinIncluded;
}
/** Sets the isMinIncluded parameter */
protected void setMinIncluded(boolean isMinIncluded) {
this.isMinIncluded = isMinIncluded;
}
/** Sets the isMaxIncluded parameter */
protected void setMaxIncluded(boolean isMaxIncluded) {
this.isMaxIncluded = isMaxIncluded;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (super.equals(obj)) {
return true;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Range)) {
return false;
}
Range r1 = (Range) obj;
if (r1.getDataType() != this.getDataType()) {
return false;
}
if (r1.isPoint() != this.isPoint()) {
return false;
}
if (r1.isMaxIncluded() != this.isMaxIncluded()) {
return false;
}
if (r1.isPoint() != this.isMinIncluded()) {
return false;
}
if (r1.getMin().doubleValue() != this.getMin().doubleValue()) {
return false;
}
if (r1.getMax().doubleValue() != this.getMax().doubleValue()) {
return false;
}
if (r1.isNanIncluded() != this.isNanIncluded()) {
return false;
}
return true;
}
public int compare(Range other) {
if (this.equals(other)) {
return 0;
}
double min1 = this.getMin().doubleValue();
double min2 = other.getMin().doubleValue();
double max1 = this.getMax().doubleValue();
double max2 = other.getMax().doubleValue();
// Different minimum
if (!RangeFactory.equals(min1, min2)) {
if (min1 < min2) {
return -1;
} else {
return 1;
}
} else {
// Check if they are included
if (this.isMinIncluded() && other.isMinIncluded()) {
// Equal max
if (RangeFactory.equals(max1, max2)) {
if (this.isMaxIncluded() && other.isMaxIncluded()) {
return 0;
} else if (this.isMaxIncluded()) {
return 1;
} else {
return -1;
}
} else {
if (max1 < max2) {
return -1;
} else {
return 1;
}
}
} else {
if (this.isMinIncluded()) {
return -1;
} else {
return 1;
}
}
}
}
@Override
public int hashCode() {
int result = 37;
result += getDataType().getClass().hashCode();
result = hash(isMaxIncluded, result);
result = hash(isMinIncluded, result);
result = hash(getMax().doubleValue(), result);
result = hash(getMin().doubleValue(), result);
return result;
}
public static int hash(boolean value, int seed) {
// Use the same values than Boolean.hashCode()
return seed * PRIME_NUMBER + (value ? 1231 : 1237);
}
public static int hash(double value, int seed) {
return hash(Double.doubleToLongBits(value), seed);
}
public static int hash(long value, int seed) {
return seed * PRIME_NUMBER + (((int) value) ^ ((int) (value >>> 32)));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getSimpleName());
if (isMinIncluded()) {
sb.append("[");
} else {
sb.append("(");
}
sb.append(getMin()).append(", ").append(getMax());
if (isMaxIncluded()) {
sb.append("]");
} else {
sb.append(")");
}
return sb.toString();
}
}