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: // Collections
021: import java.io.PrintWriter;
022: import java.text.ParseException;
023: import java.text.ParsePosition;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027:
028: // Geotools dependencies
029: import org.geotools.resources.Utilities;
030: import org.geotools.resources.XArray;
031: import org.geotools.resources.i18n.Errors;
032: import org.geotools.resources.i18n.ErrorKeys;
033:
034: /**
035: * An element in a <cite>Well Know Text</cite> (WKT). A {@code Element} is
036: * made of {@link String}, {@link Number} and other {@link Element}. For example:
037: *
038: * <blockquote><pre>
039: * PRIMEM["Greenwich", 0.0, AUTHORITY["some authority", "Greenwich"]]
040: * </pre></blockquote>
041: *
042: * Each {@code Element} object can contains an arbitrary amount of other elements.
043: * The result is a tree, which can be printed with {@link #print}.
044: * Elements can be pull in a <cite>first in, first out</cite> order.
045: *
046: * @since 2.0
047: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/wkt/Element.java $
048: * @version $Id: Element.java 21289 2006-08-30 10:52:24Z desruisseaux $
049: * @author Remi Eve
050: * @author Martin Desruisseaux
051: */
052: public final class Element {
053: /**
054: * The position where this element starts in the string to be parsed.
055: */
056: private final int offset;
057:
058: /**
059: * Keyword of this entity. For example: "PRIMEM".
060: */
061: public final String keyword;
062:
063: /**
064: * An ordered list of {@link String}s, {@link Number}s and other {@link Element}s.
065: * May be {@code null} if the keyword was not followed by a pair of brackets
066: * (e.g. "NORTH").
067: */
068: private final List list;
069:
070: /**
071: * Constructs a root element.
072: *
073: * @param singleton The only children for this root.
074: */
075: Element(final Element singleton) {
076: offset = 0;
077: keyword = null;
078: list = new LinkedList();
079: list.add(singleton);
080: }
081:
082: /**
083: * Constructs a new {@code Element}.
084: *
085: * @param text The text to parse.
086: * @param position In input, the position where to start parsing from.
087: * In output, the first character after the separator.
088: */
089: Element(final AbstractParser parser, final String text,
090: final ParsePosition position) throws ParseException {
091: /*
092: * Find the first keyword in the specified string. If a keyword is found, then
093: * the position is set to the index of the first character after the keyword.
094: */
095: int lower = position.getIndex();
096: final int length = text.length();
097: while (lower < length
098: && Character.isWhitespace(text.charAt(lower))) {
099: lower++;
100: }
101: offset = lower;
102: int upper = lower;
103: while (upper < length
104: && Character
105: .isUnicodeIdentifierPart(text.charAt(upper))) {
106: upper++;
107: }
108: if (upper <= lower) {
109: position.setErrorIndex(lower);
110: throw unparsableString(text, position);
111: }
112: keyword = text.substring(lower, upper).toUpperCase(
113: parser.symbols.locale);
114: position.setIndex(upper);
115: /*
116: * Parse the opening bracket. According CTS's specification, two characters
117: * are acceptable: '[' and '('. At the end of this method, we will require
118: * the matching closing bracket. For example if the opening bracket was '[',
119: * then we will require that the closing bracket is ']' and not ')'.
120: */
121: int bracketIndex = -1;
122: do {
123: if (++bracketIndex >= parser.symbols.openingBrackets.length) {
124: list = null;
125: return;
126: }
127: } while (!parseOptionalSeparator(text, position,
128: parser.symbols.openingBrackets[bracketIndex]));
129: list = new LinkedList();
130: /*
131: * Parse all elements inside the bracket. Elements are parsed sequentially
132: * and their type are selected according their first character:
133: *
134: * - If the first character is a quote, then the element is parsed as a String.
135: * - Otherwise, if the first character is a unicode identifier start, then the
136: * element is parsed as a chidren Element.
137: * - Otherwise, the element is parsed as a number.
138: */
139: do {
140: if (position.getIndex() >= length) {
141: throw missingCharacter(parser.symbols.close, length);
142: }
143: //
144: // Try to parse the next element as a quoted string. We will take
145: // it as a string if the first non-blank character is a quote.
146: //
147: if (parseOptionalSeparator(text, position,
148: parser.symbols.quote)) {
149: lower = position.getIndex();
150: upper = text.indexOf(parser.symbols.quote, lower);
151: if (upper < lower) {
152: position.setErrorIndex(++lower);
153: throw missingCharacter(parser.symbols.quote, lower);
154: }
155: list.add(text.substring(lower, upper).trim());
156: position.setIndex(upper + 1);
157: continue;
158: }
159: //
160: // Try to parse the next element as a number. We will take it as a number if
161: // the first non-blank character is not the begining of an unicode identifier.
162: //
163: lower = position.getIndex();
164: if (!Character.isUnicodeIdentifierStart(text.charAt(lower))) {
165: final Number number = parser
166: .parseNumber(text, position);
167: if (number == null) {
168: // Do not update the error index; it is already updated by NumberFormat.
169: throw unparsableString(text, position);
170: }
171: list.add(number);
172: continue;
173: }
174: // Otherwise, add the element as a child element.
175: list.add(new Element(parser, text, position));
176: } while (parseOptionalSeparator(text, position,
177: parser.symbols.separator));
178: parseSeparator(text, position,
179: parser.symbols.closingBrackets[bracketIndex]);
180: }
181:
182: /**
183: * Returns {@code true} if the next non-whitespace character is the specified separator.
184: * Search is performed in string {@code text} from position {@code position}. If the
185: * separator is found, then the position is set to the first character after the separator.
186: * Otherwise, the position is set on the first non-blank character.
187: *
188: * @param text The text to parse.
189: * @param position In input, the position where to start parsing from.
190: * In output, the first character after the separator.
191: * @param separator The character to search.
192: * @return {@code true} if the next non-whitespace character is the separator,
193: * or {@code false} otherwise.
194: */
195: private static boolean parseOptionalSeparator(final String text,
196: final ParsePosition position, final char separator) {
197: final int length = text.length();
198: int index = position.getIndex();
199: while (index < length) {
200: final char c = text.charAt(index);
201: if (Character.isWhitespace(c)) {
202: index++;
203: continue;
204: }
205: if (c == separator) {
206: position.setIndex(++index);
207: return true;
208: }
209: break;
210: }
211: position.setIndex(index); // MANDATORY for correct working of the constructor.
212: return false;
213: }
214:
215: /**
216: * Moves to the next non-whitespace character and checks if this character is the
217: * specified separator. If the separator is found, it is skipped. Otherwise, this
218: * method thrown a {@link ParseException}.
219: *
220: * @param text The text to parse.
221: * @param position In input, the position where to start parsing from.
222: * In output, the first character after the separator.
223: * @param separator The character to search.
224: * @throws ParseException if the separator was not found.
225: */
226: private void parseSeparator(final String text,
227: final ParsePosition position, final char separator)
228: throws ParseException {
229: if (!parseOptionalSeparator(text, position, separator)) {
230: position.setErrorIndex(position.getIndex());
231: throw unparsableString(text, position);
232: }
233: }
234:
235: //////////////////////////////////////////////////////////////////////////////////////
236: //////// ////////
237: //////// Construction of a ParseException when a string can't be parsed ////////
238: //////// ////////
239: //////////////////////////////////////////////////////////////////////////////////////
240: /**
241: * Returns a {@link ParseException} with the specified cause. A localized string
242: * <code>"Error in <{@link #keyword}>"</code> will be prepend to the message.
243: * The error index will be the starting index of this {@code Element}.
244: *
245: * @param cause The cause of the failure, or {@code null} if none.
246: * @param message The message explaining the cause of the failure, or {@code null}
247: * for reusing the same message than {@code cause}.
248: * @return The exception to be thrown.
249: */
250: public ParseException parseFailed(final Exception cause,
251: String message) {
252: if (message == null) {
253: message = cause.getLocalizedMessage();
254: }
255: ParseException exception = new ParseException(
256: complete(message), offset);
257: exception = trim("parseFailed", exception);
258: exception.initCause(cause);
259: return exception;
260: }
261:
262: /**
263: * Returns a {@link ParseException} with a "Unparsable string" message. The error message
264: * is built from the specified string starting at the specified position. Properties
265: * {@link ParsePosition#getIndex} and {@link ParsePosition#getErrorIndex} must be accurate
266: * before this method is invoked.
267: *
268: * @param text The unparsable string.
269: * @param position The position in the string.
270: * @return An exception with a formatted error message.
271: */
272: private ParseException unparsableString(final String text,
273: final ParsePosition position) {
274: final int lower = position.getErrorIndex();
275: int upper = lower;
276: final int length = text.length();
277: if (upper < length) {
278: final int type = Character.getType(text.charAt(upper));
279: while (++upper < length) {
280: if (Character.getType(text.charAt(upper)) != type) {
281: break;
282: }
283: }
284: }
285: final String message;
286: if (lower == length) {
287: message = Errors.format(ErrorKeys.UNEXPECTED_END_OF_STRING);
288: } else {
289: message = Errors.format(ErrorKeys.UNPARSABLE_STRING_$2,
290: text.substring(position.getIndex()), text
291: .substring(lower, upper));
292: }
293: return trim("unparsableString", new ParseException(
294: complete(message), lower));
295: }
296:
297: /**
298: * Returns an exception saying that a character is missing.
299: *
300: * @param c The missing character.
301: * @param position The error position.
302: */
303: private ParseException missingCharacter(final char c,
304: final int position) {
305: return trim("missingCharacter", new ParseException(
306: complete(Errors.format(ErrorKeys.MISSING_CHARACTER_$1,
307: new Character(c))), position));
308: }
309:
310: /**
311: * Returns an exception saying that a parameter is missing.
312: *
313: * @param key The name of the missing parameter.
314: */
315: private ParseException missingParameter(final String key) {
316: int error = offset;
317: if (keyword != null) {
318: error += keyword.length();
319: }
320: return trim("missingParameter", new ParseException(
321: complete(Errors.format(ErrorKeys.MISSING_PARAMETER_$1,
322: key)), error));
323: }
324:
325: /**
326: * Append a prefix "Error in <keyword>: " before the error message.
327: *
328: * @param message The message to complete.
329: * @return The completed message.
330: */
331: private String complete(String message) {
332: if (keyword != null) {
333: message = Errors.format(ErrorKeys.IN_$1, keyword) + ' '
334: + message;
335: }
336: return message;
337: }
338:
339: /**
340: * Remove the exception factory method from the stack trace. The factory is
341: * not the place where the failure occurs; the error occurs in the factory's
342: * caller.
343: *
344: * @param factory The name of the factory method.
345: * @param exception The exception to trim.
346: * @return {@code exception} for convenience.
347: */
348: private static ParseException trim(final String factory,
349: final ParseException exception) {
350: StackTraceElement[] trace = exception.getStackTrace();
351: if (trace != null && trace.length != 0) {
352: if (factory.equals(trace[0].getMethodName())) {
353: trace = (StackTraceElement[]) XArray
354: .remove(trace, 0, 1);
355: exception.setStackTrace(trace);
356: }
357: }
358: return exception;
359: }
360:
361: /**
362: * Returns {@code true} if this element is the root element. For example in a WKT like
363: * {@code "GEOGCS["name", DATUM["name, ...]]"}, this is true for {@code "GEOGCS"} and
364: * false for all other elements inside, like {@code "DATUM"}.
365: *
366: * @since 2.3
367: */
368: public boolean isRoot() {
369: return this .offset == 0;
370: }
371:
372: //////////////////////////////////////////////////////////////////////////////////////
373: //////// ////////
374: //////// Pull elements from the tree ////////
375: //////// ////////
376: //////////////////////////////////////////////////////////////////////////////////////
377: /**
378: * Removes the next {@link Number} from the list and returns it.
379: *
380: * @param key The parameter name. Used for formatting
381: * an error message if no number are found.
382: * @return The next {@link Number} on the list as a {@code double}.
383: * @throws ParseException if no more number is available.
384: */
385: public double pullDouble(final String key) throws ParseException {
386: final Iterator iterator = list.iterator();
387: while (iterator.hasNext()) {
388: final Object object = iterator.next();
389: if (object instanceof Number) {
390: iterator.remove();
391: return ((Number) object).doubleValue();
392: }
393: }
394: throw missingParameter(key);
395: }
396:
397: /**
398: * Removes the next {@link Number} from the list and returns it
399: * as an integer.
400: *
401: * @param key The parameter name. Used for formatting
402: * an error message if no number are found.
403: * @return The next {@link Number} on the list as an {@code int}.
404: * @throws ParseException if no more number is available, or the number
405: * is not an integer.
406: */
407: public int pullInteger(final String key) throws ParseException {
408: final Iterator iterator = list.iterator();
409: while (iterator.hasNext()) {
410: final Object object = iterator.next();
411: if (object instanceof Number) {
412: iterator.remove();
413: final Number number = (Number) object;
414: if (number instanceof Float || number instanceof Double) {
415: throw new ParseException(
416: complete(Errors.format(
417: ErrorKeys.ILLEGAL_ARGUMENT_$2, key,
418: number)), offset);
419: }
420: return number.intValue();
421: }
422: }
423: throw missingParameter(key);
424: }
425:
426: /**
427: * Removes the next {@link String} from the list and returns it.
428: *
429: * @param key The parameter name. Used for formatting
430: * an error message if no number are found.
431: * @return The next {@link String} on the list.
432: * @throws ParseException if no more string is available.
433: */
434: public String pullString(final String key) throws ParseException {
435: final Iterator iterator = list.iterator();
436: while (iterator.hasNext()) {
437: final Object object = iterator.next();
438: if (object instanceof String) {
439: iterator.remove();
440: return (String) object;
441: }
442: }
443: throw missingParameter(key);
444: }
445:
446: /**
447: * Removes the next {@link Element} from the list and returns it.
448: *
449: * @param key The element name (e.g. <code>"PRIMEM"</code>).
450: * @return The next {@link Element} on the list.
451: * @throws ParseException if no more element is available.
452: */
453: public Element pullElement(final String key) throws ParseException {
454: final Element element = pullOptionalElement(key);
455: if (element != null) {
456: return element;
457: }
458: throw missingParameter(key);
459: }
460:
461: /**
462: * Removes the next {@link Element} from the list and returns it.
463: *
464: * @param key The element name (e.g. <code>"PRIMEM"</code>).
465: * @return The next {@link Element} on the list,
466: * or {@code null} if no more element is available.
467: */
468: public Element pullOptionalElement(String key) {
469: key = key.toUpperCase();
470: final Iterator iterator = list.iterator();
471: while (iterator.hasNext()) {
472: final Object object = iterator.next();
473: if (object instanceof Element) {
474: final Element element = (Element) object;
475: if (element.list != null && element.keyword.equals(key)) {
476: iterator.remove();
477: return element;
478: }
479: }
480: }
481: return null;
482: }
483:
484: /**
485: * Removes and returns the next {@link Element} with no bracket.
486: * The key is used only for only for formatting an error message.
487: *
488: * @param key The parameter name. Used only for formatting an error message.
489: * @return The next {@link Element} in the list, with no bracket.
490: * @throws ParseException if no more void element is available.
491: */
492: public Element pullVoidElement(final String key)
493: throws ParseException {
494: final Iterator iterator = list.iterator();
495: while (iterator.hasNext()) {
496: final Object object = iterator.next();
497: if (object instanceof Element) {
498: final Element element = (Element) object;
499: if (element.list == null) {
500: iterator.remove();
501: return element;
502: }
503: }
504: }
505: throw missingParameter(key);
506: }
507:
508: /**
509: * Returns the next element, or {@code null} if there is no more
510: * element. The element is <strong>not</strong> removed from the list.
511: */
512: public Object peek() {
513: return list.isEmpty() ? null : list.get(0);
514: }
515:
516: /**
517: * Close this element.
518: *
519: * @throws ParseException If the list still contains some unprocessed elements.
520: */
521: public void close() throws ParseException {
522: if (list != null && !list.isEmpty()) {
523: throw new ParseException(complete(Errors.format(
524: ErrorKeys.UNEXPECTED_PARAMETER_$1, list.get(0))),
525: offset + keyword.length());
526: }
527: }
528:
529: /**
530: * Returns the keyword. This overriding is needed for correct
531: * formatting of the error message in {@link #close}.
532: */
533: public String toString() {
534: return keyword;
535: }
536:
537: /**
538: * Print this {@code Element} as a tree.
539: * This method is used for debugging purpose only.
540: *
541: * @param out The output stream.
542: * @param level The indentation level (usually 0).
543: */
544: public void print(final PrintWriter out, final int level) {
545: final int tabWidth = 4;
546: out.print(Utilities.spaces(tabWidth * level));
547: out.println(keyword);
548: if (list == null) {
549: return;
550: }
551: final int size = list.size();
552: for (int j = 0; j < size; j++) {
553: final Object object = list.get(j);
554: if (object instanceof Element) {
555: ((Element) object).print(out, level + 1);
556: } else {
557: out.print(Utilities.spaces(tabWidth * (level + 1)));
558: out.println(object);
559: }
560: }
561: }
562: }
|