/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.referencing.wkt;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
/**
* The set of symbols to use for WKT parsing and formatting.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class Symbols {
/**
* The default set of symbols.
*/
public static final Symbols DEFAULT = new Symbols(Locale.US);
// DONT't invoke the default constructor for this one.
/**
* A set of symbols with parameters between square brackets, like {@code [...]}.
*/
public static final Symbols SQUARE_BRACKETS = DEFAULT;
/**
* A set of symbols with parameters between parentheses,
* like {@code (...)}.
*/
public static final Symbols CURLY_BRACKETS = new Symbols();
static {
CURLY_BRACKETS.open = '(';
CURLY_BRACKETS.close = ')';
}
/* ----------------------------------------------------------
* NOTE: Consider all fields below as final.
* It is not only in order to make construction easier.
* If the informations provided by those fields became
* needed outside of this package, then we need to make
* them private and declare accessors instead.
* ---------------------------------------------------------- */
/**
* The locale for querying localizable information.
*/
final Locale locale;
/**
* The character used for opening brace.
* Usually {@code '['}, but {@code '('} is legal as well.
*/
char open = '[';
/**
* The character used for closing brace.
* Usually {@code ']'}, but {@code ')'} is legal as well.
*/
char close = ']';
/**
* The character used for opening an array or enumeration.
* Usually {@code '{'}.
*/
final char openArray = '{';
/**
* The character used for closing an array or enumeration.
* Usually {@code '}'}.
*/
final char closeArray = '}';
/**
* The character used for quote.
* Usually {@code '"'}.
*/
final char quote = '"';
/**
* The character used as a separator. Usually {@code ','}, but would need
* to be changed if a non-English locale is used for formatting numbers.
*/
char separator = ',';
/**
* The character used for space.
* Usually <code>' '</code>, but could be a no-break space too if unicode is allowed.
*/
final char space = ' ';
/**
* List of caracters acceptable as opening bracket. The closing bracket must
* be the character in the {@code closingBrackets} array at the same index
* than the opening bracket.
*/
final char[] openingBrackets = {'[', '('};
/**
* List of caracters acceptable as closing bracket.
*/
final char[] closingBrackets = {']', ')'};
/**
* The object to use for parsing and formatting numbers.
*
* <STRONG>Note:</STRONG> {@link NumberFormat} object are usually not thread safe.
* Consequently, each instances of {@link Parser} or {@link Formatter} must use a
* clone of this object, not this object directly (unless they synchronize on it).
*/
final NumberFormat numberFormat;
/**
* Creates a new instance initialized to the default symbols.
*/
private Symbols() {
locale = Locale.US;
numberFormat = DEFAULT.numberFormat;
}
/**
* Creates a new set of symbols for the specified locale.
*/
public Symbols(final Locale locale) {
this.locale = locale;
numberFormat = NumberFormat.getNumberInstance(locale);
numberFormat.setGroupingUsed(false);
numberFormat.setMinimumFractionDigits(1);
numberFormat.setMaximumFractionDigits(20);
/*
* The "maximum fraction digits" seems hight for the precision of floating
* points (even in double precision), but this is because the above format
* do not uses the scientific notation. For example 1E-5 is always formatted
* as 0.00001, and 1E-340 would actually need a maximum fraction digits of
* 340. For most parameters, such low values should not occurs and may be
* rounding error for the 0 value. For semi-major and semi-minor axis, we
* often want to avoid exponential notation as well.
*/
if (numberFormat instanceof DecimalFormat) {
final char decimalSeparator = ((DecimalFormat) numberFormat)
.getDecimalFormatSymbols().getDecimalSeparator();
if (decimalSeparator == ',') {
separator = ';';
}
}
}
/**
* Returns {@code true} if the specified WKT contains at least one {@code AXIS[...]} element.
* This method tries to make a quick checks taking in account a minimal set of WKT syntax rules.
*
* @since 2.4
*/
public boolean containsAxis(final CharSequence wkt) {
return indexOf(wkt, "AXIS", 0) >= 0;
}
/**
* Returns the index after the specified element in the specified WKT, or -1 if not found.
* The element must be followed (ignoring spaces) by an opening bracket. If found, this
* method returns the index of the opening bracket after the element.
*
* @param wkt The WKT to parse.
* @param element The element to search. Must contains only uppercase letters.
* @param index The index to start the search from.
*/
private int indexOf(final CharSequence wkt, final String element, int index) {
assert element.equals(element.trim().toUpperCase(locale)) : element;
assert element.indexOf(quote) < 0 : element;
boolean isQuoting = false;
final int elementLength = element.length();
final int length = wkt.length();
if (index < length) {
char c = wkt.charAt(index);
search: while (true) {
// Do not parse any content between quotes.
if (c == quote) {
isQuoting = !isQuoting;
}
if (isQuoting || !Character.isJavaIdentifierStart(c)) {
if (++index == length) {
break search;
}
c = wkt.charAt(index);
continue;
}
// Checks if we have a match.
for (int j=0; j<elementLength; j++) {
c = Character.toUpperCase(c);
if (c != element.charAt(j)) {
// No match. Skip all remaining letters and resume the search.
while (Character.isJavaIdentifierPart(c)) {
if (++index == length) {
break search;
}
c = wkt.charAt(index);
}
continue search;
}
if (++index == length) {
break search;
}
c = wkt.charAt(index);
}
// Checks if the next character (ignoring space) is an opening brace.
while (Character.isWhitespace(c)) {
if (++index == length) {
break search;
}
c = wkt.charAt(index);
}
for (int i=0; i<openingBrackets.length; i++) {
if (c == openingBrackets[i]) {
return index;
}
}
}
}
return -1;
}
}