/*
* Geotools 2 - OpenSource mapping toolkit
* (C) 2003, Geotools Project Managment Committee (PMC)
* (C) 2002, Institut de Recherche pour le Développement
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.geotools.resources;
// Collections
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
/**
* An element in a Well Know Text (WKT).
* A WKTElement
is made of {@link String}, {@link Number}
* and other {@link WKTElement}. For example:
*
*
* * Each* PRIMEM["Greenwich", 0.0, AUTHORITY["some authority", "Greenwich"]] *
WKTElement
object can contains an arbitrary amount of other elements.
* The result is a tree, which can be printed with {@link #print}.
* Elements can be pull in a first in, first out order.
*
* @source $URL$
* @version $Id$
* @author Remi Eve
* @author Martin Desruisseaux
*
* @deprecated Rempaced by {@link org.geotools.referencing.wkt.Element}.
*/
public final class WKTElement {
/**
* The position where this element starts in the string to be parsed.
*/
private final int offset;
/**
* Keyword of this entity. For example: "PRIMEM".
*/
public final String keyword;
/**
* An ordered list of {@link String}s, {@link Number}s and other {@link WKTElement}s.
* May be null
if the keyword was not followed by a pair of brackets
* (e.g. "NORTH").
*/
private final List list;
/**
* Construct a root element.
*
* @param element The only children for this root.
*/
WKTElement(final WKTElement singleton) {
offset = 0;
keyword = null;
list = new LinkedList();
list.add(singleton);
}
/**
* Construct a new WKTElement
.
*
* @param text The text to parse.
* @param position In input, the position where to start parsing from.
* In output, the first character after the separator.
* @param separator The character to search.
*/
WKTElement(final WKTFormat format, final String text, final ParsePosition position)
throws ParseException
{
/*
* Find the first keyword in the specified string. If a keyword is found, then
* the position is set to the index of the first character after the keyword.
*/
int lower = position.getIndex();
final int length = text.length();
while (lowertrue
if the next non-whitespace character is the specified separator.
* Search is performed in string text
from position position
. If the
* separator is found, then the position is set to the first character after the separator.
* Otherwise, the position is set on the first non-blank character.
*
* @param text The text to parse.
* @param position In input, the position where to start parsing from.
* In output, the first character after the separator.
* @param separator The character to search.
* @return true
if the next non-whitespace character is the separator,
* or false
otherwise.
*/
private static boolean parseOptionalSeparator(final String text,
final ParsePosition position,
final char separator)
{
final int length = text.length();
int index = position.getIndex();
while (index < length) {
final char c = text.charAt(index);
if (Character.isWhitespace(c)) {
index++;
continue;
}
if (c == separator) {
position.setIndex(++index);
return true;
}
break;
}
position.setIndex(index); // MANDATORY for correct working of the constructor.
return false;
}
/**
* Moves to the next non-whitespace character and checks if this character is the
* specified separator. If the separator is found, it is skipped. Otherwise, this
* method thrown a {@link ParseException}.
*
* @param text The text to parse.
* @param position In input, the position where to start parsing from.
* In output, the first character after the separator.
* @param separator The character to search.
* @throws ParseException if the separator was not found.
*/
private void parseSeparator(final String text,
final ParsePosition position,
final char separator)
throws ParseException
{
if (!parseOptionalSeparator(text, position, separator)) {
position.setErrorIndex(position.getIndex());
throw unparsableString(text, position);
}
}
//////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Construction of a ParseException when a string can't be parsed ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////
/**
* Returns a {@link ParseException} with the specified cause. A localized string
* "Error in <{@link #keyword}>"
will be prepend to the message.
* The error index will be the starting index of this WKTElement
.
*
* @param cause The cause of the failure, or null
if none.
* @param message The message explaining the cause of the failure, or null
* for reusing the same message than cause
.
* @return The exception to be thrown.
*/
public ParseException parseFailed(final Exception cause, String message) {
if (message == null) {
message = cause.getLocalizedMessage();
}
ParseException exception = new ParseException(complete(message), offset);
exception = trim("parseFailed", exception);
exception.initCause(cause);
return exception;
}
/**
* Returns a {@link ParseException} with a "Unparsable string" message. The error message
* is built from the specified string starting at the specified position. Properties
* {@link ParsePosition#getIndex} and {@link ParsePosition#getErrorIndex} must be accurate
* before this method is invoked.
*
* @param text The unparsable string.
* @param position The position in the string.
* @return An exception with a formatted error message.
*/
private ParseException unparsableString(final String text, final ParsePosition position) {
final int lower = position.getErrorIndex();
int upper = lower;
final int length = text.length();
if (upper < length) {
final int type = Character.getType(text.charAt(upper));
while (++upper < length) {
if (Character.getType(text.charAt(upper)) != type) {
break;
}
}
}
return trim("unparsableString", new ParseException(complete(
Errors.format(ErrorKeys.UNPARSABLE_STRING_$2,
text.substring(position.getIndex()), text.substring(lower, upper))), lower));
}
/**
* Returns an exception saying that a character is missing.
*
* @param c The missing character.
* @param position The error position.
*/
private ParseException missingCharacter(final char c, final int position) {
return trim("missingCharacter", new ParseException(complete(
Errors.format(ErrorKeys.MISSING_CHARACTER_$1, new Character(c))),
position));
}
/**
* Returns an exception saying that a parameter is missing.
*
* @param key The name of the missing parameter.
*/
private ParseException missingParameter(final String key) {
return trim("missingParameter", new ParseException(complete(
Errors.format(ErrorKeys.MISSING_PARAMETER_$1, key)),
offset + keyword.length()));
}
/**
* Append a prefix "Error in exception
for convenience.
*/
private static ParseException trim(final String factory, final ParseException exception) {
StackTraceElement[] trace = exception.getStackTrace();
if (trace!=null && trace.length!=0) {
if (factory.equals(trace[0].getMethodName())) {
trace = (StackTraceElement[]) XArray.remove(trace, 0, 1);
exception.setStackTrace(trace);
}
}
return exception;
}
//////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// Pull elements from the tree ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////
/**
* Removes the next {@link Number} from the list and returns it.
*
* @param key The parameter name. Used for formatting
* an error message if no number are found.
* @return The next {@link Number} on the list as a double
.
* @throws ParseException if no more number is available.
*/
public double pullDouble(final String key) throws ParseException {
final Iterator iterator = list.iterator();
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object instanceof Number) {
iterator.remove();
return ((Number)object).doubleValue();
}
}
throw missingParameter(key);
}
/**
* Removes the next {@link Number} from the list and returns it
* as an integer.
*
* @param key The parameter name. Used for formatting
* an error message if no number are found.
* @return The next {@link Number} on the list as an int
.
* @throws ParseException if no more number is available, or the number
* is not an integer.
*/
public int pullInteger(final String key) throws ParseException {
final Iterator iterator = list.iterator();
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object instanceof Number) {
iterator.remove();
final Number number = (Number) object;
if (number instanceof Float || number instanceof Double) {
throw new ParseException(complete(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, key, number)), offset);
}
return number.intValue();
}
}
throw missingParameter(key);
}
/**
* Removes the next {@link String} from the list and returns it.
*
* @param key The parameter name. Used for formatting
* an error message if no number are found.
* @return The next {@link String} on the list.
* @throws ParseException if no more string is available.
*/
public String pullString(final String key) throws ParseException {
final Iterator iterator = list.iterator();
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object instanceof String) {
iterator.remove();
return (String)object;
}
}
throw missingParameter(key);
}
/**
* Removes the next {@link WKTElement} from the list and returns it.
*
* @param key The element name (e.g. "PRIMEM"
).
* @return The next {@link WKTElement} on the list.
* @throws ParseException if no more element is available.
*/
public WKTElement pullElement(final String key) throws ParseException {
final WKTElement element = pullOptionalElement(key);
if (element != null) {
return element;
}
throw missingParameter(key);
}
/**
* Removes the next {@link WKTElement} from the list and returns it.
*
* @param key The element name (e.g. "PRIMEM"
).
* @return The next {@link WKTElement} on the list,
* or null
if no more element is available.
*/
public WKTElement pullOptionalElement(String key) {
key = key.toUpperCase();
final Iterator iterator = list.iterator();
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object instanceof WKTElement) {
final WKTElement element = (WKTElement) object;
if (element.list!=null && element.keyword.equals(key)) {
iterator.remove();
return element;
}
}
}
return null;
}
/**
* Removes and returns the next {@link WKTElement} with no bracket.
* The key is used only for only for formatting an error message.
*
* @param key The parameter name. Used only for formatting an error message.
* @return The next {@link WKTElement} in the list, with no bracket.
* @throws ParseException if no more void element is available.
*/
public WKTElement pullVoidElement(final String key) throws ParseException {
final Iterator iterator = list.iterator();
while (iterator.hasNext()) {
final Object object = iterator.next();
if (object instanceof WKTElement) {
final WKTElement element = (WKTElement) object;
if (element.list == null) {
iterator.remove();
return element;
}
}
}
throw missingParameter(key);
}
/**
* Returns the next element, or null
if there is no more
* element. The element is not removed from the list.
*/
public Object peek() {
return list.isEmpty() ? null : list.get(0);
}
/**
* Close this element.
*
* @throws ParseException If the list still contains some unprocessed elements.
*/
public void close() throws ParseException {
if (list!=null && !list.isEmpty()) {
throw new ParseException(complete(Errors.format(
ErrorKeys.UNEXPECTED_PARAMETER_$1, list.get(0))),
offset+keyword.length());
}
}
/**
* Returns the keyword. This overriding is needed for correct
* formatting of the error message in {@link #close}.
*/
public String toString() {
return keyword;
}
/**
* Print this WKTElement
as a tree.
* This method is used for debugging purpose only.
*
* @param out The output stream.
* @param level The indentation level (usually 0).
*/
public void print(final PrintWriter out, final int level) {
final int tabWidth = 4;
out.print(Utilities.spaces(tabWidth * level));
out.println(keyword);
if (list == null) {
return;
}
final int size = list.size();
for (int j=0; j