/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2010-2012, Geomatys
*
* 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.geotoolkit.internal.image.io;
import java.util.Map;
import java.util.List;
import java.util.HashMap;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import org.geotoolkit.image.io.DimensionSet;
import org.geotoolkit.image.io.DimensionSlice;
import org.geotoolkit.image.io.NamedImageStore;
import org.geotoolkit.image.io.MultidimensionalImageStore;
import org.geotoolkit.image.io.IllegalImageDimensionException;
import org.geotoolkit.resources.Errors;
import org.apache.sis.util.NullArgumentException;
import org.geotoolkit.util.collection.XCollections;
import org.apache.sis.internal.util.UnmodifiableArrayList;
/**
* A {@link DimensionSet} extended with convenience methods for {@link ImageReader}s and
* {@link ImageWriter}s implementing the {@link NamedImageStore} interface in addition of
* {@link MultidimensionalImageStore}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.15
*
* @see org.geotoolkit.image.io.plugin.NetcdfImageReader
*
* @since 3.15
* @module
*/
public final class DimensionManager extends DimensionSet {
/**
* The index of the image to read in situations where the {@code imageIndex} argument can
* not be used directly. The value, which is {@value}, is defined as a constant in order
* to spot the places where the code makes such "image index replacements".
* <p>
* Note that using the image index 0 means that the first variable given to the
* {@link #setImageNames(String[])} or {@link #setBandNames(int, String[])} methods
* will be used, if any.
*/
public static final int DEFAULT_IMAGE_INDEX = 0;
/**
* The user-supplied names of the variables to be read from the data file. The first name
* is assigned to image index 0, the second name to image index 1, <i>etc</i>. This list
* shall be immutable.
* <p>
* This list is {@code null} if the user did not specified explicitly the variables to
* read. In such case, the {@code ImageReader} implementation shall provides a default
* list of variables.
*/
private List<String> variableNames;
/**
* The names of the variables to be assigned to bands, or {@code null} if none.
*/
private Map<Integer,List<String>> bandNames;
/**
* Creates a new {@code DimensionManager} instance for the given image reader or writer.
*
* @param store The image reader or writer for which this instance is created, or {@code null}.
*/
public DimensionManager(final MultidimensionalImageStore store) {
super(store);
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
super.clear();
XCollections.clear(bandNames);
variableNames = null;
}
/**
* Returns {@code true} if the image API is used for navigating in
* a hyper-cube dimension.
*
* @return {@code true} if the image API is used.
*/
public boolean usesImageAPI() {
return getAPIs().contains(DimensionSlice.API.IMAGES);
}
/**
* Sets the user-supplied names of the variables to be read in a data files.
* A value of {@code null} removes the user-supplied names, in which case the
* {@link ImageReader} implementation will be responsible for providing a
* default list of variables.
*
* @param names The list of variables to be assigned to image index, or {@code null}
* for removing the user-supplied names.
*/
public void setImageNames(String... names) {
if (names != null) {
names = names.clone();
ensureNonNull(names);
variableNames = UnmodifiableArrayList.wrap(names);
} else {
variableNames = null;
}
}
/**
* Returns the user-supplied names of the variables to be read. This list is {@code null}
* if the user did not specified explicitly the variables to read. In such case, the
* {@link ImageReader} implementation shall provides a default list of variables.
*
* @return The names of the variables to be read, or {@code null} if
* the user did not supplied explicitly a list of variables.
*/
public List<String> getImageNames() {
return variableNames;
}
/**
* Sets the names of the bands for the given image, or {@code null} for removing any naming.
*
* @param imageIndex Index of the image for which to set the band names.
* @param names The variable names of the bands for the given image,
* or {@code null} for removing any naming.
*/
public void setBandNames(final int imageIndex, String... names) {
if (names == null) {
if (bandNames != null) {
bandNames.remove(imageIndex);
}
} else {
if (names.length == 0) {
throw new IllegalArgumentException(errors().getString(Errors.Keys.EmptyArray));
}
if (bandNames == null) {
bandNames = new HashMap<>();
}
names = names.clone();
ensureNonNull(names);
bandNames.put(imageIndex, UnmodifiableArrayList.wrap(names));
}
}
/**
* Returns the names of the bands for the given image, or {@code null} if none.
*
* @param imageIndex Index of the image for which to get the band names.
* @return The variable names of the bands for the given image, or {@code null}
* if the bands for the given image are unnamed.
*/
public List<String> getBandNames(final int imageIndex) {
return (bandNames != null) ? bandNames.get(imageIndex) : null;
}
/**
* Get the name of the variable to read for the given <cite>internal</cite> image index.
* This is usually the name at the given index in the {@link #variableNames} list. However
* a special case is performed if the user invoked the {@link #setBandNames(String[])} method
* (i.e. specified explicitly which variable to assign to each band). In such case we will
* load the first variable.
*
* {@note Using the first variable is an arbitrary choice, but work well if
* the bands are going to be read in sequential order.}
*
* @param internalIndex The index of the image to be read, <strong>after</strong>
* processing by {@link #replaceImageIndex(int)}.
* @return The name of the variable to load, or {@code null} if unspecified.
*/
public String getVariableName(final int internalIndex) {
final List<String> bandNames = getBandNames(internalIndex);
if (bandNames != null) {
for (final String name : bandNames) {
if (name != null) {
return name;
}
}
}
if (variableNames != null) {
return variableNames.get(internalIndex);
}
return null;
}
/**
* Eventually updates the given image index for the currently API assignation.
* If the {@code IMAGES} API is assigned to a dimension, then this method ensures
* that the variables for at most one image has been specified by the user, in order
* to avoid confusion.
*
* @param imageIndex The user-specified image index.
* @return The image index to use.
* @throws IllegalImageDimensionException If this class is used in a way that may leads to confusion.
*/
public int replaceImageIndex(int imageIndex) throws IllegalImageDimensionException {
if (imageIndex != DEFAULT_IMAGE_INDEX && usesImageAPI()) {
String variableName = null;
if (variableNames != null) {
switch (variableNames.size()) {
case 0: break;
case 1: variableName = variableNames.get(0); break;
default: throw new IllegalImageDimensionException(errors().getString(
Errors.Keys.DuplicatedValue_1, "variableName"));
}
}
if (bandNames != null) {
for (final Map.Entry<Integer,List<String>> entry : bandNames.entrySet()) {
final int index = entry.getKey();
if (index != DEFAULT_IMAGE_INDEX) {
throw new IllegalImageDimensionException(errors().getString(
Errors.Keys.UnexpectedParameter_1, "bandNames(" + index + ')'));
}
if (variableName != null) {
final List<String> names = entry.getValue();
if (!names.isEmpty() && !names.contains(variableName)) {
throw new IllegalImageDimensionException(errors().getString(
Errors.Keys.InconsistentValue));
}
}
}
}
imageIndex = DEFAULT_IMAGE_INDEX;
}
return imageIndex;
}
/**
* Ensures that the given array does not contain any null element.
*
* @param names The array to check.
* @throws NullArgumentException If at least one element of the given array is null.
*/
private void ensureNonNull(final String[] names) throws NullArgumentException {
for (int i=0; i<names.length; i++) {
final String name = names[i];
if (name == null) {
throw new NullArgumentException(errors().getString(
Errors.Keys.NullArgument_1, "names[" + i + ']'));
}
}
}
/**
* Returns the error resources bundle.
*/
private Errors errors() {
return Errors.getResources(getLocale());
}
}