/* * 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. */ package org.geotoolkit.parameter; import java.util.Map; import java.util.Set; import java.util.List; import java.util.HashMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.measure.Unit; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ObjectConverters; import org.apache.sis.util.UnconvertibleObjectException; import org.opengis.util.InternationalString; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.metadata.quality.ConformanceResult; import org.opengis.metadata.Identifier; import org.geotoolkit.lang.Static; import org.geotoolkit.resources.Errors; import org.geotoolkit.resources.Vocabulary; import org.geotoolkit.resources.Descriptions; import org.apache.sis.util.logging.Logging; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.metadata.iso.quality.DefaultConformanceResult; import org.apache.sis.util.iso.DefaultNameSpace; import static org.apache.sis.parameter.Parameters.castOrWrap; /** * Helper methods for working with the parameter API from {@link org.opengis.parameter} package. * This class provides methods for {@linkplain #search searching}, {@linkplain #ensureSet setting * a value}, <i>etc.</i> from a parameter name. * Names in simple {@code String} form are preferred over the full {@code ParameterDescriptor} * object because: * * <ul> * <li><p>The parameter descriptor may not be always available. For example a user may looks for * the {@code "semi_major"} axis length (because it is documented in OGC specification under * that name) but doesn't know and doesn't care about who is providing the implementation. In * such case, he doesn't have the parameter's descriptor. He only have the parameter's name, * and creating a descriptor from that name (a descriptor independent of any implementation) * is tedious.</p></li> * <li><p>Parameter descriptors are implementation-dependent. For example if a user searches for * the above-cited {@code "semi_major"} axis length using the {@linkplain * org.geotoolkit.referencing.operation.provider.MapProjection#SEMI_MAJOR Geotk's descriptor} * for that parameter, we will fail to find this parameter in any alternative * {@link ParameterValueGroup} implementations. This is against GeoAPI's inter-operability goal.</p></li> * </ul> * * When a method in this class expects a full {@code ParameterDescriptor} object in argument * (for example {@link #value(ParameterDescriptor, ParameterValueGroup)}), the * {@linkplain ParameterDescriptor#getName descriptor name} and alias are used for the search - this * class does <strong>not</strong> look for strictly equal {@code ParameterDescriptor} objects. * * @author Jody Garnett (Refractions) * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.20 * * @since 2.1 * @module */ public final class Parameters extends Static { /** * Small number for floating point comparisons. */ private static final double EPS = 1E-8; /** * The explanation for conformance results. */ private static final InternationalString EXPLAIN = Descriptions.formatInternational(Descriptions.Keys.ConformanceMeansValidParameters); /** * An empty parameter group. This group contains no parameters. * * @deprecated Will be removed. */ @Deprecated public static final ParameterDescriptorGroup EMPTY_GROUP = new org.apache.sis.parameter.DefaultParameterDescriptorGroup( Collections.singletonMap(ParameterDescriptorGroup.NAME_KEY, Vocabulary.format(Vocabulary.Keys.Empty)), 1, 1); /** * Do not allow instantiation of this utility class. */ private Parameters() { } /** * Checks a parameter value against its {@linkplain ParameterDescriptor parameter descriptor}. * This method compares the {@linkplain ParameterValue#getValue() value} against the expected * {@linkplain ParameterDescriptor#getValueClass value class}, the * [{@linkplain ParameterDescriptor#getMinimumValue minimum} … * {@linkplain ParameterDescriptor#getMaximumValue maximum}] range and the set of * {@linkplain ParameterDescriptor#getValidValues valid values}. * <p> * This is a convenience method for testing a single parameter value assuming that its * descriptor is correct. It returns a {@code boolean} because in case of failure, the * faulty parameter is known: it is the one given in argument to this method. For a more * generic test which can work on group of parameters and provide more details about which * parameter failed and why, use the * {@link #isValid(GeneralParameterValue, GeneralParameterDescriptor)} method instead. * * @param value The parameter to test. * @return {@code true} if the given parameter is valid. * * @category verification */ public static boolean isValid(final ParameterValue<?> value) { // LGPL return isValidValue(value.getDescriptor(), value.getValue(), false) == null; } /** * Checks a parameter value against its descriptor, and returns an error message if it is * not conformant. This method is similar to {@link AbstractParameter#ensureValidValue}. * If one implementation is modified, the other one should be updated accordingly. * * @param <T> The expected type of value. * @param value The parameter to test. * @param localized {@code true} if a fully localized message is wanted in case of failure, * or {@code false} for a sentinel value (not useful for reporting to the user). * @param descriptor The descriptor to use for fetching the conditions. * @return {@code null} if the given parameter is valid, or an error message otherwise. */ private static <T> InternationalString isValidValue(final ParameterDescriptor<T> descriptor, final Object value, final boolean localized) // LGPL { /* * Accept null values only if explicitly authorized. */ if (value == null) { if (descriptor.getMinimumOccurs() == 0) { return null; // Test pass. } return localized ? Errors.formatInternational(Errors.Keys.NoParameter_1, AbstractParameter.getName(descriptor)) : EXPLAIN; } /* * Check the type. */ final Class<?> type = value.getClass(); final Class<T> expected = descriptor.getValueClass(); if (!expected.isAssignableFrom(type)) { return localized ? Errors.formatInternational(Errors.Keys.IllegalClass_2, type, expected) : EXPLAIN; } final T typedValue = expected.cast(value); /* * Check the range (if any). */ final Comparable<T> minimum = descriptor.getMinimumValue(); final Comparable<T> maximum = descriptor.getMaximumValue(); if ((minimum != null && minimum.compareTo(typedValue) > 0) || (maximum != null && maximum.compareTo(typedValue) < 0)) { return localized ? Errors.formatInternational(Errors.Keys.ValueOutOfBounds_3, value, minimum, maximum) : EXPLAIN; } /* * Check the enumeration (if any). */ final Set<T> validValues = descriptor.getValidValues(); if (validValues!=null && !validValues.contains(value)) { return localized ? Errors.formatInternational(Errors.Keys.IllegalArgument_2, AbstractParameter.getName(descriptor), value) : EXPLAIN; } return null; // Test pass. } /** * Checks a parameter value against the conditions specified by the given descriptor. * If the given parameter is an instance of {@link ParameterValue}, then this method * compares the {@linkplain ParameterValue#getValue() value} against the expected * {@linkplain ParameterDescriptor#getValueClass value class}, the * [{@linkplain ParameterDescriptor#getMinimumValue minimum} … * {@linkplain ParameterDescriptor#getMaximumValue maximum}] range and the set of * {@linkplain ParameterDescriptor#getValidValues valid values}. * If this method is an instance of {@link ParameterValueGroup}, then the check * is applied recursively for every parameter in this group. * * @param value The parameter to test. * @param descriptor The descriptor to use for fetching the conditions. * @return A conformance result having the attribute <code>{@linkplain ConformanceResult#pass() * pass}=true</code> if the parameter is valid. If the parameter is not valid, then * the result {@linkplain ConformanceResult#getExplanation() explanation} gives the * raison. * * @since 3.05 * @category verification */ public static ConformanceResult isValid(final GeneralParameterValue value, final GeneralParameterDescriptor descriptor) { /* * Create a conformance result initialized to a successful check. */ final DefaultConformanceResult result = new DefaultConformanceResult( descriptor.getName().getAuthority(), EXPLAIN, true); final GeneralParameterValue failure = isValid(value, descriptor, result); /* * If the validity test failed, update the conformance result accordingly. */ if (failure != null) { final Identifier name = failure.getDescriptor().getName(); result.setExplanation(Descriptions.formatInternational( Descriptions.Keys.NonConformParameter_2, name, result.getExplanation())); result.setSpecification(name.getAuthority()); result.setPass(Boolean.FALSE); } return result; } /** * Checks a parameter value against the conditions specified by the given descriptor. * In case of failure, the explanation message of the given {@code result} is updated * with the cause, and this method returns the descriptor of the parameter that failed * the check. * * @param value The parameter to test. * @param descriptor The descriptor to use for fetching the conditions. * @param result The conformance result to update in case of failure. * @return {@code null} in case of success, or the faulty parameter in case of failure. */ private static GeneralParameterValue isValid(final GeneralParameterValue value, final GeneralParameterDescriptor descriptor, final DefaultConformanceResult result) { if (descriptor instanceof ParameterDescriptor<?>) { /* * For a single parameter (the most common case), the value shall be an instance * of ParameterValue. If it is not, then the error message will be formatted at * the end of this method. */ if (value instanceof ParameterValue<?>) { final InternationalString failure = isValidValue((ParameterDescriptor<?>) descriptor, ((ParameterValue<?>) value).getValue(), true); if (failure != null) { result.setExplanation(failure); return value; } return null; // The test pass. } } else if (descriptor instanceof ParameterDescriptorGroup) { /* * For a group, the value shall be an instance of ParameterValueGroup. If it * is not, then the error message will be formatted at the end of this method. */ if (value instanceof ParameterValueGroup) { final Map<GeneralParameterDescriptor,Integer> count = new HashMap<>(); final ParameterDescriptorGroup group = (ParameterDescriptorGroup) descriptor; for (final GeneralParameterValue element : ((ParameterValueGroup) value).values()) { final String name = getName(element.getDescriptor(), group); final GeneralParameterDescriptor desc; try { desc = group.descriptor(name); } catch (ParameterNotFoundException e) { result.setExplanation(Errors.formatInternational( Errors.Keys.UnexpectedParameter_1, name)); return value; } final GeneralParameterValue failure = isValid(element, desc, result); if (failure != null) { return failure; } // Count the occurrence of parameters. Integer old = count.put(desc, 1); if (old != null) { count.put(desc, old + 1); } } /* * All parameters pass their individual test. * Now check their cardinality. */ for (final GeneralParameterDescriptor desc : group.descriptors()) { final Integer nw = count.get(desc); final int n = (nw != null) ? nw : 0; final int min = desc.getMinimumOccurs(); final int max = desc.getMaximumOccurs(); if (n < min || n > max) { final String name = desc.getName().getCode(); final short key; final Object[] param; if (n == 0) { key = Errors.Keys.NoParameter_1; param = new Object[] {name}; } else { key = Errors.Keys.IllegalOccursForParameter_4; param = new Object[] {name, nw, min, max}; } result.setExplanation(Errors.formatInternational(key, param)); return value; } } return null; // The test pass. } } /* * If we reach this point, the type of the parameter value is not consistent * with the type of the descriptor. Format the error message. */ final Class<? extends GeneralParameterValue> type; if (value instanceof ParameterValue<?>) { type = ParameterValue.class; } else if (value instanceof ParameterValueGroup) { type = ParameterValueGroup.class; } else { type = GeneralParameterValue.class; } result.setExplanation(Errors.formatInternational(Errors.Keys.IllegalOperationForValueClass_1, type)); return value; } /** * Returns the name of the given parameter, using the authority code space expected by * the given group if possible. * * @param parameter The parameter for which the name is wanted. * @param group The group to use for determining the authority code space. * @return The name of the given parameter. * * @since 3.05 */ private static String getName(final GeneralParameterDescriptor parameter, final ParameterDescriptorGroup group) { String name = IdentifiedObjects.getName(parameter, group.getName().getAuthority()); if (name == null) { name = parameter.getName().getCode(); } return name; } /** * Returns the value of the given parameter in the given group if defined, or create a * default parameter otherwise. This method is equivalent to: * * {@preformat java * return cast(group.parameter(parameter.getName().getCode())); * } * * except that this method tries to use an identifier of the same authority than the * given {@code group} if possible. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter object. * @throws ParameterNotFoundException if the parameter is not valid for the given group. * * @see ParameterValueGroup#parameter(String) * * @since 3.18 * @category query * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static ParameterValue<?> getOrCreate(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { return castOrWrap(group).getOrCreate(parameter); } /** * Returns the parameter value as an object for the given descriptor. * The method uses the {@link ParameterValue#getValue()} method for fetching the value. * * @param <T> The type of the parameter value. * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @since 3.00 * @category query * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static <T> T value(final ParameterDescriptor<T> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { return castOrWrap(group).getValue(parameter); } /** * Returns the parameter value as a string for the given descriptor. * This method uses the {@link ParameterValue#stringValue()} method for fetching the value. * Note that the result may be different than the above {@link #value value} method if the * {@code ParameterValue} implementation performs string conversions. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @category query * @since 3.00 * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static String stringValue(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { try { return castOrWrap(group).stringValue((ParameterDescriptor) parameter); } catch (IllegalStateException e) { return null; // Deprecated practice, but done for now for compatibility with previous behaviour. } } /** * Returns the parameter value as a boolean for the given descriptor. * This method uses the {@link ParameterValue#booleanValue()} method for fetching the value. * Note that the result may be different than the above {@link #value value} method if the * {@code ParameterValue} implementation performs numeric conversions. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @category query * @since 3.18 * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static Boolean booleanValue(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { try { return castOrWrap(group).booleanValue((ParameterDescriptor) parameter); } catch (IllegalStateException e) { return null; // Deprecated practice, but done for now for compatibility with previous behaviour. } } /** * Returns the parameter value as an integer for the given descriptor. * This method uses the {@link ParameterValue#intValue()} method for fetching the value. * Note that the result may be different than the above {@link #value value} method if the * {@code ParameterValue} implementation performs numeric conversions. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @category query * @since 3.00 * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static Integer integerValue(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { try { return castOrWrap(group).intValue((ParameterDescriptor) parameter); } catch (IllegalStateException e) { return null; // Deprecated practice, but done for now for compatibility with previous behaviour. } } /** * Returns the parameter value as a list of integers for the given descriptor. * This method uses the {@link ParameterValue#intValueList()} method for fetching the values. * Note that the result may be different than the above {@link #value value} method if the * {@code ParameterValue} implementation performs numeric conversions. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @category query * @since 3.18 * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static int[] integerValueList(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { try { return castOrWrap(group).intValueList((ParameterDescriptor) parameter); } catch (IllegalStateException e) { return null; // Deprecated practice, but done for now for compatibility with previous behaviour. } } /** * Returns the parameter value as a floating point number for the given descriptor. * Values are automatically converted into the standard units specified by the * supplied {@code param} argument. * <p> * This method uses the {@link ParameterValue#doubleValue(Unit)} method for fetching the value. * Note that the result may be different than the above {@link #value value} method if the * {@code ParameterValue} implementation performs numeric conversions. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code NaN} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @category query * @since 3.00 * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static double doubleValue(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { try { return castOrWrap(group).doubleValue((ParameterDescriptor) parameter); } catch (IllegalStateException e) { return Double.NaN; // Deprecated practice, but done for now for compatibility with previous behaviour. } } /** * Returns the parameter value as a list of floating point numbers for the given descriptor. * Values are automatically converted into the standard units specified by the * supplied {@code param} argument. * <p> * This method uses the {@link ParameterValue#doubleValueList(Unit)} method for fetching the * values. Note that the result may be different than the above {@link #value value} method * if the {@code ParameterValue} implementation performs numeric conversions. * * @param parameter The parameter to look for. * @param group The parameter value group to search into. * @return The requested parameter value, or {@code null} if the parameter * is optional and the user didn't provided any value. * @throws ParameterNotFoundException if the parameter is mandatory and not found in the group. * * @category query * @since 3.18 * * @deprecated Moved to Apache SIS {@link org.apache.sis.parameter.Parameters}. */ @Deprecated public static double[] doubleValueList(final ParameterDescriptor<?> parameter, final ParameterValueGroup group) throws ParameterNotFoundException { try { return castOrWrap(group).doubleValueList((ParameterDescriptor) parameter); } catch (IllegalStateException e) { return null; // Deprecated practice, but done for now for compatibility with previous behaviour. } } /** * Searches all parameters with the specified name. The given {@code name} is * compared against parameter {@linkplain GeneralParameterDescriptor#getName name} and * {@linkplain GeneralParameterDescriptor#getAlias alias}. This method search recursively * in subgroups up to the specified depth: * <p> * <ul> * <li>If {@code maxDepth} is equal to 0, then this method returns {@code param} * if and only if it matches the specified name.</li> * <li>If {@code maxDepth} is equal to 1 and {@code param} is an instance of * {@link ParameterDescriptorGroup}, then this method checks all elements * in this group but not in subgroups.</li> * <li>...</li> * <li>If {@code maxDepth} is a high number (e.g. 100), then this method checks all elements * in all subgroups up to the specified depth, which is likely to be never reached. In * this case, {@code maxDepth} can be seen as a safeguard against never ending loops, for * example if parameters graph contains cyclic entries.</li> * </ul> * * @param parameter The parameter to inspect. * @param name The name of the parameter to search for. See the * <a href="#skip-navbar_top">class javadoc</a> for a rational about the usage * of name as a key instead of {@linkplain ParameterDescriptor descriptor}. * @param maxDepth The maximal depth while descending down the parameter tree. * @return The set (possibly empty) of parameters with the given name. * * @category query */ public static List<GeneralParameterValue> search(final GeneralParameterValue parameter, final String name, int maxDepth) // LGPL { final List<GeneralParameterValue> list = new ArrayList<>(); search(parameter, name, maxDepth, list); return list; } /** * Implementation of the search algorithm. The result is stored in the supplied set. */ private static void search(final GeneralParameterValue parameter, final String name, final int maxDepth, final Collection<GeneralParameterValue> list) { if (maxDepth >= 0) { if (IdentifiedObjects.isHeuristicMatchForName(parameter.getDescriptor(), name)) { list.add(parameter); } if ((maxDepth != 0) && (parameter instanceof ParameterValueGroup)) { for (final GeneralParameterValue value : ((ParameterValueGroup) parameter).values()) { search(value, name, maxDepth-1, list); } } } } /** * Gets the * {@linkplain ParameterDescriptor#getName name}-{@linkplain ParameterValue#getValue value} * pairs. This method copies all parameter values into the supplied {@code destination} map. * Keys are parameter names as {@link String} objects, and values are parameter values as * arbitrary objects. * <p> * If the given parameters is a group having sub-groups, then all subgroups are extracted * recursively with their keys prefixed by the sub-groups name followed by the * {@linkplain DefaultNameSpace#DEFAULT_SEPARATOR default namespace separator}. * <p> * This map provides a convenient way to copy the content of a {@link ParameterValueGroup} * to a {@link java.util.Properties} instance. * * @param parameters The parameters to extract values from. * @param destination The destination map. * * @category query * @since 3.18 */ public static void copy(final GeneralParameterValue parameters, final Map<? super String, Object> destination) { copy(parameters, destination, null, 0, true); } /** * Implementation of the public {@link #copy(GeneralParameterValue, Map)} method * for recursive invocations. * * @param parameters The parameters to extract values from. * @param destination The destination map. * @param buffer A buffer which contains the prefix of every keys, or {@code null}. * @param base The number of valid characters in the buffer. * @param isRoot {@code true} if the group to be added is the root, or {@code false} * on recursive invocations. * @return The buffer, which may have been created by some recursive invocation. * * @category query */ private static StringBuilder copy(final GeneralParameterValue parameters, final Map<? super String, Object> destination, StringBuilder buffer, int base, final boolean isRoot) { final String name = parameters.getDescriptor().getName().getCode(); if (parameters instanceof ParameterValue<?>) { final Object value = ((ParameterValue<?>) parameters).getValue(); String key = name; if (base > 0) { buffer.setLength(base); key = buffer.append(name).toString(); } final Object old = destination.put(key, value); if (old != null && !old.equals(value)) { // This code will fails to detect if a null value was explicitly supplied // previously. We assume that such case is uncommon and not a big deal. throw new IllegalArgumentException(Errors.format(Errors.Keys.InconsistentValue)); } } if (parameters instanceof ParameterValueGroup) { if (!isRoot) { if (buffer == null) { buffer = new StringBuilder(32); } buffer.setLength(base); base = buffer.append(name).append(DefaultNameSpace.DEFAULT_SEPARATOR).length(); } for (final GeneralParameterValue value : ((ParameterValueGroup) parameters).values()) { buffer = copy(value, destination, buffer, base, false); } } return buffer; } /** * Copies all parameter values from {@code source} to {@code target}. A typical usage of * this method is for transferring values from an arbitrary implementation to some specific * implementation (e.g. a parameter group implementation backed by a * {@link java.awt.image.renderable.ParameterBlock} for image processing operations). * * @param source The parameters to copy. * @param target Where to copy the source parameters. * * @category update * @since 2.2 * * @deprecated Moved to Apache SIS as {@link org.apache.sis.parameter.Parameters#copy(ParameterValueGroup, ParameterValueGroup). */ @Deprecated private static void copy(final ParameterValueGroup source, final ParameterValueGroup target) { for (final GeneralParameterValue param : source.values()) { final String name = param.getDescriptor().getName().getCode(); if (param instanceof ParameterValueGroup) { copy((ParameterValueGroup) param, target.addGroup(name)); } else { target.parameter(name).setValue(((ParameterValue<?>) param).getValue()); } } } /** * Ensures that a value is set for the parameter of the specified name. This method returns {@code true} * if the parameter value <i>has been</i> or <i>should be</i> changed (depending on the {@code force} * argument value), or {@code false} otherwise. More specifically this method performs the following steps: * * <ul> * <li>If the parameter for the given name has no value, unconditionally assign the given * value to that parameter and returns {@code true}.</li> * <li>Otherwise, compare the current parameter value with the specified value. * If the values are approximatively equal, do nothing and return {@code false}.</li> * <li>Otherwise there is a choice: * <ul> * <li>If the {@code force} argument is {@code true}, overwrite the current parameter * value with the given value.</li> * <li>Otherwise left the parameter value unchanged but log a warning.</li> * </ul> * Then return {@code true}.</li> * </ul> * * This method can be used when the same parameter values may be specified more than once * (for example from different sources of information), and we want to ensure that all * those values are consistent, without treating mismatches as fatal errors. * * @param parameters The set of projection parameters. * @param name The parameter name to set. * @param value The value to set, or to expect if the parameter is already set. * @param unit The value unit. * @param force {@code true} for forcing the parameter to the specified {@code value} in case of mismatch. * @return {@code true} if they were a mismatch (in which case the value has been updated if the {@code force} * argument is {@code true}), or {@code false} if the parameter has been left unchanged. * @throws ParameterNotFoundException If no parameter of the given name has been found. * * @category update * * @deprecated Not used anymore. To avoid because of arbitrary threshold. */ @Deprecated public static boolean ensureSet(final ParameterValueGroup parameters, final String name, final double value, final Unit<?> unit, boolean force) throws ParameterNotFoundException { final ParameterValue<?> parameter = parameters.parameter(name); try { final double current = parameter.doubleValue(unit); if (Math.abs(current / value - 1) <= EPS) { return false; } if (Double.isNaN(current)) { force = true; } } catch (IllegalStateException exception) { // No value was set for this parameter, and there is no default value. force = true; } // A value was set, but is different than the expected value. if (force) { parameter.setValue(value, unit); } else { Logging.log(Parameters.class, "ensureSet", new LogRecord(Level.FINE, Errors.format(Errors.Keys.ValueAlreadyDefined_1, name))); } return true; } /** * Convert a ParameterValueGroup in a Map of values. Map keys will be parameter names, and their associated value are * indeed map values. For nested Parameter group, a list of maps will be created inside the output map. Theorically, * this allows infinite group depth. * * @param source : Group of parametrs to convert. * @return A map which contains all parameters and groups of input Parameter group. */ public static Map<String,Object> toMap(final ParameterValueGroup source) { ArgumentChecks.ensureNonNull("source", source); final HashMap<String, Object> result = new HashMap<>(); String parameterName; for (GeneralParameterValue value : source.values()) { parameterName = value.getDescriptor().getName().getCode(); if (value instanceof ParameterValue) { result.put(parameterName, ((ParameterValue) value).getValue()); } else if (value instanceof ParameterValueGroup) { Collection<Map<String, Object>> subGroup = (List<Map<String, Object>>) result.get(parameterName); if (subGroup == null) { subGroup = new ArrayList<>(); result.put(parameterName, subGroup); } subGroup.add(toMap((ParameterValueGroup) value)); } } return result; } /** * Transform a Map in a ParameterValueGroup. A default parameter is first created and all key found in the map * that match the descriptor will be completed. */ public static ParameterValueGroup toParameter(final Map<String, ?> params, final ParameterDescriptorGroup desc) { ArgumentChecks.ensureNonNull("params", params); ArgumentChecks.ensureNonNull("desc", desc); return toParameter(params, desc, true); } /** * Transform a Map in a ParameterValueGroup. A default parameter is first created and all key found in the map * that match the descriptor will be completed. * * @param params The map containing entries to put in a ParameterValueGroup * @param desc The descriptor for the group to create. * @param checkMandatory if True, a parameter will be returned only if the map provide all mandatory parameters with * no default value. * @return A parameter value group matching input descriptor, filled with input map value. * Return null if input map is incompatible with input descriptor. * * @throws ParameterNotFoundException If the map does not contains all needed mandatory * parameters required by this descriptor. * @throws UnconvertibleObjectException If an error occurred while putting a value from input map * to target Parameter group. */ public static ParameterValueGroup toParameter(final Map<String, ?> params, final ParameterDescriptorGroup desc, final boolean checkMandatory) throws ParameterNotFoundException, UnconvertibleObjectException { ArgumentChecks.ensureNonNull("params", params); ArgumentChecks.ensureNonNull("desc", desc); if (checkMandatory) { for (GeneralParameterDescriptor de : desc.descriptors()) { if (de.getMinimumOccurs() > 0 && !(params.containsKey(de.getName().getCode()))) { if (de instanceof ParameterDescriptor && ((ParameterDescriptor) de).getDefaultValue() == null) { //a mandatory parameter is not present throw new ParameterNotFoundException("A mandatory parameter "+de.getName()+" was not found in the input parameters.", de.getName().getCode()); } } } } final ParameterValueGroup parameter = desc.createValue(); GeneralParameterDescriptor subDesc; for (final Map.Entry<String, ?> entry : params.entrySet()) { try { subDesc = desc.descriptor(entry.getKey()); } catch (ParameterNotFoundException ex) { //do nothing, the map may contain other values for other uses continue; } if (subDesc instanceof ParameterDescriptorGroup) { if (entry.getValue() instanceof Map) { parameter.values().add( toParameter((Map) entry.getValue(), (ParameterDescriptorGroup) subDesc, checkMandatory)); } else if (entry.getValue() instanceof Collection) { final List values = new ArrayList((Collection) entry.getValue()); final int nbGroups = values.size(); //already exist groups int nbParamGroups = parameter.groups(entry.getKey()).size(); //create missing groups if (nbGroups > nbParamGroups) { int toAdd = nbGroups - nbParamGroups; for (int i = 0; i < toAdd; i++) { parameter.addGroup(entry.getKey()); } } // convert and copy map values into parameter groups final List<ParameterValueGroup> paramGroups = parameter.groups(entry.getKey()); for (int i = 0; i < nbGroups; i++) { Object valObj = values.get(i); if (valObj instanceof Map) { ParameterValueGroup newGroup = toParameter((Map) valObj, (ParameterDescriptorGroup) subDesc, checkMandatory); copy(newGroup, paramGroups.get(i)); } else { throw new IllegalArgumentException("Illegal value for parameter " + entry.getKey() + ". It's a parameter group, so we should have a nested map as input."); } } } else { throw new IllegalArgumentException("Illegal value for parameter " + entry.getKey() + ". It's a parameter group, so we should have a nested map as input."); } } else if (subDesc instanceof ParameterDescriptor) { final ParameterDescriptor pdesc = (ParameterDescriptor) subDesc; try { final ParameterValue param = Parameters.getOrCreate(pdesc, parameter); param.setValue( ObjectConverters.convert(entry.getValue(), pdesc.getValueClass())); } catch (ParameterNotFoundException e) { // If mandatory check is activated, possible errors at this point have already been checked. continue; } } else { throw new IllegalArgumentException("Unsupported parameter type."); } } return parameter; } }