001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.parameter;
017:
018: // J2SE dependencies and extensions
019: import java.lang.reflect.Array;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Collections;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.LinkedHashMap;
026: import java.util.Map;
027: import java.util.Set;
028: import java.util.logging.Level;
029: import java.util.logging.Logger;
030: import java.util.logging.LogRecord;
031: import javax.units.Unit;
032:
033: // OpenGIS dependencies
034: import org.opengis.parameter.GeneralParameterDescriptor;
035: import org.opengis.parameter.GeneralParameterValue;
036: import org.opengis.parameter.ParameterDescriptor;
037: import org.opengis.parameter.ParameterDescriptorGroup;
038: import org.opengis.parameter.ParameterValue;
039: import org.opengis.parameter.ParameterValueGroup;
040: import org.opengis.parameter.ParameterNotFoundException;
041: import org.opengis.parameter.InvalidParameterTypeException;
042:
043: // Geotools dependencies
044: import org.geotools.referencing.AbstractIdentifiedObject;
045: import org.geotools.util.logging.Logging;
046:
047: /**
048: * Utility class for methods helping implementing, and working with the
049: * parameter API from {@link org.opengis.parameter} package.
050: * <p>
051: * <h3>Design note</h3>
052: * This class contains some methods working on a specific parameter in a group (e.g.
053: * {@linkplain #search searching}, {@linkplain #ensureSet setting a value}, <cite>etc.</cite>).
054: * Parameters are identified by their {@linkplain ParameterDescriptor#getName name} instead of
055: * their full {@linkplain ParameterDescriptor descriptor} object, because:
056: * <ul>
057: * <li>The parameter descriptor may not be always available. For example a user may looks for
058: * the {@code "semi_major"} axis length (because it is documented in OGC specification under
059: * that name) but doesn't know and doesn't care about who is providing the implementation. In
060: * such case, he doesn't have the parameter's descriptor. He only have the parameter's name,
061: * and creating a descriptor from that name (a descriptor independent of any implementation)
062: * is tedious.</li>.
063: * <li>Parameter descriptors are implementation-dependent. For example if a user searchs for
064: * the above-cited {@code "semi_major"} axis length using the {@linkplain
065: * org.geotools.referencing.operation.projection.MapProjection.AbstractProvider#SEMI_MAJOR
066: * Geotools's descriptor} for this parameter, we will fail to find this parameter in any
067: * alternative {@link ParameterValueGroup} implementations. This is against GeoAPI's
068: * inter-operability goal.</li>
069: * </ul>
070: * <p>
071: * The above doesn't mean that parameter's descriptor should not be used. They are used for
072: * inspecting meta-data about parameters, not as a key for searching parameters in a group.
073: * Since each parameter's name should be unique in a given parameter group (because
074: * {@linkplain ParameterDescriptor#getMaximumOccurs maximum occurs} is always 1 for single
075: * parameter), the parameter name is a suffisient key.
076: *
077: * @since 2.1
078: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/parameter/Parameters.java $
079: * @version $Id: Parameters.java 27862 2007-11-12 19:51:19Z desruisseaux $
080: * @author Jody Garnett (Refractions Research)
081: * @author Martin Desruisseaux
082: */
083: public class Parameters {
084: /**
085: * Small number for floating point comparaisons.
086: */
087: private static final double EPS = 1E-8;
088:
089: /**
090: * An empty parameter group. This group contains no parameters.
091: */
092: public static ParameterDescriptorGroup EMPTY_GROUP = new DefaultParameterDescriptorGroup(
093: "empty", // TODO: localize
094: new GeneralParameterDescriptor[0]);
095:
096: /**
097: * Checks a parameter value against its {@linkplain ParameterDescriptor parameter descriptor}.
098: * This method takes care of handling checking arrays and collections against parameter
099: * descriptor.
100: * <br><br>
101: * When the {@linkplain ParameterDescriptor#getValueClass value class} is an array (like
102: * {@code double[].class}) or a {@linkplain Collection collection} (like {@code List.class}),
103: * the descriptor
104: * {@linkplain ParameterDescriptor#getMinimumValue minimum value},
105: * {@linkplain ParameterDescriptor#getMaximumValue maximum value} and
106: * {@linkplain ParameterDescriptor#getValidValues valid values}
107: * will be used to check the elements.
108: *
109: * @param parameter The parameter to test.
110: * @return true if parameter is valid.
111: *
112: * @see Parameter#ensureValidValue
113: */
114: public static boolean isValid(final ParameterValue parameter) {
115: final ParameterDescriptor descriptor = (ParameterDescriptor) parameter
116: .getDescriptor();
117: final Object value = parameter.getValue();
118: final Class type = (value == null) ? Void.TYPE : value
119: .getClass();
120: final Class kind = descriptor.getValueClass();
121:
122: if (kind.isInstance(value)) {
123: return false; // value not of the correct type
124: }
125: if (type.isArray()) {
126: // handle checking elements in an aray
127: final int length = Array.getLength(value);
128: for (int i = 0; i < length; i++) {
129: if (!isValidValue(Array.get(value, i), descriptor)) {
130: return false;
131: }
132: }
133: } else if (value instanceof Collection) {
134: // handle checking elements in a collection
135: final Collection collection = (Collection) value;
136: for (final Iterator i = collection.iterator(); i.hasNext();) {
137: if (!isValidValue(i.next(), descriptor)) {
138: return false;
139: }
140: }
141: } else {
142: isValidValue(value, descriptor);
143: }
144: return true;
145: }
146:
147: /**
148: * Called on a single {@linkplain ParameterValue parameter value}, or on elements of a
149: * parameter value. This method ensures that
150: * {@linkplain ParameterDescriptor#getMinimumValue minimum value},
151: * {@linkplain ParameterDescriptor#getMaximumValue maximum value} and
152: * {@linkplain ParameterDescriptor#getValidValues valid values}
153: * all think the provided value is okay.
154: *
155: * @param value The value to test.
156: * @param descriptor The descriptor for the value.
157: * @return true if parameter is valid.
158: *
159: * @see Parameter#ensureValidValue
160: */
161: private static boolean isValidValue(final Object value,
162: final ParameterDescriptor descriptor) {
163: final Class type = (value == null) ? Void.TYPE : value
164: .getClass();
165: final Class expected = descriptor.getValueClass();
166: final Set validValues = descriptor.getValidValues();
167: if (validValues != null && !validValues.contains(value)) {
168: return false;
169: }
170: final Comparable min = descriptor.getMinimumValue();
171: if (min != null && min.compareTo(value) > 0) {
172: return false;
173: }
174: final Comparable max = descriptor.getMaximumValue();
175: if (max != null && max.compareTo(value) < 0) {
176: return false;
177: }
178: return true;
179: }
180:
181: /**
182: * Searchs all parameters with the specified name. The given {@code name} is compared against
183: * parameter {@link GeneralParameterDescriptor#getName name} and
184: * {@link GeneralParameterDescriptor#getAlias alias}. This method search recursively
185: * in subgroups up to the specified depth:
186: * <p>
187: * <ul>
188: * <li>If {@code maxDepth} is equals to 0, then this method returns {@code param}
189: * if and only if it matches the specified name.</li>
190: * <li>If {@code maxDepth} is equals to 1 and {@code param} is an instance of
191: * {@link ParameterDescriptorGroup}, then this method checks all elements
192: * in this group but not in subgroups.</li>
193: * <li>...</li>
194: * <li>If {@code maxDepth} is a high number (e.g. 100), then this method checks all elements
195: * in all subgroups up to the specified depth, which is likely to be never reached. In
196: * this case, {@code maxDepth} can be seen as a safeguard against never ending loops, for
197: * example if parameters graph contains cyclic entries.</li>
198: * </ul>
199: *
200: * @param param The parameter to inspect.
201: * @param name The name of the parameter to search for. See the class javadoc
202: * for a rational about the usage of name as a key instead of
203: * {@linkplain ParameterDescriptor descriptor}.
204: * @return The set (possibly empty) of parameters with the given name.
205: */
206: public static List search(final GeneralParameterValue param,
207: final String name, int maxDepth) {
208: final List list = new ArrayList();
209: search(param, name, maxDepth, list);
210: return list;
211: }
212:
213: /**
214: * Implementation of the search algorithm. The result is stored in the supplied set.
215: */
216: private static void search(final GeneralParameterValue param,
217: final String name, final int maxDepth, final Collection list) {
218: if (maxDepth >= 0) {
219: if (AbstractIdentifiedObject.nameMatches(param
220: .getDescriptor(), name)) {
221: list.add(param);
222: }
223: if (maxDepth != 0 && param instanceof ParameterValueGroup) {
224: for (Iterator it = ((ParameterValueGroup) param)
225: .values().iterator(); it.hasNext();) {
226: search((GeneralParameterValue) it.next(), name,
227: maxDepth - 1, list);
228: }
229: }
230: }
231: }
232:
233: /**
234: * Copies all parameter values from {@code source} to {@code target}. A typical usage of
235: * this method is for transfering values from an arbitrary implementation to some specific
236: * implementation (e.g. a parameter group implementation backed by a
237: * {@link java.awt.image.renderable.ParameterBlock} for image processing operations).
238: *
239: * @since 2.2
240: */
241: public static void copy(final ParameterValueGroup source,
242: final ParameterValueGroup target) {
243: for (final Iterator it = source.values().iterator(); it
244: .hasNext();) {
245: final GeneralParameterValue param = (GeneralParameterValue) it
246: .next();
247: final String name = param.getDescriptor().getName()
248: .getCode();
249: if (param instanceof ParameterValueGroup) {
250: copy((ParameterValueGroup) param, target.addGroup(name));
251: } else {
252: target.parameter(name).setValue(
253: ((ParameterValue) param).getValue());
254: }
255: }
256: }
257:
258: /**
259: * Gets a flat view of
260: * {@linkplain ParameterDescriptor#getName name}-{@linkplain ParameterValue#getValue value}
261: * pairs. This method copies all parameter values into the supplied {@code destination} map.
262: * Keys are parameter names as {@link String} objects, and values are parameter values as
263: * arbitrary objects. All subgroups (if any) are extracted recursively.
264: *
265: * @param parameters The parameters to extract values from.
266: * @param destination The destination map, or {@code null} for a default one.
267: * @return {@code destination}, or a new map if {@code destination} was null.
268: */
269: public static Map toNameValueMap(
270: final GeneralParameterValue parameters, Map destination) {
271: if (destination == null) {
272: destination = new LinkedHashMap();
273: }
274: if (parameters instanceof ParameterValue) {
275: final ParameterValue param = (ParameterValue) parameters;
276: final Object value = param.getValue();
277: final Object old = destination.put(param.getDescriptor()
278: .getName().getCode(), value);
279: if (old != null && !old.equals(value)) {
280: // TODO: This code will fails to detect if a null value was explicitly supplied
281: // previously. We assume that this case should be uncommon and not a big deal.
282: throw new IllegalArgumentException("Inconsistent value"); // TODO: localize.
283: }
284: }
285: if (parameters instanceof ParameterValueGroup) {
286: final ParameterValueGroup group = (ParameterValueGroup) parameters;
287: for (final Iterator it = group.values().iterator(); it
288: .hasNext();) {
289: destination = toNameValueMap((GeneralParameterValue) it
290: .next(), destination);
291: }
292: }
293: return destination;
294: }
295:
296: /**
297: * Ensures that the specified parameter is set. The {@code value} is set if and only if
298: * no value were already set by the user for the given {@code name}.
299: * <p>
300: * The {@code force} argument said what to do if the named parameter is already set. If the
301: * value matches, nothing is done in all case. If there is a mismatch and {@code force} is
302: * {@code true}, then the parameter is overridden with the specified {@code value}. Otherwise,
303: * the parameter is left unchanged but a warning is logged with the {@link Level#FINE FINE}
304: * level.
305: *
306: * @param parameters The set of projection parameters.
307: * @param name The parameter name to set.
308: * @param value The value to set, or to expect if the parameter is already set.
309: * @param unit The value unit.
310: * @param force {@code true} for forcing the parameter to the specified {@code value}
311: * is case of mismatch.
312: * @return {@code true} if the were a mismatch, or {@code false} if the parameters can be
313: * used with no change.
314: */
315: public static boolean ensureSet(
316: final ParameterValueGroup parameters, final String name,
317: final double value, final Unit unit, final boolean force) {
318: final ParameterValue parameter;
319: try {
320: parameter = parameters.parameter(name);
321: } catch (ParameterNotFoundException ignore) {
322: /*
323: * Parameter not found. This exception should not occurs most of the time.
324: * If it occurs, we will not try to set the parameter here, but the same
325: * exception is likely to occurs at MathTransform creation time. The later
326: * is the expected place for this exception, so we will let it happen there.
327: */
328: return false;
329: }
330: try {
331: if (Math.abs(parameter.doubleValue(unit) / value - 1) <= EPS) {
332: return false;
333: }
334: } catch (InvalidParameterTypeException exception) {
335: /*
336: * The parameter is not a floating point value. Don't try to set it. An exception is
337: * likely to be thrown at MathTransform creation time, which is the expected place.
338: */
339: return false;
340: } catch (IllegalStateException exception) {
341: /*
342: * No value were set for this parameter, and there is no default value.
343: */
344: parameter.setValue(value, unit);
345: return true;
346: }
347: /*
348: * A value were set, but is different from the expected value.
349: */
350: if (force) {
351: parameter.setValue(value, unit);
352: } else {
353: // TODO: localize
354: final LogRecord record = new LogRecord(Level.FINE,
355: "Axis length mismatch.");
356: record.setSourceClassName(Parameters.class.getName());
357: record.setSourceMethodName("ensureSet");
358: Logging.getLogger("org.geotools.parameter").log(record);
359: }
360: return true;
361: }
362: }
|