001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2002, 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: package org.geotools.referencing.wkt;
019:
020: // Formatting
021: import java.io.BufferedReader;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024: import java.io.Writer;
025: import java.text.DecimalFormat;
026: import java.text.FieldPosition;
027: import java.text.Format;
028: import java.text.NumberFormat;
029: import java.text.ParseException;
030: import java.text.ParsePosition;
031:
032: // OpenGIS dependencies
033: import org.opengis.metadata.citation.Citation;
034: import org.opengis.parameter.GeneralParameterValue;
035: import org.opengis.parameter.InvalidParameterValueException;
036: import org.opengis.referencing.IdentifiedObject;
037: import org.opengis.referencing.operation.MathTransform;
038:
039: // Geotools dependencies
040: import org.geotools.resources.Utilities;
041: import org.geotools.resources.i18n.Errors;
042: import org.geotools.resources.i18n.ErrorKeys;
043:
044: /**
045: * Base class for <cite>Well Know Text</cite> (WKT) parser.
046: *
047: * @since 2.0
048: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/wkt/AbstractParser.java $
049: * @version $Id: AbstractParser.java 25477 2007-05-10 13:01:00Z desruisseaux $
050: * @author Remi Eve
051: * @author Martin Desruisseaux
052: *
053: * @see <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</A>
054: * @see <A HREF="http://gdal.velocet.ca/~warmerda/wktproblems.html">OGC WKT Coordinate System Issues</A>
055: */
056: public abstract class AbstractParser extends Format {
057: /**
058: * Set to {@code true} if parsing of number in scientific notation is allowed.
059: * The way to achieve that is currently a hack, because {@link NumberFormat} has no
060: * API for managing that as of J2SE 1.5.
061: *
062: * @todo See if a future version of J2SE allows us to get ride of this ugly hack.
063: */
064: private static final boolean SCIENTIFIC_NOTATION = true;
065:
066: /**
067: * A formatter using the same symbols than this parser.
068: * Will be created by the {@link #format} method only when first needed.
069: */
070: private transient Formatter formatter;
071:
072: /**
073: * The symbols to use for parsing WKT.
074: */
075: final Symbols symbols;
076:
077: /**
078: * The object to use for parsing numbers.
079: */
080: private final NumberFormat numberFormat;
081:
082: /**
083: * Constructs a parser using the specified set of symbols.
084: */
085: public AbstractParser(final Symbols symbols) {
086: this .symbols = symbols;
087: this .numberFormat = (NumberFormat) symbols.numberFormat.clone();
088: if (SCIENTIFIC_NOTATION
089: && numberFormat instanceof DecimalFormat) {
090: final DecimalFormat numberFormat = (DecimalFormat) this .numberFormat;
091: String pattern = numberFormat.toPattern();
092: if (pattern.indexOf("E0") < 0) {
093: final int split = pattern.indexOf(';');
094: if (split >= 0) {
095: pattern = pattern.substring(0, split) + "E0"
096: + pattern.substring(split);
097: }
098: pattern += "E0";
099: numberFormat.applyPattern(pattern);
100: }
101: }
102: }
103:
104: /**
105: * Returns the preferred authority for formatting WKT entities.
106: * The {@link #format format} methods will uses the name specified
107: * by this authority, if available.
108: */
109: public Citation getAuthority() {
110: return getFormatter().authority;
111: }
112:
113: /**
114: * Set the preferred authority for formatting WKT entities.
115: * The {@link #format format} methods will uses the name specified
116: * by this authority, if available.
117: */
118: public void setAuthority(final Citation authority) {
119: if (authority == null) {
120: throw new IllegalArgumentException(Errors.format(
121: ErrorKeys.NULL_ARGUMENT_$1, "authority"));
122: }
123: getFormatter().authority = authority;
124: }
125:
126: /**
127: * Returns {@code true} if syntax coloring is enabled.
128: * By default, syntax coloring is disabled.
129: *
130: * @since 2.4
131: */
132: public boolean isColorEnabled() {
133: return getFormatter().colorEnabled;
134: }
135:
136: /**
137: * Enables or disables syntax coloring on ANSI X3.64 (aka ECMA-48 and ISO/IEC 6429) compatible
138: * terminal. This apply only when formatting text. By default, syntax coloring is disabled.
139: * When enabled, {@link #format(Object)} tries to highlight most of the elements compared by
140: * {@link org.geotools.referencing.CRS#equalsIgnoreMetadata}.
141: *
142: * @since 2.4
143: */
144: public void setColorEnabled(final boolean enabled) {
145: getFormatter().colorEnabled = enabled;
146: }
147:
148: /**
149: * Parses a <cite>Well Know Text</cite> (WKT).
150: *
151: * @param text The text to be parsed.
152: * @return The object.
153: * @throws ParseException if the string can't be parsed.
154: */
155: public final Object parseObject(final String text)
156: throws ParseException {
157: final Element element = getTree(text, new ParsePosition(0));
158: final Object object = parse(element);
159: element.close();
160: return object;
161: }
162:
163: /**
164: * Parses a <cite>Well Know Text</cite> (WKT).
165: *
166: * @param text The text to be parsed.
167: * @param position The position to start parsing from.
168: * @return The object.
169: */
170: public final Object parseObject(final String text,
171: final ParsePosition position) {
172: final int origin = position.getIndex();
173: try {
174: return parse(getTree(text, position));
175: } catch (ParseException exception) {
176: position.setIndex(origin);
177: if (position.getErrorIndex() < origin) {
178: position.setErrorIndex(exception.getErrorOffset());
179: }
180: return null;
181: }
182: }
183:
184: /**
185: * Parse the number at the given position.
186: */
187: final Number parseNumber(String text, final ParsePosition position) {
188: if (SCIENTIFIC_NOTATION) {
189: /*
190: * HACK: DecimalFormat.parse(...) do not understand lower case 'e' for scientific
191: * notation. It understand upper case 'E' only. Performs the replacement...
192: */
193: final int base = position.getIndex();
194: Number number = numberFormat.parse(text, position);
195: if (number != null) {
196: int i = position.getIndex();
197: if (i < text.length() && text.charAt(i) == 'e') {
198: final StringBuffer buffer = new StringBuffer(text);
199: buffer.setCharAt(i, 'E');
200: text = buffer.toString();
201: position.setIndex(base);
202: number = numberFormat.parse(text, position);
203: }
204: }
205: return number;
206: } else {
207: return numberFormat.parse(text, position);
208: }
209: }
210:
211: /**
212: * Parses the next element in the specified <cite>Well Know Text</cite> (WKT) tree.
213: *
214: * @param element The element to be parsed.
215: * @return The object.
216: * @throws ParseException if the element can't be parsed.
217: */
218: protected abstract Object parse(final Element element)
219: throws ParseException;
220:
221: /**
222: * Returns a tree of {@link Element} for the specified text.
223: *
224: * @param text The text to parse.
225: * @param position In input, the position where to start parsing from.
226: * In output, the first character after the separator.
227: */
228: protected final Element getTree(final String text,
229: final ParsePosition position) throws ParseException {
230: return new Element(new Element(this , text, position));
231: }
232:
233: /**
234: * Returns the formatter. Creates it if needed.
235: */
236: private Formatter getFormatter() {
237: if (formatter == null) {
238: if (SCIENTIFIC_NOTATION) {
239: // We do not want to expose the "scientific notation hack" to the formatter.
240: // TODO: Remove this block if some future version of J2SE 1.5 provides something
241: // like 'allowScientificNotationParsing(true)' in DecimalFormat.
242: formatter = new Formatter(symbols,
243: (NumberFormat) symbols.numberFormat.clone());
244: } else {
245: formatter = new Formatter(symbols, numberFormat);
246: }
247: }
248: return formatter;
249: }
250:
251: /**
252: * Format the specified object as a Well Know Text.
253: * Formatting will uses the same set of symbols than the one used for parsing.
254: *
255: * @see #getWarning
256: */
257: public StringBuffer format(final Object object,
258: final StringBuffer toAppendTo, final FieldPosition pos) {
259: final Formatter formatter = getFormatter();
260: try {
261: formatter.clear();
262: formatter.buffer = toAppendTo;
263: formatter.bufferBase = toAppendTo.length();
264: if (object instanceof MathTransform) {
265: formatter.append((MathTransform) object);
266: } else if (object instanceof GeneralParameterValue) {
267: // Special processing for parameter values, which is formatted
268: // directly in 'Formatter'. Note that in GeoAPI, this interface
269: // doesn't share the same parent interface than other interfaces.
270: formatter.append((GeneralParameterValue) object);
271: } else {
272: formatter.append((IdentifiedObject) object);
273: }
274: return toAppendTo;
275: } finally {
276: formatter.buffer = null;
277: }
278: }
279:
280: /**
281: * Read WKT strings from an input stream and reformat them to the specified
282: * output stream. WKT strings are read until the the end-of-stream, or until
283: * an unparsable WKT has been hit. In this later case, an error message is
284: * formatted to the specified error stream.
285: *
286: * @param in The input stream.
287: * @param out The output stream.
288: * @param err The error stream.
289: * @throws IOException if an error occured while reading from the input stream
290: * or writting to the output stream.
291: */
292: public void reformat(final BufferedReader in, final Writer out,
293: final PrintWriter err) throws IOException {
294: final String lineSeparator = System.getProperty(
295: "line.separator", "\n");
296: String line = null;
297: try {
298: while ((line = in.readLine()) != null) {
299: if ((line = line.trim()).length() != 0) {
300: out.write(lineSeparator);
301: out.write(format(parseObject(line)));
302: out.write(lineSeparator);
303: out.write(lineSeparator);
304: out.flush();
305: }
306: }
307: } catch (ParseException exception) {
308: err.println(exception.getLocalizedMessage());
309: if (line != null) {
310: reportError(err, line, exception.getErrorOffset());
311: }
312: } catch (InvalidParameterValueException exception) {
313: err.print(Errors.format(ErrorKeys.IN_$1, exception
314: .getParameterName()));
315: err.print(' ');
316: err.println(exception.getLocalizedMessage());
317: }
318: }
319:
320: /**
321: * If a warning occured during the last WKT {@linkplain #format formatting},
322: * returns the warning. Otherwise returns {@code null}. The warning is cleared
323: * every time a new object is formatted.
324: *
325: * @since 2.4
326: */
327: public String getWarning() {
328: if (formatter != null && formatter.isInvalidWKT()) {
329: if (formatter.warning != null) {
330: return formatter.warning;
331: }
332: return Errors.format(ErrorKeys.INVALID_WKT_FORMAT_$1,
333: Utilities.getShortName(formatter
334: .getUnformattableClass()));
335: }
336: return null;
337: }
338:
339: /**
340: * Report a failure while parsing the specified line.
341: *
342: * @param err The stream where to report the failure.
343: * @param line The line that failed.
344: * @param errorOffset The error offset in the specified line. This is usually the
345: * value provided by {@link ParseException#getErrorOffset}.
346: */
347: static void reportError(final PrintWriter err, String line,
348: int errorOffset) {
349: line = line.replace('\r', ' ').replace('\n', ' ');
350: final int WINDOW_WIDTH = 80; // Arbitrary value.
351: int stop = line.length();
352: int base = errorOffset - WINDOW_WIDTH / 2;
353: final int baseMax = stop - WINDOW_WIDTH;
354: final boolean hasTrailing = (Math.max(base, 0) < baseMax);
355: if (!hasTrailing) {
356: base = baseMax;
357: }
358: if (base < 0) {
359: base = 0;
360: }
361: stop = Math.min(stop, base + WINDOW_WIDTH);
362: if (hasTrailing) {
363: stop -= 3;
364: }
365: if (base != 0) {
366: err.print("...");
367: errorOffset += 3;
368: base += 3;
369: }
370: err.print(line.substring(base, stop));
371: if (hasTrailing) {
372: err.println("...");
373: } else {
374: err.println();
375: }
376: err.print(Utilities.spaces(errorOffset - base));
377: err.println('^');
378: }
379: }
|