org.geotoolkit.parameter.AbstractParameter Maven / Gradle / Ivy
/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2004-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-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.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotoolkit.parameter;
import java.util.Set;
import java.io.Writer;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import javax.measure.unit.Unit;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.InvalidParameterValueException;
import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.Cloneable;
import org.geotoolkit.io.TableWriter;
import org.geotoolkit.measure.Units;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.io.wkt.Formatter;
import org.geotoolkit.io.wkt.FormattableObject;
import org.geotoolkit.naming.DefaultNameSpace;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
/**
* The root class of {@link ParameterValue} and {@link ParameterValueGroup} implementations.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @see AbstractParameterDescriptor
*
* @since 2.0
* @module
*/
public abstract class AbstractParameter extends FormattableObject
implements GeneralParameterValue, Serializable, Cloneable
{
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 8458179223988766398L;
/**
* The abstract definition of this parameter or group of parameters.
*/
final GeneralParameterDescriptor descriptor;
/**
* Constructs a parameter value from the specified descriptor.
*
* @param descriptor The abstract definition of this parameter or group of parameters.
*/
protected AbstractParameter(final GeneralParameterDescriptor descriptor) {
this.descriptor = descriptor;
ensureNonNull("descriptor", descriptor);
}
/**
* Returns the abstract definition of this parameter or group of parameters.
*/
@Override
public GeneralParameterDescriptor getDescriptor() {
return descriptor;
}
/**
* Ensures that the given value is valid according the specified parameter descriptor.
* This convenience method ensures that {@code value} is assignable to the
* {@linkplain ParameterDescriptor#getValueClass() expected class}, is between the
* {@linkplain ParameterDescriptor#getMinimumValue() minimum} and
* {@linkplain ParameterDescriptor#getMaximumValue() maximum} values and is one of the
* {@linkplain ParameterDescriptor#getValidValues() set of valid values}.
* If the value fails any of those tests, then an exception is thrown.
*
* This method is similar to {@link Parameters#isValid(ParameterValue)} except that the
* exception contains an error message formatted with a description of the failure raison.
*
* @param The type of parameter value. The given {@code value} should typically be an
* instance of this class. This is not required by this method signature but is
* checked by this method implementation.
* @param descriptor The parameter descriptor to check against.
* @param value The value to check, or {@code null}.
* @return The value casted to the descriptor parameterized type, or the
* {@linkplain ParameterDescriptor#getDefaultValue() default value}
* if the given value was null while the parameter is mandatory.
* @throws InvalidParameterValueException if the parameter value is invalid.
*/
static T ensureValidValue(final ParameterDescriptor descriptor, final Object value)
throws InvalidParameterValueException
{
if (value == null) {
if (descriptor.getMinimumOccurs() != 0) {
return descriptor.getDefaultValue();
}
return null;
}
final String error;
/*
* Note: the implementation below is similar (except for different error message) to the
* one in Parameters.isValidValue(ParameterDescriptor, Object). If one implementation is
* modified, the other should be updated accordingly. The main difference is that null
* values are replaced by the default value instead than being a conformance error.
*/
final Class type = descriptor.getValueClass();
if (!type.isInstance(value)) {
error = Errors.format(Errors.Keys.ILLEGAL_OPERATION_FOR_VALUE_CLASS_$1, value.getClass());
} else {
final T typedValue = type.cast(value);
final Comparable minimum = descriptor.getMinimumValue();
final Comparable maximum = descriptor.getMaximumValue();
if ((minimum != null && minimum.compareTo(typedValue) > 0) ||
(maximum != null && maximum.compareTo(typedValue) < 0))
{
error = Errors.format(Errors.Keys.VALUE_OUT_OF_BOUNDS_$3, value, minimum, maximum);
} else {
final Set validValues = descriptor.getValidValues();
if (validValues!=null && !validValues.contains(value)) {
error = Errors.format(Errors.Keys.ILLEGAL_ARGUMENT_$2, getName(descriptor), value);
} else {
/*
* Passed every tests - the value is valid.
*/
return typedValue;
}
}
}
throw new InvalidParameterValueException(error, getName(descriptor), value);
}
/**
* Returns an exception initialized with a "Unitless parameter" error message for the
* specified descriptor.
*/
static IllegalStateException unitlessParameter(final GeneralParameterDescriptor descriptor) {
return new IllegalStateException(Errors.format(
Errors.Keys.UNITLESS_PARAMETER_$1, getName(descriptor)));
}
/**
* Convenience method returning the name of the specified descriptor. This method is used
* mostly for output to be read by human, not for processing. Consequently, we may consider
* to returns a localized name in a future version.
*/
static String getName(final GeneralParameterDescriptor descriptor) {
return descriptor.getName().getCode();
}
/**
* Returns the unit type as one of error message code. Used for checking unit with a better
* error message formatting if needed.
*/
static int getUnitMessageID(final Unit> unit) {
if (Units.isLinear (unit)) return Errors.Keys.NON_LINEAR_UNIT_$1;
if (Units.isAngular (unit)) return Errors.Keys.NON_ANGULAR_UNIT_$1;
if (Units.isTemporal(unit)) return Errors.Keys.NON_TEMPORAL_UNIT_$1;
if (Units.isScale (unit)) return Errors.Keys.NON_SCALE_UNIT_$1;
return Errors.Keys.INCOMPATIBLE_UNIT_$1;
}
/**
* Returns a copy of this parameter value or group.
*/
@Override
public AbstractParameter clone() {
try {
return (AbstractParameter) super.clone();
} catch (CloneNotSupportedException exception) {
// Should not happen, since we are cloneable
throw new AssertionError(exception);
}
}
/**
* Compares the specified object with this parameter for equality.
*
* @param object The object to compare to {@code this}.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final Object object) {
if (object != null && object.getClass() == getClass()) {
final AbstractParameter that = (AbstractParameter) object;
return Utilities.equals(this.descriptor, that.descriptor);
}
return false;
}
/**
* Returns a hash value for this parameter. This value doesn't need
* to be the same in past or future versions of this class.
*/
@Override
public int hashCode() {
return descriptor.hashCode() ^ (int) serialVersionUID;
}
/**
* Returns a string representation of this parameter. The default implementation delegates
* the work to {@link #write(TableWriter)}. Subclass can override the later method instead
* than {@code toString()}.
*/
@Override
public String toString() {
final TableWriter table = new TableWriter(null, 1);
table.setMultiLinesCells(true);
try {
write(table);
} catch (IOException exception) {
// Should never happen, since we write to a StringWriter.
throw new AssertionError(exception);
}
return table.toString();
}
/**
* Writes the content of this parameter to the specified table. This method provides a more
* convenient way to align the values than overriding the {@link #toString} method. The table
* columns are defined as below:
*
*
* - The parameter name
* - The separator
* - The parameter value
*
*
* The default implementation is suitable for most cases. However, subclasses are free to
* override this method with the following idiom:
*
* {@preformat java
* table.write("parameter name");
* table.nextColumn()
* table.write('=');
* table.nextColumn()
* table.write("parameter value");
* table.nextLine()
* }
*
* @param table The table where to format the parameter value.
* @throws IOException if an error occurs during output operation.
*/
protected void write(final TableWriter table) throws IOException {
table.write(getName(descriptor));
table.nextColumn();
if (this instanceof ParameterValue>) {
/*
* Provides a default implementation for parameter value. This implementation doesn't
* need to be a Geotk's one. Putting a default implementation here avoid duplication
* in all subclasses implementing the same interface.
*/
table.write('=');
table.nextColumn();
append(table, ((ParameterValue>) this).getValue());
} else if (this instanceof ParameterValueGroup) {
/*
* Provides a default implementation for parameter value group, for the same reasons
* then the previous block. Reminder: the above 'instanceof' check for interface, not
* for subclass. This explain why we use it instead of method overriding.
*/
table.write(DefaultNameSpace.DEFAULT_SEPARATOR);
table.nextColumn();
TableWriter inner = null;
for (final GeneralParameterValue value : ((ParameterValueGroup) this).values()) {
if (value instanceof AbstractParameter) {
if (inner == null) {
inner = new TableWriter(table, 1);
inner.setMultiLinesCells(true);
}
((AbstractParameter) value).write(inner);
} else {
// Unknow implementation. It will break the formatting. Too bad...
if (inner != null) {
inner.flush();
inner = null;
}
table.write(value.toString());
table.write('\n');
}
}
if (inner != null) {
inner.flush();
}
} else {
/*
* No know parameter value for this default implementation.
*/
}
table.nextLine();
}
/**
* Append the specified value to a stream. If the value is an array, then
* the array element are appended recursively (i.e. the array may contains
* sub-array).
*/
private static void append(final Writer buffer, final Object value) throws IOException {
if (value == null) {
buffer.write("null");
} else if (value.getClass().isArray()) {
buffer.write('{');
final int length = Array.getLength(value);
final int limit = Math.min(5, length);
for (int i=0; i limit) {
buffer.write(", ...");
}
buffer.write('}');
} else {
final boolean isNumeric = (value instanceof Number);
if (!isNumeric) {
buffer.write('"');
}
buffer.write(value.toString());
if (!isNumeric) {
buffer.write('"');
}
}
}
/**
* Formats the inner part of this parameter as
* Well
* Known Text (WKT). This method doesn't need to be overridden, since the formatter
* already know how to {@linkplain Formatter#append(GeneralParameterValue) format parameters}.
*/
@Override
public String formatWKT(final Formatter formatter) {
return "PARAMETER";
}
}