/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.parameter;
import java.io.Writer;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Iterator;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.geotools.util.Utilities;
import org.geotools.io.TableWriter;
import org.geotools.referencing.wkt.Formattable;
import org.geotools.referencing.wkt.Formatter;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* Abstract parameter value or group of parameter values.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see AbstractParameterDescriptor
*/
public abstract class AbstractParameter extends Formattable
implements GeneralParameterValue, Serializable
{
/**
* Serial number for interoperability 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.
*/
public GeneralParameterDescriptor getDescriptor() {
return descriptor;
}
/**
* Makes sure that an argument is non-null. This method was already defined in
* {@link org.geotools.referencing.AbstractIdentifiedObject}, but is defined here again
* in order to get a more appropriate stack trace, and for access by class which do not
* inherit from {@link org.geotools.referencing.AbstractIdentifiedObject}.
*
* @param name Argument name.
* @param object User argument.
* @throws IllegalArgumentException if {@code object} is null.
*/
static void ensureNonNull(final String name, final Object object)
throws IllegalArgumentException
{
if (object == null) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, name));
}
}
/**
* Makes sure an array element is non-null. This is
* a convenience method for subclass constructors.
*
* @param name Argument name.
* @param array The array to look at.
* @param index Index of the element to check.
* @throws IllegalArgumentException if {@code array[i]} is null.
*/
static void ensureNonNull(final String name, final Object[] array, final int index)
throws IllegalArgumentException
{
if (array[index] == null) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NULL_ARGUMENT_$1, name+'['+index+']'));
}
}
/**
* Verify that the specified value is of the specified class.
*
* @param expectedClass the expected class.
* @param value The expected value, or {@code null}.
* @throws IllegalArgumentException if {@code value} is non-null and has a non-assignable
* class.
*/
static <T> void ensureValidClass(final Class<?> expectedClass, final Object value)
throws IllegalArgumentException
{
if (value != null) {
final Class<?> valueClass = value.getClass();
if (!expectedClass.isAssignableFrom(valueClass)) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_CLASS_$2, valueClass, expectedClass));
}
}
}
/**
* 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(ErrorKeys.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 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().equals(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}, which should be overridden by subclasses.
*/
@Override
public final 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();
}
/**
* Write the content of this parameter to the specified table. This method make it easier
* to align values properly than overriding the {@link #toString} method. The table's columns
* are defined as below:
* <ol>
* <li>The parameter name</li>
* <li>The separator</li>
* <li>The parameter value</li>
* </ol>
*
* <P>The default implementation is suitable for most cases. However, subclasses are free to
* override this method with the following idiom:</P>
*
* <blockquote><pre>
* table.{@linkplain TableWriter#write(String) write}("<var>parameter name</var>");
* table.{@linkplain TableWriter#nextColumn() nextColumn}()
* table.{@linkplain TableWriter#write(String) write}('=');
* table.{@linkplain TableWriter#nextColumn() nextColumn}()
* table.{@linkplain TableWriter#write(String) write}("<var>parameter value</var>");
* table.{@linkplain TableWriter#nextLine() nextLine}()
* </pre></blockquote>
*
* @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 Geotools'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(':');
table.nextColumn();
TableWriter inner = null;
for (final Iterator it=((ParameterValueGroup) this).values().iterator(); it.hasNext();) {
final GeneralParameterValue value = (GeneralParameterValue) it.next();
if (value instanceof AbstractParameter) {
if (inner == null) {
inner = new TableWriter(table, 1);
}
((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(System.getProperty("line.separator", "\r"));
}
}
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; i++) {
if (i != 0) {
buffer.write(", ");
}
append(buffer, Array.get(value, i));
}
if (length > limit) {
buffer.write(", ...");
}
buffer.write('}');
} else {
final boolean isNumeric = (value instanceof Number);
if (!isNumeric) {
buffer.write('"');
}
buffer.write(value.toString());
if (!isNumeric) {
buffer.write('"');
}
}
}
/**
* Format the inner part of this parameter as
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
* Known Text</cite> (WKT)</A>. This method doesn't need to be overridden, since the formatter
* already know how to {@linkplain Formatter#append(GeneralParameterValue) format parameters}.
*/
@Override
protected final String formatWKT(final Formatter formatter) {
return "PARAMETER";
}
}