/* 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.piecewise;
import it.geosolutions.jaiext.range.Range;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Locale;
import java.util.MissingResourceException;
/**
* Convenience implementation of the {@link Domain1D} interface.
*
* @author Simone Giannecchini
*
* @source $URL$
*/
public class DefaultDomain1D<E extends DefaultDomainElement1D> extends AbstractList<E> implements
Domain1D<E> {
/*
* (non-Javadoc)
*
* @see Domain1D#getName()
*/
public synchronized String getName() {
if (name == null) {
final StringBuffer buffer = new StringBuffer(30);
final Locale locale = Locale.getDefault();
if (main != null) {
buffer.append(main.getName());
} else {
buffer.append('(');
buffer.append("Untitled");
buffer.append(')');
}
name = buffer.toString();
}
return name;
}
/*
* (non-Javadoc)
*
* @see Domain1D#getRange()
*/
public Range getApproximateDomainRange() {
synchronized (elements) {
// @todo TODO should I include the NaN value?
if (range == null) {
Range range = null;
for (E element : elements) {
final Range extent = element.getRange();
if (!Double.isNaN(extent.getMin().doubleValue())
&& !Double.isNaN(extent.getMax().doubleValue())) {
if (range != null) {// TODO FIXME ADD RANGE UNION
range = range.union(extent);
} else {
range = extent;
}
}
}
this.range = range;
}
return range;
}
}
/**
* The list of elements. This list most be sorted in increasing order of left range element.
*/
private E[] elements;
/**
* {@code true} if there is gaps between elements, or {@code false} otherwise. A gap is found if for example the range of value is [-9999 ..
* -9999] for the first domain element and [0 .. 1000] for the second one.
*/
private boolean hasGaps;
/**
* The "main" domain element, or {@code null} if there is none. The main domain element is the quantitative domain element with the widest range
* of sample values.
*/
private E main;
/**
* List of {@link #inputMinimum} values for each domain element in {@link #elements} . This array <strong>must</strong> be in increasing order.
* Actually, this is the need to sort this array that determines the element order in {@link #elements} .
*/
private double[] minimums;
/**
* The name for this domain element list. Will be constructed only when first needed.
*
* @see #getName
*/
private String name;
/**
* The range of values in this domain element list. This is the union of the range of values of every elements, excluding {@code NaN} values. This
* field will be computed only when first requested.
*/
private Range range;
/**
* Lazily initialized hashcode for this class
*/
private int hashCode = -1;
/**
* Constructor for {@link DefaultDomain1D}.
*
* @param inDomainElements {@link DomainElement1D} objects that make up this list.
*/
public DefaultDomain1D(E[] inDomainElements) {
init(inDomainElements);
}
/**
* @param inDomainElements
* @throws IllegalArgumentException
* @throws MissingResourceException
*/
@SuppressWarnings("unchecked")
private void init(E[] inDomainElements) throws IllegalArgumentException,
MissingResourceException {
// /////////////////////////////////////////////////////////////////////
//
// input checks
//
// /////////////////////////////////////////////////////////////////////
PiecewiseUtilities.ensureNonNull("DomainElement1D[]", inDomainElements);
// @todo TODOCHECK ME
if (inDomainElements == null)
this.elements = (E[]) new DefaultDomainElement1D[] { new DefaultPassthroughPiecewiseTransform1DElement(
"p0") };
else
this.elements = inDomainElements.clone();
// /////////////////////////////////////////////////////////////////////
//
// Sort the input elements.
//
// /////////////////////////////////////////////////////////////////////
Arrays.sort(this.elements);
// /////////////////////////////////////////////////////////////////////
//
// Construct the array of minimum values. During the loop, we make sure
// there is no overlapping in input and output.
//
// /////////////////////////////////////////////////////////////////////
hasGaps = false;
minimums = new double[elements.length];
for (int i = 0; i < elements.length; i++) {
final DefaultDomainElement1D c = elements[i];
final double inMinimum = minimums[i] = c.getInputMinimum();
if (i != 0) {
assert !(inMinimum < minimums[i - 1]) : inMinimum;
// Use '!' to accept NaN.
final DefaultDomainElement1D previous = elements[i - 1];
if (PiecewiseUtilities.compare(inMinimum, previous.getInputMaximum()) <= 0) {
PiecewiseUtilities.domainElementsOverlap(elements, i);
}
// Check if there is a gap between this domain element and the
// previous one.
if (!Double.isNaN(inMinimum)// TODO FIXME ADD POSSIBILITY TO GET THE MAXIMUM NOT INCLUDED/INCLUDED
&& inMinimum != ((Range) previous.getRange()).getMax(false).doubleValue()) {
hasGaps = true;
}
}
}
/*
* Search for what seems to be the "main" domain element. This loop looks for the quantitative domain element (if there is one) with the
* widest range of sample values.
*/
double range = 0;
E main = null;
for (int i = elements.length; --i >= 0;) {
final E candidate = elements[i];
if (Double.isInfinite(candidate.getInputMinimum())
&& Double.isInfinite(candidate.getInputMaximum())) {
range = Double.POSITIVE_INFINITY;
main = candidate;
continue;
}
final double candidateRange = candidate.getInputMaximum() - candidate.getInputMinimum();
if (candidateRange >= range) {
range = candidateRange;
main = candidate;
}
}
this.main = main;
// postcondition
assert PiecewiseUtilities.isSorted(elements);
}
/**
* Returns the domain element of the specified sample value. If no domain element fits, then this method returns {@code null}.
*
* @param value The value.
* @return The domain element of the supplied value, or {@code null}.
*/
public E findDomainElement(final double value) {
int i = getDomainElementIndex(value);
// //
//
// Checks
//
// //
if (i < 0)
return null;
E domainElement1D;
if (i > elements.length)
return null;
// //
//
// First of all let's check if we spotted a break point in out domains
// element. If so the index we got is not an insertion point but it is
// an actual domain element index. This happens when we catch precisely
// a minimum element for a domain.
//
// //
if (i < elements.length) {
domainElement1D = elements[i];
if (domainElement1D.contains(value))
return domainElement1D;
// if the index was 0, unless we caught the smallest minimum we have
// got something smaller than the leftmost domain
if (i == 0)
return null;
}
// //
//
// Ok, now we know that we did not precisely caught a minimum for a
// domain, we therefore got an insertion point. This means that, unless
// we have fallen into a gap we need to subtract 1 to check for
// inclusion in the right domain.
//
// //
domainElement1D = elements[i - 1];
if (domainElement1D.contains(value))
return domainElement1D;
// //
//
// Well, if we get here, we have definitely fallen into a gap or the
// value is beyond the limits of the last domain, too bad....
//
// //
assert i >= elements.length || hasGaps : value;
return null;
}
/**
* @param sample
* @return
*/
private int getDomainElementIndex(final double sample) {
int i = -1;
// Special 'binarySearch' for NaN
i = PiecewiseUtilities.binarySearch(minimums, sample);
if (i >= 0) {
// The value is exactly equals to one of minimum,
// or is one of NaN values. There is nothing else to do.
assert Double.doubleToRawLongBits(sample) == Double.doubleToRawLongBits(minimums[i]);
return i;
}
assert i == Arrays.binarySearch(minimums, sample) : i;
// 'binarySearch' found the index of "insertion point" (-(insertion
// point) - 1). The
// insertion point is defined as the point at which the key would be
// inserted into the list: the index of the first element greater than
// the key, or list.size(), if all elements in the list are less than
// the specified key. Note that this guarantees that the return value
// will be >= 0 if and only if the key is found.
i = -i - 1;
return i;
}
// ////////////////////////////////////////////////////////////////////////////////////////
// ////// ////////
// ////// I M P L E M E N T A T I O N O F List I N T E R F A C E ////////
// ////// ////////
// ////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the number of elements in this list.
*/
@Override
public int size() {
return elements.length;
}
/**
* Returns the element at the specified position in this list.
*/
@Override
public E get(final int i) {
return elements[i];
}
/**
* Returns all elements in this {@code }.
*/
@Override
public Object[] toArray() {
return (DomainElement1D[]) elements.clone();
}
/**
* Compares the specified object with this domain element list for equality. If the two objects are instances of the {@link DefaultDomain1D}
* class, then the test check for the equality of the single elements.
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(final Object object) {
if (this == object)
return true;
if (!(object instanceof DefaultDomain1D))
return false;
final DefaultDomain1D that = (DefaultDomain1D) object;
if (getEquivalenceClass() != that.getEquivalenceClass())
return false;
if (!this.getName().equals(that.getName()))
return false;
if (!this.getApproximateDomainRange().equals(that.getApproximateDomainRange()))
return false;
if (Arrays.equals(this.elements, that.elements)) {
assert Arrays.equals(this.minimums, that.minimums);
return true;
}
return false;
}
protected Class<?> getEquivalenceClass() {
return DefaultDomain1D.class;
}
/*
* (non-Javadoc)
*
* @see Domain1D#hasGaps()
*/
public boolean hasGaps() {
return hasGaps;
}
/**
* Return what seems to be the main {@link DomainElement1D} for this list.
*
* @return what seems to be the main {@link DomainElement1D} for this list.
*/
public E getMain() {
return main;
}
/**
* @return
*/
public double[] getMinimums() {
return (double[]) minimums.clone();
}
@Override
public int hashCode() {
if (hashCode < 0) {
int result = PiecewiseUtilities.deepHashCode(elements);
result = PiecewiseUtilities.deepHashCode(minimums);
result = PiecewiseUtilities.hash(getName(), result);
hashCode = PiecewiseUtilities.hash(getApproximateDomainRange(), hashCode);
}
return hashCode;
}
}