001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2004, Institut de Recherche pour le Développement
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: *
018: * This package contains documentation from OpenGIS specifications.
019: * OpenGIS consortium's work is fully acknowledged here.
020: */
021: package org.geotools.referencing.wkt;
022:
023: // J2SE dependencies and extensions
024: import java.lang.reflect.Array;
025: import java.text.FieldPosition;
026: import java.text.NumberFormat;
027: import java.util.Collection;
028: import java.util.Iterator;
029: import java.util.Locale;
030: import javax.units.NonSI;
031: import javax.units.SI;
032: import javax.units.Unit;
033: import javax.units.UnitFormat;
034:
035: // OpenGIS dependencies
036: import org.opengis.metadata.Identifier;
037: import org.opengis.metadata.citation.Citation;
038: import org.opengis.parameter.GeneralParameterValue;
039: import org.opengis.parameter.ParameterDescriptor;
040: import org.opengis.parameter.ParameterValue;
041: import org.opengis.parameter.ParameterValueGroup;
042: import org.opengis.referencing.IdentifiedObject;
043: import org.opengis.referencing.datum.Datum;
044: import org.opengis.referencing.cs.CoordinateSystemAxis;
045: import org.opengis.referencing.operation.MathTransform;
046: import org.opengis.referencing.operation.OperationMethod;
047: import org.opengis.util.CodeList;
048: import org.opengis.util.GenericName;
049: import org.opengis.util.InternationalString;
050:
051: // Geotools dependencies
052: import org.geotools.metadata.iso.citation.Citations;
053: import org.geotools.resources.Arguments;
054: import org.geotools.resources.Utilities;
055: import org.geotools.resources.X364;
056: import org.geotools.resources.XMath;
057: import org.geotools.resources.i18n.Errors;
058: import org.geotools.resources.i18n.ErrorKeys;
059:
060: /**
061: * Format {@link Formattable} objects as
062: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
063: * Known Text</cite> (WKT)</A>.
064: *
065: * A formatter is constructed with a specified set of symbols.
066: * The {@linkplain Locale locale} associated with the symbols is used for querying
067: * {@linkplain org.opengis.metadata.citation.Citation#getTitle authority titles}.
068: *
069: * @since 2.0
070: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/wkt/Formatter.java $
071: * @version $Id: Formatter.java 25477 2007-05-10 13:01:00Z desruisseaux $
072: * @author Martin Desruisseaux
073: *
074: * @see <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</A>
075: * @see <A HREF="http://gdal.velocet.ca/~warmerda/wktproblems.html">OGC WKT Coordinate System Issues</A>
076: */
077: public class Formatter {
078: /**
079: * Do not format an {@code "AUTHORITY"} element for instances of those classes.
080: *
081: * @see #authorityAllowed
082: */
083: private static final Class[] AUTHORITY_EXCLUDE = { CoordinateSystemAxis.class };
084:
085: /**
086: * ANSI X3.64 codes for syntax coloring. Used only if syntax coloring
087: * has been explicitly enabled.
088: */
089: private static final String NUMBER_COLOR = X364.YELLOW, // Floating point numbers only, not integers.
090: INTEGER_COLOR = X364.YELLOW,
091: UNIT_COLOR = X364.YELLOW,
092: AXIS_COLOR = X364.CYAN,
093: CODELIST_COLOR = X364.CYAN,
094: PARAMETER_COLOR = X364.GREEN,
095: METHOD_COLOR = X364.GREEN,
096: DATUM_COLOR = X364.GREEN,
097: ERROR_COLOR = X364.BACKGROUND_RED;
098:
099: /**
100: * The symbols to use for this formatter.
101: */
102: private final Symbols symbols;
103:
104: /**
105: * The preferred authority for object or parameter names.
106: *
107: * @see AbstractParser#getAuthority
108: * @see AbstractParser#setAuthority
109: */
110: Citation authority = Citations.OGC;
111:
112: /**
113: * Whatever we allow syntax coloring on ANSI X3.64 compatible terminal.
114: *
115: * @see AbstractParser#isColorEnabled
116: * @see AbstractParser#setColorEnabled
117: */
118: boolean colorEnabled = false;
119:
120: /**
121: * The unit for formatting measures, or {@code null} for the "natural" unit of each WKT
122: * element. This value is set for example by "GEOGCS", which force its enclosing "PRIMEM" to
123: * take the same units than itself.
124: */
125: private Unit linearUnit, angularUnit;
126:
127: /**
128: * The object to use for formatting numbers.
129: */
130: private final NumberFormat numberFormat;
131:
132: /**
133: * The object to use for formatting units.
134: */
135: private final UnitFormat unitFormat = UnitFormat.getAsciiInstance();
136:
137: /**
138: * Dummy field position.
139: */
140: private final FieldPosition dummy = new FieldPosition(0);
141:
142: /**
143: * The buffer in which to format. Consider this field as private final; the only
144: * method to set the buffer to a new value is {@link AbstractParser#format}.
145: */
146: StringBuffer buffer;
147:
148: /**
149: * The starting point in the buffer. Always 0, except when used by
150: * {@link AbstractParser#format}.
151: */
152: int bufferBase;
153:
154: /**
155: * The amount of space to use in indentation, or 0 if indentation is disabled.
156: */
157: final int indentation;
158:
159: /**
160: * The amount of space to write on the left side of each line. This amount is increased
161: * by {@code indentation} every time a {@link Formattable} object is appended in a
162: * new indentation level.
163: */
164: private int margin;
165:
166: /**
167: * {@code true} if a new line were requested during the execution
168: * of {@link #append(Formattable)}. This is used to determine if
169: * {@code UNIT} and {@code AUTHORITY} elements should appears
170: * on a new line too.
171: */
172: private boolean lineChanged;
173:
174: /**
175: * {@code true} if the WKT is invalid. Similar to {@link #unformattable}, except that
176: * this field is reset to {@code false} after the invalid part has been processed by
177: * {@link #append(Formattable)}. This field is for internal use only.
178: */
179: private boolean invalidWKT;
180:
181: /**
182: * Non-null if the WKT is invalid. If non-null, then this field contains the interface class
183: * of the problematic part (e.g. {@link org.opengis.referencing.crs.EngineeringCRS}).
184: */
185: private Class/*<?>*/unformattable;
186:
187: /**
188: * Warning that may be produced during WKT formatting, or {@code null} if none.
189: */
190: String warning;
191:
192: /**
193: * Creates a new instance of the formatter with the default symbols.
194: */
195: public Formatter() {
196: this (Symbols.DEFAULT, 0);
197: }
198:
199: /**
200: * Creates a new instance of the formatter. The whole WKT will be formatted
201: * on a single line.
202: *
203: * @param symbols The symbols.
204: */
205: public Formatter(final Symbols symbols) {
206: this (symbols, 0);
207: }
208:
209: /**
210: * Creates a new instance of the formatter with the specified indentation width.
211: * The WKT will be formatted on many lines, and the indentation width will have
212: * the value specified to this constructor. If the specified indentation is 0,
213: * then the whole WKT will be formatted on a single line.
214: *
215: * @param symbols The symbols.
216: * @param indentation The amount of spaces to use in indentation. Typical values are 2 or 4.
217: */
218: public Formatter(final Symbols symbols, final int indentation) {
219: this .symbols = symbols;
220: this .indentation = indentation;
221: if (symbols == null) {
222: throw new IllegalArgumentException(Errors.format(
223: ErrorKeys.NULL_ARGUMENT_$1, "symbols"));
224: }
225: if (indentation < 0) {
226: throw new IllegalArgumentException(Errors.format(
227: ErrorKeys.ILLEGAL_ARGUMENT_$2, "indentation",
228: new Integer(indentation)));
229: }
230: numberFormat = (NumberFormat) symbols.numberFormat.clone();
231: buffer = new StringBuffer();
232: }
233:
234: /**
235: * Constructor for private use by {@link AbstractParser#format} only.
236: * This constructor help to share some objects with {@link AbstractParser}.
237: */
238: Formatter(final Symbols symbols, final NumberFormat numberFormat) {
239: this .symbols = symbols;
240: indentation = Formattable.getIndentation();
241: this .numberFormat = numberFormat; // No clone needed.
242: // Do not set the buffer. It will be set by AbstractParser.format.
243: }
244:
245: /**
246: * Set the colors using the specified ANSI escape. The color is ignored
247: * unless syntax coloring has been explicitly enabled. The {@code color}
248: * should be a constant from {@link X364}.
249: */
250: private void setColor(final String color) {
251: if (colorEnabled) {
252: buffer.append(color);
253: }
254: }
255:
256: /**
257: * Reset the colors to the default. This method do nothing
258: * unless syntax coloring has been explicitly enabled.
259: */
260: private void resetColor() {
261: if (colorEnabled) {
262: buffer.append(X364.DEFAULT);
263: }
264: }
265:
266: /**
267: * Returns the color to uses for the name of the specified object.
268: */
269: private static String getNameColor(final IdentifiedObject object) {
270: if (object instanceof Datum) {
271: return DATUM_COLOR;
272: }
273: if (object instanceof OperationMethod) {
274: return METHOD_COLOR;
275: }
276: if (object instanceof CoordinateSystemAxis) {
277: return AXIS_COLOR;
278: }
279: // Note: we can't test for MathTransform here, since it is not an IdentifiedObject.
280: // If we want to provide a color for the MathTransform name, we would need to
281: // do that in 'append(String)' method, but the later is for general string...
282: return null;
283: }
284:
285: /**
286: * Add a separator to the buffer, if needed.
287: *
288: * @param newLine if {@code true}, add a line separator too.
289: */
290: private void appendSeparator(final boolean newLine) {
291: int length = buffer.length();
292: char c;
293: do {
294: if (length == bufferBase) {
295: return;
296: }
297: c = buffer.charAt(--length);
298: if (c == symbols.open || c == symbols.openArray) {
299: return;
300: }
301: } while (Character.isWhitespace(c) || c == symbols.space);
302: buffer.append(symbols.separator);
303: buffer.append(symbols.space);
304: if (newLine && indentation != 0) {
305: buffer.append(System.getProperty("line.separator", "\n"));
306: buffer.append(Utilities.spaces(margin));
307: lineChanged = true;
308: }
309: }
310:
311: /**
312: * Append the specified {@code Formattable} object. This method will automatically append
313: * the keyword (e.g. <code>"GEOCS"</code>), the name and the authority code, and will invokes
314: * <code>formattable.{@linkplain Formattable#formatWKT formatWKT}(this)</code> for completing
315: * the inner part of the WKT.
316: *
317: * @param formattable The formattable object to append to the WKT.
318: */
319: public void append(final Formattable formattable) {
320: /*
321: * Formats the opening bracket and the object name (e.g. "NAD27").
322: * The WKT entity name (e.g. "PROJCS") will be formatted later.
323: * The result of this code portion looks like the following:
324: *
325: * <previous text>,
326: * ["NAD27 / Idaho Central"
327: */
328: appendSeparator(true);
329: int base = buffer.length();
330: buffer.append(symbols.open);
331: final IdentifiedObject info = (formattable instanceof IdentifiedObject) ? (IdentifiedObject) formattable
332: : null;
333: if (info != null) {
334: final String c = getNameColor(info);
335: if (c != null) {
336: setColor(c);
337: }
338: buffer.append(symbols.quote);
339: buffer.append(getName(info));
340: buffer.append(symbols.quote);
341: if (c != null) {
342: resetColor();
343: }
344: }
345: /*
346: * Formats the part after the object name, then insert the WKT element name
347: * in front of them. The result of this code portion looks like the following:
348: *
349: * <previous text>,
350: * PROJCS["NAD27 / Idaho Central",
351: * GEOGCS[...etc...],
352: * ...etc...
353: */
354: indent(+1);
355: lineChanged = false;
356: String keyword = formattable.formatWKT(this );
357: if (colorEnabled && invalidWKT) {
358: invalidWKT = false;
359: buffer.insert(base, ERROR_COLOR + X364.BACKGROUND_DEFAULT);
360: base += ERROR_COLOR.length();
361: }
362: buffer.insert(base, keyword);
363: /*
364: * Formats the AUTHORITY[<name>,<code>] entity, if there is one. The entity
365: * will be on the same line than the enclosing one if no line separator were
366: * added (e.g. SPHEROID["Clarke 1866", ..., AUTHORITY["EPSG","7008"]]), or on
367: * a new line otherwise. After this block, the result looks like the following:
368: *
369: * <previous text>,
370: * PROJCS["NAD27 / Idaho Central",
371: * GEOGCS[...etc...],
372: * ...etc...
373: * AUTHORITY["EPSG","26769"]]
374: */
375: final Identifier identifier = getIdentifier(info);
376: if (identifier != null && authorityAllowed(info)) {
377: final Citation authority = identifier.getAuthority();
378: if (authority != null) {
379: /*
380: * Since WKT often use abbreviations, search for the shortest
381: * title or alternate title. If one is found, it will be used
382: * as the authority name (e.g. "EPSG").
383: */
384: InternationalString inter = authority.getTitle();
385: String title = (inter != null) ? inter
386: .toString(symbols.locale) : null;
387: for (final Iterator it = authority.getAlternateTitles()
388: .iterator(); it.hasNext();) {
389: inter = (InternationalString) it.next();
390: if (inter != null) {
391: final String candidate = inter
392: .toString(symbols.locale);
393: if (candidate != null) {
394: if (title == null
395: || candidate.length() < title
396: .length()) {
397: title = candidate;
398: }
399: }
400: }
401: }
402: if (title != null) {
403: appendSeparator(lineChanged);
404: buffer.append("AUTHORITY");
405: buffer.append(symbols.open);
406: buffer.append(symbols.quote);
407: buffer.append(title);
408: buffer.append(symbols.quote);
409: final String code = identifier.getCode();
410: if (code != null) {
411: buffer.append(symbols.separator);
412: buffer.append(symbols.quote);
413: buffer.append(code);
414: buffer.append(symbols.quote);
415: }
416: buffer.append(symbols.close);
417: }
418: }
419: }
420: buffer.append(symbols.close);
421: lineChanged = true;
422: indent(-1);
423: }
424:
425: /**
426: * Append the specified OpenGIS's {@code IdentifiedObject} object.
427: *
428: * @param info The info object to append to the WKT.
429: */
430: public void append(final IdentifiedObject info) {
431: if (info instanceof Formattable) {
432: append((Formattable) info);
433: } else {
434: append(new Adapter(info));
435: }
436: }
437:
438: /**
439: * Append the specified math transform.
440: *
441: * @param transform The transform object to append to the WKT.
442: */
443: public void append(final MathTransform transform) {
444: if (transform instanceof Formattable) {
445: append((Formattable) transform);
446: } else {
447: append(new Adapter(transform));
448: }
449: }
450:
451: /**
452: * Append a code list to the WKT.
453: */
454: public void append(final CodeList code) {
455: if (code != null) {
456: appendSeparator(false);
457: setColor(CODELIST_COLOR);
458: final String name = code.name();
459: final boolean needQuotes = (name.indexOf(' ') >= 0);
460: if (needQuotes) {
461: buffer.append(symbols.quote);
462: }
463: buffer.append(name);
464: if (needQuotes) {
465: buffer.append(symbols.quote);
466: setInvalidWKT(code.getClass());
467: }
468: resetColor();
469: }
470: }
471:
472: /**
473: * Append a {@linkplain ParameterValue parameter} in WKT form. If the supplied parameter
474: * is actually a {@linkplain ParameterValueGroup parameter group}, all parameters will be
475: * inlined.
476: */
477: public void append(final GeneralParameterValue parameter) {
478: if (parameter instanceof ParameterValueGroup) {
479: for (final Iterator it = ((ParameterValueGroup) parameter)
480: .values().iterator(); it.hasNext();) {
481: append((GeneralParameterValue) it.next());
482: }
483: }
484: if (parameter instanceof ParameterValue) {
485: final ParameterValue param = (ParameterValue) parameter;
486: // Covariance: Remove cast if covariance is allowed.
487: final ParameterDescriptor descriptor = (ParameterDescriptor) param
488: .getDescriptor();
489: final Unit valueUnit = descriptor.getUnit();
490: Unit unit = valueUnit;
491: if (unit != null && !Unit.ONE.equals(unit)) {
492: if (linearUnit != null && unit.isCompatible(linearUnit)) {
493: unit = linearUnit;
494: } else if (angularUnit != null
495: && unit.isCompatible(angularUnit)) {
496: unit = angularUnit;
497: }
498: }
499: appendSeparator(true);
500: final int start = buffer.length();
501: buffer.append("PARAMETER");
502: final int stop = buffer.length();
503: buffer.append(symbols.open);
504: setColor(PARAMETER_COLOR);
505: buffer.append(symbols.quote);
506: buffer.append(getName(descriptor));
507: buffer.append(symbols.quote);
508: resetColor();
509: buffer.append(symbols.separator);
510: buffer.append(symbols.space);
511: if (unit != null) {
512: double value;
513: try {
514: value = param.doubleValue(unit);
515: } catch (IllegalStateException exception) {
516: // May happen if a parameter is mandatory (e.g. "semi-major")
517: // but no value has been set for this parameter.
518: if (colorEnabled) {
519: buffer.insert(stop, X364.BACKGROUND_DEFAULT);
520: buffer.insert(start, ERROR_COLOR);
521: }
522: warning = exception.getLocalizedMessage();
523: value = Double.NaN;
524: }
525: if (!unit.equals(valueUnit)) {
526: value = XMath.fixRoundingError(value, 9);
527: }
528: format(value);
529: } else {
530: appendObject(param.getValue());
531: }
532: buffer.append(symbols.close);
533: }
534: }
535:
536: /**
537: * Append the specified value to a string buffer. If the value is an array, then the
538: * array elements are appended recursively (i.e. the array may contains sub-array).
539: */
540: private void appendObject(final Object value) {
541: if (value == null) {
542: buffer.append("null");
543: return;
544: }
545: if (value.getClass().isArray()) {
546: buffer.append(symbols.openArray);
547: final int length = Array.getLength(value);
548: for (int i = 0; i < length; i++) {
549: if (i != 0) {
550: buffer.append(symbols.separator);
551: buffer.append(symbols.space);
552: }
553: appendObject(Array.get(value, i));
554: }
555: buffer.append(symbols.closeArray);
556: return;
557: }
558: if (value instanceof Number) {
559: format((Number) value);
560: } else {
561: buffer.append(symbols.quote);
562: buffer.append(value);
563: buffer.append(symbols.quote);
564: }
565: }
566:
567: /**
568: * Append an integer number. A comma (or any other element
569: * separator) will be written before the number if needed.
570: */
571: public void append(final int number) {
572: appendSeparator(false);
573: format(number);
574: }
575:
576: /**
577: * Append a floating point number. A comma (or any other element
578: * separator) will be written before the number if needed.
579: */
580: public void append(final double number) {
581: appendSeparator(false);
582: format(number);
583: }
584:
585: /**
586: * Appends a unit in WKT form. For example, {@code append(SI.KILOMETER)}
587: * can append "<code>UNIT["km", 1000]</code>" to the WKT.
588: */
589: public void append(final Unit unit) {
590: if (unit != null) {
591: appendSeparator(lineChanged);
592: buffer.append("UNIT");
593: buffer.append(symbols.open);
594: setColor(UNIT_COLOR);
595: buffer.append(symbols.quote);
596: if (NonSI.DEGREE_ANGLE.equals(unit)) {
597: buffer.append("degree");
598: } else {
599: unitFormat.format(unit, buffer, dummy);
600: }
601: buffer.append(symbols.quote);
602: resetColor();
603: Unit base = null;
604: if (SI.METER.isCompatible(unit)) {
605: base = SI.METER;
606: } else if (SI.SECOND.isCompatible(unit)) {
607: base = SI.SECOND;
608: } else if (SI.RADIAN.isCompatible(unit)) {
609: if (!Unit.ONE.equals(unit)) {
610: base = SI.RADIAN;
611: }
612: }
613: if (base != null) {
614: append(unit.getConverterTo(base).convert(1));
615: }
616: buffer.append(symbols.close);
617: }
618: }
619:
620: /**
621: * Append a character string. The string will be written between quotes.
622: * A comma (or any other element separator) will be written before the string if needed.
623: */
624: public void append(final String text) {
625: appendSeparator(false);
626: buffer.append(symbols.quote);
627: buffer.append(text);
628: buffer.append(symbols.quote);
629: }
630:
631: /**
632: * Format an arbitrary number.
633: */
634: private void format(final Number number) {
635: if (number instanceof Byte || number instanceof Short
636: || number instanceof Integer) {
637: format(number.intValue());
638: } else {
639: format(number.doubleValue());
640: }
641: }
642:
643: /**
644: * Format an integer number.
645: */
646: private void format(final int number) {
647: setColor(INTEGER_COLOR);
648: final int fraction = numberFormat.getMinimumFractionDigits();
649: numberFormat.setMinimumFractionDigits(0);
650: numberFormat.format(number, buffer, dummy);
651: numberFormat.setMinimumFractionDigits(fraction);
652: resetColor();
653: }
654:
655: /**
656: * Format a floating point number.
657: */
658: private void format(final double number) {
659: setColor(NUMBER_COLOR);
660: numberFormat.format(number, buffer, dummy);
661: resetColor();
662: }
663:
664: /**
665: * Increase or reduce the indentation. A value of {@code +1} increase
666: * the indentation by the amount of spaces specified at construction time,
667: * and a value of {@code +1} reduce it.
668: */
669: private void indent(final int amount) {
670: margin = Math.max(0, margin + indentation * amount);
671: }
672:
673: /**
674: * Tells if an {@code "AUTHORITY"} element is allowed for the specified object.
675: */
676: private static boolean authorityAllowed(final IdentifiedObject info) {
677: for (int i = 0; i < AUTHORITY_EXCLUDE.length; i++) {
678: if (AUTHORITY_EXCLUDE[i].isInstance(info)) {
679: return false;
680: }
681: }
682: return true;
683: }
684:
685: /**
686: * Returns the preferred identifier for the specified object. If the specified
687: * object contains an identifier from the preferred authority (usually
688: * {@linkplain Citations#OGC Open Geospatial}), then this identifier is
689: * returned. Otherwise, the first identifier is returned. If the specified
690: * object contains no identifier, then this method returns {@code null}.
691: *
692: * @param info The object to looks for a preferred identifier.
693: * @return The preferred identifier, or {@code null} if none.
694: *
695: * @since 2.3
696: */
697: public Identifier getIdentifier(final IdentifiedObject info) {
698: Identifier first = null;
699: if (info != null) {
700: final Collection/*<Identifier>*/identifiers = info
701: .getIdentifiers();
702: if (identifiers != null) {
703: for (final Iterator it = identifiers.iterator(); it
704: .hasNext();) {
705: final Identifier id = (Identifier) it.next();
706: if (authorityMatches(id.getAuthority())) {
707: return id;
708: }
709: if (first == null) {
710: first = id;
711: }
712: }
713: }
714: }
715: return first;
716: }
717:
718: /**
719: * Checks if the specified authority can be recognized as the expected authority.
720: * This implementation do not requires an exact matches. A matching title is enough.
721: */
722: private boolean authorityMatches(final Citation citation) {
723: if (authority == citation) {
724: return true;
725: }
726: return (citation != null)
727: && authority
728: .getTitle()
729: .toString(Locale.US)
730: .equalsIgnoreCase(
731: citation.getTitle().toString(Locale.US));
732: }
733:
734: /**
735: * Returns the preferred name for the specified object. If the specified
736: * object contains a name from the preferred authority (usually
737: * {@linkplain Citations#OGC Open Geospatial}), then this name is
738: * returned. Otherwise, the first name found is returned.
739: *
740: * @param info The object to looks for a preferred name.
741: * @return The preferred name.
742: */
743: public String getName(final IdentifiedObject info) {
744: final Identifier name = info.getName();
745: if (!authorityMatches(name.getAuthority())) {
746: final Collection/*<GenericName>*/aliases = info.getAlias();
747: if (aliases != null) {
748: /*
749: * The main name doesn't matches. Search in alias. We will first
750: * check if alias implements Identifier (this is the case of
751: * Geotools implementation). Otherwise, we will look at the
752: * scope in generic name.
753: */
754: for (final Iterator it = aliases.iterator(); it
755: .hasNext();) {
756: final GenericName alias = (GenericName) it.next();
757: if (alias instanceof Identifier) {
758: final Identifier candidate = (Identifier) alias;
759: if (authorityMatches(candidate.getAuthority())) {
760: return candidate.getCode();
761: }
762: }
763: }
764: final String title = authority.getTitle().toString(
765: Locale.US);
766: for (final Iterator it = aliases.iterator(); it
767: .hasNext();) {
768: final GenericName alias = (GenericName) it.next();
769: final GenericName scope = alias.getScope();
770: if (scope != null) {
771: if (title.equalsIgnoreCase(scope.toString())) {
772: return alias.asLocalName().toString();
773: }
774: }
775: }
776: }
777: }
778: return name.getCode();
779: }
780:
781: /**
782: * The linear unit for formatting measures, or {@code null} for the "natural" unit of each
783: * WKT element.
784: *
785: * @return The unit for measure. Default value is {@code null}.
786: */
787: public Unit getLinearUnit() {
788: return linearUnit;
789: }
790:
791: /**
792: * Set the unit for formatting linear measures.
793: *
794: * @param unit The new unit, or {@code null}.
795: */
796: public void setLinearUnit(final Unit unit) {
797: if (unit != null && !SI.METER.isCompatible(unit)) {
798: throw new IllegalArgumentException(Errors.format(
799: ErrorKeys.NON_LINEAR_UNIT_$1, unit));
800: }
801: linearUnit = unit;
802: }
803:
804: /**
805: * The angular unit for formatting measures, or {@code null} for the "natural" unit of
806: * each WKT element. This value is set for example by "GEOGCS", which force its enclosing
807: * "PRIMEM" to take the same units than itself.
808: *
809: * @return The unit for measure. Default value is {@code null}.
810: */
811: public Unit getAngularUnit() {
812: return angularUnit;
813: }
814:
815: /**
816: * Set the angular unit for formatting measures.
817: *
818: * @param unit The new unit, or {@code null}.
819: */
820: public void setAngularUnit(final Unit unit) {
821: if (unit != null
822: && (!SI.RADIAN.isCompatible(unit) || Unit.ONE
823: .equals(unit))) {
824: throw new IllegalArgumentException(Errors.format(
825: ErrorKeys.NON_ANGULAR_UNIT_$1, unit));
826: }
827: angularUnit = unit;
828: }
829:
830: /**
831: * Returns {@code true} if the WKT in this formatter is not strictly compliant to the
832: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">WKT
833: * specification</A>. This method returns {@code true} if {@link #setInvalidWKT} has
834: * been invoked at least once. The action to take regarding invalid WKT is caller-dependant.
835: * For example {@link Formattable#toString} will accepts loose WKT formatting and ignore this
836: * flag, while {@link Formattable#toWKT} requires strict WKT formatting and will thrown an
837: * exception if this flag is set.
838: */
839: public boolean isInvalidWKT() {
840: return unformattable != null
841: || (buffer != null && buffer.length() == 0);
842: /*
843: * Note: we really use a "and" condition (not an other "or") for the buffer test because
844: * the buffer is reset to 'null' by AbstractParser after a successfull formatting.
845: */
846: }
847:
848: /**
849: * Returns the class declared by the last call to {@link #setInvalidWKT}.
850: */
851: final Class getUnformattableClass() {
852: return unformattable;
853: }
854:
855: /**
856: * Set a flag marking the current WKT as not strictly compliant to the
857: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">WKT
858: * specification</A>. This method is invoked by {@link Formattable#formatWKT} methods when the
859: * object to format is more complex than what the WKT specification allows. For example this
860: * method is invoked when an {@linkplain org.geotools.referencing.crs.DefaultEngineeringCRS
861: * engineering CRS} uses different unit for each axis, An application can tests
862: * {@link #isInvalidWKT} later for checking WKT validity.
863: *
864: * @param unformattable The type of the component that can't be formatted,
865: * for example {@link org.opengis.referencing.crs.EngineeringCRS}.
866: *
867: * @see UnformattableObjectException#getUnformattableClass
868: *
869: * @since 2.4
870: */
871: public void setInvalidWKT(final Class unformattable) {
872: this .unformattable = unformattable;
873: invalidWKT = true;
874: }
875:
876: /**
877: * Set a flag marking the current WKT as not strictly compliant to the WKT specification.
878: *
879: * @deprecated Replaced by {@link #setInvalidWKT(Class)}.
880: */
881: public void setInvalidWKT() {
882: setInvalidWKT(IdentifiedObject.class);
883: }
884:
885: /**
886: * Returns the WKT in its current state.
887: */
888: public String toString() {
889: return buffer.toString();
890: }
891:
892: /**
893: * Clear this formatter. All properties (including {@linkplain #getLinearUnit unit}
894: * and {@linkplain #isInvalidWKT WKT validity flag} are reset to their default value.
895: * After this method call, this {@code Formatter} object is ready for formatting
896: * a new object.
897: */
898: public void clear() {
899: if (buffer != null) {
900: buffer.setLength(0);
901: }
902: linearUnit = null;
903: angularUnit = null;
904: unformattable = null;
905: warning = null;
906: invalidWKT = false;
907: lineChanged = false;
908: margin = 0;
909: }
910:
911: /**
912: * Set the preferred indentation from the command line. This indentation is used by
913: * {@link Formattable#toWKT()} when no indentation were explicitly requested. This
914: * method can be invoked from the command line using the following syntax:
915: *
916: * <blockquote>
917: * {@code java org.geotools.referencing.wkt.Formatter -indentation=}<var><preferred
918: * indentation></var>
919: * </blockquote>
920: */
921: public static void main(String[] args) {
922: final Arguments arguments = new Arguments(args);
923: final int indentation = arguments
924: .getRequiredInteger(Formattable.INDENTATION);
925: args = arguments.getRemainingArguments(0);
926: Formattable.setIndentation(indentation);
927: }
928: }
|