/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.
*/
package org.geotools.parameter;
import java.text.MessageFormat;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.ResourceBundle;
import java.util.Locale;
import java.net.URI;
import java.net.URISyntaxException;
import java.awt.image.RenderedImage;
import javax.media.jai.util.Range;
import javax.media.jai.EnumeratedParameter;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.ParameterListDescriptor;
import javax.media.jai.RegistryElementDescriptor;
import javax.media.jai.registry.RenderedRegistryMode;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.OnLineFunction;
import org.opengis.metadata.citation.Role;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.citation.ResponsibleParty;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.util.InternationalString;
import org.opengis.util.GenericName;
import org.geotools.util.Utilities;
import org.geotools.util.NameFactory;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.opengis.parameter.InvalidParameterNameException;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.metadata.iso.citation.ContactImpl;
import org.geotools.metadata.iso.citation.CitationImpl;
import org.geotools.metadata.iso.citation.OnLineResourceImpl;
import org.geotools.metadata.iso.citation.ResponsiblePartyImpl;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.XArray;
/**
* Wraps a JAI's {@link ParameterListDescriptor}. This adaptor is provided for interoperability
* with <A HREF="http://java.sun.com/products/java-media/jai/">Java Advanced Imaging</A>. A JAI
* parameter list descriptor is part of an {@linkplain OperationDescriptor operation descriptor}.
* This adaptor make it easier to access parameters for a JAI operation through the general GeoAPI
* parameters framework.
*
* @since 2.2
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class ImagingParameterDescriptors extends DefaultParameterDescriptorGroup {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 2127050865911951239L;
/**
* Mapping between values of the "Vendor" resource (in OperationDescriptor)
* and the citation for know authorities.
*/
private static final Object[] AUTHORITIES = {
"com.sun.media.jai", Citations.JAI,
"org.geotools", Citations.GEOTOOLS,
"jaitools.media.jai",Citations.JAI
};
/**
* The default <cite>source type map</cite> as a (<code>{@linkplain RenderedImage}.class</code>,
* <code>{@linkplain GridCoverage}.class</code>) key-value pair. This is the default argument
* for wrapping a JAI operation in the
* {@value javax.media.jai.registry.RenderedRegistryMode#MODE_NAME} registry mode.
*/
@SuppressWarnings("unchecked")
public static final Map<Class<?>,Class<?>> DEFAULT_SOURCE_TYPE_MAP = (Map)
Collections.singletonMap(RenderedImage.class, GridCoverage.class);
/**
* The registry mode, usually {@value javax.media.jai.registry.RenderedRegistryMode#MODE_NAME}.
* This field is {@code null} if {@link #operation} is null.
*/
protected final String registryMode;
/**
* The JAI's operation descriptor, or {@code null} if none. This is usually an
* instance of {@link OperationDescriptor}, but this is not strictly required.
*/
protected final RegistryElementDescriptor operation;
/**
* The Java Advanced Imaging parameter descriptor. If {@link #operation} is non-null, then
* this attribute is defined by {@link RegistryElementDescriptor#getParameterListDescriptor}.
*/
protected final ParameterListDescriptor descriptor;
/**
* Constructs a parameter descriptor wrapping the specified JAI operation, including sources.
* The {@linkplain #getName name for this parameter group} will be inferred from the
* {@linkplain RegistryElementDescriptor#getName name of the supplied registry element}
* using the {@link #properties properties} method.
*
* The <cite>source type map</cite> default to a (<code>{@linkplain RenderedImage}.class</code>,
* <code>{@linkplain GridCoverage}.class</code>) key-value pair and the <cite>registry
* mode</cite> default to {@value javax.media.jai.registry.RenderedRegistryMode#MODE_NAME}.
*
* @param operation The JAI's operation descriptor, usually as an instance of
* {@link OperationDescriptor}.
*/
public ImagingParameterDescriptors(final RegistryElementDescriptor operation) {
this(properties(operation), operation, DEFAULT_SOURCE_TYPE_MAP,
RenderedRegistryMode.MODE_NAME);
}
/**
* Constructs a parameter descriptor wrapping the specified JAI operation, including sources.
* The {@linkplain #getName name for this parameter group} will be inferred from the
* {@linkplain RegistryElementDescriptor#getName name of the supplied registry element}
* using the {@link #properties properties} method.
*
* The <cite>source type map</cite> default to a (<code>{@linkplain RenderedImage}.class</code>,
* <code>{@linkplain GridCoverage}.class</code>) key-value pair and the <cite>registry
* mode</cite> default to {@value javax.media.jai.registry.RenderedRegistryMode#MODE_NAME}.
*
* @param operation The JAI's operation descriptor, usually as an instance of
* {@link OperationDescriptor}.
* @param extension Additional parameters to put in this descriptor, or {@code null} if none.
* If a parameter has the same name than an {@code operation} parameter, then the
* extension overrides the later.
*
* @since 2.4
*/
public ImagingParameterDescriptors(final RegistryElementDescriptor operation,
final Collection<ParameterDescriptor> extension)
{
this(properties(operation), operation, RenderedRegistryMode.MODE_NAME,
DEFAULT_SOURCE_TYPE_MAP, extension);
}
/**
* Constructs a parameter descriptor wrapping the specified JAI operation, including sources.
* The properties map is given unchanged to the
* {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
* @param operation The JAI's operation descriptor, usually as an instance of
* {@link OperationDescriptor}.
* @param sourceTypeMap Mapping from JAI source type to this group source type. Typically a
* singleton with the (<code>{@linkplain RenderedImage}.class</code>,
* <code>{@linkplain GridCoverage}.class</code>) key-value pair.
* @param registryMode The JAI's registry mode (usually
* {@value javax.media.jai.registry.RenderedRegistryMode#MODE_NAME}).
*
* @deprecated Replaced by {@link #ImagingParameterDescriptors(Map,
* RegistryElementDescriptor,String,Map,Collection}.
*/
public ImagingParameterDescriptors(final Map<String,?> properties,
final RegistryElementDescriptor operation,
final Map<Class<?>,Class<?>> sourceTypeMap,
final String registryMode)
{
this(properties,
operation.getParameterListDescriptor(registryMode),
operation, registryMode, sourceTypeMap, null);
}
/**
* Constructs a parameter descriptor wrapping the specified JAI operation, including sources.
* The properties map is given unchanged to the
* {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
* @param operation The JAI's operation descriptor, usually as an instance of
* {@link OperationDescriptor}.
* @param registryMode The JAI's registry mode (usually
* {@value javax.media.jai.registry.RenderedRegistryMode#MODE_NAME}).
* @param sourceTypeMap Mapping from JAI source type to this group source type. Typically a
* singleton with the (<code>{@linkplain RenderedImage}.class</code>,
* <code>{@linkplain GridCoverage}.class</code>) key-value pair.
* @param extension Additional parameters to put in this descriptor, or {@code null} if none.
* If a parameter has the same name than an {@code operation} parameter, then the
* extension overrides the later.
*
* @since 2.4
*/
public ImagingParameterDescriptors(final Map<String,?> properties,
final RegistryElementDescriptor operation,
final String registryMode,
final Map<Class<?>,Class<?>> sourceTypeMap,
final Collection<ParameterDescriptor> extension)
{
this(properties,
operation.getParameterListDescriptor(registryMode),
operation, registryMode, sourceTypeMap, extension);
}
/**
* Constructs a parameter descriptor wrapping the specified JAI parameter list descriptor.
* The properties map is given unchanged to the
* {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
* @param descriptor The JAI descriptor.
*/
public ImagingParameterDescriptors(final Map<String,?> properties,
final ParameterListDescriptor descriptor)
{
this(properties, descriptor, null, null, null, null);
}
/**
* Constructs a parameter descriptor wrapping the specified JAI descriptor.
* If {@code operation} is non-null, then {@code descriptor} should be derived from it.
* This constructor is private in order to ensure this condition.
*/
private ImagingParameterDescriptors(final Map<String,?> properties,
final ParameterListDescriptor descriptor,
final RegistryElementDescriptor operation,
final String registryMode,
final Map<Class<?>,Class<?>> sourceTypeMap,
final Collection<ParameterDescriptor> extension)
{
super(properties, 1, 1, asDescriptors(descriptor, operation, registryMode, sourceTypeMap, extension));
this.descriptor = descriptor;
this.operation = operation;
this.registryMode = registryMode;
}
/**
* Infers from the specified JAI operation a set of properties that can be given to the
* {@linkplain #ImagingParameterDescriptors(Map,RegistryElementDescriptor,Map,String)
* constructor}. The returned map includes values (when available) for the following keys:
* <p>
* <table border="1">
* <tr>
* <th nowrap>Key</th>
* <th nowrap>Inferred from</th>
* </tr>
* <tr>
* <td>{@link #NAME_KEY NAME_KEY}</td>
* <td>{@linkplain RegistryElementDescriptor#getName descriptor name}</td>
* </tr>
* <tr>
* <td>{@link #ALIAS_KEY ALIAS_KEY}</td>
* <td>{@code "Vendor"} (for the {@linkplain GenericName#getScope scope}) and
* {@code "LocalName"} {@linkplain OperationDescriptor#getResources resources}</td>
* </tr>
* <tr>
* <td>{@link Identifier#AUTHORITY_KEY AUTHORITY_KEY}</td>
* <td>{@linkplain Citations#JAI JAI} or {@linkplain Citations#GEOTOOLS Geotools}
* inferred from the vendor, extented with {@code "DocURL"}
* {@linkplain OperationDescriptor#getResources resources} as
* {@linkplain ResponsibleParty#getContactInfo contact information}.</td></td>
* </tr>
* <tr>
* <td>{@link ReferenceIdentifier#VERSION_KEY VERSION_KEY}</td>
* <td>{@code "Version"} {@linkplain OperationDescriptor#getResources resources}</td>
* </tr>
* <tr>
* <td>{@link #REMARKS_KEY REMARKS_KEY}</td>
* <td>{@code "Description"} {@linkplain OperationDescriptor#getResources resources}</td>
* </tr>
* </table>
* <p>
* For JAI image operation (for example {@code "Add"}, the end result is fully-qualified name
* like {@code "JAI:Add"} and one alias like {@code "com.sun.media.jai.Add"}.
* <p>
* This method returns a modifiable map. Users can safely changes its content in order to
* select for example a different name.
*/
public static Map<String,Object> properties(final RegistryElementDescriptor operation) {
String name = operation.getName();
final Map<String,Object> properties = new HashMap<String,Object>();
if (operation instanceof OperationDescriptor) {
/*
* Gets the vendor name (if available) using US locale in order to get something as
* close as possible to a kind of "locale-independent" string. This string will be
* used in order to remove the prefix (if any) from the global name, for example in
* "org.geotools.Combine" operation name. We can remove the prefix because it will
* appears in the GenericName's scope below (as an alias).
*/
final OperationDescriptor op = (OperationDescriptor) operation;
final ResourceBundle bundle = op.getResourceBundle(Locale.getDefault());
String vendor = op.getResourceBundle(Locale.US).getString("Vendor");
Citation authority = null;
if (vendor != null) {
vendor = vendor.trim();
name = ImagingParameterDescription.trimPrefix(name, vendor);
for (int i=0; i<AUTHORITIES.length; i+=2) {
if (vendor.equalsIgnoreCase((String) AUTHORITIES[i])) {
authority = (Citation) AUTHORITIES[i+1];
break;
}
}
}
if (authority == null) {
/*
* jump off, since it will break the creation of CitationImpl
* with a missleading "null source" message.
*/
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NO_SUCH_AUTHORITY_CODE_$2, "AUTHORITIES", vendor));
}
/*
* If we are able to construct an URI, replaces the contact info for the first (and only
* the first) responsible party. Exactly one responsible party should be presents, since
* the authority is one of the hard-coded AUTHORITIES list above. We replace completely
* the contact info; for example we do not retain any telephone number because it would
* be a mismatch with the new URI purpose (this new URI do not links to information that
* can be used to contact the individual or organisation - it is information about an
* image operation, and I'm not sure that anyone wants to phone to an image operation).
*/
final InternationalString description;
description = new ImagingParameterDescription(op, "Description", null);
try {
final URI uri = new URI(bundle.getString("DocURL"));
final OnLineResourceImpl resource = new OnLineResourceImpl(uri);
resource.setFunction(OnLineFunction.INFORMATION);
resource.setDescription(description);
final CitationImpl citation = new CitationImpl(authority);
final Collection<ResponsibleParty> parties = citation.getCitedResponsibleParties();
final ResponsibleParty oldParty;
if (true) {
final Iterator<ResponsibleParty> it = parties.iterator();
if (it.hasNext()) {
oldParty = it.next();
it.remove(); // This party will be re-injected with a new URI below.
}
else {
oldParty = null;
}
}
final ResponsiblePartyImpl party = new ResponsiblePartyImpl(oldParty);
party.setRole(Role.RESOURCE_PROVIDER);
party.setContactInfo(new ContactImpl(resource));
parties.add(party);
authority = (Citation) citation.unmodifiable();
} catch (URISyntaxException exception) {
// Invalid URI syntax. Ignore, since this property
// was really just for information purpose.
}
/*
* At this point, all properties have been created. Stores them in the map.
* The name should be stored as a String (not as an Identifier), otherwise
* the version and the authority would be ignored. For JAI image operation,
* the end result is fully-qualified name like "JAI:Add" and one alias like
* "com.sun.media.jai.Add".
*/
final GenericName alias = NameFactory.create(new InternationalString[] {
new ImagingParameterDescription(op, "Vendor" , null), // Scope name
new ImagingParameterDescription(op, "LocalName", "Vendor") // Local name
}, '.');
properties.put(ALIAS_KEY, alias);
properties.put(REMARKS_KEY, description);
properties.put(ReferenceIdentifier.VERSION_KEY, bundle.getString("Version"));
properties.put(Identifier.AUTHORITY_KEY, authority);
}
properties.put(NAME_KEY, name);
return properties;
}
/**
* Returns the JAI's parameters as {@link ParameterDescriptor} objects.
* This method is a work around for RFE #4093999 in Sun's bug database
* ("Relax constraint on placement of this()/super() call in constructors").
*/
private static ParameterDescriptor[] asDescriptors(
final ParameterListDescriptor descriptor,
final RegistryElementDescriptor operation, final String registryMode,
final Map<Class<?>,Class<?>> sourceTypeMap,
final Collection<ParameterDescriptor> extension)
{
/*
* Creates the list of JAI descriptor to be replaced by user-supplied parameters.
* Note that this map will be modified again in the remaining of this method.
*/
ensureNonNull("descriptor", descriptor);
final Map<String,ParameterDescriptor> replacements =
new LinkedHashMap<String,ParameterDescriptor>();
if (extension != null) {
for (final ParameterDescriptor d : extension) {
final String name = d.getName().getCode().trim().toLowerCase();
if (replacements.put(name, d) != null) {
throw new InvalidParameterNameException(Errors.format(
ErrorKeys.DUPLICATED_VALUES_$1, name), name);
}
}
}
/*
* JAI considers sources as a special kind of parameters, while GridCoverageProcessor makes
* no distinction. If the registry element "operation" is really a JAI's OperationDescriptor
* (which should occurs most of the time), prepend the JAI's sources before all ordinary
* parameters. In addition, transforms the source type if needed.
*/
final int numSources;
final int numParameters = descriptor.getNumParameters();
final Map<String,CharSequence> properties = new HashMap<String,CharSequence>();
ParameterDescriptor[] desc;
if (operation instanceof OperationDescriptor) {
final OperationDescriptor op = (OperationDescriptor) operation;
final String[] names = op.getSourceNames();
final Class<?>[] types = op.getSourceClasses(registryMode);
numSources = op.getNumSources();
desc = new ParameterDescriptor[numParameters + numSources];
for (int i=0; i<numSources; i++) {
Class<?> type = (Class<?>) sourceTypeMap.get(types[i]);
if (type == null) {
type = types[i];
}
String name = names[i];
properties.clear();
if (numSources == 1) {
/*
* If there is only one source argument, rename for example "Source0"
* as "Source" for better compliance with OpenGIS usage. However, we
* will keep the original name as an alias.
*/
final int length = name.length();
if (length != 0) {
final char c = name.charAt(length-1);
if (c=='0' || c=='1') {
properties.put(ALIAS_KEY, name);
name = name.substring(0, length-1);
}
}
}
properties.put(NAME_KEY, name);
desc[i] = new DefaultParameterDescriptor(properties, type,
null, // validValues
null, // defaultValue
null, // minimum
null, // maximum
null, // unit
true); // required
}
} else {
numSources = 0;
desc = new ParameterDescriptor[numParameters];
}
/*
* Source parameters completed. Now get the ordinary parameters. We check for
* replacement of JAI parameters by user-supplied parameters in this process.
*/
final String[] names = descriptor.getParamNames();
final Class<?>[] classes = descriptor.getParamClasses();
final Object[] defaults = descriptor.getParamDefaults();
for (int i=0; i<numParameters; i++) {
final String name = names[i];
final ParameterDescriptor replacement = replacements.remove(name.trim().toLowerCase());
if (replacement != null) {
desc[i + numSources] = replacement;
continue;
}
final Class<?> type = classes[i];
final Range range = descriptor.getParamValueRange(name);
final Comparable<?> min, max;
if (range != null) {
min = range.getMinValue();
max = range.getMaxValue();
} else {
min = null;
max = null;
}
EnumeratedParameter[] validValues;
if (EnumeratedParameter.class.isAssignableFrom(type)) try {
validValues = descriptor.getEnumeratedParameterValues(name);
} catch (UnsupportedOperationException exception) {
validValues = null;
} else {
validValues = null;
}
Object defaultValue = defaults[i];
if (defaultValue == ParameterListDescriptor.NO_PARAMETER_DEFAULT) {
defaultValue = null;
}
properties.clear();
properties.put(NAME_KEY, name);
if (operation instanceof OperationDescriptor) {
final ImagingParameterDescription remark =
new ImagingParameterDescription((OperationDescriptor) operation, i);
if (remark.exists()) {
properties.put(REMARKS_KEY, remark);
}
}
desc[i + numSources] = new DefaultParameterDescriptor(properties,
type, validValues, defaultValue, min, max, null, true);
}
/*
* Appends the remaining extra descriptors. Note that some descriptor may
* have been removed from the 'replacements' map before we reach this point.
*/
int i = desc.length;
desc = XArray.resize(desc, i + replacements.size());
for (final ParameterDescriptor d : replacements.values()) {
desc[i++] = d;
}
return desc;
}
/**
* Creates a new instance of parameter value group. A JAI {@link javax.media.jai.ParameterList}
* is created for holding parameter values, and wrapped into an {@link ImagingParameters}
* instance.
*
* @return The new value initialized to the default value.
*/
@Override
public ParameterValueGroup createValue() {
return new ImagingParameters(this);
}
/**
* Compares the specified object with this parameter group for equality.
*
* @param object The object to compare to {@code this}.
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
if (object == this) {
// Slight optimization
return true;
}
if (super.equals(object, compareMetadata)) {
final ImagingParameterDescriptors that = (ImagingParameterDescriptors) object;
return Utilities.equals(this.operation, that.operation) &&
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 super.hashCode() ^ descriptor.hashCode();
}
}