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
019: import java.io.FilterWriter;
020: import java.io.IOException;
021: import java.io.Writer;
022: import java.lang.reflect.Array;
023: import java.text.DateFormat;
024: import java.text.NumberFormat;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.Date;
028: import java.util.Iterator;
029: import java.util.LinkedHashMap;
030: import java.util.List;
031: import java.util.Locale;
032: import java.util.Map;
033: import java.util.Set;
034:
035: // OpenGIS dependencies
036: import org.opengis.metadata.Identifier;
037: import org.opengis.parameter.GeneralParameterDescriptor;
038: import org.opengis.parameter.GeneralParameterValue;
039: import org.opengis.parameter.ParameterDescriptor;
040: import org.opengis.parameter.ParameterDescriptorGroup;
041: import org.opengis.parameter.ParameterValue;
042: import org.opengis.parameter.ParameterValueGroup;
043: import org.opengis.referencing.IdentifiedObject;
044: import org.opengis.referencing.operation.OperationMethod;
045: import org.opengis.util.InternationalString;
046: import org.opengis.util.GenericName;
047:
048: // Geotools dependencies
049: import org.geotools.io.TableWriter;
050: import org.geotools.measure.Angle;
051: import org.geotools.measure.AngleFormat;
052: import org.geotools.resources.Arguments;
053: import org.geotools.resources.Utilities;
054: import org.geotools.resources.XArray;
055: import org.geotools.resources.i18n.Vocabulary;
056: import org.geotools.resources.i18n.VocabularyKeys;
057:
058: /**
059: * Format {@linkplain ParameterDescriptorGroup parameter descriptors} or
060: * {@linkplain ParameterValueGroup parameter values} in a tabular format.
061: * This writer assumes a monospaced font and an encoding capable to provide
062: * drawing box characters (e.g. unicode).
063: *
064: * @since 2.1
065: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/parameter/ParameterWriter.java $
066: * @version $Id: ParameterWriter.java 23632 2006-12-29 22:13:51Z desruisseaux $
067: * @author Martin Desruisseaux
068: */
069: public class ParameterWriter extends FilterWriter {
070: /**
071: * The locale.
072: */
073: private Locale locale = Locale.getDefault();
074:
075: /**
076: * The formatter to use for numbers. Will be created only when first needed.
077: */
078: private transient NumberFormat numberFormat;
079:
080: /**
081: * The formatter to use for dates. Will be created only when first needed.
082: */
083: private transient DateFormat dateFormat;
084:
085: /**
086: * The formatter to use for angles. Will be created only when first needed.
087: */
088: private transient AngleFormat angleFormat;
089:
090: /**
091: * Creates a new formatter writting parameters to the
092: * {@linkplain System#out default output stream}.
093: */
094: public ParameterWriter() {
095: this (Arguments.getWriter(System.out));
096: }
097:
098: /**
099: * Creates a new formatter writting parameters to the specified output stream.
100: */
101: public ParameterWriter(final Writer out) {
102: super (out);
103: }
104:
105: /**
106: * Prints the elements of an operation to the
107: * {@linkplain System#out default output stream}.
108: * This is a convenience method for <code>new
109: * ParameterWriter().{@linkplain #format(OperationMethod) format}(operation)</code>.
110: */
111: public static void print(final OperationMethod operation) {
112: final ParameterWriter writer = new ParameterWriter();
113: try {
114: writer.format(operation);
115: } catch (IOException exception) {
116: // Should never happen, since we are writting to System.out.
117: throw new AssertionError(exception);
118: }
119: }
120:
121: /**
122: * Prints the elements of a descriptor group to the
123: * {@linkplain System#out default output stream}.
124: * This is a convenience method for <code>new
125: * ParameterWriter().{@linkplain #format(ParameterDescriptorGroup)
126: * format}(descriptor)</code>.
127: */
128: public static void print(final ParameterDescriptorGroup descriptor) {
129: final ParameterWriter writer = new ParameterWriter();
130: try {
131: writer.format(descriptor);
132: } catch (IOException exception) {
133: // Should never happen, since we are writting to System.out.
134: throw new AssertionError(exception);
135: }
136: }
137:
138: /**
139: * Prints the elements of a parameter group to the
140: * {@linkplain System#out default output stream}.
141: * This is a convenience method for <code>new
142: * ParameterWriter().{@linkplain #format(ParameterValueGroup)
143: * format}(values)</code>.
144: */
145: public static void print(final ParameterValueGroup values) {
146: final ParameterWriter writer = new ParameterWriter();
147: try {
148: writer.format(values);
149: } catch (IOException exception) {
150: // Should never happen, since we are writting to System.out.
151: throw new AssertionError(exception);
152: }
153: }
154:
155: /**
156: * Prints the elements of an operation to the output stream.
157: *
158: * @param operation The operation method to format.
159: * @throws IOException if an error occured will writing to the stream.
160: */
161: public void format(final OperationMethod operation)
162: throws IOException {
163: synchronized (lock) {
164: format(operation.getName().getCode(), operation
165: .getParameters(), null);
166: }
167: }
168:
169: /**
170: * Prints the elements of a descriptor group to the output stream.
171: *
172: * @param descriptor The descriptor group to format.
173: * @throws IOException if an error occured will writing to the stream.
174: */
175: public void format(final ParameterDescriptorGroup descriptor)
176: throws IOException {
177: synchronized (lock) {
178: format(descriptor.getName().getCode(), descriptor, null);
179: }
180: }
181:
182: /**
183: * Prints the elements of a parameter group to the output stream.
184: *
185: * @param values The parameter group to format.
186: * @throws IOException if an error occured will writing to the stream.
187: */
188: public void format(final ParameterValueGroup values)
189: throws IOException {
190: final ParameterDescriptorGroup descriptor = (ParameterDescriptorGroup) values
191: .getDescriptor();
192: synchronized (lock) {
193: // TODO: remove cast when we will be allowe to use J2SE 1.5.
194: format(descriptor.getName().getCode(), descriptor, values);
195: }
196: }
197:
198: /**
199: * Implementation of public {@code format} methods.
200: *
201: * @param name The group name, usually {@code descriptor.getCode().getName()}.
202: * @param descriptor The parameter descriptor. Should be equals to
203: * {@code values.getDescriptor()} if {@code values} is non null.
204: * @param values The parameter values, or {@code null} if none.
205: * @throws IOException if an error occured will writing to the stream.
206: */
207: private void format(final String name,
208: final ParameterDescriptorGroup group,
209: final ParameterValueGroup values) throws IOException {
210: /*
211: * Write the operation name (including aliases) before the table.
212: */
213: final String lineSeparator = System.getProperty(
214: "line.separator", "\n");
215: out.write(' ');
216: out.write(name);
217: out.write(lineSeparator);
218: Collection/*<GenericName>*/alias = group.getAlias();
219: if (alias != null) {
220: boolean first = true;
221: for (final Iterator i = alias.iterator(); i.hasNext();) {
222: final GenericName a = (GenericName) i.next();
223: out.write(first ? " alias " : " ");
224: out.write(a.toInternationalString().toString(locale));
225: out.write(lineSeparator);
226: first = false;
227: }
228: }
229: /*
230: * Format the table header (i.e. column names).
231: */
232: final Vocabulary resources = Vocabulary.getResources(locale);
233: final TableWriter table = new TableWriter(out, " \u2502 ");
234: table.setMultiLinesCells(true);
235: table.writeHorizontalSeparator();
236: table.write(resources.getString(VocabularyKeys.NAME));
237: table.nextColumn();
238: table.write(resources.getString(VocabularyKeys.CLASS));
239: table.nextColumn();
240: table.write("Minimum"); // TODO localize
241: table.nextColumn();
242: table.write("Maximum"); // TODO localize
243: table.nextColumn();
244: table
245: .write(resources
246: .getString((values == null) ? VocabularyKeys.DEFAULT_VALUE
247: : VocabularyKeys.VALUE));
248: table.nextColumn();
249: table.write("Units"); // TODO localize
250: table.nextLine();
251: table.nextLine('\u2550');
252: /*
253: * Format each element in the parameter group. If values were supplied, we will
254: * iterate through the values instead of the descriptor. We do it that way because
255: * the descriptor can't know which optional values are included and which one are
256: * omitted.
257: */
258: List deferredGroups = null;
259: final Object[] array1 = new Object[1];
260: final Collection elements = (values != null) ? values.values()
261: : group.descriptors();
262: for (final Iterator it = elements.iterator(); it.hasNext();) {
263: final Object element = it.next();
264: final GeneralParameterValue generalValue;
265: final GeneralParameterDescriptor generalDescriptor;
266: if (values != null) {
267: generalValue = (GeneralParameterValue) element;
268: generalDescriptor = generalValue.getDescriptor();
269: } else {
270: generalValue = null;
271: generalDescriptor = (GeneralParameterDescriptor) element;
272: }
273: /*
274: * If the current element is a group, we will format it later (after
275: * all ordinary elements) in order avoid breaking the table layout.
276: */
277: if (generalDescriptor instanceof ParameterDescriptorGroup) {
278: if (deferredGroups == null) {
279: deferredGroups = new ArrayList();
280: }
281: deferredGroups.add(element);
282: continue;
283: }
284: /*
285: * Format the element name, including all alias (if any).
286: * Each alias will be formatted on its own line.
287: */
288: final Identifier identifier = generalDescriptor.getName();
289: table.write(identifier.getCode());
290: alias = generalDescriptor.getAlias();
291: if (alias != null) {
292: for (final Iterator i = alias.iterator(); i.hasNext();) {
293: final GenericName a = (GenericName) i.next();
294: if (!identifier.equals(a)) {
295: table.write(lineSeparator);
296: table.write(a.asLocalName()
297: .toInternationalString().toString(
298: locale));
299: }
300: }
301: }
302: table.nextColumn();
303: /*
304: * Format the current element as an ordinary descriptor. If we are iterating
305: * over the descriptors rather than values, then the "value" column will be
306: * filled with the default value specified in descriptors.
307: */
308: if (generalDescriptor instanceof ParameterDescriptor) {
309: final ParameterDescriptor descriptor = (ParameterDescriptor) generalDescriptor;
310: table.write(Utilities.getShortName(descriptor
311: .getValueClass()));
312: table.nextColumn();
313: table.setAlignment(TableWriter.ALIGN_RIGHT);
314: Object value = descriptor.getMinimumValue();
315: if (value != null) {
316: table.write(formatValue(value));
317: }
318: table.nextColumn();
319: value = descriptor.getMaximumValue();
320: if (value != null) {
321: table.write(formatValue(value));
322: }
323: table.nextColumn();
324: if (generalValue != null) {
325: value = ((ParameterValue) generalValue).getValue();
326: } else {
327: value = descriptor.getDefaultValue();
328: }
329: /*
330: * Wraps the value in an array. Because it may be an array of primitive
331: * type, we can't cast to Object[]. Then, each array's element will be
332: * formatted on its own line.
333: */
334: final Object array;
335: if (value != null && value.getClass().isArray()) {
336: array = value;
337: } else {
338: array = array1;
339: array1[0] = value;
340: }
341: final int length = Array.getLength(array);
342: for (int i = 0; i < length; i++) {
343: value = Array.get(array, i);
344: if (value != null) {
345: if (i != 0) {
346: table.write(lineSeparator);
347: }
348: table.write(formatValue(value));
349: }
350: }
351: table.nextColumn();
352: table.setAlignment(TableWriter.ALIGN_LEFT);
353: value = descriptor.getUnit();
354: if (value != null) {
355: table.write(value.toString());
356: }
357: }
358: table.writeHorizontalSeparator();
359: }
360: table.flush();
361: /*
362: * Now format all groups deferred to the end of this table.
363: * Most of the time, there is no such group.
364: */
365: if (deferredGroups != null) {
366: for (final Iterator it = deferredGroups.iterator(); it
367: .hasNext();) {
368: final Object element = it.next();
369: final ParameterValueGroup value;
370: final ParameterDescriptorGroup descriptor;
371: if (element instanceof ParameterValueGroup) {
372: value = (ParameterValueGroup) element;
373: descriptor = (ParameterDescriptorGroup) value
374: .getDescriptor();
375: // TODO: remove cast when we will be allowed to use J2SE 1.5.
376: } else {
377: value = null;
378: descriptor = (ParameterDescriptorGroup) element;
379: }
380: out.write(lineSeparator);
381: format(name + '/' + descriptor.getName().getCode(),
382: descriptor, value);
383: }
384: }
385: }
386:
387: /**
388: * Format a summary of a collection of {@linkplain IdentifiedObject identified objects}.
389: * The summary contains the identifier name and alias aligned in a table.
390: *
391: * @param parameters The collection of parameters to format.
392: * @param scopes The set of scopes to include in the table, of {@code null} for all
393: * of them. A restricted a set will produce a table with less columns.
394: * @throws IOException if an error occured will writing to the stream.
395: */
396: public void summary(final Collection parameters,
397: final Set/*<Sring>*/scopes) throws IOException {
398: /*
399: * Prepares the list of alias before any write to the output stream.
400: * We need to prepare the list first, because not all identified objects
401: * may have generic names with the same scopes in the same order.
402: *
403: * titles - The column number for each column title.
404: * names - The names (including alias) for each line.
405: */
406: final Map titles = new LinkedHashMap/*<Object,Integer>*/();
407: final List names = new ArrayList/*<String[]>*/();
408: final Locale locale = this .locale; // Protect from changes.
409: String[] descriptions = null;
410: titles.put(null, new Integer(0)); // Special value for the identifier column.
411: for (final Iterator it = parameters.iterator(); it.hasNext();) {
412: final IdentifiedObject element = (IdentifiedObject) it
413: .next();
414: final Collection/*<GenericName>*/aliases = element
415: .getAlias();
416: String[] elementNames = new String[titles.size()];
417: elementNames[0] = element.getName().getCode();
418: if (aliases != null) {
419: /*
420: * The primary name has been fetch (before this block) for one element, and we
421: * determined that some alias may be available in addition. Add local alias
422: * (i.e. names without their scope) to the 'elementNames' row.
423: */
424: int count = 0;
425: for (final Iterator i = aliases.iterator(); i.hasNext();) {
426: final GenericName alias = (GenericName) i.next();
427: final GenericName scope = alias.getScope();
428: final GenericName name = alias.asLocalName();
429: final Object title;
430: if (scope != null) {
431: if (scopes != null
432: && !scopes.contains(scope.toString())) {
433: /*
434: * The user requested only a subset of alias (the 'scopes' argument),
435: * and the current alias is not a member of this subset. Continue the
436: * search to other alias.
437: */
438: continue;
439: }
440: title = scope.toInternationalString().toString(
441: locale);
442: } else {
443: title = new Integer(count++);
444: }
445: /*
446: * The alias scope is used as the column's title. If the alias has no scope,
447: * then a sequencial number is used instead. Now check if the column already
448: * exists. If it exists, fetch its position. If it doesn't exist, inserts the
449: * new column at the end of existing columns.
450: */
451: Integer position = (Integer) titles.get(title);
452: if (position == null) {
453: position = new Integer(titles.size());
454: titles.put(title, position);
455: }
456: /*
457: * Now stores the alias local name at the position we just determined above.
458: * Note that more than one value may exist for the same column. For example
459: * both "WGS 84" and "4326" may appear as EPSG alias (as EPSG name and EPSG
460: * identifier respectively), depending how the parameters given by the user
461: * were constructed.
462: */
463: final int index = position.intValue();
464: if (index >= elementNames.length) {
465: elementNames = (String[]) XArray.resize(
466: elementNames, index + 1);
467: }
468: final String oldName = elementNames[index];
469: final String newName = name.toInternationalString()
470: .toString(locale);
471: if (oldName == null
472: || oldName.length() > newName.length()) {
473: /*
474: * Keep the shortest string, since it is often a code used
475: * for identification (e.g. EPSG code). It also help to fit
476: * the table in the window's width.
477: */
478: elementNames[index] = newName;
479: }
480: }
481: }
482: /*
483: * Before to add the name and alias to the list, fetch the remarks (if any).
484: * They are stored in a separated list and will appear as the very last column.
485: */
486: final InternationalString remarks = element.getRemarks();
487: if (remarks != null) {
488: if (descriptions == null) {
489: descriptions = new String[parameters.size()];
490: }
491: descriptions[names.size()] = remarks.toString(locale);
492: }
493: names.add(elementNames);
494: }
495: /*
496: * Trim the columns that duplicates the identifier column (#0). This is
497: * usually the case of the OGC column (usually #1), since we already use
498: * OGC name as the main identifier in most cases.
499: */
500: final boolean[] hide = new boolean[titles.size()];
501: trim: for (int column = hide.length; --column >= 1;) {
502: for (final Iterator it = names.iterator(); it.hasNext();) {
503: final String[] alias = (String[]) it.next();
504: if (alias.length > column) {
505: final String name = alias[column];
506: if (name != null && !name.equals(alias[0])) {
507: // No need to looks at the next lines.
508: // Move to previous column.
509: continue trim;
510: }
511: }
512: }
513: // A column duplicating the identifier column has been found.
514: hide[column] = true;
515: }
516: /*
517: * Writes the table. The header will contains one column for each alias's
518: * scope (or authority) declared in 'titles', in the same order. It will
519: * also contains a "Description" column if there is some.
520: */
521: int column = 0;
522: synchronized (lock) {
523: final TableWriter table = new TableWriter(out, " \u2502 ");
524: table.setMultiLinesCells(true);
525: table.writeHorizontalSeparator();
526: /*
527: * Writes all column headers.
528: */
529: for (final Iterator it = titles.keySet().iterator(); it
530: .hasNext();) {
531: final Object element = it.next();
532: if (hide[column++]) {
533: continue;
534: }
535: final String title;
536: if (element == null) {
537: title = "Identifier"; // TODO: localize
538: } else if (element instanceof String) {
539: title = (String) element;
540: } else {
541: title = "Alias " + element; // TODO: localize
542: }
543: table.write(title);
544: table.nextColumn();
545: }
546: if (descriptions != null) {
547: table.write("Description"); // TODO: localize
548: }
549: table.writeHorizontalSeparator();
550: /*
551: * Writes all row.
552: */
553: int counter = 0;
554: for (final Iterator it = names.iterator(); it.hasNext();) {
555: final String[] aliases = (String[]) it.next();
556: for (column = 0; column < hide.length; column++) {
557: if (hide[column]) {
558: continue;
559: }
560: if (column < aliases.length) {
561: final String alias = aliases[column];
562: if (alias != null) {
563: table.write(alias);
564: }
565: }
566: table.nextColumn();
567: }
568: if (descriptions != null) {
569: final String remarks = descriptions[counter++];
570: if (remarks != null) {
571: table.write(remarks);
572: }
573: }
574: table.nextLine();
575: }
576: table.writeHorizontalSeparator();
577: table.flush();
578: }
579: }
580:
581: /**
582: * Returns the current locale. Newly constructed {@code ParameterWriter}
583: * use the {@linkplain Locale#getDefault system default}.
584: */
585: public Locale getLocale() {
586: return locale;
587: }
588:
589: /**
590: * Set the locale to use for table formatting.
591: */
592: public void setLocale(final Locale locale) {
593: synchronized (lock) {
594: this .locale = locale;
595: numberFormat = null;
596: dateFormat = null;
597: angleFormat = null;
598: }
599: }
600:
601: /**
602: * Format the specified value as a string. This method is automatically invoked
603: * by {@code format(...)} methods. The default implementation format
604: * {@link Number}, {@link Date} and {@link Angle} object according the
605: * {@linkplain #getLocale current locale}. This method can been overridden if
606: * more objects need to be formatted in a special way.
607: *
608: * @param value the value to format.
609: * @return The value formatted as a string.
610: */
611: protected String formatValue(final Object value) {
612: if (value instanceof Number) {
613: if (numberFormat == null) {
614: numberFormat = NumberFormat.getNumberInstance(locale);
615: }
616: return numberFormat.format(value);
617: }
618: if (value instanceof Date) {
619: if (dateFormat == null) {
620: dateFormat = DateFormat.getDateInstance(
621: DateFormat.MEDIUM, locale);
622: }
623: return dateFormat.format(value);
624: }
625: if (value instanceof Angle) {
626: if (angleFormat == null) {
627: angleFormat = AngleFormat.getInstance(locale);
628: }
629: return angleFormat.format(value);
630: }
631: return String.valueOf(value);
632: }
633: }
|