You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2015/05/19 01:31:27 UTC
svn commit: r1680131 - in /sis/branches/JDK8/core:
sis-metadata/src/main/java/org/apache/sis/io/wkt/
sis-metadata/src/test/java/org/apache/sis/io/wkt/
sis-metadata/src/test/java/org/apache/sis/test/suite/
sis-utility/src/main/java/org/apache/sis/util/r...
Author: desruisseaux
Date: Mon May 18 23:31:27 2015
New Revision: 1680131
URL: http://svn.apache.org/r1680131
Log:
Referencing: starting the port of WKT parser.
Added:
sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java (with props)
sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java (with props)
sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java (with props)
Modified:
sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
Added: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java?rev=1680131&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java (added)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] Mon May 18 23:31:27 2015
@@ -0,0 +1,574 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.io.wkt;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.io.PrintWriter;
+import java.text.ParsePosition;
+import java.text.ParseException;
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.metadata.WKTKeywords;
+
+import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
+import static org.apache.sis.util.CharSequences.skipLeadingWhitespaces;
+
+
+/**
+ * An element in a <cite>Well Know Text</cite> (WKT). An {@code Element} is made of {@link String},
+ * {@link Number} and other {@link Element}. For example:
+ *
+ * {@preformat text
+ * PrimeMeridian[“Greenwich”, 0.0, AngleUnit[“degree”, 0.017453292519943295]]]
+ * }
+ *
+ * Each {@code Element} object can contain an arbitrary amount of other elements.
+ * The result is a tree, which can be printed with {@link #print(PrintWriter, int)} for debugging purpose.
+ * Elements can be pulled in a <cite>first in, first out</cite> order.
+ *
+ * @author Rémi Ève (IRD)
+ * @author Martin Desruisseaux (IRD, Geomatys)
+ * @since 0.6
+ * @version 0.6
+ * @module
+ */
+final class Element {
+ /**
+ * Hard-coded list of elements in which to parse values as dates instead than numbers.
+ * We may try to find a more generic approach in a future version.
+ */
+ private static final String[] TEMPORAL = {
+ WKTKeywords.TimeOrigin,
+ WKTKeywords.TimeExtent
+ };
+
+ /**
+ * The position where this element starts in the string to be parsed.
+ */
+ private final int offset;
+
+ /**
+ * Keyword of this entity. For example: {@code "PrimeMeridian"}.
+ */
+ public final String keyword;
+
+ /**
+ * An ordered list of {@link String}s, {@link Number}s and other {@link Element}s.
+ * May be {@code null} if the keyword was not followed by a pair of brackets (e.g. "north").
+ */
+ private final List<Object> list;
+
+ /**
+ * Constructs a root element.
+ *
+ * @param singleton The only children for this root.
+ */
+ Element(final Element singleton) {
+ offset = 0;
+ keyword = null;
+ list = new LinkedList<>();
+ list.add(singleton);
+ }
+
+ /**
+ * Constructs a new {@code Element}.
+ *
+ * @param text The text to parse.
+ * @param position On input, the position where to start parsing from.
+ * On output, the first character after the separator.
+ */
+ Element(final Parser parser, 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.
+ */
+ offset = position.getIndex();
+ final int length = text.length();
+ int lower = skipLeadingWhitespaces(text, offset, length);
+ { // This block is for keeping some variables local.
+ int c = text.codePointAt(lower);
+ if (!Character.isUnicodeIdentifierStart(c)) {
+ keyword = text;
+ position.setErrorIndex(lower);
+ throw unparsableString(parser, text, position);
+ }
+ int upper = lower;
+ while ((upper += Character.charCount(c)) < length) {
+ c = text.codePointAt(upper);
+ if (!Character.isUnicodeIdentifierPart(c)) break;
+ }
+ keyword = text.substring(lower, upper);
+ lower = skipLeadingWhitespaces(text, upper, length);
+ }
+ final boolean isTemporal = ArraysExt.containsIgnoreCase(TEMPORAL, keyword);
+ /*
+ * At this point we have extracted the keyword (e.g. "PrimeMeridian"). Now parse the opening bracket.
+ * According WKT's specification, two characters are acceptable: '[' and '('. We accept both, but we
+ * will require the matching closing bracket at the end of this method. For example if the opening
+ * bracket was '[', then we will require that the closing bracket is ']' and not ')'.
+ */
+ if (lower >= length) {
+ position.setErrorIndex(lower);
+ list = null;
+ return;
+ }
+ final int openingBracket = text.codePointAt(lower);
+ final int closingBracket = parser.symbols.matchingBracket(openingBracket);
+ if (closingBracket < 0) {
+ position.setErrorIndex(lower);
+ list = null;
+ return;
+ }
+ lower = skipLeadingWhitespaces(text, lower + Character.charCount(openingBracket), length);
+ /*
+ * Parse all elements inside the bracket. Elements are parsed sequentially
+ * and their type are selected according their first character:
+ *
+ * - If the first character is a quote, then the value is returned as a String.
+ * - Otherwise, if the first character is a unicode identifier start, then the element is parsed as a chid Element.
+ * - Otherwise, if the characters are "true" of "false" (ignoring case), then the value is returned as a boolean.
+ * - Otherwise, the element is parsed as a number or as a date, depending of 'isTemporal' boolean value.
+ */
+ list = new LinkedList<>();
+ final String separator = parser.symbols.trimmedSeparator();
+ do {
+ if (lower >= length) {
+ position.setIndex(offset);
+ position.setErrorIndex(lower);
+ throw missingCharacter(parser, closingBracket, length);
+ }
+ final int c = text.codePointAt(lower);
+ final int closingQuote = parser.symbols.matchingQuote(c);
+ if (closingQuote >= 0) {
+ /*
+ * Try to parse the next element as a quoted string. We will take it as a string if the first non-blank
+ * character is a quote. Note that a double quote means that the quote should be included as-is in the
+ * parsed text.
+ */
+ final int n = Character.charCount(closingQuote);
+ lower += Character.charCount(c);
+ CharSequence content = null;
+ do {
+ final int upper = text.indexOf(closingQuote, lower);
+ if (upper < lower) {
+ position.setIndex(offset);
+ position.setErrorIndex(lower);
+ throw missingCharacter(parser, closingQuote, lower);
+ }
+ if (content == null) {
+ content = text.substring(lower, upper); // First text fragment, and usually the only one.
+ } else {
+ /*
+ * We will enter in this block only if we found at least one double quote.
+ * Convert the first text fragment to a StringBuilder so we can concatenate
+ * the next text fragments with only one quote between them.
+ */
+ if (content instanceof String) {
+ content = new StringBuilder((String) content);
+ }
+ ((StringBuilder) content).appendCodePoint(closingQuote).append(text, lower, upper);
+ }
+ lower = upper + n; // After the closing quote.
+ } while (lower < text.length() && text.codePointAt(lower) == closingQuote);
+ list.add(content.toString());
+ } else if (!Character.isUnicodeIdentifierStart(c)) {
+ /*
+ * Try to parse the next element as a date or a number. We will attempt such parsing
+ * if the first non-blank character is not the beginning of an unicode identifier.
+ * Otherwise we will assume that the next element is the keyword of a child 'Element'.
+ */
+ position.setIndex(lower);
+ final Object value;
+ if (isTemporal) {
+ value = parser.parseDate(text, position);
+ } else {
+ value = parser.parseNumber(text, position);
+ }
+ if (value == null) {
+ position.setIndex(offset);
+ // Do not update the error index; it is already updated by NumberFormat.
+ throw unparsableString(parser, text, position);
+ }
+ list.add(value);
+ lower = position.getIndex();
+ } else if (text.regionMatches(true, lower, "true", 0, 4)) {
+ list.add(Boolean.TRUE);
+ lower += 4;
+ } else if (text.regionMatches(true, lower, "false", 0, 5)) {
+ list.add(Boolean.FALSE);
+ lower += 5;
+ } else {
+ // Otherwise, add the element as a child element.
+ position.setIndex(lower);
+ list.add(new Element(parser, text, position));
+ lower = position.getIndex();
+ }
+ lower = skipLeadingWhitespaces(text, lower, length);
+ } while (text.regionMatches(lower, separator, 0, separator.length()));
+ /*
+ * Verify that the closing bracket is present.
+ */
+ if (lower < length) {
+ final int c = text.codePointAt(lower);
+ if (c == closingBracket) {
+ position.setIndex(lower + Character.charCount(c));
+ return;
+ }
+ }
+ position.setIndex(offset);
+ position.setErrorIndex(lower);
+ throw unparsableString(parser, text, position);
+ }
+
+
+
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ //////// ////////
+ //////// Construction of a ParseException when a string can not be parsed ////////
+ //////// ////////
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns a {@link ParseException} with the specified cause. A localized string
+ * <code>"Error in <{@link #keyword}>"</code> will be prepend to the message.
+ * The error index will be the starting index of this {@code Element}.
+ *
+ * @param cause The cause of the failure, or {@code null} if none.
+ * @param message The message explaining the cause of the failure, or {@code null}
+ * for reusing the same message than {@code cause}.
+ * @return The exception to be thrown.
+ */
+ final ParseException parseFailed(final Parser parser, final Exception cause, String message) {
+ if (message == null) {
+ message = Exceptions.getLocalizedMessage(cause, parser.displayLocale);
+ }
+ return (ParseException) new ParseException(Errors.getResources(parser.displayLocale)
+ .getString(Errors.Keys.ErrorIn_2, keyword, message), offset).initCause(cause);
+ }
+
+ /**
+ * 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 Parser parser, final String text, final ParsePosition position) {
+ final String message;
+ final Errors resources = Errors.getResources(parser.displayLocale);
+ final int errorIndex = Math.max(position.getIndex(), position.getErrorIndex());
+ final int length = text.length();
+ if (errorIndex == length) {
+ message = resources.getString(Errors.Keys.UnexpectedEndOfString_1, keyword);
+ } else {
+ message = resources.getString(Errors.Keys.UnparsableStringInElement_2, keyword,
+ CharSequences.token(text, errorIndex));
+ }
+ return new ParseException(message, errorIndex);
+ }
+
+ /**
+ * Returns an exception saying that a character is missing.
+ *
+ * @param c The missing character.
+ * @param position The error position.
+ */
+ private ParseException missingCharacter(final Parser parser, final int c, final int position) {
+ final StringBuilder buffer = new StringBuilder(2).appendCodePoint(c);
+ return new ParseException(Errors.getResources(parser.displayLocale)
+ .getString(Errors.Keys.MissingCharacterInElement_2, keyword, buffer), position);
+ }
+
+ /**
+ * Returns an exception saying that a component is missing.
+ *
+ * @param key The name of the missing component.
+ */
+ private ParseException missingParameter(final String key) {
+ int error = offset;
+ if (keyword != null) {
+ error += keyword.length();
+ }
+ return new ParseException(Errors.format(Errors.Keys.MissingComponentInElement_2, keyword, key), error);
+ }
+
+ /**
+ * Returns {@code true} if this element is the root element. For example in a WKT like
+ * {@code "GeodeticCRS["name", Datum["name, ...]]"}, this is true for {@code "GeodeticCRS"}
+ * and false for all other elements inside, like {@code "Datum"}.
+ *
+ * @return {@code true} if this element is the root element.
+ */
+ public boolean isRoot() {
+ return this.offset == 0;
+ }
+
+
+
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ //////// ////////
+ //////// Pull elements from the tree ////////
+ //////// ////////
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Removes the next {@link Date} from the list and returns it.
+ *
+ * @param key The parameter name. Used for formatting an error message if no date is found.
+ * @return The next {@link Date} on the list.
+ * @throws ParseException if no more date is available.
+ */
+ public Date pullDate(final String key) throws ParseException {
+ final Iterator<Object> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ final Object object = iterator.next();
+ if (object instanceof Date) {
+ iterator.remove();
+ return (Date) object;
+ }
+ }
+ throw missingParameter(key);
+ }
+
+ /**
+ * 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 is found.
+ * @return The next {@link Number} on the list as a {@code double}.
+ * @throws ParseException if no more number is available.
+ */
+ public double pullDouble(final String key) throws ParseException {
+ final Iterator<Object> 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 is found.
+ * @return The next {@link Number} on the list as an {@code 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<Object> 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(Errors.format(
+ Errors.Keys.UnparsableStringForClass_2, Integer.class, number), offset);
+ }
+ return number.intValue();
+ }
+ }
+ throw missingParameter(key);
+ }
+
+ /**
+ * Removes the next {@link Boolean} from the list and returns it.
+ *
+ * @param key The parameter name. Used for formatting an error message if no boolean is found.
+ * @return The next {@link Boolean} on the list as a {@code boolean}.
+ * @throws ParseException if no more boolean is available.
+ */
+ public boolean pullBoolean(final String key) throws ParseException {
+ final Iterator<Object> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ final Object object = iterator.next();
+ if (object instanceof Boolean) {
+ iterator.remove();
+ return (Boolean) object;
+ }
+ }
+ 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 is 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<Object> 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 Object} from the list and returns it.
+ *
+ * @param key The parameter name. Used for formatting an error message if no number is found.
+ * @return The next {@link Object} on the list (never {@code null}).
+ * @throws ParseException if no more object is available.
+ */
+ public Object pullObject(final String key) throws ParseException {
+ final Iterator<Object> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ final Object object = iterator.next();
+ if (object != null) {
+ iterator.remove();
+ return object;
+ }
+ }
+ throw missingParameter(key);
+ }
+
+ /**
+ * Removes the next {@link Element} from the list and returns it.
+ *
+ * @param key The element name (e.g. {@code "PRIMEM"}).
+ * @return The next {@link Element} on the list.
+ * @throws ParseException if no more element is available.
+ */
+ public Element pullElement(final String key) throws ParseException {
+ final Element element = pullOptionalElement(key);
+ if (element != null) {
+ return element;
+ }
+ throw missingParameter(key);
+ }
+
+ /**
+ * Removes the next {@link Element} from the list and returns it.
+ *
+ * @param key The element name (e.g. {@code "PRIMEM"}).
+ * @return The next {@link Element} on the list, or {@code null} if no more element is available.
+ */
+ public Element pullOptionalElement(String key) {
+ key = key.toUpperCase();
+ final Iterator<Object> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ final Object object = iterator.next();
+ if (object instanceof Element) {
+ final Element element = (Element) object;
+ if (element.list!=null && element.keyword.equals(key)) {
+ iterator.remove();
+ return element;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes and returns the next {@link Element} 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 Element} in the list, with no bracket.
+ * @throws ParseException if no more void element is available.
+ */
+ public Element pullVoidElement(final String key) throws ParseException {
+ final Iterator<Object> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ final Object object = iterator.next();
+ if (object instanceof Element) {
+ final Element element = (Element) object;
+ if (element.list == null) {
+ iterator.remove();
+ return element;
+ }
+ }
+ }
+ throw missingParameter(key);
+ }
+
+ /**
+ * Returns the next element, or {@code null} if there is no more
+ * element. The element is <strong>not</strong> removed from the list.
+ *
+ * @return The next element, or {@code null} if there is no more elements.
+ */
+ public Object peek() {
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ /**
+ * Closes this element.
+ *
+ * @throws ParseException If the list still contains some unprocessed elements.
+ */
+ public void close() throws ParseException {
+ if (!isNullOrEmpty(list)) {
+ throw new ParseException(Errors.format(Errors.Keys.UnexpectedParameter_1, list.get(0)), offset + keyword.length());
+ }
+ }
+
+ /**
+ * Returns the keyword. This overriding is needed for correct
+ * formatting of the error message in {@link #close}.
+ */
+ @Override
+ public String toString() {
+ return keyword;
+ }
+
+ /**
+ * Prints this {@code Element} as a tree.
+ * This method is used for debugging purpose only.
+ *
+ * @param out The output stream.
+ * @param level The indentation level (usually 0).
+ */
+ @Debug
+ public void print(final PrintWriter out, final int level) {
+ final int tabWidth = 4;
+ out.print(CharSequences.spaces(tabWidth * level));
+ out.println(keyword);
+ if (list == null) {
+ return;
+ }
+ final int size = list.size();
+ for (int j=0; j<size; j++) {
+ final Object object = list.get(j);
+ if (object instanceof Element) {
+ ((Element) object).print(out, level+1);
+ } else {
+ out.print(CharSequences.spaces(tabWidth * (level+1)));
+ out.println(object);
+ }
+ }
+ }
+}
Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Added: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java?rev=1680131&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java (added)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java [UTF-8] Mon May 18 23:31:27 2015
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.io.wkt;
+
+import java.util.Map;
+import java.util.Date;
+import java.util.Locale;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.DecimalFormat;
+import java.text.ParsePosition;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import org.apache.sis.util.Workaround;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
+
+/**
+ * Parses <cite>Well Known Text</cite> (WKT). Parsers are the converse of {@link Formatter}.
+ * Like the later, a parser is constructed with a given set of {@linkplain Symbols symbols}.
+ * Parsers also need a set of factories to be used for instantiating the parsed objects.
+ *
+ * <p>In current version, parsers are usually not intended to be subclassed outside this package.
+ * A few exceptions exist, for example {@link ReferencingParser#alterProperties(Map)} is one of
+ * the few hooks provided for overriding.</p>
+ *
+ * <p>Parsers are not synchronized. It is recommended to create separate parser instances for each thread.
+ * If multiple threads access a parser concurrently, it must be synchronized externally.</p>
+ *
+ * @author Rémi Eve (IRD)
+ * @author Martin Desruisseaux (IRD, Geomatys)
+ * @since 0.6
+ * @version 0.6
+ * @module
+ */
+abstract class Parser {
+ /**
+ * Set to {@code true} if parsing of number in scientific notation is allowed.
+ * The way to achieve that is currently a hack, because {@link NumberFormat}
+ * has no API for managing that as of JDK 1.8.
+ *
+ * @todo See if a future version of JDK allows us to get ride of this ugly hack.
+ */
+ @Workaround(library = "JDK", version = "1.8")
+ static final boolean SCIENTIFIC_NOTATION = true;
+
+ /**
+ * The locale for error messages (not for number parsing), or {@code null} for the system default.
+ */
+ final Locale displayLocale;
+
+ /**
+ * The symbols to use for parsing WKT.
+ */
+ final Symbols symbols;
+
+ /**
+ * The symbol for scientific notation, or {@code null} if none.
+ * This is usually {@code "E"} (note the upper case), but could also be something like {@code "×10^"}.
+ */
+ private final String exponentSymbol;
+
+ /**
+ * The object to use for parsing numbers.
+ */
+ private final NumberFormat numberFormat;
+
+ /**
+ * The object to use for parsing dates, created when first needed.
+ */
+ private DateFormat dateFormat;
+
+ /**
+ * Constructs a parser using the specified set of symbols.
+ *
+ * @param symbols The set of symbols to use.
+ * @param displayLocale The locale for error messages (not for number parsing),
+ * or {@code null} for the system default.
+ */
+ Parser(final Symbols symbols, final Locale displayLocale) {
+ this.displayLocale = displayLocale;
+ ensureNonNull("symbols", symbols);
+ this.symbols = symbols;
+ numberFormat = symbols.createNumberFormat();
+ if (SCIENTIFIC_NOTATION && numberFormat instanceof DecimalFormat) {
+ final DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
+ exponentSymbol = decimalFormat.getDecimalFormatSymbols().getExponentSeparator();
+ String pattern = decimalFormat.toPattern();
+ if (!pattern.contains("E0")) {
+ final StringBuilder buffer = new StringBuilder(pattern);
+ final int split = pattern.indexOf(';');
+ if (split >= 0) {
+ buffer.insert(split, "E0");
+ }
+ buffer.append("E0");
+ decimalFormat.applyPattern(buffer.toString());
+ }
+ } else {
+ exponentSymbol = null;
+ }
+ }
+
+ /**
+ * Parses a <cite>Well Know Text</cite> (WKT).
+ *
+ * @param text The text to be parsed.
+ * @param position The position to start parsing from.
+ * @return The parsed object.
+ * @throws ParseException if the string can not be parsed.
+ */
+ public final Object parseObject(final String text, final ParsePosition position) throws ParseException {
+ final int origin = position.getIndex();
+ try {
+ final Element element = getTree(text, position);
+ final Object object = parse(element);
+ element.close();
+ return object;
+ } catch (ParseException exception) {
+ position.setIndex(origin);
+ if (position.getErrorIndex() < origin) {
+ position.setErrorIndex(exception.getErrorOffset());
+ }
+ throw exception;
+ }
+ }
+
+ /**
+ * Parses the number at the given position.
+ */
+ final Number parseNumber(String text, final ParsePosition position) {
+ /*
+ * HACK: DecimalFormat.parse(…) does not understand lower case 'e' for scientific notation.
+ * It understands upper case 'E' only, so we need to perform the replacement here.
+ */
+ final int base = position.getIndex();
+ Number number = numberFormat.parse(text, position);
+ if (number != null && exponentSymbol != null) {
+ int i = position.getIndex();
+ if (text.regionMatches(true, i, exponentSymbol, 0, exponentSymbol.length())) {
+ text = new StringBuilder(text).replace(i, i + exponentSymbol.length(), exponentSymbol).toString();
+ position.setIndex(base);
+ number = numberFormat.parse(text, position);
+ }
+ }
+ return number;
+ }
+
+ /**
+ * Parses the date at the given position.
+ */
+ final Date parseDate(final String text, final ParsePosition position) {
+ if (dateFormat == null) {
+ dateFormat = new SimpleDateFormat(WKTFormat.DATE_PATTERN, symbols.getLocale());
+ }
+ return dateFormat.parse(text, position);
+ }
+
+ /**
+ * Parses the next element in the specified <cite>Well Know Text</cite> (WKT) tree.
+ *
+ * @param element The element to be parsed.
+ * @return The parsed object.
+ * @throws ParseException if the element can not be parsed.
+ */
+ abstract Object parse(final Element element) throws ParseException;
+
+ /**
+ * Returns a tree of {@link Element} for the specified text.
+ *
+ * @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.
+ * @return The tree of elements to parse.
+ * @throws ParseException If an parsing error occurred while creating the tree.
+ */
+ final Element getTree(final String text, final ParsePosition position) throws ParseException {
+ return new Element(new Element(this, text, position));
+ }
+}
Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java?rev=1680131&r1=1680130&r2=1680131&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java [UTF-8] Mon May 18 23:31:27 2015
@@ -66,7 +66,7 @@ import static org.apache.sis.util.Argume
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @since 0.4
- * @version 0.4
+ * @version 0.6
* @module
*
* @see WKTFormat#getSymbols()
@@ -164,6 +164,11 @@ public class Symbols implements Localize
private String separator;
/**
+ * Same value than {@link #separator} but without leading and trailing spaces.
+ */
+ private transient String trimmedSeparator;
+
+ /**
* {@code true} if this instance shall be considered as immutable.
*/
private boolean isImmutable;
@@ -175,13 +180,14 @@ public class Symbols implements Localize
*/
public Symbols(final Symbols symbols) {
ensureNonNull("symbols", symbols);
- locale = symbols.locale;
- brackets = symbols.brackets;
- quotes = symbols.quotes;
- quote = symbols.quote;
- openSequence = symbols.openSequence;
- closeSequence = symbols.closeSequence;
- separator = symbols.separator;
+ locale = symbols.locale;
+ brackets = symbols.brackets;
+ quotes = symbols.quotes;
+ quote = symbols.quote;
+ openSequence = symbols.openSequence;
+ closeSequence = symbols.closeSequence;
+ separator = symbols.separator;
+ trimmedSeparator = symbols.trimmedSeparator;
}
/**
@@ -189,14 +195,15 @@ public class Symbols implements Localize
* The given array is stored by reference - it is not cloned.
*/
private Symbols(final int[] brackets, final int[] quotes) {
- this.locale = Locale.ROOT;
- this.brackets = brackets;
- this.quotes = quotes;
- this.quote = "\"";
- this.openSequence = '{';
- this.closeSequence = '}';
- this.separator = ", ";
- this.isImmutable = true;
+ this.locale = Locale.ROOT;
+ this.brackets = brackets;
+ this.quotes = quotes;
+ this.quote = "\"";
+ this.openSequence = '{';
+ this.closeSequence = '}';
+ this.separator = ", ";
+ this.trimmedSeparator = ",";
+ this.isImmutable = true;
}
/**
@@ -251,19 +258,26 @@ public class Symbols implements Localize
}
/**
- * If the given character is an opening bracket, returns the matching closing bracket.
- * Otherwise returns -1.
+ * Implementation of {@link #matchingBracket(int)} and {@link #matchingQuote(int)}.
*/
- final int matchingBracket(final int c) {
- for (int i=0; i<brackets.length; i+=2) {
- if (brackets[i] == c) {
- return brackets[i+1];
+ private static int matching(final int[] chars, final int c) {
+ for (int i = 0; i < chars.length; i += 2) {
+ if (chars[i] == c) {
+ return chars[i + 1];
}
}
return -1;
}
/**
+ * If the given character is an opening bracket, returns the matching closing bracket.
+ * Otherwise returns -1.
+ */
+ final int matchingBracket(final int c) {
+ return matching(brackets, c);
+ }
+
+ /**
* Returns the number of paired brackets. For example if the WKT parser accepts both the
* {@code […]} and {@code (…)} bracket pairs, then this method returns 2.
*
@@ -324,6 +338,14 @@ public class Symbols implements Localize
}
/**
+ * If the given character is an opening quote, returns the matching closing quote.
+ * Otherwise returns -1.
+ */
+ final int matchingQuote(final int c) {
+ return matching(quotes, c);
+ }
+
+ /**
* Returns the number of paired quotes. For example if the WKT parser accepts both the
* {@code "…"} and {@code “…”} quote pairs, then this method returns 2.
*
@@ -473,8 +495,17 @@ public class Symbols implements Localize
*/
public void setSeparator(final String separator) {
checkWritePermission();
- ensureNonEmpty("separator", separator);
+ final String s = CharSequences.trimWhitespaces(separator.trim());
+ ensureNonEmpty("separator", s);
this.separator = separator;
+ trimmedSeparator = s;
+ }
+
+ /**
+ * Returns the separator without trailing spaces.
+ */
+ final String trimmedSeparator() {
+ return trimmedSeparator;
}
/**
@@ -688,6 +719,7 @@ public class Symbols implements Localize
if (equals(CURLY_BRACKETS)) return CURLY_BRACKETS;
}
quote = String.valueOf(Character.toChars(quotes[1]));
+ trimmedSeparator = CharSequences.trimWhitespaces(separator.trim());
return this;
}
}
Added: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java?rev=1680131&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java (added)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java [UTF-8] Mon May 18 23:31:27 2015
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.io.wkt;
+
+import java.util.Locale;
+import java.text.ParsePosition;
+import java.text.ParseException;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link Element} class.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.6
+ * @version 0.6
+ * @module
+ */
+public final strictfp class ElementTest extends TestCase {
+ /**
+ * Tests a {@link Element#pullString(String)}.
+ *
+ * @throws ParseException if an error occurred during the parsing.
+ */
+ @Test
+ public void testString() throws ParseException {
+ final ParsePosition position = new ParsePosition(0);
+ final Element element = new Element(new ParserMock(), "Datum[\"World Geodetic System 1984\"]", position);
+ assertEquals("Datum", element.keyword);
+ assertEquals("World Geodetic System 1984", element.pullString("name"));
+ }
+
+ /**
+ * A dummy parser for testing purpose.
+ */
+ private static final class ParserMock extends Parser {
+ ParserMock() {
+ super(Symbols.SQUARE_BRACKETS, Locale.ENGLISH);
+ }
+
+ @Override
+ Object parse(Element element) throws ParseException {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
Propchange: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java?rev=1680131&r1=1680130&r2=1680131&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] Mon May 18 23:31:27 2015
@@ -97,6 +97,7 @@ import org.junit.BeforeClass;
org.apache.sis.io.wkt.CharEncodingTest.class,
org.apache.sis.io.wkt.ColorsTest.class,
org.apache.sis.io.wkt.FormatterTest.class,
+ org.apache.sis.io.wkt.ElementTest.class,
org.apache.sis.io.wkt.WKTFormatTest.class
})
public final strictfp class MetadataTestSuite extends TestSuite {
Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1680131&r1=1680130&r2=1680131&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Mon May 18 23:31:27 2015
@@ -246,6 +246,11 @@ public final class Errors extends Indexe
public static final short EmptyProperty_1 = 23;
/**
+ * Error in “{0}”: {1}
+ */
+ public static final short ErrorIn_2 = 190;
+
+ /**
* Argument ‘{0}’ shall not contain more than {1} elements. A number of {2} is excessive.
*/
public static final short ExcessiveArgumentSize_3 = 24;
@@ -525,6 +530,16 @@ public final class Errors extends Indexe
public static final short MissingAuthority_1 = 135;
/**
+ * Missing a ‘{1}’ character in “{0}” element.
+ */
+ public static final short MissingCharacterInElement_2 = 188;
+
+ /**
+ * Missing a “{1}” component in “{0}”.
+ */
+ public static final short MissingComponentInElement_2 = 189;
+
+ /**
* No horizontal dimension found in “{0}”.
*/
public static final short MissingHorizontalDimension_1 = 169;
@@ -951,6 +966,11 @@ public final class Errors extends Indexe
public static final short UnparsableStringForClass_3 = 125;
/**
+ * Can not parse “{1}” in element “{0}”.
+ */
+ public static final short UnparsableStringInElement_2 = 187;
+
+ /**
* Feature named “{0}” has not yet been resolved.
*/
public static final short UnresolvedFeatureName_1 = 164;
Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1680131&r1=1680130&r2=1680131&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Mon May 18 23:31:27 2015
@@ -60,6 +60,7 @@ EmptyArgument_1 = Argu
EmptyDictionary = The dictionary shall contain at least one entry.
EmptyEnvelope2D = Envelope must be at least two-dimensional and non-empty.
EmptyProperty_1 = Property named \u201c{0}\u201d shall not be empty.
+ErrorIn_2 = Error in \u201c{0}\u201d: {1}
ExcessiveArgumentSize_3 = Argument \u2018{0}\u2019 shall not contain more than {1} elements. A number of {2} is excessive.
ExcessiveListSize_2 = A size of {1} elements is excessive for the \u201c{0}\u201d list.
ExcessiveNumberOfDimensions_1 = For this algorithm, {0} is an excessive number of dimensions.
@@ -115,6 +116,8 @@ MismatchedPropertyType_1 = Mism
MismatchedTransformDimension_3 = The transform has {2} {0,choice,0#source|1#target} dimension{2,choice,1#|2#s}, while {1} was expected.
MismatchedValueClass_3 = Class of \u201c{0}\u201d values is \u2018{2}\u2019, but the requested type is \u2018{1}\u2019.
MissingAuthority_1 = No authority was specified for code \u201c{0}\u201d. The expected syntax is \u201cAUTHORITY:CODE\u201d.
+MissingCharacterInElement_2 = Missing a \u2018{1}\u2019 character in \u201c{0}\u201d element.
+MissingComponentInElement_2 = Missing a \u201c{1}\u201d component in \u201c{0}\u201d.
MissingInterpolationOrdinates = Not enough dimension in \u2018MathTransform\u2019 input or output coordinates for the interpolation points.
MissingNamespace_1 = \u201c{0}\u201d has no namespace.
MissingRequiredModule_1 = This operation requires the \u201c{0}\u201d module.
@@ -201,6 +204,7 @@ UnmodifiableMetadata = This
UnmodifiableObject_1 = This instance of \u2018{0}\u2019 is not modifiable.
UnparsableStringForClass_2 = Text \u201c{1}\u201d can not be parsed as an object of type \u2018{0}\u2019.
UnparsableStringForClass_3 = Text \u201c{1}\u201d can not be parsed as an object of type \u2018{0}\u2019, because of the \u201c{2}\u201d characters.
+UnparsableStringInElement_2 = Can not parse \u201c{1}\u201d in element \u201c{0}\u201d.
UnresolvedFeatureName_1 = Feature named \u201c{0}\u201d has not yet been resolved.
UnspecifiedCRS = Coordinate reference system has not been specified.
UnspecifiedFormatForClass_1 = No format is specified for objects of class \u2018{0}\u2019.
Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1680131&r1=1680130&r2=1680131&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Mon May 18 23:31:27 2015
@@ -50,6 +50,7 @@ EmptyArgument_1 = L\u2
EmptyDictionary = Le dictionnaire doit contenir au moins une entr\u00e9e.
EmptyEnvelope2D = L\u2019enveloppe doit avoir au moins deux dimensions et ne pas \u00eatre vide.
EmptyProperty_1 = La propri\u00e9t\u00e9 nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb ne doit pas \u00eatre vide.
+ErrorIn_2 = Erreur dans \u00ab\u202f{0}\u202f\u00bb\u2008: {1}
ExcessiveArgumentSize_3 = L\u2019argument \u2018{0}\u2019 ne peut pas contenir plus de {1} \u00e9l\u00e9ments. Un nombre de {2} est excessif.
ExcessiveListSize_2 = Une taille de {1} \u00e9l\u00e9ments est excessive pour la liste \u00ab\u202f{0}\u202f\u00bb.
ExcessiveNumberOfDimensions_1 = Pour cet algorithme, {0} est un trop grand nombre de dimensions.
@@ -105,6 +106,8 @@ MismatchedPropertyType_1 = Le t
MismatchedTransformDimension_3 = La {0,choice,0#source|1#destination} de la transformation a {2} dimension{2,choice,1#|2#s}, alors qu\u2019on en attendait {1}.
MismatchedValueClass_3 = Les valeurs de \u00ab\u202f{0}\u202f\u00bb sont de la classe \u2018{2}\u2019, alors que le type demand\u00e9 \u00e9tait \u2018{1}\u2019.
MissingAuthority_1 = Aucune autorit\u00e9 n\u2019a \u00e9t\u00e9 sp\u00e9cifi\u00e9e pour le code \u00ab\u202f{0}\u202f\u00bb. Le format attendu est \u00ab\u202fAUTORIT\u00c9:CODE\u202f\u00bb.
+MissingCharacterInElement_2 = Un caract\u00e8re \u2018{1}\u2019 est manquant dans l\u2019\u00e9l\u00e9ment \u00ab\u202f{0}\u202f\u00bb.
+MissingComponentInElement_2 = Il manque une composante \u00ab\u202f{1}\u202f\u00bb dans l\u2019\u00e9l\u00e9ment \u00ab\u202f{0}\u202f\u00bb.
MissingInterpolationOrdinates = La dimension des coordonn\u00e9es en entr\u00e9 ou en sortie du \u2018MathTransform\u2019 n\u2019est pas suffisante pour contenir les points d\u2019interpolation.
MissingNamespace_1 = \u201c{0}\u201d est d\u00e9fini sans espace de noms.
MissingRequiredModule_1 = Cette op\u00e9ration requiert le module \u00ab\u202f{0}\u202f\u00bb.
@@ -193,6 +196,7 @@ UnspecifiedFormatForClass_1 = Aucu
UnspecifiedParameterValues = Les valeurs des param\u00e8tres n\u2019ont pas \u00e9t\u00e9 sp\u00e9cifi\u00e9es.
UnparsableStringForClass_2 = Le texte \u00ab\u202f{1}\u202f\u00bb n\u2019est pas reconnu comme un objet de type \u2018{0}\u2019.
UnparsableStringForClass_3 = Le texte \u00ab\u202f{1}\u202f\u00bb n\u2019est pas reconnu comme un objet de type \u2018{0}\u2019, \u00e0 cause des caract\u00e8res \u00ab\u202f{2}\u202f\u00bb.
+UnparsableStringInElement_2 = Le texte \u00ab\u202f{1}\u202f\u00bb dans l\u2019\u00e9l\u00e9ment \u00ab\u202f{0}\u202f\u00bb ne peut pas \u00eatre lu.
UnresolvedFeatureName_1 = L\u2019entit\u00e9 nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb n\u2019a pas encore \u00e9t\u00e9 r\u00e9solue.
UnsupportedImplementation_1 = Les instances de \u2018{0}\u2019 ne peuvent pas \u00eatre g\u00e9r\u00e9es parce que les impl\u00e9mentations arbitraires ne sont pas encore support\u00e9es.
UnsupportedOperation_1 = L\u2019op\u00e9ration \u2018{0}\u2019 n\u2019est pas support\u00e9e.