/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2011-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.referencing.adapters;
import java.util.Set;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import javax.measure.Unit;
import ucar.unidata.util.Parameter;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.InvalidParameterTypeException;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.parameter.ParameterDirection;
import org.apache.sis.internal.util.Numerics;
import org.geotoolkit.util.Cloneable;
import org.geotoolkit.resources.Errors;
/**
* Wraps a NetCDF {@link Parameter} object in a GeoAPI {@link ParameterValue}. The NetCDF
* {@code Parameter} class is both a parameter value and its own descriptor. Consequently
* this adapter implements both the {@link ParameterValue} and {@link ParameterDescriptor}
* interfaces.
* <p>
* NetCDF {@code Parameter} instances can store {@link String}, {@code double} and {@code double[]}
* values. Those values can be obtained by the {@link #stringValue()}, {@link #doubleValue()} and
* {@link #doubleValueList()} methods in the {@code ParameterValue} interface. All other
* {@code fooValue()} methods delegate to one of the above-cited methods and convert the result.
*
* @param <T> The type of parameter value.
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.20
* @module
*/
final class NetcdfParameter<T> extends NetcdfIdentifiedObject implements
ParameterDescriptor<T>, ParameterValue<T>, Serializable, Cloneable
{
/**
* Serial number for cross-version compatibility.
*/
private static final long serialVersionUID = 9121732767541762825L;
/**
* The NetCDF parameter, never {@code null}. A new NetCDF parameter instance will be
* assigned to this field when a setter method is invoked.
*/
private Parameter parameter;
/**
* Creates a new wrapper for the given NetCDF parameter. The {@linkplain #getValueClass()
* value class} will be inferred from the given parameter.
* <p>
* This constructor can not have public access because we can not guarantee that the
* {@code <T>} parameterized type is consistent with the given NetCDF parameter object,
* since the NetCDF class is not parameterized. New instances of {@code NetcdfParameter}
* shall be created only from methods having a {@code ParameterValue<?>} return type.
*
* @param parameter The parameter to wrap.
*/
NetcdfParameter(final Parameter parameter) {
this.parameter = parameter;
}
/**
* Returns the NetCDF object wrapped by this adapter. Note that it may be different
* than the NetCDF object given to the constructor if a setter method has been invoked.
*/
@Override
public Parameter delegate() {
return parameter;
}
/**
* Returns the parameter name. This method delegates to {@link Parameter#getName()}.
*
* @see Parameter#getName()
*/
@Override
public String getCode() {
return parameter.getName();
}
@Override
public ParameterDirection getDirection() {
return null;
}
/**
* Returns the parameter descriptor, which is {@code this}.
*/
@Override
public ParameterDescriptor<T> getDescriptor() {
return this;
}
/**
* Returns a new value for this descriptor.
*/
@Override
public ParameterValue<T> createValue() {
return new NetcdfParameter<>(parameter);
}
/**
* Returns the parameter type typically as a {@link String}, {@link Double} or {@code double[]}
* class. The above-cited types are the ones supported by the NetCDF parameter implementation.
*/
@Override
@SuppressWarnings("unchecked")
public Class<T> getValueClass() {
return (Class<T>) getValueClass(parameter);
}
/**
* Implementation of the public {@link #getValueClass()} method.
*
* @param parameter The NetCDF parameter from which to infer the value class.
*/
private static Class<?> getValueClass(final Parameter parameter) {
if (parameter.isString()) return String.class;
if (parameter.getLength() == 1) return Double.class;
return double[].class;
}
/**
* Returns {@code null} since the {@link Parameter} class does not define a set of valid values.
*/
@Override
public Set<T> getValidValues() {
return null;
}
/**
* Returns the {@linkplain #getValue() current value} as the default value.
* This is consistent with the {@link #createValue()} method, which creates
* a copy of {@code NetcdfParameter} containing the same parameter value.
*/
@Override
public T getDefaultValue() {
return getValue();
}
/**
* Returns {@code null} since there is no descriptor defining a minimal value.
*/
@Override
public Comparable<T> getMinimumValue() {
return null;
}
/**
* Returns {@code null} since there is no descriptor defining a maximal value.
*/
@Override
public Comparable<T> getMaximumValue() {
return null;
}
/**
* Returns {@code null} since the parameter does not hold information about the unit of measure.
*/
@Override
public Unit<?> getUnit() {
return null;
}
/**
* Returns 0 since this parameter is assumed optional.
*/
@Override
public int getMinimumOccurs() {
return 0;
}
/**
* Returns 1 since this parameter is not a group of parameters.
*/
@Override
public int getMaximumOccurs() {
return 1;
}
/**
* Returns the exception to throw for a getter method invoked with a parameter of the wrong type.
*
* @param parameter The NetCDF parameter.
* @param requested The requested type.
* @return The exception to throw.
*/
private static InvalidParameterTypeException invalidType(
final Parameter parameter, final Class<?> requested)
{
return new InvalidParameterTypeException(Errors.format(
Errors.Keys.CantConvertFromType_2, getValueClass(parameter), requested),
parameter.getName());
}
/**
* Always throws an exception since this method doesn't know the parameter unit.
*
* @throws IllegalArgumentException Always thrown.
*/
@Override
public double doubleValue(final Unit<?> unit) throws IllegalArgumentException {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UndefinedProperty_1, "unit"));
}
/**
* Returns the {@linkplain Parameter#getNumericValue() parameter numeric value}
* as a floating point value. This method first ensures that the parameter value
* {@linkplain Parameter#isString() is not a string} and that the
* {@linkplain Parameter#getLength() number of numeric values} is equals to 1,
* then delegates to the {@link Parameter#getNumericValue()} method.
*
* @return The parameter numeric value.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@code double} type.
*
* @see Parameter#getNumericValue()
*/
@Override
public double doubleValue() throws InvalidParameterTypeException {
final Parameter parameter = this.parameter;
if (parameter.isString() || parameter.getLength() != 1) {
throw invalidType(parameter, Double.class);
}
return parameter.getNumericValue();
}
/**
* Returns the {@linkplain Parameter#getNumericValue() parameter numeric value} as
* an integer value. This method invokes {@link #doubleValue()}, casts the result
* to the {@code int} type and ensures that there is no loss of accuracy.
*
* @return The parameter numeric value.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@code int} type.
*
* @see Parameter#getNumericValue()
*/
@Override
public int intValue() throws InvalidParameterTypeException {
final double value = doubleValue();
final int result = (int) value;
if (result != value) {
throw invalidType(parameter, Integer.class);
}
return result;
}
/**
* Returns the parameter value as a boolean value. This method invokes {@link #stringValue()},
* then returns {@code true} or {@code false} if the string is equals, ignoring cases, to
* {@code "true"} or {@code "false"} respectively.
*
* @return The parameter boolean value.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@code boolean} type.
*
* @see Parameter#getStringValue()
*/
@Override
public boolean booleanValue() throws InvalidParameterTypeException {
final String value = stringValue().trim();
if (value.equalsIgnoreCase("true")) return true;
if (value.equalsIgnoreCase("false")) return false;
throw invalidType(parameter, Boolean.class);
}
/**
* Returns the {@linkplain Parameter#getStringValue() parameter string value}.
*
* @return The parameter string value.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@link String} type.
*
* @see Parameter#getStringValue()
*/
@Override
public String stringValue() throws InvalidParameterTypeException {
final Parameter parameter = this.parameter;
if (!parameter.isString()) {
throw invalidType(parameter, String.class);
}
return parameter.getStringValue();
}
/**
* Always throws an exception since this method doesn't know the parameter unit.
*
* @throws IllegalArgumentException Always thrown.
*/
@Override
public double[] doubleValueList(final Unit<?> unit) throws IllegalArgumentException {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UndefinedProperty_1, "unit"));
}
/**
* Returns the {@linkplain Parameter#getNumericValues() parameter numeric values}
* as an array of floating point values.
*
* @return The parameter numeric values.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@code double[]} type.
*
* @see Parameter#getNumericValues()
*/
@Override
public double[] doubleValueList() throws InvalidParameterTypeException {
final Parameter parameter = this.parameter;
if (parameter.isString()) {
throw invalidType(parameter, double[].class);
}
return parameter.getNumericValues();
}
/**
* Returns the {@linkplain Parameter#getNumericValues() parameter numeric values}
* as an array of integer values. This method invokes {@link #doubleValueList()},
* casts each value to the {@code int} type and ensures that there is no loss of
* accuracy.
*
* @return The parameter numeric values.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@code int[]} type.
*
* @see Parameter#getNumericValues()
*/
@Override
public int[] intValueList() throws InvalidParameterTypeException {
final double[] values = doubleValueList();
final int[] result = Numerics.copyAsInts(values);
for (int i=0; i<result.length; i++) {
if (result[i] != values[i]) {
throw invalidType(parameter, int[].class);
}
}
return result;
}
/**
* Returns the {@linkplain Parameter#getStringValue() parameter string value} as a URI.
* This method invokes {@link #stringValue()}, then parse the string as a URI.
*
* @return The parameter URI value.
* @throws InvalidParameterTypeException If the parameter value is not convertible
* to the {@link URI} type.
*
* @see Parameter#getStringValue()
*/
@Override
public URI valueFile() throws InvalidParameterTypeException {
try {
return new URI(stringValue());
} catch (URISyntaxException cause) {
final InvalidParameterTypeException e = invalidType(parameter, URI.class);
e.initCause(cause);
throw e;
}
}
/**
* Returns the parameter value as an object. This method delegates to one of the
* {@code fooValue()} method depending on the {@linkplain #getValueClass() value class},
*
* @return The parameter value as an object.
*/
@Override
public T getValue() {
final Class<T> type = getValueClass();
final Object value;
if (type.isAssignableFrom(String .class)) value = stringValue();
else if (type.isAssignableFrom(Double .class)) value = doubleValue();
else if (type.isAssignableFrom(Integer .class)) value = intValue();
else if (type.isAssignableFrom(double[].class)) value = doubleValueList();
else if (type.isAssignableFrom(int[] .class)) value = intValueList();
else if (type.isAssignableFrom(Boolean .class)) value = booleanValue();
else if (type.isAssignableFrom(URI .class)) value = valueFile();
else throw new InvalidParameterTypeException(Errors.format(
Errors.Keys.UnknownType_1, type), parameter.getName());
return type.cast(value);
}
/**
* Returns the exception to throw for a setter method invoked with a parameter of the wrong type.
*
* @param parameter The NetCDF parameter.
* @param value The value given by the user.
* @return The exception to throw.
*/
private static InvalidParameterValueException invalidValue(
final Parameter parameter, final Object value)
{
return new InvalidParameterValueException(Errors.format(
Errors.Keys.CantConvertFromType_2, value.getClass(), getValueClass(parameter)),
parameter.getName(), value);
}
/**
* Unsupported operation, since the {@link Parameter} class has no information about unit
* of measurement.
*
* @throws IllegalArgumentException Always thrown.
*/
@Override
public void setValue(final double[] values, final Unit<?> unit) throws IllegalArgumentException {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedParameter_1, "unit"));
}
/**
* Unsupported operation, since the {@link Parameter} class has no information about unit
* of measurement.
*
* @throws IllegalArgumentException Always thrown.
*/
@Override
public void setValue(final double value, final Unit<?> unit) throws IllegalArgumentException {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedParameter_1, "unit"));
}
/**
* Sets this parameter to the given value. This method creates a new {@link Parameter}
* instance with the given value.
*
* @throws InvalidParameterValueException If this parameter can not accept the {@code double} type.
*
* @see Parameter#Parameter(String, double)
*/
@Override
public void setValue(final double value) throws InvalidParameterValueException {
final Parameter parameter = this.parameter;
if (parameter.isString() || parameter.getLength() != 1) {
throw invalidValue(parameter, value);
}
this.parameter = new Parameter(getCode(), value);
}
/**
* Sets this parameter to the given value. This method casts the given value to the
* {@code double} type and delegates to {@link #setValue(double)}.
*
* @throws InvalidParameterValueException If this parameter can not accept the {@code int} type.
*
* @see Parameter#Parameter(String, double)
*/
@Override
public void setValue(final int value) throws InvalidParameterValueException {
setValue((double) value);
}
/**
* Sets this parameter to the given boolean. This method formats the given value to the
* {@code "true"} or {@code "false"} string, then delegates to {@link #setValue(Object)}.
*
* @throws InvalidParameterValueException If this parameter can not accept the {@code boolean} type.
*
* @see Parameter#Parameter(String, String)
*/
@Override
public void setValue(final boolean value) throws InvalidParameterValueException {
setValue(Boolean.toString(value));
}
/**
* Sets this parameter to the given {@link String} or {@link Number}. If the given
* value is a number, then this method delegates to {@link #setValue(double)}.
*
* @throws InvalidParameterValueException If this parameter can not accept the given value.
*
* @see Parameter#Parameter(String, String)
*/
@Override
public void setValue(final Object value) throws InvalidParameterValueException {
if (value instanceof Number) {
setValue(((Number) value).doubleValue());
} else if ((value instanceof CharSequence) && parameter.isString()) {
parameter = new Parameter(parameter.getName(), value.toString());
} else {
throw invalidValue(parameter, value);
}
}
/**
* Returns a copy of this parameter value.
*/
@Override
@SuppressWarnings("unchecked")
public NetcdfParameter<T> clone() {
try {
return (NetcdfParameter<T>) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // Should never happen since we are cloneable.
}
}
}