001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2001, Institut de Recherche pour le D�veloppement
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.referencing.operation.transform;
019:
020: // J2SE dependencies
021: import java.util.Arrays;
022:
023: // OpenGIS dependencies
024: import org.opengis.referencing.FactoryException;
025: import org.opengis.referencing.operation.Matrix;
026: import org.opengis.referencing.operation.MathTransform;
027: import org.opengis.referencing.operation.MathTransformFactory;
028:
029: // Geotools dependencies
030: import org.geotools.factory.Hints;
031: import org.geotools.referencing.ReferencingFactoryFinder;
032: import org.geotools.referencing.operation.LinearTransform;
033: import org.geotools.referencing.operation.matrix.XMatrix;
034: import org.geotools.referencing.operation.matrix.MatrixFactory;
035: import org.geotools.referencing.operation.matrix.GeneralMatrix;
036: import org.geotools.resources.XArray;
037: import org.geotools.resources.i18n.Errors;
038: import org.geotools.resources.i18n.ErrorKeys;
039:
040: /**
041: * An utility class for the separation of {@linkplain ConcatenatedTransform concatenation} of
042: * {@linkplain PassThroughTransform pass through transforms}. Given an arbitrary
043: * {@linkplain MathTransform math transform}, this utility class will returns a new math transform
044: * that operates only of a given set of source dimensions. For example if the supplied
045: * {@code transform} has (<var>x</var>, <var>y</var>, <var>z</var>) inputs and
046: * (<var>longitude</var>, <var>latitude</var>, <var>height</var>) outputs, then
047: * the following code:
048: *
049: * <blockquote><pre>
050: * {@linkplain #addSourceDimensionRange addSourceDimensionRange}(0, 2);
051: * MathTransform mt = {@linkplain #separate separate}(transform);
052: * </pre></blockquote>
053: *
054: * <P>will returns a transform with (<var>x</var>, <var>y</var>) inputs and (probably)
055: * (<var>longitude</var>, <var>latitude</var>) outputs. The later can be verified with
056: * a call to {@link #getTargetDimensions}.</P>
057: *
058: * @since 2.1
059: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/DimensionFilter.java $
060: * @version $Id: DimensionFilter.java 29058 2008-02-03 17:47:07Z desruisseaux $
061: * @author Martin Desruisseaux
062: *
063: * @todo This class is specific to Geotools implementation; it is better to avoid it if
064: * you can. It could be generalized a bit if we perform the same operations on
065: * {@link org.opengis.referencing.operation.CoordinateOperation} interfaces instead
066: * of math transforms. We should revisit this issue after grid coverage API has been
067: * revisited (since grid coverage is a user of this class).
068: *
069: * @todo This class contains a set of static methods that could be factored out in
070: * some kind of {@code org.geotools.util.SortedIntegerSet} implementation.
071: */
072: public class DimensionFilter {
073: /**
074: * The input dimensions to keep.
075: * This sequence can contains any integers in the range 0 inclusive to
076: * <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>
077: * exclusive.
078: */
079: private int[] sourceDimensions;
080:
081: /**
082: * The output dimensions to keep.
083: * This sequence can contains any integers in the range 0 inclusive to
084: * <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>
085: * exclusive.
086: */
087: private int[] targetDimensions;
088:
089: /**
090: * The factory for the creation of new math transforms.
091: */
092: private final MathTransformFactory factory;
093:
094: /**
095: * Constructs a dimension filter with the
096: * {@linkplain ReferencingFactoryFinder#getMathTransformFactory default math transform factory}.
097: */
098: public DimensionFilter() {
099: this (ReferencingFactoryFinder.getMathTransformFactory(null));
100: }
101:
102: /**
103: * Constructs a dimension filter with a
104: * {@linkplain ReferencingFactoryFinder#getMathTransformFactory math transform factory
105: * built using the provided hints}.
106: *
107: * @param hints Hints to control the creation of the {@link MathTransformFactory}.
108: */
109: public DimensionFilter(Hints hints) {
110: this (ReferencingFactoryFinder.getMathTransformFactory(hints));
111: }
112:
113: /**
114: * Constructs a dimension filter with the specified factory.
115: *
116: * @param factory The factory for the creation of new math transforms.
117: */
118: public DimensionFilter(final MathTransformFactory factory) {
119: this .factory = factory;
120: }
121:
122: /**
123: * Clear any {@linkplain #getSourceDimensions source} and
124: * {@linkplain #getTargetDimensions target dimension} setting.
125: */
126: public void clear() {
127: sourceDimensions = null;
128: targetDimensions = null;
129: }
130:
131: /**
132: * Add an input dimension to keep. The {@code dimension} applies to the
133: * source dimensions of the transform to be given to
134: * <code>{@linkplain #separate separate}(transform)</code>.
135: * The number must be in the range 0 inclusive to
136: * <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>
137: * exclusive.
138: *
139: * @param dimension The dimension to add.
140: * @throws IllegalArgumentException if {@code dimension} is negative.
141: */
142: public void addSourceDimension(final int dimension)
143: throws IllegalArgumentException {
144: sourceDimensions = add(sourceDimensions, dimension);
145: }
146:
147: /**
148: * Add input dimensions to keep. The {@code dimensions} apply to the
149: * source dimensions of the transform to be given to
150: * <code>{@linkplain #separate separate}(transform)</code>.
151: * All numbers must be in the range 0 inclusive to
152: * <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>
153: * exclusive. The {@code dimensions} values must be in strictly increasing order.
154: *
155: * @param dimensions The new sequence of dimensions.
156: * @throws IllegalArgumentException if {@code dimensions} contains negative values or
157: * is not a strictly increasing sequence.
158: */
159: public void addSourceDimensions(final int[] dimensions)
160: throws IllegalArgumentException {
161: sourceDimensions = add(sourceDimensions, dimensions);
162: }
163:
164: /**
165: * Add a range of input dimensions to keep. The {@code lower} and {@code upper} values
166: * apply to the source dimensions of the transform to be given to
167: * <code>{@linkplain #separate separate}(transform)</code>.
168: *
169: * @param lower The lower dimension, inclusive. Must not be smaller than 0.
170: * @param upper The upper dimension, exclusive. Must not be greater than
171: * <code>transform.{@linkplain MathTransform#getSourceDimensions getSourceDimensions()}</code>.
172: */
173: public void addSourceDimensionRange(final int lower, final int upper)
174: throws IllegalArgumentException {
175: sourceDimensions = add(sourceDimensions, lower, upper);
176: }
177:
178: /**
179: * Returns the input dimensions. This information is available only if at least one
180: * setter method has been explicitly invoked for source dimensions.
181: *
182: * @return The input dimension as a sequence of strictly increasing values.
183: * @throws IllegalStateException if input dimensions have not been set.
184: */
185: public int[] getSourceDimensions() throws IllegalStateException {
186: if (sourceDimensions != null) {
187: return (int[]) sourceDimensions.clone();
188: }
189: throw new IllegalStateException();
190: }
191:
192: /**
193: * Add an output dimension to keep. The {@code dimension} applies to the
194: * target dimensions of the transform to be given to
195: * <code>{@linkplain #separate separate}(transform)</code>.
196: * The number must be in the range 0 inclusive to
197: * <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>
198: * exclusive.
199: *
200: * @param dimension The dimension to add.
201: * @throws IllegalArgumentException if {@code dimension} is negative.
202: */
203: public void addTargetDimension(final int dimension)
204: throws IllegalArgumentException {
205: targetDimensions = add(targetDimensions, dimension);
206: }
207:
208: /**
209: * Add output dimensions to keep. The {@code dimensions} apply to the
210: * target dimensions of the transform to be given to
211: * <code>{@linkplain #separate separate}(transform)</code>.
212: * All numbers must be in the range 0 inclusive to
213: * <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>
214: * exclusive. The {@code dimensions} values must be in strictly increasing order.
215: *
216: * @param dimensions The new sequence of dimensions.
217: * @throws IllegalArgumentException if {@code dimensions} contains negative values or
218: * is not a strictly increasing sequence.
219: */
220: public void addTargetDimensions(int[] dimensions)
221: throws IllegalArgumentException {
222: targetDimensions = add(targetDimensions, dimensions);
223: }
224:
225: /**
226: * Add a range of output dimensions to keep. The {@code lower} and {@code upper} values
227: * apply to the target dimensions of the transform to be given to
228: * <code>{@linkplain #separate separate}(transform)</code>.
229: *
230: * @param lower The lower dimension, inclusive. Must not be smaller than 0.
231: * @param upper The upper dimension, exclusive. Must not be greater than
232: * <code>transform.{@linkplain MathTransform#getTargetDimensions getTargetDimensions()}</code>.
233: */
234: public void addTargetDimensionRange(final int lower, final int upper)
235: throws IllegalArgumentException {
236: targetDimensions = add(targetDimensions, lower, upper);
237: }
238:
239: /**
240: * Returns the output dimensions. This information is available only if one of the following
241: * conditions is meet:
242: *
243: * <ul>
244: * <li>Target dimensions has been explicitly set using setter methods.</li>
245: * <li>No target dimensions were set but <code>{@linkplain #separate separate}(transform)</code>
246: * has been invoked at least once, in which case the target dimensions are inferred
247: * automatically from the {@linkplain #getSourceDimensions source dimensions} and the
248: * {@code transform}.</li>
249: * </ul>
250: *
251: * @return The output dimension as a sequence of strictly increasing values.
252: * @throws IllegalStateException if this information is not available.
253: */
254: public int[] getTargetDimensions() throws IllegalStateException {
255: if (targetDimensions != null) {
256: return (int[]) targetDimensions.clone();
257: }
258: throw new IllegalStateException();
259: }
260:
261: /**
262: * Separates the specified math transform. This method returns a math transform that uses
263: * only the specified {@linkplain #getSourceDimensions source dimensions} and returns only
264: * the specified {@linkplain #getTargetDimensions target dimensions}. Special case:
265: *
266: * <ul>
267: * <li><p>If {@linkplain #getSourceDimensions source dimensions} are unspecified, then the
268: * returned transform will expects all source dimensions as input but will produces only
269: * the specified {@linkplain #getTargetDimensions target dimensions} as output.</p></li>
270: *
271: * <li><p>If {@linkplain #getTargetDimensions target dimensions} are unspecified, then the
272: * returned transform will expects only the specified {@linkplain #getSourceDimensions
273: * source dimensions} as input, and the target dimensions will be inferred
274: * automatically.</p></li>
275: * </ul>
276: *
277: * @param transform The transform to separate.
278: * @return The separated math transform.
279: * @throws FactoryException if the transform can't be separated.
280: */
281: public MathTransform separate(MathTransform transform)
282: throws FactoryException {
283: if (sourceDimensions == null) {
284: sourceDimensions = series(0, transform
285: .getSourceDimensions());
286: if (targetDimensions == null) {
287: targetDimensions = series(0, transform
288: .getTargetDimensions());
289: return transform;
290: }
291: return separateOutput(transform);
292: }
293: final int[] target = targetDimensions;
294: transform = separateInput(transform);
295: assert XArray.isStrictlySorted(targetDimensions);
296: if (target != null) {
297: final int[] step = targetDimensions;
298: targetDimensions = new int[target.length];
299: for (int i = 0; i < target.length; i++) {
300: final int j = Arrays.binarySearch(step, target[i]);
301: if (j < 0) {
302: /*
303: * The user is asking for some target dimensions that we can't keep, probably
304: * because at least one of the requested target dimension as a dependency to
305: * an source dimension that do not appears in the list of source dimensions to
306: * kept.
307: *
308: * TODO: provide a more accurate error message.
309: */
310: throw new FactoryException(Errors
311: .format(ErrorKeys.INSEPARABLE_TRANSFORM));
312: }
313: targetDimensions[i] = j;
314: }
315: transform = separateOutput(transform);
316: targetDimensions = target;
317: }
318: assert sourceDimensions.length == transform
319: .getSourceDimensions() : transform;
320: assert targetDimensions.length == transform
321: .getTargetDimensions() : transform;
322: return transform;
323: }
324:
325: /**
326: * Separates the math transform on the basis of {@linkplain #sourceDimensions input dimensions}.
327: * The remaining {@linkplain #targetDimensions output dimensions} will be selected automatically
328: * according the specified input dimensions.
329: *
330: * @param transform The transform to reduces.
331: * @return A transform expecting only the specified input dimensions.
332: * @throws FactoryException if the transform is not separable.
333: */
334: private MathTransform separateInput(final MathTransform transform)
335: throws FactoryException {
336: final int dimSource = transform.getSourceDimensions();
337: final int dimTarget = transform.getTargetDimensions();
338: final int dimInput = sourceDimensions.length;
339: final int lower = sourceDimensions[0];
340: final int upper = sourceDimensions[dimInput - 1] + 1;
341: assert XArray.isStrictlySorted(sourceDimensions);
342: if (upper > dimSource) {
343: throw new IllegalArgumentException(Errors.format(
344: ErrorKeys.ILLEGAL_ARGUMENT_$2, "sourceDimensions",
345: new Integer(upper - 1)));
346: }
347: /*
348: * Check for easiest cases: same transform, identity transform or concatenated transforms.
349: */
350: if (dimInput == dimSource) {
351: assert lower == 0 && upper == dimSource;
352: targetDimensions = series(0, dimTarget);
353: return transform;
354: }
355: if (transform.isIdentity()) {
356: targetDimensions = sourceDimensions;
357: return factory.createAffineTransform(MatrixFactory
358: .create(dimInput + 1));
359: }
360: if (transform instanceof ConcatenatedTransform) {
361: final ConcatenatedTransform ctr = (ConcatenatedTransform) transform;
362: final int[] original = sourceDimensions;
363: final MathTransform step1, step2;
364: step1 = separateInput(ctr.transform1);
365: sourceDimensions = targetDimensions;
366: step2 = separateInput(ctr.transform2);
367: sourceDimensions = original;
368: return factory.createConcatenatedTransform(step1, step2);
369: }
370: /*
371: * Special case for the pass through transform: if at least one input dimension
372: * belong to the passthrough's sub-transform, then delegates part of the work to
373: * {@code subTransform(passThrough.transform, ...)}
374: */
375: if (transform instanceof PassThroughTransform) {
376: final PassThroughTransform passThrough = (PassThroughTransform) transform;
377: final int dimPass = passThrough.subTransform
378: .getSourceDimensions();
379: final int dimDiff = passThrough.subTransform
380: .getTargetDimensions()
381: - dimPass;
382: final int subLower = passThrough.firstAffectedOrdinate;
383: final int subUpper = subLower + dimPass;
384: final DimensionFilter subFilter = new DimensionFilter(
385: factory);
386: for (int i = 0; i < sourceDimensions.length; i++) {
387: int n = sourceDimensions[i];
388: if (n >= subLower && n < subUpper) {
389: // Dimension n belong to the subtransform.
390: subFilter.addSourceDimension(n - subLower);
391: } else {
392: // Dimension n belong to heading or trailing dimensions.
393: // Passthrough, after adjustement for trailing dimensions.
394: if (n >= subUpper) {
395: n += dimDiff;
396: }
397: targetDimensions = add(targetDimensions, n);
398: }
399: }
400: if (subFilter.sourceDimensions == null) {
401: /*
402: * No source dimensions belong to the sub-transform. The only remaining
403: * sources are heading and trailing dimensions. A passthrough transform
404: * without its sub-transform is an identity transform...
405: */
406: return factory.createAffineTransform(MatrixFactory
407: .create(dimInput + 1));
408: }
409: /*
410: * There is at least one dimension to separate in the sub-transform. Performs this
411: * separation and gets the list of output dimensions. We need to offset the output
412: * dimensions by the amount of leading dimensions once the separation is done, in
413: * order to translate from the sub-transform's dimension numbering to the transform's
414: * numbering.
415: */
416: final MathTransform subTransform = subFilter
417: .separateInput(passThrough.subTransform);
418: for (int i = 0; i < subFilter.targetDimensions.length; i++) {
419: subFilter.targetDimensions[i] += subLower;
420: }
421: targetDimensions = add(targetDimensions,
422: subFilter.targetDimensions);
423: /*
424: * If all source dimensions not in the sub-transform are consecutive numbers, we can
425: * use our pass though transform implementation. The "consecutive numbers" requirement
426: * (expressed in the 'if' statement below) is a consequence of a limitation in our
427: * current implementation: our pass through transform doesn't accept arbitrary index
428: * for modified ordinates.
429: */
430: if (containsAll(sourceDimensions, lower, subLower)
431: && containsAll(sourceDimensions, subUpper, upper)) {
432: final int firstAffectedOrdinate = Math.max(0, subLower
433: - lower);
434: final int numTrailingOrdinates = Math.max(0, upper
435: - subUpper);
436: return factory.createPassThroughTransform(
437: firstAffectedOrdinate, subTransform,
438: numTrailingOrdinates);
439: }
440: // TODO: handle more general case here...
441: targetDimensions = null; // Clear before to fallback on the LinearTransform case.
442: }
443: /*
444: * If the transform is affine (or at least projective), express the transform as a matrix.
445: * Then, select output dimensions that depends only on selected input dimensions. If an
446: * output dimension depends on at least one discarted input dimension, then this output
447: * dimension will be discarted as well.
448: */
449: if (transform instanceof LinearTransform) {
450: int nRows = 0;
451: boolean hasLastRow = false;
452: final Matrix matrix = ((LinearTransform) transform)
453: .getMatrix();
454: assert dimSource + 1 == matrix.getNumCol()
455: && dimTarget + 1 == matrix.getNumRow() : matrix;
456: double[][] rows = new double[dimTarget + 1][];
457: reduce: for (int j = 0; j < rows.length; j++) {
458: final double[] row = new double[dimInput + 1];
459: /*
460: * For each output dimension (i.e. a matrix row), find the matrix elements for
461: * each input dimension to be kept. If a dependance to at least one discarted
462: * input dimension is found, then the whole output dimension is discarted.
463: *
464: * NOTE: The following loop stops at matrix.getNumCol()-1 because we don't
465: * want to check the translation term.
466: */
467: int nCols = 0, scan = 0;
468: for (int i = 0; i < dimSource; i++) {
469: final double element = matrix.getElement(j, i);
470: if (scan < sourceDimensions.length
471: && sourceDimensions[scan] == i) {
472: row[nCols++] = element;
473: scan++;
474: } else if (element != 0) {
475: // Output dimension 'j' depends on one of discarted input dimension 'i'.
476: // The whole row will be discarted.
477: continue reduce;
478: }
479: }
480: row[nCols++] = matrix.getElement(j, dimSource); // Copy the translation term.
481: assert nCols == row.length : nCols;
482: if (j == dimTarget) {
483: hasLastRow = true;
484: } else {
485: targetDimensions = add(targetDimensions, j);
486: }
487: rows[nRows++] = row;
488: }
489: rows = (double[][]) XArray.resize(rows, nRows);
490: if (hasLastRow) {
491: return factory.createAffineTransform(new GeneralMatrix(
492: rows));
493: }
494: // In an affine transform, the last row is not supposed to have dependency
495: // to any input dimension. But in this particuler case, our matrix has such
496: // dependencies. TODO: is there anything we could do about that?
497: }
498: throw new FactoryException(Errors
499: .format(ErrorKeys.INSEPARABLE_TRANSFORM));
500: }
501:
502: /**
503: * Creates a transform which retains only a subset of an other transform's outputs. The number
504: * and nature of inputs stay unchanged. For example if the supplied {@code transform} has
505: * (<var>longitude</var>, <var>latitude</var>, <var>height</var>) outputs, then a sub-transform
506: * may be used to keep only the (<var>longitude</var>, <var>latitude</var>) part. In most cases,
507: * the created sub-transform is non-invertible since it loose informations.
508: * <br><br>
509: * This transform may be see as a non-square matrix transform with less rows
510: * than columns, concatenated with {@code transform}. However, invoking
511: * {@code createFilterTransfom(...)} allows the optimization of some common cases.
512: *
513: * @param transform The transform to reduces.
514: * @return The {@code transform} keeping only the output dimensions.
515: * @throws FactoryException if the transform can't be created.
516: */
517: private MathTransform separateOutput(MathTransform transform)
518: throws FactoryException {
519: final int dimSource = transform.getSourceDimensions();
520: final int dimTarget = transform.getTargetDimensions();
521: final int dimOutput = targetDimensions.length;
522: final int lower = targetDimensions[0];
523: final int upper = targetDimensions[dimOutput - 1];
524: assert XArray.isStrictlySorted(targetDimensions);
525: if (upper > dimTarget) {
526: throw new IllegalArgumentException(Errors.format(
527: ErrorKeys.ILLEGAL_ARGUMENT_$2, "targetDimensions",
528: new Integer(upper)));
529: }
530: if (dimOutput == dimTarget) {
531: assert lower == 0 && upper == dimTarget;
532: return transform;
533: }
534: /*
535: * If the transform is an instance of "pass through" transform but no dimension from its
536: * subtransform is requested, then ignore the subtransform (i.e. treat the whole transform
537: * as identity, except for the number of output dimension which may be different from the
538: * number of input dimension).
539: */
540: int dimPass = 0;
541: int dimDiff = 0;
542: int dimStep = dimTarget;
543: if (transform instanceof PassThroughTransform) {
544: final PassThroughTransform passThrough = (PassThroughTransform) transform;
545: final int subLower = passThrough.firstAffectedOrdinate;
546: final int subUpper = subLower
547: + passThrough.subTransform.getTargetDimensions();
548: if (!containsAny(targetDimensions, subLower, subUpper)) {
549: transform = null;
550: dimStep = dimSource;
551: dimPass = subLower;
552: dimDiff = (subLower + passThrough.subTransform
553: .getSourceDimensions())
554: - subUpper;
555: }
556: }
557: /*
558: * Creates the matrix to be used as a filter, [x'] [1 0 0 0] [x]
559: * and concatenates it to the transform. The [z'] = [0 0 1 0] [y]
560: * matrix will contains only a 1 for the output [1 ] [0 0 0 1] [z]
561: * dimension to keep, as in the following example: [1]
562: */
563: final XMatrix matrix = MatrixFactory.create(dimOutput + 1,
564: dimStep + 1);
565: matrix.setZero();
566: for (int j = 0; j < dimOutput; j++) {
567: int i = targetDimensions[j];
568: if (i >= dimPass) {
569: i += dimDiff;
570: }
571: matrix.setElement(j, i, 1);
572: }
573: // Affine transform has one more row/column than dimension.
574: matrix.setElement(dimOutput, dimStep, 1);
575: MathTransform filtered = factory.createAffineTransform(matrix);
576: if (transform != null) {
577: filtered = factory.createConcatenatedTransform(transform,
578: filtered);
579: }
580: return filtered;
581: }
582:
583: /**
584: * Returns {@code true} if the given sequence contains all index in the range {@code lower}
585: * inclusive to {@code upper} exclusive.
586: *
587: * @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to test.
588: * @param lower The lower value, inclusive.
589: * @param upper The upper value, exclusive.
590: * @return {@code true} if the full range was found in the sequence.
591: */
592: private static boolean containsAll(final int[] sequence,
593: final int lower, int upper) {
594: if (lower == upper) {
595: return true;
596: }
597: if (sequence != null) {
598: assert XArray.isStrictlySorted(sequence);
599: int index = Arrays.binarySearch(sequence, lower);
600: if (index >= 0) {
601: index += --upper - lower;
602: if (index >= 0 && index < sequence.length) {
603: return sequence[index] == upper;
604: }
605: }
606: }
607: return false;
608: }
609:
610: /**
611: * Returns {@code true} if the given sequence contains any value in the given range.
612: *
613: * @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to test.
614: * @param lower The lower value, inclusive.
615: * @param upper The upper value, exclusive.
616: * @return {@code true} if the sequence contains at least one value in the given range.
617: */
618: private static boolean containsAny(final int[] sequence,
619: final int lower, final int upper) {
620: if (upper == lower) {
621: return true;
622: }
623: if (sequence != null) {
624: assert XArray.isStrictlySorted(sequence);
625: int index = Arrays.binarySearch(sequence, lower);
626: if (index >= 0) {
627: return true;
628: }
629: index = ~index; // Tild, not minus sign.
630: return index < sequence.length && sequence[index] < upper;
631: }
632: return false;
633: }
634:
635: /**
636: * Add the specified {@code dimension} to the specified sequence. Values are added
637: * in increasing order. Duplicated values are not added.
638: *
639: * @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
640: */
641: private static int[] add(int[] sequence, int dimension)
642: throws IllegalArgumentException {
643: if (dimension < 0) {
644: throw new IllegalArgumentException(Errors.format(
645: ErrorKeys.ILLEGAL_ARGUMENT_$2, "dimension",
646: new Integer(dimension)));
647: }
648: if (sequence == null) {
649: return new int[] { dimension };
650: }
651: assert XArray.isStrictlySorted(sequence);
652: int i = Arrays.binarySearch(sequence, dimension);
653: if (i < 0) {
654: i = ~i; // Tild, not the minus sign.
655: sequence = XArray.insert(sequence, i, 1);
656: sequence[i] = dimension;
657: }
658: assert Arrays.binarySearch(sequence, dimension) == i;
659: return sequence;
660: }
661:
662: /**
663: * Add the specified {@code dimensions} to the specified sequence. Values are added
664: * in increasing order. Duplicated values are not added.
665: *
666: * @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
667: */
668: private static int[] add(int[] sequence, final int[] dimensions)
669: throws IllegalArgumentException {
670: if (dimensions.length != 0) {
671: ensureValidSeries(dimensions);
672: if (sequence == null) {
673: sequence = (int[]) dimensions.clone();
674: } else {
675: // Note: the following loop is unefficient, but should suffise since this
676: // case should not occurs often and arrays should be small anyway.
677: for (int i = 0; i < dimensions.length; i++) {
678: sequence = add(sequence, dimensions[i]);
679: }
680: }
681: }
682: return sequence;
683: }
684:
685: /**
686: * Add the specified range to the specified sequence. Values are added
687: * in increasing order. Duplicated values are not added.
688: *
689: * @param sequence The {@link #sourceDimensions} or {@link #targetDimensions} sequence to update.
690: * @throws IllegalArgumentException if {@code lower} is not smaller than {@code upper}.
691: */
692: private static int[] add(int[] sequence, int lower, final int upper)
693: throws IllegalArgumentException {
694: if (lower < 0 || lower >= upper) {
695: throw new IllegalArgumentException(Errors.format(
696: ErrorKeys.ILLEGAL_ARGUMENT_$2, "lower",
697: new Integer(lower)));
698: }
699: if (sequence == null) {
700: sequence = series(lower, upper);
701: } else {
702: // Note: the following loop is unefficient, but should suffise since this
703: // case should not occurs often and arrays should be small anyway.
704: while (lower < upper) {
705: sequence = add(sequence, lower++);
706: }
707: }
708: assert containsAll(sequence, lower, upper);
709: return sequence;
710: }
711:
712: /**
713: * Returns a series of increasing values starting at {@code lower}.
714: */
715: private static int[] series(final int lower, final int upper)
716: throws IllegalArgumentException {
717: final int[] sequence = new int[upper - lower];
718: for (int i = 0; i < sequence.length; i++) {
719: sequence[i] = i + lower;
720: }
721: return sequence;
722: }
723:
724: /**
725: * Ensures that the specified array contains strictly increasing non-negative values.
726: *
727: * @param dimensions The sequence to check.
728: * @throws IllegalArgumentException if the specified sequence is not a valid series.
729: */
730: private static void ensureValidSeries(final int[] dimensions)
731: throws IllegalArgumentException {
732: int last = -1;
733: for (int i = 0; i < dimensions.length; i++) {
734: final int value = dimensions[i];
735: if (value <= last) {
736: throw new IllegalArgumentException(Errors.format(
737: ErrorKeys.ILLEGAL_ARGUMENT_$2, "dimensions["
738: + i + ']', new Integer(value)));
739: }
740: last = value;
741: }
742: }
743: }
|