/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.referencing.operation.transform;
import java.util.Arrays;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.geotools.factory.Hints;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.operation.LinearTransform;
import org.geotools.referencing.operation.matrix.XMatrix;
import org.geotools.referencing.operation.matrix.MatrixFactory;
import org.geotools.referencing.operation.matrix.GeneralMatrix;
import org.geotools.resources.XArray;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* An utility class for the separation of {@linkplain ConcatenatedTransform concatenation} of
* {@linkplain PassThroughTransform pass through transforms}. Given an arbitrary
* {@linkplain MathTransform math transform}, this utility class will returns a new math transform
* that operates only of a given set of source dimensions. For example if the supplied
* {@code transform} has (<var>x</var>, <var>y</var>, <var>z</var>) inputs and
* (<var>longitude</var>, <var>latitude</var>, <var>height</var>) outputs, then
* the following code:
*
* <blockquote><pre>
* {@linkplain #addSourceDimensionRange addSourceDimensionRange}(0, 2);
* MathTransform mt = {@linkplain #separate separate}(transform);
* </pre></blockquote>
*
* <P>will returns a transform with (<var>x</var>, <var>y</var>) inputs and (probably)
* (<var>longitude</var>, <var>latitude</var>) outputs. The later can be verified with
* a call to {@link #getTargetDimensions}.</P>
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Simone Giannecchini
*
* @todo This class is specific to Geotools implementation; it is better to avoid it if
* you can. It could be generalized a bit if we perform the same operations on
* {@link org.opengis.referencing.operation.CoordinateOperation} interfaces instead
* of math transforms. We should revisit this issue after grid coverage API has been
* revisited (since grid coverage is a user of this class).
*
* @todo This class contains a set of static methods that could be factored out in
* some kind of {@code org.geotools.util.SortedIntegerSet} implementation.
*/
public class DimensionFilter {
/**
* Hint key for specifying a particular instance of {@code DimensionFilter} to use.
*
* @see #getInstance
*
* @since 2.5
*/
public static final Hints.Key INSTANCE = new Hints.Key(DimensionFilter.class);
/**
* The input dimensions to keep, in strictly increasing order.
* This sequence can contains any integers in the range 0 inclusive to
* <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>
* exclusive.
*/
private int[] sourceDimensions;
/**
* The output dimensions to keep, in strictly increasing order.
* This sequence can contains any integers in the range 0 inclusive to
* <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>
* exclusive.
*/
private int[] targetDimensions;
/**
* The factory for the creation of new math transforms.
*/
private final MathTransformFactory factory;
/**
* Constructs a dimension filter with the
* {@linkplain ReferencingFactoryFinder#getMathTransformFactory default math transform factory}.
*/
public DimensionFilter() {
this(ReferencingFactoryFinder.getMathTransformFactory(null));
}
/**
* Constructs a dimension filter with a
* {@linkplain ReferencingFactoryFinder#getMathTransformFactory math transform factory
* built using the provided hints}.
*
* @param hints Hints to control the creation of the {@link MathTransformFactory}.
*/
public DimensionFilter(final Hints hints) {
this(ReferencingFactoryFinder.getMathTransformFactory(hints));
}
/**
* Constructs a dimension filter with the specified factory.
*
* @param factory The factory for the creation of new math transforms.
*/
public DimensionFilter(final MathTransformFactory factory) {
this.factory = factory;
}
/**
* Creates or returns an existing instance from the given set of hints. If the hints contain
* a value for the {@link #INSTANCE} key, this value is returned. Otherwise a new instance is
* {@linkplain #DimensionFilter(Hints) created} with the given hints.
*
* @param hints The hints, or {@code null} if none.
* @return An existing or a new {@code DimensionFilter} instance (never {@code null}).
*
* @see #INSTANCE
*
* @since 2.5
*/
public static DimensionFilter getInstance(final Hints hints) {
if (hints != null) {
final DimensionFilter candidate = (DimensionFilter) hints.get(INSTANCE);
if (candidate != null) {
candidate.clear();
return candidate;
}
}
return new DimensionFilter(hints);
}
/**
* Clears any {@linkplain #getSourceDimensions source} and
* {@linkplain #getTargetDimensions target dimension} setting.
*/
public void clear() {
sourceDimensions = null;
targetDimensions = null;
}
/**
* Add an input dimension to keep. The {@code dimension} applies to the
* source dimensions of the transform to be given to
* <code>{@linkplain #separate separate}(transform)</code>.
* The number must be in the range 0 inclusive to
* <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>
* exclusive.
*
* @param dimension The dimension to add.
* @throws IllegalArgumentException if {@code dimension} is negative.
*/
public void addSourceDimension(final int dimension) throws IllegalArgumentException {
sourceDimensions = add(sourceDimensions, dimension);
}
/**
* Add input dimensions to keep. The {@code dimensions} apply to the
* source dimensions of the transform to be given to
* <code>{@linkplain #separate separate}(transform)</code>.
* All numbers must be in the range 0 inclusive to
* <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>
* exclusive. The {@code dimensions} values must be in strictly increasing order.
*
* @param dimensions The new sequence of dimensions.
* @throws IllegalArgumentException if {@code dimensions} contains negative values or
* is not a strictly increasing sequence.
*/
public void addSourceDimensions(final int[] dimensions) throws IllegalArgumentException {
sourceDimensions = add(sourceDimensions, dimensions);
}
/**
* Add a range of input dimensions to keep. The {@code lower} and {@code upper} values
* apply to the source dimensions of the transform to be given to
* <code>{@linkplain #separate separate}(transform)</code>.
*
* @param lower The lower dimension, inclusive. Must not be smaller than 0.
* @param upper The upper dimension, exclusive. Must not be greater than
* <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>.
*/
public void addSourceDimensionRange(final int lower, final int upper)
throws IllegalArgumentException
{
sourceDimensions = add(sourceDimensions, lower, upper);
}
/**
* Returns the input dimensions. This information is available only if at least one
* setter method has been explicitly invoked for source dimensions.
*
* @return The input dimension as a sequence of strictly increasing values.
* @throws IllegalStateException if input dimensions have not been set.
*/
public int[] getSourceDimensions() throws IllegalStateException {
if (sourceDimensions != null) {
return sourceDimensions.clone();
}
throw new IllegalStateException();
}
/**
* Add an output dimension to keep. The {@code dimension} applies to the
* target dimensions of the transform to be given to
* <code>{@linkplain #separate separate}(transform)</code>.
* The number must be in the range 0 inclusive to
* <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>
* exclusive.
*
* @param dimension The dimension to add.
* @throws IllegalArgumentException if {@code dimension} is negative.
*/
public void addTargetDimension(final int dimension) throws IllegalArgumentException {
targetDimensions = add(targetDimensions, dimension);
}
/**
* Add output dimensions to keep. The {@code dimensions} apply to the
* target dimensions of the transform to be given to
* <code>{@linkplain #separate separate}(transform)</code>.
* All numbers must be in the range 0 inclusive to
* <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>
* exclusive. The {@code dimensions} values must be in strictly increasing order.
*
* @param dimensions The new sequence of dimensions.
* @throws IllegalArgumentException if {@code dimensions} contains negative values or
* is not a strictly increasing sequence.
*/
public void addTargetDimensions(int[] dimensions) throws IllegalArgumentException {
targetDimensions = add(targetDimensions, dimensions);
}
/**
* Add a range of output dimensions to keep. The {@code lower} and {@code upper} values
* apply to the target dimensions of the transform to be given to
* <code>{@linkplain #separate separate}(transform)</code>.
*
* @param lower The lower dimension, inclusive. Must not be smaller than 0.
* @param upper The upper dimension, exclusive. Must not be greater than
* <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>.
*/
public void addTargetDimensionRange(final int lower, final int upper)
throws IllegalArgumentException
{
targetDimensions = add(targetDimensions, lower, upper);
}
/**
* Returns the output dimensions. This information is available only if one of the following
* conditions is meet:
* <p>
* <ul>
* <li>Target dimensions has been explicitly set using setter methods.</li>
* <li>No target dimensions were set but <code>{@linkplain #separate separate}(transform)</code>
* has been invoked at least once, in which case the target dimensions are inferred
* automatically from the {@linkplain #getSourceDimensions source dimensions} and the
* {@code transform}.</li>
* </ul>
*
* @return The output dimension as a sequence of strictly increasing values.
* @throws IllegalStateException if this information is not available.
*/
public int[] getTargetDimensions() throws IllegalStateException {
if (targetDimensions != null) {
return targetDimensions.clone();
}
throw new IllegalStateException();
}
/**
* Separates the specified math transform. This method returns a math transform that uses
* only the specified {@linkplain #getSourceDimensions source dimensions} and returns only
* the specified {@linkplain #getTargetDimensions target dimensions}. Special case:
* <p>
* <ul>
* <li><p>If {@linkplain #getSourceDimensions source dimensions} are unspecified, then the
* returned transform will expects all source dimensions as input but will produces only
* the specified {@linkplain #getTargetDimensions target dimensions} as output.</p></li>
*
* <li><p>If {@linkplain #getTargetDimensions target dimensions} are unspecified, then the
* returned transform will expects only the specified {@linkplain #getSourceDimensions
* source dimensions} as input, and the target dimensions will be inferred
* automatically.</p></li>
* </ul>
*
* @param transform The transform to separate.
* @return The separated math transform.
* @throws FactoryException if the transform can't be separated.
*/
public MathTransform separate(MathTransform transform) throws FactoryException {
if (sourceDimensions == null) {
sourceDimensions = series(0, transform.getSourceDimensions());
if (targetDimensions == null) {
targetDimensions = series(0, transform.getTargetDimensions());
return transform;
}
return separateOutput(transform);
}
final int[] target = targetDimensions;
transform = separateInput(transform);
assert XArray.isStrictlySorted(targetDimensions);
if (target != null) {
final int[] step = targetDimensions;
targetDimensions = new int[target.length];
for (int i=0; i<target.length; i++) {
final int j = Arrays.binarySearch(step, target[i]);
if (j < 0) {
/*
* The user is asking for some target dimensions that we can't keep, probably
* because at least one of the requested target dimension as a dependency to
* an source dimension that do not appears in the list of source dimensions to
* kept.
*
* TODO: provide a more accurate error message.
*/
throw new FactoryException(Errors.format(ErrorKeys.INSEPARABLE_TRANSFORM));
}
targetDimensions[i] = j;
}
transform = separateOutput(transform);
targetDimensions = target;
}
assert sourceDimensions.length == transform.getSourceDimensions() : transform;
assert targetDimensions.length == transform.getTargetDimensions() : transform;
return transform;
}
/**
* Separates the math transform on the basis of {@linkplain #sourceDimensions input dimensions}.
* The remaining {@linkplain #targetDimensions output dimensions} will be selected automatically
* according the specified input dimensions.
*
* @param transform The transform to reduces.
* @return A transform expecting only the specified input dimensions.
* @throws FactoryException if the transform is not separable.
*/
private MathTransform separateInput(final MathTransform transform) throws FactoryException {
final int dimSource = transform.getSourceDimensions();
final int dimTarget = transform.getTargetDimensions();
final int dimInput = sourceDimensions.length;
final int lower = sourceDimensions[0];
final int upper = sourceDimensions[dimInput-1] + 1;
assert XArray.isStrictlySorted(sourceDimensions);
if (upper > dimSource) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "sourceDimensions", upper-1));
}
/*
* Check for easiest cases: same transform, identity transform or concatenated transforms.
*/
if (dimInput == dimSource) {
assert lower==0 && upper==dimSource;
targetDimensions = series(0, dimTarget);
return transform;
}
if (transform.isIdentity()) {
targetDimensions = sourceDimensions;
return factory.createAffineTransform(MatrixFactory.create(dimInput+1));
}
if (transform instanceof ConcatenatedTransform) {
final ConcatenatedTransform ctr = (ConcatenatedTransform) transform;
final int[] original = sourceDimensions;
final MathTransform step1, step2;
step1 = separateInput(ctr.transform1); sourceDimensions = targetDimensions;
step2 = separateInput(ctr.transform2); sourceDimensions = original;
return factory.createConcatenatedTransform(step1, step2);
}
/*
* Special case for the pass through transform: if at least one input dimension
* belong to the passthrough's sub-transform, then delegates part of the work to
* {@code subTransform(passThrough.transform, ...)}
*/
if (transform instanceof PassThroughTransform) {
final PassThroughTransform passThrough = (PassThroughTransform) transform;
final int dimPass = passThrough.subTransform.getSourceDimensions();
final int dimDiff = passThrough.subTransform.getTargetDimensions() - dimPass;
final int subLower = passThrough.firstAffectedOrdinate;
final int subUpper = subLower + dimPass;
final DimensionFilter subFilter = new DimensionFilter(factory);
for (int i=0; i<sourceDimensions.length; i++) {
int n = sourceDimensions[i];
if (n>=subLower && n<subUpper) {
// Dimension n belong to the subtransform.
subFilter.addSourceDimension(n - subLower);
} else {
// Dimension n belong to heading or trailing dimensions.
// Passthrough, after adjustement for trailing dimensions.
if (n >= subUpper) {
n += dimDiff;
}
targetDimensions = add(targetDimensions, n);
}
}
if (subFilter.sourceDimensions == null) {
/*
* No source dimensions belong to the sub-transform. The only remaining
* sources are heading and trailing dimensions. A passthrough transform
* without its sub-transform is an identity transform...
*/
return factory.createAffineTransform(MatrixFactory.create(dimInput+1));
}
/*
* There is at least one dimension to separate in the sub-transform. Performs this
* separation and gets the list of output dimensions. We need to offset the output
* dimensions by the amount of leading dimensions once the separation is done, in
* order to translate from the sub-transform's dimension numbering to the transform's
* numbering.
*/
final MathTransform subTransform = subFilter.separateInput(passThrough.subTransform);
for (int i=0; i<subFilter.targetDimensions.length; i++) {
subFilter.targetDimensions[i] += subLower;
}
targetDimensions = add(targetDimensions, subFilter.targetDimensions);
/*
* If all source dimensions not in the sub-transform are consecutive numbers, we can
* use our pass though transform implementation. The "consecutive numbers" requirement
* (expressed in the 'if' statement below) is a consequence of a limitation in our
* current implementation: our pass through transform doesn't accept arbitrary index
* for modified ordinates.
*/
if (containsAll(sourceDimensions, lower, subLower) &&
containsAll(sourceDimensions, subUpper, upper))
{
final int firstAffectedOrdinate = Math.max(0, subLower-lower);
final int numTrailingOrdinates = Math.max(0, upper-subUpper);
return factory.createPassThroughTransform(
firstAffectedOrdinate, subTransform, numTrailingOrdinates);
}
// TODO: handle more general case here...
targetDimensions = null; // Clear before to fallback on the LinearTransform case.
}
/*
* If the transform is affine (or at least projective), express the transform as a matrix.
* Then, select output dimensions that depends only on selected input dimensions. If an
* output dimension depends on at least one discarted input dimension, then this output
* dimension will be discarted as well.
*/
if (transform instanceof LinearTransform) {
int nRows = 0;
boolean hasLastRow = false;
final Matrix matrix = ((LinearTransform) transform).getMatrix();
assert dimSource+1 == matrix.getNumCol() &&
dimTarget+1 == matrix.getNumRow() : matrix;
double[][] rows = new double[dimTarget+1][];
reduce: for (int j=0; j<rows.length; j++) {
final double[] row = new double[dimInput+1];
/*
* For each output dimension (i.e. a matrix row), find the matrix elements for
* each input dimension to be kept. If a dependance to at least one discarted
* input dimension is found, then the whole output dimension is discarted.
*
* NOTE: The following loop stops at matrix.getNumCol()-1 because we don't
* want to check the translation term.
*/
int nCols=0, scan=0;
for (int i=0; i<dimSource; i++) {
final double element = matrix.getElement(j,i);
if (scan<sourceDimensions.length && sourceDimensions[scan]==i) {
row[nCols++] = element;
scan++;
} else if (element != 0) {
// Output dimension 'j' depends on one of discarted input dimension 'i'.
// The whole row will be discarted.
continue reduce;
}
}
row[nCols++] = matrix.getElement(j, dimSource); // Copy the translation term.
assert nCols == row.length : nCols;
if (j == dimTarget) {
hasLastRow = true;
} else {
targetDimensions = add(targetDimensions, j);
}
rows[nRows++] = row;
}
rows = XArray.resize(rows, nRows);
if (hasLastRow) {
return factory.createAffineTransform(new GeneralMatrix(rows));
}
// In an affine transform, the last row is not supposed to have dependency
// to any input dimension. But in this particuler case, our matrix has such
// dependencies. TODO: is there anything we could do about that?
}
throw new FactoryException(Errors.format(ErrorKeys.INSEPARABLE_TRANSFORM));
}
/**
* Creates a transform which retains only a subset of an other transform's outputs. The number
* and nature of inputs stay unchanged. For example if the supplied {@code transform} has
* (<var>longitude</var>, <var>latitude</var>, <var>height</var>) outputs, then a sub-transform
* may be used to keep only the (<var>longitude</var>, <var>latitude</var>) part. In most cases,
* the created sub-transform is non-invertible since it loose informations.
* <p>
* This transform may be see as a non-square matrix transform with less rows
* than columns, concatenated with {@code transform}. However, invoking
* {@code createFilterTransfom(...)} allows the optimization of some common cases.
*
* @param transform The transform to reduces.
* @return The {@code transform} keeping only the output dimensions.
* @throws FactoryException if the transform can't be created.
*/
private MathTransform separateOutput(MathTransform transform) throws FactoryException {
final int dimSource = transform.getSourceDimensions();
final int dimTarget = transform.getTargetDimensions();
final int dimOutput = targetDimensions.length;
final int lower = targetDimensions[0];
final int upper = targetDimensions[dimOutput-1];
assert XArray.isStrictlySorted(targetDimensions);
if (upper > dimTarget) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "targetDimensions", upper));
}
if (dimOutput == dimTarget) {
assert lower==0 && upper==dimTarget;
return transform;
}
/*
* If the transform is an instance of "pass through" transform but no dimension from its
* subtransform is requested, then ignore the subtransform (i.e. treat the whole transform
* as identity, except for the number of output dimension which may be different from the
* number of input dimension).
*/
int dimPass = 0;
int dimDiff = 0;
int dimStep = dimTarget;
if (transform instanceof PassThroughTransform) {
final PassThroughTransform passThrough = (PassThroughTransform) transform;
final int subLower = passThrough.firstAffectedOrdinate;
final int subUpper = subLower + passThrough.subTransform.getTargetDimensions();
if (!containsAny(targetDimensions, subLower, subUpper)) {
transform = null;
dimStep = dimSource;
dimPass = subLower;
dimDiff = (subLower + passThrough.subTransform.getSourceDimensions()) - subUpper;
}
}
/*
* Creates the matrix to be used as a filter, [x'] [1 0 0 0] [x]
* and concatenates it to the transform. The [z'] = [0 0 1 0] [y]
* matrix will contains only a 1 for the output [1 ] [0 0 0 1] [z]
* dimension to keep, as in the following example: [1]
*/
final XMatrix matrix = MatrixFactory.create(dimOutput+1, dimStep+1);
matrix.setZero();
for (int j=0; j<dimOutput; j++) {
int i = targetDimensions[j];
if (i >= dimPass) {
i += dimDiff;
}
matrix.setElement(j, i, 1);
}
// Affine transform has one more row/column than dimension.
matrix.setElement(dimOutput, dimStep, 1);
MathTransform filtered = factory.createAffineTransform(matrix);
if (transform != null) {
filtered = factory.createConcatenatedTransform(transform, filtered);
}
return filtered;
}
/**
* Returns {@code true} if the given sequence contains all index in the range {@code lower}
* inclusive to {@code upper} exclusive.
*
* @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to test.
* @param lower The lower value, inclusive.
* @param upper The upper value, exclusive.
* @return {@code true} if the full range was found in the sequence.
*/
private static boolean containsAll(final int[] sequence, final int lower, int upper) {
if (lower == upper) {
return true;
}
if (sequence != null) {
assert XArray.isStrictlySorted(sequence);
int index = Arrays.binarySearch(sequence, lower);
if (index >= 0) {
index += --upper - lower;
if (index>=0 && index<sequence.length) {
return sequence[index] == upper;
}
}
}
return false;
}
/**
* Returns {@code true} if the given sequence contains any value in the given range.
*
* @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to test.
* @param lower The lower value, inclusive.
* @param upper The upper value, exclusive.
* @return {@code true} if the sequence contains at least one value in the given range.
*/
private static boolean containsAny(final int[] sequence, final int lower, final int upper) {
if (upper == lower) {
return true;
}
if (sequence != null) {
assert XArray.isStrictlySorted(sequence);
int index = Arrays.binarySearch(sequence, lower);
if (index >= 0) {
return true;
}
index = ~index; // Tild, not minus sign.
return index < sequence.length && sequence[index] < upper;
}
return false;
}
/**
* Adds the specified {@code dimension} to the specified sequence. Values are added
* in increasing order. Duplicated values are not added.
*
* @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
*/
private static int[] add(int[] sequence, int dimension) throws IllegalArgumentException {
if (dimension < 0) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "dimension", dimension));
}
if (sequence == null) {
return new int[] {dimension};
}
assert XArray.isStrictlySorted(sequence);
int i = Arrays.binarySearch(sequence, dimension);
if (i < 0) {
i = ~i; // Tild, not the minus sign.
sequence = XArray.insert(sequence, i, 1);
sequence[i] = dimension;
}
assert Arrays.binarySearch(sequence, dimension) == i;
return sequence;
}
/**
* Adds the specified {@code dimensions} to the specified sequence. Values are added
* in increasing order. Duplicated values are not added.
*
* @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
*/
private static int[] add(int[] sequence, final int[] dimensions)
throws IllegalArgumentException
{
if (dimensions.length != 0) {
ensureValidSeries(dimensions);
if (sequence == null) {
sequence = dimensions.clone();
} else {
// Note: the following loop is unefficient, but should suffise since this
// case should not occurs often and arrays should be small anyway.
for (int i=0; i<dimensions.length; i++) {
sequence = add(sequence, dimensions[i]);
}
}
}
return sequence;
}
/**
* Adds the specified range to the specified sequence. Values are added
* in increasing order. Duplicated values are not added.
*
* @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
* @throws IllegalArgumentException if {@code lower} is not smaller than {@code upper}.
*/
private static int[] add(int[] sequence, int lower, final int upper)
throws IllegalArgumentException
{
if (lower<0 || lower>=upper) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "lower", lower));
}
if (sequence == null) {
sequence = series(lower, upper);
} else {
// Note: the following loop is unefficient, but should suffise since this
// case should not occurs often and arrays should be small anyway.
while (lower < upper) {
sequence = add(sequence, lower++);
}
}
assert containsAll(sequence, lower, upper);
return sequence;
}
/**
* Returns a series of increasing values starting at {@code lower}.
*/
private static int[] series(final int lower, final int upper) throws IllegalArgumentException {
final int[] sequence = new int[upper-lower];
for (int i=0; i<sequence.length; i++) {
sequence[i] = i+lower;
}
return sequence;
}
/**
* Ensures that the specified array contains strictly increasing non-negative values.
*
* @param dimensions The sequence to check.
* @throws IllegalArgumentException if the specified sequence is not a valid series.
*/
private static void ensureValidSeries(final int[] dimensions) throws IllegalArgumentException {
int last = -1;
for (int i=0; i<dimensions.length; i++) {
final int value = dimensions[i];
if (value <= last) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "dimensions[" + i + ']', value));
}
last = value;
}
}
}