blob: 2341753c6261b274df33b7fcc09ed5ed2e715268 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.parameter;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.HashSet;
import java.util.Collections;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.InvalidParameterNameException;
import org.apache.sis.internal.jaxb.referencing.CC_OperationParameterGroup;
import org.apache.sis.internal.metadata.MetadataUtilities;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
import static org.apache.sis.util.Utilities.deepEquals;
/**
* The definition of a group of related parameters used by an operation method.
* {@code DefaultParameterDescriptorGroup} instances are immutable and thus thread-safe.
*
* <h2>Instantiation</h2>
* Parameter descriptors are usually pre-defined by the SIS library and available through the following methods:
*
* <ul>
* <li>{@link org.apache.sis.referencing.operation.DefaultOperationMethod#getParameters()}</li>
* </ul>
*
* If nevertheless a {@code ParameterDescriptorGroup} needs to be instantiated directly,
* then the {@link ParameterBuilder} class may make the task easier.
*
* <div class="note"><b>Example:</b>
* The following example declares the parameters for a <cite>Mercator (variant A)</cite> projection method
* valid from 80°S to 84°N on all the longitude range (±180°).
*
* {@preformat java
* class Mercator {
* static final ParameterDescriptorGroup PARAMETERS;
* static {
* ParameterBuilder builder = new ParameterBuilder();
* builder.setCodeSpace(Citations.EPSG, "EPSG").setRequired(true);
* ParameterDescriptor<?>[] parameters = {
* builder.addName("Latitude of natural origin") .createBounded( -80, +84, 0, Units.DEGREE),
* builder.addName("Longitude of natural origin") .createBounded(-180, +180, 0, Units.DEGREE),
* builder.addName("Scale factor at natural origin").createStrictlyPositive(1, Units.UNITY),
* builder.addName("False easting") .create(0, Units.METRE),
* builder.addName("False northing") .create(0, Units.METRE)
* };
* builder.addIdentifier("9804") // Primary key in EPSG database.
* .addName("Mercator (variant A)") // EPSG name since October 2010.
* .addName("Mercator (1SP)") // EPSG name prior October 2010.
* .addName(Citations.OGC, "Mercator_1SP"); // Name found in some OGC specifications.
* PARAMETERS = builder.createGroup(parameters);
* }
* }
* }
* </div>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Johann Sorel (Geomatys)
* @version 0.6
*
* @see DefaultParameterValueGroup
* @see DefaultParameterDescriptor
*
* @since 0.4
* @module
*/
@XmlType(name = "OperationParameterGroupType")
@XmlRootElement(name = "OperationParameterGroup")
public class DefaultParameterDescriptorGroup extends AbstractParameterDescriptor implements ParameterDescriptorGroup {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 6058599597772994456L;
/**
* The {@linkplain #descriptors() parameter descriptors} for this group.
*
* <p><b>Consider this field as final!</b>
* This field is modified only at unmarshalling time by {@link #setDescriptors(GeneralParameterDescriptor[])}</p>
*
* @see #descriptors()
*/
private List<GeneralParameterDescriptor> descriptors;
/**
* Constructs a parameter group from a set of properties. The properties map is given unchanged to the
* {@linkplain AbstractParameterDescriptor#AbstractParameterDescriptor(Map, int, int) super-class constructor}.
* The following table is a reminder of main (not all) properties:
*
* <table class="sis">
* <caption>Recognized properties (non exhaustive list)</caption>
* <tr>
* <th>Property name</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
* <td>{@link #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
* <td>{@link org.opengis.util.GenericName} or {@link CharSequence} (optionally as array)</td>
* <td>{@link #getAlias()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
* <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
* <td>{@link #getIdentifiers()}</td>
* </tr>
* <tr>
* <td>"description"</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getDescription()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getRemarks()}</td>
* </tr>
* </table>
*
* @param properties the properties to be given to the new parameter group.
* @param minimumOccurs the {@linkplain #getMinimumOccurs() minimum number of times} that values
* for this parameter group are required, or 0 if no restriction.
* @param maximumOccurs the {@linkplain #getMaximumOccurs() maximum number of times} that values
* for this parameter group are required, or {@link Integer#MAX_VALUE} if no restriction.
* @param parameters the {@linkplain #descriptors() parameter descriptors} for this group.
*
* @throws InvalidParameterNameException if a parameter name is duplicated.
*/
public DefaultParameterDescriptorGroup(final Map<String,?> properties,
final int minimumOccurs, final int maximumOccurs, GeneralParameterDescriptor... parameters)
{
super(properties, minimumOccurs, maximumOccurs);
ArgumentChecks.ensureNonNull("parameters", parameters);
verifyNames(properties, parameters = parameters.clone());
descriptors = asList(parameters);
}
/**
* Constructs a group with the same parameters than another group. This is a convenience constructor for
* operations that expect the same parameters than another operation, but perform a different process.
*
* <div class="note"><b>Example:</b>
* the various <cite>"Coordinate Frame Rotation"</cite> variants (EPSG codes 1032, 1038 and 9607)
* expect the same parameters than their <cite>"Position Vector transformation"</cite> counterpart
* (EPSG codes 1033, 1037 and 9606) but perform the rotation in the opposite direction.</div>
*
* @param properties the properties to be given to the new parameter group.
* @param parameters the existing group from which to copy the {@linkplain #descriptors() parameter descriptors}.
*
* @since 0.7
*/
public DefaultParameterDescriptorGroup(final Map<String,?> properties, final ParameterDescriptorGroup parameters) {
super(properties, parameters.getMinimumOccurs(), parameters.getMaximumOccurs());
descriptors = parameters.descriptors(); // We will share the same instance if it is safe.
if (!(parameters instanceof DefaultParameterDescriptorGroup)
|| ((DefaultParameterDescriptorGroup) parameters).descriptors != descriptors)
{
// Note sure where the list come from, we are better to copy its content.
final GeneralParameterDescriptor[] p = descriptors.toArray(new GeneralParameterDescriptor[descriptors.size()]);
verifyNames(properties, p);
descriptors = asList(p);
}
}
/**
* Creates a mandatory parameter group without cloning the given array. This constructor shall
* be used only when we know that the given array is already a copy of the user-provided array.
*/
DefaultParameterDescriptorGroup(final Map<String,?> properties, final GeneralParameterDescriptor[] parameters) {
super(properties, 1, 1);
verifyNames(properties, parameters);
descriptors = asList(parameters);
}
/**
* Ensures that the given name array does not contain duplicate values.
*
* @param properties the properties given to the constructor, or {@code null} if unknown.
*/
private static void verifyNames(final Map<String,?> properties, final GeneralParameterDescriptor[] parameters) {
for (int i=0; i<parameters.length; i++) {
final GeneralParameterDescriptor parameter = parameters[i];
ArgumentChecks.ensureNonNullElement("parameters", i, parameter);
final String name = parameter.getName().getCode();
for (int j=0; j<i; j++) {
if (IdentifiedObjects.isHeuristicMatchForName(parameters[j], name)) {
throw new InvalidParameterNameException(Resources.forProperties(properties).getString(
Resources.Keys.DuplicatedParameterName_4, Verifier.getDisplayName(parameters[j]), j, name, i),
name);
}
}
}
}
/**
* Creates a new descriptor with the same values than the specified one.
* This copy constructor provides a way to convert an arbitrary implementation into a SIS one or a
* user-defined one (as a subclass), usually in order to leverage some implementation-specific API.
*
* <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
*
* @param descriptor the descriptor to shallow copy.
*
* @see #castOrCopy(ParameterDescriptorGroup)
*/
protected DefaultParameterDescriptorGroup(final ParameterDescriptorGroup descriptor) {
super(descriptor);
final List<GeneralParameterDescriptor> c = descriptor.descriptors();
if (descriptor instanceof DefaultParameterDescriptorGroup &&
((DefaultParameterDescriptorGroup) descriptor).descriptors == c)
{
descriptors = c; // Share the immutable instance (no need to clone).
} else {
descriptors = asList(c.toArray(new GeneralParameterDescriptor[c.size()]));
}
}
/**
* Returns the given array of parameters as an unmodifiable list.
*/
private static List<GeneralParameterDescriptor> asList(final GeneralParameterDescriptor[] parameters) {
switch (parameters.length) {
case 0: return Collections.emptyList();
case 1: return Collections.singletonList(parameters[0]);
case 2: // fall through
case 3: return UnmodifiableArrayList.wrap(parameters);
default: return new AsList(parameters);
}
}
/**
* The {@link DefaultParameterDescriptorGroup#descriptors} as an unmodifiable list.
* This class overrides {@link #contains(Object)} with a faster implementation based on {@link HashSet}.
* This optimizations is helpful for map projection implementations, which test often for a parameter validity.
*/
private static final class AsList extends UnmodifiableArrayList<GeneralParameterDescriptor> {
/** For compatibility with different versions. */
private static final long serialVersionUID = -2116304004367396735L;
/** The element as a set, created when first needed. */
private transient volatile Set<GeneralParameterDescriptor> asSet;
/** Constructs a list for the specified array. */
public AsList(final GeneralParameterDescriptor[] array) {
super(array);
}
/** Tests for the inclusion of the specified descriptor. */
@Override public boolean contains(final Object object) {
Set<GeneralParameterDescriptor> s = asSet;
if (s == null) {
asSet = s = new HashSet<>(this); // No synchronization: not a big problem if created twice.
}
return s.contains(object);
}
}
/**
* Returns a SIS group implementation with the same values than the given arbitrary implementation.
* If the given object is {@code null}, then this method returns {@code null}.
* Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
* Otherwise a new SIS implementation is created and initialized to the values of the given object.
*
* @param object the object to get as a SIS implementation, or {@code null} if none.
* @return a SIS implementation containing the values of the given object (may be the
* given object itself), or {@code null} if the argument was null.
*/
public static DefaultParameterDescriptorGroup castOrCopy(final ParameterDescriptorGroup object) {
return (object == null) || (object instanceof DefaultParameterDescriptorGroup)
? (DefaultParameterDescriptorGroup) object : new DefaultParameterDescriptorGroup(object);
}
/**
* Returns the GeoAPI interface implemented by this class.
* The SIS implementation returns {@code ParameterDescriptorGroup.class}.
*
* <div class="note"><b>Note for implementers:</b>
* Subclasses usually do not need to override this method since GeoAPI does not define {@code ParameterDescriptorGroup}
* sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their own
* set of interfaces.</div>
*
* @return {@code ParameterDescriptorGroup.class} or a user-defined sub-interface.
*/
@Override
public Class<? extends ParameterDescriptorGroup> getInterface() {
return ParameterDescriptorGroup.class;
}
/**
* Returns all parameters in this group.
*
* @return the parameter descriptors in this group.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public List<GeneralParameterDescriptor> descriptors() {
return descriptors; // Unmodifiable.
}
/**
* Returns the first parameter in this group for the specified name.
* This method does not search in sub-groups.
*
* @param name the name of the parameter to search for.
* @return the parameter for the given identifier name.
* @throws ParameterNotFoundException if there is no parameter for the given name.
*/
@Override
@SuppressWarnings("null")
public GeneralParameterDescriptor descriptor(final String name) throws ParameterNotFoundException {
// Quick search for an exact match.
ArgumentChecks.ensureNonNull("name", name);
for (final GeneralParameterDescriptor param : descriptors) {
if (name.equals(param.getName().getCode())) {
return param;
}
}
// More costly search before to give up.
GeneralParameterDescriptor fallback = null, ambiguity = null;
for (final GeneralParameterDescriptor param : descriptors) {
if (IdentifiedObjects.isHeuristicMatchForName(param, name)) {
if (fallback == null) {
fallback = param;
} else {
ambiguity = param;
}
}
}
if (fallback != null && ambiguity == null) {
return fallback;
}
throw new ParameterNotFoundException(ambiguity != null
? Errors.format(Errors.Keys.AmbiguousName_3,
IdentifiedObjects.toString(fallback.getName()),
IdentifiedObjects.toString(ambiguity.getName()), name)
: Resources.format(Resources.Keys.ParameterNotFound_2, Verifier.getDisplayName(this), name), name);
}
/**
* Creates a new instance of {@linkplain DefaultParameterValueGroup parameter value group}
* initialized with the {@linkplain DefaultParameterDescriptor#getDefaultValue default values}.
* The {@linkplain DefaultParameterValueGroup#getDescriptor() parameter descriptor} for the
* created group will be {@code this} object.
*
* @return a new parameter instance initialized to the default value.
*/
@Override
public ParameterValueGroup createValue() {
return new DefaultParameterValueGroup(this);
}
/**
* Compares the specified object with this parameter group for equality.
*
* @return {@inheritDoc}
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true; // Optimization for a common case.
}
if (super.equals(object, mode)) {
switch (mode) {
case STRICT: {
return descriptors.equals(((DefaultParameterDescriptorGroup) object).descriptors);
}
default: {
return deepEquals(descriptors(), ((ParameterDescriptorGroup) object).descriptors(), mode);
}
}
}
return false;
}
/**
* Invoked by {@link #hashCode()} for computing the hash code when first needed.
*
* @return {@inheritDoc}
*/
@Override
protected long computeHashCode() {
return super.computeHashCode() + descriptors.hashCode();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// XML support with JAXB ////////
//////// ////////
//////// The following methods are invoked by JAXB using reflection (even if ////////
//////// they are private) or are helpers for other methods invoked by JAXB. ////////
//////// Those methods can be safely removed if Geographic Markup Language ////////
//////// (GML) support is not needed. ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Constructs a new object in which every attributes are set to a null value or an empty list.
* <strong>This is not a valid object.</strong> This constructor is strictly reserved to JAXB
* and to {@link DefaultParameterValueGroup}, which will assign values later.
*
* @see #setDescriptors(GeneralParameterDescriptor[])
*/
DefaultParameterDescriptorGroup() {
descriptors = Collections.emptyList();
}
/**
* Invoked by JAXB for getting the parameters to marshal.
*/
@XmlElement(name = "parameter", required = true)
private GeneralParameterDescriptor[] getDescriptors() {
final List<GeneralParameterDescriptor> descriptors = descriptors(); // Give to user a chance to override.
return descriptors.toArray(new GeneralParameterDescriptor[descriptors.size()]);
}
/**
* Invoked by JAXB for setting the unmarshalled parameter descriptors.
*/
private void setDescriptors(final GeneralParameterDescriptor[] parameters) {
if (descriptors.isEmpty()) {
verifyNames(null, parameters);
descriptors = asList(parameters);
} else {
MetadataUtilities.propertyAlreadySet(DefaultParameterValue.class, "setDescriptors", "parameter");
}
}
/**
* Merges the given parameter descriptors with the descriptors currently in this group.
* The descriptors are set twice during {@link DefaultParameterValueGroup} unmarshalling:
*
* <ol>
* <li>First, the descriptors are set during unmarshalling of this {@code DefaultParameterDescriptorGroup}.
* But the value class of {@code ParameterDescriptor} components are unknown because this information
* is not part of GML.</li>
* <li>Next, this method is invoked during unmarshalling of the {@code DefaultParameterValueGroup} enclosing
* element with the descriptors found inside the {@code ParameterValue} components. The later do have the
* {@code valueClass} information, so we want to use them in replacement of descriptors of step 1.</li>
* </ol>
*
* @param fromValues descriptors declared in the {@code ParameterValue} instances of a {@code ParameterValueGroup}.
* @param replacements an {@code IdentityHashMap} where to store the replacements that the caller needs to apply in
* the {@code GeneralParameterValue} instances.
*/
final void merge(GeneralParameterDescriptor[] fromValues,
final Map<GeneralParameterDescriptor,GeneralParameterDescriptor> replacements)
{
fromValues = CC_OperationParameterGroup.merge(descriptors, fromValues, replacements);
verifyNames(null, fromValues);
descriptors = asList(fromValues);
}
}