| /* |
| * 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); |
| } |
| } |