sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1689674 [1/2] - in /sis/branches/JDK7: ./ core/sis-metadata/src/main/java/org/apache/sis/io/wkt/ core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ core/sis-referencing/src/main/java/org/apache/sis/referencing/ core/sis-referencing/src...
Date Tue, 07 Jul 2015 13:56:09 GMT
Author: desruisseaux
Date: Tue Jul  7 13:56:08 2015
New Revision: 1689674

URL: http://svn.apache.org/r1689674
Log:
Merge from JDK8 branch the support of WKT fragments.

Modified:
    sis/branches/JDK7/   (props changed)
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties
    sis/branches/JDK7/src/main/javadoc/stylesheet.css

Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Jul  7 13:56:08 2015
@@ -1,4 +1,4 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394913-1508480
-/sis/branches/JDK8:1584960-1689363
+/sis/branches/JDK8:1584960-1689672
 /sis/trunk:1394364-1508466,1519089-1519674

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -26,6 +26,9 @@ import java.text.NumberFormat;
 import java.text.DecimalFormat;
 import java.text.ParsePosition;
 import java.text.ParseException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.LogRecord;
 import javax.measure.unit.Unit;
 import javax.measure.unit.UnitFormat;
 import org.opengis.util.FactoryException;
@@ -33,6 +36,7 @@ import org.opengis.util.InternationalStr
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Workaround;
+import org.apache.sis.util.logging.Logging;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
@@ -85,6 +89,13 @@ abstract class AbstractParser implements
     static final boolean SCIENTIFIC_NOTATION = true;
 
     /**
+     * The logger to use for reporting warnings when this parser is used through the {@link #createFromWKT(String)}.
+     * This happen most often when the user invoke the {@link org.apache.sis.referencing.CRS#fromWKT(String)}
+     * convenience method.
+     */
+    private static final Logger LOGGER = Logging.getLogger(AbstractParser.class);
+
+    /**
      * The locale for error messages (not for number parsing), or {@code null} for the system default.
      */
     final Locale errorLocale;
@@ -116,6 +127,12 @@ abstract class AbstractParser implements
     private UnitFormat unitFormat;
 
     /**
+     * Reference to the {@link WKTFormat#fragments} map, or an empty map if none.
+     * This parser will only read this map, never write to it.
+     */
+    final Map<String,Element> fragments;
+
+    /**
      * Keyword of unknown elements. The ISO 19162 specification requires that we ignore unknown elements,
      * but we will nevertheless report them as warnings.
      * The meaning of this map is:
@@ -139,19 +156,21 @@ abstract class AbstractParser implements
      * Constructs a parser using the specified set of symbols.
      *
      * @param symbols       The set of symbols to use.
+     * @param fragments     Reference to the {@link WKTFormat#fragments} map, or an empty map if none.
      * @param numberFormat  The number format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param dateFormat    The date format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param unitFormat    The unit format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param errorLocale   The locale for error messages (not for parsing), or {@code null} for the system default.
      */
-    AbstractParser(final Symbols symbols, NumberFormat numberFormat, final DateFormat dateFormat,
-            final UnitFormat unitFormat, final Locale errorLocale)
+    AbstractParser(final Symbols symbols, final Map<String,Element> fragments, NumberFormat numberFormat,
+            final DateFormat dateFormat, final UnitFormat unitFormat, final Locale errorLocale)
     {
         ensureNonNull("symbols", symbols);
         if (numberFormat == null) {
             numberFormat = symbols.createNumberFormat();
         }
         this.symbols     = symbols;
+        this.fragments   = fragments;
         this.dateFormat  = dateFormat;
         this.unitFormat  = unitFormat;
         this.errorLocale = errorLocale;
@@ -177,6 +196,12 @@ abstract class AbstractParser implements
     }
 
     /**
+     * Returns the name of the class providing the publicly-accessible {@code createFromWKT(String)} method.
+     * This information is used for logging purpose only.
+     */
+    abstract String getPublicFacade();
+
+    /**
      * Creates the object from a string. This method is for implementation of {@code createFromWKT(String)}
      * method is SIS factories only.
      *
@@ -189,8 +214,9 @@ abstract class AbstractParser implements
      */
     @Override
     public final Object createFromWKT(final String text) throws FactoryException {
+        final Object value;
         try {
-            return parseObject(text, new ParsePosition(0));
+            value = parseObject(text, new ParsePosition(0));
         } catch (ParseException exception) {
             final Throwable cause = exception.getCause();
             if (cause instanceof FactoryException) {
@@ -198,6 +224,15 @@ abstract class AbstractParser implements
             }
             throw new FactoryException(exception.getMessage(), exception);
         }
+        final Warnings warnings = getAndClearWarnings(value);
+        if (warnings != null) {
+            final LogRecord record = new LogRecord(Level.WARNING, warnings.toString());
+            record.setSourceClassName(getPublicFacade());
+            record.setSourceMethodName("createFromWKT");
+            record.setLoggerName(LOGGER.getName());
+            LOGGER.log(record);
+        }
+        return value;
     }
 
     /**
@@ -211,7 +246,7 @@ abstract class AbstractParser implements
     public Object parseObject(final String text, final ParsePosition position) throws ParseException {
         warnings = null;
         ignoredElements.clear();
-        final Element element = new Element(new Element(this, text, position));
+        final Element element = new Element("<root>", new Element(this, text, position, null));
         final Object object = parseObject(element);
         element.close(ignoredElements);
         return object;
@@ -295,7 +330,7 @@ abstract class AbstractParser implements
      */
     final void warning(final Element parent, final Element element, final Exception ex) {
         if (warnings == null) {
-            warnings = new Warnings(errorLocale, (byte) 1, ignoredElements);
+            warnings = new Warnings(errorLocale, true, ignoredElements);
         }
         warnings.add(null, ex, new String[] {parent.keyword, element.keyword});
     }
@@ -308,7 +343,7 @@ abstract class AbstractParser implements
      */
     final void warning(final InternationalString message, final Exception ex) {
         if (warnings == null) {
-            warnings = new Warnings(errorLocale, (byte) 1, ignoredElements);
+            warnings = new Warnings(errorLocale, true, ignoredElements);
         }
         warnings.add(message, ex, null);
     }
@@ -329,7 +364,7 @@ abstract class AbstractParser implements
             if (ignoredElements.isEmpty()) {
                 return null;
             }
-            w = new Warnings(errorLocale, (byte) 1, ignoredElements);
+            w = new Warnings(errorLocale, true, ignoredElements);
         }
         w.setRoot(object);
         return w;

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -19,11 +19,11 @@ package org.apache.sis.io.wkt;
 import java.util.Date;
 import java.util.Map;
 import java.util.List;
-import java.util.Deque;
-import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Iterator;
 import java.util.Locale;
-import java.io.PrintWriter;
+import java.io.Serializable;
 import java.text.ParsePosition;
 import java.text.ParseException;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -34,10 +34,14 @@ import org.apache.sis.util.CharSequences
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.util.LocalizedParseException;
 
 import static org.apache.sis.util.CharSequences.skipLeadingWhitespaces;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.JDK8;
+
 
 /**
  * An element in a <cite>Well Know Text</cite> (WKT). An {@code Element} is made of {@link String},
@@ -48,7 +52,7 @@ import static org.apache.sis.util.CharSe
  * }
  *
  * 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.
+ * The result is a tree, which can be seen with {@link #toString()} for debugging purpose.
  * Elements can be pulled in a <cite>first in, first out</cite> order.
  *
  * @author  Rémi Ève (IRD)
@@ -57,7 +61,12 @@ import static org.apache.sis.util.CharSe
  * @version 0.6
  * @module
  */
-final class Element {
+final class Element implements Serializable {
+    /**
+     * Indirectly for {@link WKTFormat} serialization compatibility.
+     */
+    private static final long serialVersionUID = 2366817541632131955L;
+
     /**
      * Kind of value expected in the element. Value 0 means "not yet determined".
      */
@@ -91,10 +100,12 @@ final class Element {
     public final String keyword;
 
     /**
-     * An ordered list of {@link String}s, {@link Number}s and other {@link Element}s.
+     * An ordered sequence 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").
+     *
+     * <p>Access to this list should be done using the iterator, not by random access.</p>
      */
-    private final Deque<Object> list;
+    private final List<Object> list;
 
     /**
      * The locale to be used for formatting an error message if the parsing fails, or {@code null} for
@@ -105,24 +116,58 @@ final class Element {
     /**
      * Constructs a root element.
      *
+     * @param name An arbitrary name for the root element.
      * @param singleton The only children for this root.
      */
-    Element(final Element singleton) {
-        offset  = 0;
-        keyword = null;
+    Element(final String name, final Element singleton) {
+        keyword = name;
+        offset  = singleton.offset;
         locale  = singleton.locale;
         list    = new LinkedList<>();   // Needs to be a modifiable list.
         list.add(singleton);
     }
 
     /**
+     * Creates a modifiable copy of the given element.
+     */
+    private Element(final Element toCopy) {
+        keyword = toCopy.keyword;
+        offset  = toCopy.offset;
+        locale  = toCopy.locale;
+        list    = new LinkedList<>(toCopy.list);   // Needs to be a modifiable list.
+        final ListIterator<Object> it = list.listIterator();
+        while (it.hasNext()) {
+            final Object value = it.next();
+            if (value instanceof Element) {
+                final Element fragment = (Element) value;
+                if (fragment.list != null) {
+                    it.set(new Element(fragment));
+                }
+            }
+        }
+    }
+
+    /**
      * Constructs a new {@code Element}.
+     * The {@code sharedValues} argument have two meanings:
+     * <ul>
+     *   <li><p>If {@code null}, then the caller is parsing a WKT string. The {@code Element}
+     *     must be mutable because its content will be emptied as the parsing progress.</p></li>
      *
-     * @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 AbstractParser parser, final String text, final ParsePosition position) throws ParseException {
+     *   <li><p>If non-null, then the caller is storing a WKT fragment. We create the elements but the caller will
+     *     not parse them immediately. The {@code Element} should be immutable because the fragment will potentially
+     *     be reused many time. Since the fragment may be stored for a long time, the {@code sharedValues} map will
+     *     be used for sharing unique instance of each value if possible.</p></li>
+     * </ul>
+     *
+     * @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.
+     * @param sharedValues If parsing a fragment, a map with the values found in other elements. Otherwise {@code null}.
+     */
+    Element(final AbstractParser parser, final String text, final ParsePosition position,
+            final Map<Object,Object> sharedValues) 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.
@@ -167,85 +212,123 @@ final class Element {
          * 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.
+         *   - If the first character is '$', insert the named WKT fragment in-place.
+         *   - Otherwise, if the characters are "true" of "false" (ignoring case), then the value is taken as a boolean.
          *   - 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, if the first character is a quote, then the value is taken as a String.
          *   - Otherwise, the element is parsed as a number or as a date, depending of 'isTemporal' boolean value.
          */
-        list = new LinkedList<>();
+        final List<Object> list = new LinkedList<>();
         final String separator = parser.symbols.trimmedSeparator();
         while (lower < length) {
             final int firstChar = text.codePointAt(lower);
-            final int closingQuote = parser.symbols.matchingQuote(firstChar);
-            if (closingQuote >= 0) {
+            if (firstChar == Symbols.FRAGMENT_VALUE) {
                 /*
-                 * 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.
+                 * WKTFormat allows to substitute strings like "$FOO" by a WKT fragment. This is something similar
+                 * to environment variables in Unix. If we find the "$" character, get the identifier behind "$"
+                 * and insert the corresponding WKT fragment here.
                  */
-                final int n = Character.charCount(closingQuote);
-                lower += Character.charCount(firstChar) - n;    // This will usually let 'lower' unchanged.
-                CharSequence content = null;
-                do {
-                    final int upper = text.indexOf(closingQuote, lower += n);
-                    if (upper < lower) {
-                        position.setIndex(offset);
-                        position.setErrorIndex(lower);
-                        throw missingCharacter(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);
+                int upper = ++lower;      // Increment of 1 is okay because FRAGMENT_VALUE is a 'char'.
+                while (upper < length) {
+                    final int c = text.codePointAt(upper);
+                    if (!Character.isUnicodeIdentifierPart(c)) break;
+                    upper += Character.charCount(c);
+                }
+                final String id = text.substring(lower, upper);
+                Element fragment = parser.fragments.get(id);
+                if (fragment == null) {
+                    position.setIndex(offset);
+                    position.setErrorIndex(lower);
+                    throw new LocalizedParseException(locale, Errors.Keys.NoSuchValue_1, new Object[] {id}, lower);
+                }
+                if (fragment.list != null) {
+                    fragment = new Element(fragment);
+                }
+                list.add(fragment);
+                lower = upper;
+            } else if (Character.isUnicodeIdentifierStart(firstChar)) {
                 /*
-                 * Leading and trailing spaces should be ignored according ISO 19162 §B.4.
-                 * Note that the specification suggests also to replace consecutive white
-                 * spaces by a single space, but we don't do that yet.
+                 * If the character is the beginning of a Unicode identifier, add as a child element
+                 * except for the boolean "true" and "false" values which are handled in a special way.
                  */
-                list.add(CharSequences.trimWhitespaces(content).toString());
-            } else if (!Character.isUnicodeIdentifierStart(firstChar)) {
+                if (lower != (lower = regionMatches(text, lower, "true"))) {
+                    list.add(Boolean.TRUE);
+                } else if (lower != (lower = regionMatches(text, lower, "false"))) {
+                    list.add(Boolean.FALSE);
+                } else {
+                    position.setIndex(lower);
+                    list.add(new Element(parser, text, position, sharedValues));
+                    lower = position.getIndex();
+                }
+            } else {
+                Object value;
+                final int closingQuote = parser.symbols.matchingQuote(firstChar);
+                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(firstChar) - n;    // This will usually let 'lower' unchanged.
+                    CharSequence content = null;
+                    do {
+                        final int upper = text.indexOf(closingQuote, lower += n);
+                        if (upper < lower) {
+                            throw missingCharacter(closingQuote, lower, position);
+                        }
+                        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);
+                    /*
+                     * Leading and trailing spaces should be ignored according ISO 19162 §B.4.
+                     * Note that the specification suggests also to replace consecutive white
+                     * spaces by a single space, but we don't do that yet.
+                     */
+                    value = CharSequences.trimWhitespaces(content).toString();
+                } else {
+                    /*
+                     * Try to parse the next element as a date or a number. We attempt such parsing when
+                     * the first non-blank character is not the beginning of an unicode identifier.
+                     * Otherwise we assume that the next element is the keyword of a child 'Element'.
+                     */
+                    position.setIndex(lower);
+                    if (valueType == 0) {
+                        valueType = ArraysExt.containsIgnoreCase(TIME_KEYWORDS, keyword) ? TEMPORAL : NUMERIC;
+                    }
+                    switch (valueType) {
+                        case TEMPORAL: value = parser.parseDate  (text, position); break;
+                        case NUMERIC:  value = parser.parseNumber(text, position); break;
+                        default: throw new AssertionError(valueType);  // Should never happen.
+                    }
+                    if (value == null) {
+                        // Do not update the error index; it is already updated by NumberFormat.
+                        throw unparsableString(text, position);
+                    }
+                    lower = position.getIndex();
+                }
                 /*
-                 * 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'.
+                 * Store the value, using shared instances if this Element may be stored for a long time.
                  */
-                position.setIndex(lower);
-                final Object value;
-                if (valueType == 0) {
-                    valueType = ArraysExt.containsIgnoreCase(TIME_KEYWORDS, keyword) ? TEMPORAL : NUMERIC;
-                }
-                switch (valueType) {
-                    case TEMPORAL: value = parser.parseDate  (text, position); break;
-                    case NUMERIC:  value = parser.parseNumber(text, position); break;
-                    default: throw new AssertionError(valueType);  // Should never happen.
-                }
-                if (value == null) {
-                    position.setIndex(offset);
-                    // Do not update the error index; it is already updated by NumberFormat.
-                    throw unparsableString(text, position);
+                if (sharedValues != null) {
+                    final Object e = JDK8.putIfAbsent(sharedValues, value, value);
+                    if (e != null) {
+                        value = e;
+                    }
                 }
                 list.add(value);
-                lower = position.getIndex();
-            } else if (lower != (lower = regionMatches(text, lower, "true"))) {
-                list.add(Boolean.TRUE);
-            } else if (lower != (lower = regionMatches(text, lower, "false"))) {
-                list.add(Boolean.FALSE);
-            } else {
-                // Otherwise, add the element as a child element.
-                position.setIndex(lower);
-                list.add(new Element(parser, text, position));
-                lower = position.getIndex();
             }
             /*
              * At this point we finished to parse the component. If we find a separator (usually a coma),
@@ -259,16 +342,18 @@ final class Element {
                 final int c = text.codePointAt(lower);
                 if (c == closingBracket) {
                     position.setIndex(lower + Character.charCount(c));
+                    if (sharedValues != null) {
+                        this.list = UnmodifiableArrayList.wrap(list.toArray());
+                    } else {
+                        this.list = list;
+                    }
                     return;
                 }
-                position.setIndex(offset);
                 position.setErrorIndex(lower);
                 throw unparsableString(text, position);
             }
         }
-        position.setIndex(offset);
-        position.setErrorIndex(lower);
-        throw missingCharacter(closingBracket, lower);
+        throw missingCharacter(closingBracket, lower, position);
     }
 
     /**
@@ -320,7 +405,7 @@ final class Element {
     private ParseException unparsableString(final String text, final ParsePosition position) {
         final short errorKey;
         final CharSequence[] arguments;
-        final int errorIndex = Math.max(position.getIndex(), position.getErrorIndex());
+        final int errorIndex = Math.max(offset, position.getErrorIndex());
         final int length = text.length();
         if (errorIndex == length) {
             errorKey  = Errors.Keys.UnexpectedEndOfString_1;
@@ -329,19 +414,23 @@ final class Element {
             errorKey  = Errors.Keys.UnparsableStringInElement_2;
             arguments = new CharSequence[] {keyword, CharSequences.token(text, errorIndex)};
         }
+        position.setIndex(offset);
         return new LocalizedParseException(locale, errorKey, arguments, errorIndex);
     }
 
     /**
      * Returns an exception saying that a character is missing.
      *
-     * @param c The missing character.
-     * @param position The error position.
+     * @param c          The missing character.
+     * @param errorIndex The error position.
+     * @param position   The position to update with the error index.
      */
-    private ParseException missingCharacter(final int c, final int position) {
+    private ParseException missingCharacter(final int c, final int errorIndex, final ParsePosition position) {
+        position.setIndex(offset);
+        position.setErrorIndex(errorIndex);
         final StringBuilder buffer = new StringBuilder(2).appendCodePoint(c);
         return new LocalizedParseException(locale, Errors.Keys.MissingCharacterInElement_2,
-                new CharSequence[] {keyword, buffer}, position);
+                new CharSequence[] {keyword, buffer}, errorIndex);
     }
 
     /**
@@ -660,24 +749,21 @@ final class Element {
      * Closes this element. This method verifies that there is no unprocessed value (dates,
      * numbers, booleans or strings), but ignores inner elements as required by ISO 19162.
      *
-     * If the given {@code ignored} map is non-null, then this method will add the keywords
-     * of ignored elements in that map as below:
+     * This method add the keywords of ignored elements in the {@code ignoredElements} map as below:
      * <ul>
      *   <li><b>Keys</b>: keyword of ignored elements. Note that a key may be null.</li>
      *   <li><b>Values</b>: keywords of all elements containing an element identified by the above-cited key.
      *       This list is used for helping the users to locate the ignored elements.</li>
      * </ul>
      *
-     * @param  ignoredElements The collection where to declare ignored elements, or {@code null}.
+     * @param  ignoredElements The collection where to declare ignored elements.
      * @throws ParseException If the list still contains some unprocessed values.
      */
     final void close(final Map<String, List<String>> ignoredElements) throws ParseException {
         if (list != null) {
             for (final Object value : list) {
                 if (value instanceof Element) {
-                    if (ignoredElements != null) {
-                        CollectionsExt.addToMultiValuesMap(ignoredElements, ((Element) value).keyword, keyword);
-                    }
+                    CollectionsExt.addToMultiValuesMap(ignoredElements, ((Element) value).keyword, keyword);
                 } else {
                     throw new LocalizedParseException(locale, Errors.Keys.UnexpectedValueInElement_2,
                             new Object[] {keyword, value}, offset + keyword.length());

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -1475,7 +1475,7 @@ public class Formatter implements Locali
      */
     private Warnings warnings() {
         if (warnings == null) {
-            warnings = new Warnings(locale, (byte) 0, Collections.<String, List<String>>emptyMap());
+            warnings = new Warnings(locale, false, Collections.<String, List<String>>emptyMap());
         }
         return warnings;
     }

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.text.DateFormat;
@@ -194,7 +195,8 @@ final class GeodeticObjectParser extends
     public GeodeticObjectParser(final Map<String,?> defaultProperties,
             final ObjectFactory factories, final MathTransformFactory mtFactory)
     {
-        super(Symbols.getDefault(), null, null, null, mtFactory, (Locale) defaultProperties.get(Errors.LOCALE_KEY));
+        super(Symbols.getDefault(), Collections.<String,Element>emptyMap(), null, null, null,
+                mtFactory, (Locale) defaultProperties.get(Errors.LOCALE_KEY));
         crsFactory      = (CRSFactory)   factories;
         csFactory       = (CSFactory)    factories;
         datumFactory    = (DatumFactory) factories;
@@ -210,6 +212,7 @@ final class GeodeticObjectParser extends
      * This constructor is for {@link WKTFormat} usage only.
      *
      * @param symbols       The set of symbols to use.
+     * @param fragments     Reference to the {@link WKTFormat#fragments} map, or an empty map if none.
      * @param numberFormat  The number format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param dateFormat    The date format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param unitFormat    The unit format provided by {@link WKTFormat}, or {@code null} for a default format.
@@ -217,11 +220,13 @@ final class GeodeticObjectParser extends
      * @param errorLocale   The locale for error messages (not for parsing), or {@code null} for the system default.
      * @param factories     On input, the factories to use. On output, the factories used. Can be null.
      */
-    GeodeticObjectParser(final Symbols symbols, final NumberFormat numberFormat, final DateFormat dateFormat,
-            final UnitFormat unitFormat, final Convention convention, final Transliterator transliterator,
-            final Locale errorLocale, final Map<Class<?>,Factory> factories)
+    GeodeticObjectParser(final Symbols symbols, final Map<String,Element> fragments,
+            final NumberFormat numberFormat, final DateFormat dateFormat, final UnitFormat unitFormat,
+            final Convention convention, final Transliterator transliterator, final Locale errorLocale,
+            final Map<Class<?>,Factory> factories)
     {
-        super(symbols, numberFormat, dateFormat, unitFormat, getFactory(MathTransformFactory.class, factories), errorLocale);
+        super(symbols, fragments, numberFormat, dateFormat, unitFormat,
+                getFactory(MathTransformFactory.class, factories), errorLocale);
         this.transliterator = transliterator;
         crsFactory      = getFactory(CRSFactory.class,   factories);
         csFactory       = getFactory(CSFactory.class,    factories);
@@ -249,6 +254,15 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Returns the name of the class providing the publicly-accessible {@code createFromWKT(String)} method.
+     * This information is used for logging purpose only.
+     */
+    @Override
+    String getPublicFacade() {
+        return "org.apache.sis.referencing.factory.GeodeticObjectFactory";
+    }
+
+    /**
      * Parses a <cite>Well Know Text</cite> (WKT).
      *
      * @param  text The text to be parsed.

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Map;
+import java.util.Collections;
 import java.util.Locale;
 import java.text.DateFormat;
 import java.text.NumberFormat;
@@ -36,7 +38,6 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.OperationMethod;
-import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.util.LocalizedParseException;
@@ -102,13 +103,6 @@ class MathTransformParser extends Abstra
     private transient OperationMethod lastMethod;
 
     /**
-     * Creates a parser using the default set of symbols and factory.
-     */
-    public MathTransformParser() {
-        this(DefaultFactories.forBuildin(MathTransformFactory.class));
-    }
-
-    /**
      * Creates a parser for the given factory.
      *
      * <p><b>Maintenance note:</b> this constructor is invoked through reflection by
@@ -119,28 +113,39 @@ class MathTransformParser extends Abstra
      * @param mtFactory The factory to use to create {@link MathTransform} objects.
      */
     public MathTransformParser(final MathTransformFactory mtFactory) {
-        this(Symbols.getDefault(), null, null, null, mtFactory, null);
+        this(Symbols.getDefault(), Collections.<String,Element>emptyMap(), null, null, null, mtFactory, null);
     }
 
     /**
      * Creates a parser using the specified set of symbols and factory.
      *
      * @param symbols       The set of symbols to use.
+     * @param fragments     Reference to the {@link WKTFormat#fragments} map, or an empty map if none.
      * @param numberFormat  The number format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param dateFormat    The date format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param unitFormat    The unit format provided by {@link WKTFormat}, or {@code null} for a default format.
      * @param mtFactory     The factory to use to create {@link MathTransform} objects.
      * @param errorLocale   The locale for error messages (not for parsing), or {@code null} for the system default.
      */
-    MathTransformParser(final Symbols symbols, final NumberFormat numberFormat, final DateFormat dateFormat,
-            final UnitFormat unitFormat, final MathTransformFactory mtFactory, final Locale errorLocale)
+    MathTransformParser(final Symbols symbols, final Map<String,Element> fragments,
+            final NumberFormat numberFormat, final DateFormat dateFormat, final UnitFormat unitFormat,
+            final MathTransformFactory mtFactory, final Locale errorLocale)
     {
-        super(symbols, numberFormat, dateFormat, unitFormat, errorLocale);
+        super(symbols, fragments, numberFormat, dateFormat, unitFormat, errorLocale);
         this.mtFactory = mtFactory;
         ensureNonNull("mtFactory", mtFactory);
     }
 
     /**
+     * Returns the name of the class providing the publicly-accessible {@code createFromWKT(String)} method.
+     * This information is used for logging purpose only.
+     */
+    @Override
+    String getPublicFacade() {
+        return "org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory";
+    }
+
+    /**
      * Parses the next element in the specified <cite>Well Know Text</cite> (WKT) tree.
      *
      * @param  element The element to be parsed.

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Symbols.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -79,6 +79,11 @@ public class Symbols implements Localize
     private static final long serialVersionUID = -1730166945430878916L;
 
     /**
+     * The prefix character for the value of a WKT fragment.
+     */
+    static final char FRAGMENT_VALUE = '$';
+
+    /**
      * A set of symbols with values between square brackets, like {@code DATUM["WGS84"]}.
      * This instance defines:
      *
@@ -432,8 +437,8 @@ public class Symbols implements Localize
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, name, pair));
             }
             final int c = pair.codePointAt(0);
-            ensureValidUnicodeCodePoint(name, array[j++] = c);
-            ensureValidUnicodeCodePoint(name, array[j++] = pair.codePointAt(Character.charCount(c)));
+            ensureValidQuoteOrBracket(name, array[j++] = c);
+            ensureValidQuoteOrBracket(name, array[j++] = pair.codePointAt(Character.charCount(c)));
             if (i >= n) {
                 break;
             }
@@ -443,6 +448,17 @@ public class Symbols implements Localize
     }
 
     /**
+     * Ensures that the given code point is a valid Unicode code point but not a Unicode identifier part.
+     */
+    private static void ensureValidQuoteOrBracket(final String name, final int code) {
+        ensureValidUnicodeCodePoint(name, code);
+        if (Character.isUnicodeIdentifierPart(code) || Character.isSpaceChar(code) || code == FRAGMENT_VALUE) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2,
+                    name, String.valueOf(Character.toChars(code))));
+        }
+    }
+
+    /**
      * Returns the character used for opening a sequence of values.
      * This is usually <code>'{'</code>.
      *
@@ -470,8 +486,8 @@ public class Symbols implements Localize
      */
     public void setSequenceBrackets(final int openSequence, final int closeSequence) {
         checkWritePermission();
-        ensureValidUnicodeCodePoint("openSequence",  openSequence);
-        ensureValidUnicodeCodePoint("closeSequence", closeSequence);
+        ensureValidQuoteOrBracket("openSequence",  openSequence);
+        ensureValidQuoteOrBracket("closeSequence", closeSequence);
         this.openSequence  = openSequence;
         this.closeSequence = closeSequence;
     }

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -21,6 +21,7 @@ import java.util.Locale;
 import java.util.TimeZone;
 import java.util.Map;
 import java.util.HashMap;
+import java.util.TreeMap;
 import java.io.IOException;
 import java.text.Format;
 import java.text.NumberFormat;
@@ -34,22 +35,27 @@ import org.opengis.util.InternationalStr
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.io.CompoundFormat;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.StandardDateFormat;
+import org.apache.sis.internal.util.LocalizedParseException;
+
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.JDK8;
 
 
 /**
- * Parser and formatter for <cite>Well Known Text</cite> (WKT) objects.
+ * Parser and formatter for <cite>Well Known Text</cite> (WKT) strings.
  * This format handles a pair of {@link Parser} and {@link Formatter},
- * to be used by {@code parse} and {@code format} methods respectively.
+ * used by the {@code parse(…)} and {@code format(…)} methods respectively.
  * {@code WKTFormat} objects allow the following configuration:
  *
  * <ul>
  *   <li>The preferred authority of {@linkplain IdentifiedObject#getName() object name} to
  *       format (see {@link Formatter#getNameAuthority()} for more information).</li>
  *   <li>The {@linkplain Symbols symbols} to use (curly braces or brackets, <i>etc</i>).</li>
- *   <li>The {@link Transliterator transliterator} (i.e. replacements to use for Unicode characters).</li>
+ *   <li>The {@linkplain Transliterator transliterator} to use for replacing Unicode characters by ASCII ones.</li>
  *   <li>Whether ANSI X3.64 colors are allowed or not (default is not).</li>
  *   <li>The indentation.</li>
  * </ul>
@@ -57,24 +63,27 @@ import org.apache.sis.internal.util.Stan
  * <div class="section">String expansion</div>
  * Because the strings to be parsed by this class are long and tend to contain repetitive substrings,
  * {@code WKTFormat} provides a mechanism for performing string substitutions before the parsing take place.
- * Long strings can be assigned short names by calls to the
- * <code>{@linkplain #definitions()}.put(<var>key</var>,<var>value</var>)</code> method.
- * After definitions have been added, any call to a parsing method will replace all occurrences
- * of a short name by the associated long string.
- *
- * <p>The short names must comply with the rules of Java identifiers. It is recommended, but not
- * required, to prefix the names by some symbol like {@code "$"} in order to avoid ambiguity.
- * Note however that this class doesn't replace occurrences between quoted text, so string
- * expansion still relatively safe even when used with non-prefixed identifiers.</p>
+ * Long strings can be assigned short names by calls to the {@link #addFragment(String, String)} method.
+ * After fragments have been added, any call to a parsing method will replace all occurrences (except in
+ * quoted text) of tokens like {@code $foo} by the WKT fragment named "foo".
  *
  * <div class="note"><b>Example:</b>
  * In the example below, the {@code $WGS84} substring which appear in the argument given to the
- * {@code parseObject(…)} method will be expanded into the full {@code GEOGCS["WGS84", …]} string
- * before the parsing proceed.
+ * {@code parseObject(…)} method will be expanded into the full {@code GeodeticCRS[“WGS84”, …]}
+ * string before the parsing proceed.
+ *
+ * <blockquote><code>
+ * {@linkplain #addFragment addFragment}("deg", "AngleUnit[“degree”, 0.0174532925199433]");<br>
+ * {@linkplain #addFragment addFragment}("lat", "Axis[“Latitude”, NORTH, <strong>$deg</strong>]");<br>
+ * {@linkplain #addFragment addFragment}("lon", "Axis[“Longitude”, EAST, <strong>$deg</strong>]");<br>
+ * {@linkplain #addFragment addFragment}("MyBaseCRS", "GeodeticCRS[“WGS84”, Datum[</code> <i>…etc…</i> <code>],
+ * CS[</code> <i>…etc…</i> <code>], <strong>$lat</strong>, <strong>$lon</strong>]");<br>
+ * Object crs = {@linkplain #parseObject(String) parseObject}("ProjectedCRS[“Mercator_1SP”, <strong>$MyBaseCRS</strong>,
+ * </code> <i>…etc…</i> <code>]");
+ * </code></blockquote>
  *
- * <blockquote><code>{@linkplain #definitions()}.put("$WGS84", "GEOGCS[\"WGS84\", DATUM[</code> <i>…etc…</i> <code>]]);<br>
- * Object crs = {@linkplain #parseObject(String) parseObject}("PROJCS[\"Mercator_1SP\", <strong>$WGS84</strong>,
- * PROJECTION[</code> <i>…etc…</i> <code>]]");</code></blockquote>
+ * Note that the parsing of WKT fragment does not always produce the same object.
+ * In particular, the default linear and angular units depend on the context in which the WKT fragment appears.
  * </div>
  *
  * <div class="section">Limitations</div>
@@ -166,6 +175,23 @@ public class WKTFormat extends CompoundF
     private byte indentation;
 
     /**
+     * WKT fragments that can be inserted in longer WKT strings, or {@code null} if none. Keys are short identifiers
+     * and values are WKT subtrees to substitute to the identifiers when they are found in a WKT to parse.
+     */
+    private Map<String,Element> fragments;
+
+    /**
+     * Temporary map used by {@link #addFragment(String, String)} for reusing existing instances when possible.
+     * Keys and values are the same {@link String}, {@link Boolean}, {@link Number} or {@link Date} instances.
+     *
+     * <p>This reference is set to null when we assume that no more fragments will be added to this format.
+     * It is not a problem if this map is destroyed too aggressively, since it will be recreated when needed.
+     * The only cost of destroying the map too aggressively is that we may have more instance duplications
+     * than what we would otherwise have.</p>
+     */
+    private transient Map<Object,Object> sharedValues;
+
+    /**
      * A formatter using the same symbols than the {@linkplain #parser}.
      * Will be created by the {@link #format(Object, Appendable)} method when first needed.
      */
@@ -412,7 +438,7 @@ public class WKTFormat extends CompoundF
      *   <tr><td>GEOTIFF</td>   <td>CT_Mercator</td></tr>
      * </table></div>
      *
-     * If no authority has been {@link #setNameAuthority(Citation) explicitly set}, then this
+     * If no authority has been {@linkplain #setNameAuthority(Citation) explicitly set}, then this
      * method returns the default authority for the current {@linkplain #getConvention() convention}.
      *
      * @return The organization, standard or project to look for when fetching projection and parameter names.
@@ -501,22 +527,95 @@ public class WKTFormat extends CompoundF
      * Creates an object from the given character sequence.
      * The parsing begins at the index given by the {@code pos} argument.
      *
-     * @param  text The character sequence for the object to parse.
-     * @param  pos  The position where to start the parsing.
+     * @param  wkt The character sequence for the object to parse.
+     * @param  pos The position where to start the parsing.
      * @return The parsed object.
-     * @throws ParseException If an error occurred while parsing the object.
+     * @throws ParseException If an error occurred while parsing the WKT.
      */
     @Override
-    public Object parse(final CharSequence text, final ParsePosition pos) throws ParseException {
+    public Object parse(final CharSequence wkt, final ParsePosition pos) throws ParseException {
         warnings = null;
-        ArgumentChecks.ensureNonNull("text", text);
-        ArgumentChecks.ensureNonNull("pos",  pos);
+        sharedValues = null;
+        ArgumentChecks.ensureNonEmpty("wkt", wkt);
+        ArgumentChecks.ensureNonNull ("pos", pos);
+        final AbstractParser parser = parser();
+        Object object = null;
+        try {
+            return object = parser.parseObject(wkt.toString(), pos);
+        } finally {
+            warnings = parser.getAndClearWarnings(object);
+        }
+    }
+
+    /**
+     * Adds a fragment of Well Know Text (WKT). The {@code wkt} argument given to this method
+     * can contains itself other fragments specified in some previous calls to this method.
+     *
+     * <div class="note"><b>Example</b>
+     * if the following method is invoked:
+     *
+     * {@preformat java
+     *   addFragment("MyEllipsoid", "Ellipsoid[“Bessel 1841”, 6377397.155, 299.1528128, ID[“EPSG”,“7004”]]");
+     * }
+     *
+     * Then other WKT strings parsed by this {@code WKTFormat} instance can refer to the above fragment as below
+     * (WKT after the ellipsoid omitted for brevity):
+     *
+     * {@preformat java
+     *   Object crs = parseObject("GeodeticCRS[“Tokyo”, Datum[“Tokyo”, $MyEllipsoid], …]");
+     * }
+     * </div>
+     *
+     * @param  identifier The name to assign to the WKT fragment. Identifiers are case-sensitive.
+     * @param  wkt The Well Know Text (WKT) fragment represented by the given identifier.
+     * @throws IllegalArgumentException if the name is invalid or if a fragment is already present for that name.
+     * @throws ParseException If an error occurred while parsing the given WKT.
+     */
+    public void addFragment(final String identifier, final String wkt) throws IllegalArgumentException, ParseException {
+        ArgumentChecks.ensureNonEmpty("wkt", wkt);
+        ArgumentChecks.ensureNonEmpty("identifier", identifier);
+        short error = Errors.Keys.NotAUnicodeIdentifier_1;
+        if (CharSequences.isUnicodeIdentifier(identifier)) {
+            if (sharedValues == null) {
+                sharedValues = new HashMap<>();
+            }
+            final ParsePosition pos = new ParsePosition(0);
+            final Element element = new Element(parser(), wkt, pos, sharedValues);
+            final int index = CharSequences.skipLeadingWhitespaces(wkt, pos.getIndex(), wkt.length());
+            if (index < wkt.length()) {
+                throw new LocalizedParseException(getLocale(), Errors.Keys.UnexpectedCharactersAfter_2,
+                        new Object[] {identifier + " = " + element.keyword + "[…]", CharSequences.token(wkt, index)}, index);
+            }
+            if (JDK8.putIfAbsent(fragments, identifier, element) == null) {
+                return;
+            }
+            error = Errors.Keys.ElementAlreadyPresent_1;
+        }
+        throw new IllegalArgumentException(Errors.getResources(getLocale()).getString(error, identifier));
+    }
+
+    /**
+     * Removes all fragments previously added by {@link #addFragment(String, String)}.
+     */
+    public void removeAllFragments() {
+        if (fragments != null) {
+            fragments.clear();
+        }
+    }
+
+    /**
+     * Returns the parser, created when first needed.
+     */
+    private AbstractParser parser() {
         AbstractParser parser = this.parser;
         if (parser == null) {
+            if (fragments == null) {
+                fragments = new TreeMap<>();
+            }
             if (factories == null) {
                 factories = new HashMap<>();
             }
-            this.parser = parser = new GeodeticObjectParser(symbols,
+            this.parser = parser = new GeodeticObjectParser(symbols, fragments,
                     (NumberFormat) getFormat(Number.class),
                     (DateFormat)   getFormat(Date.class),
                     (UnitFormat)   getFormat(Unit.class),
@@ -525,12 +624,7 @@ public class WKTFormat extends CompoundF
                     getLocale(),
                     factories);
         }
-        Object object = null;
-        try {
-            return object = parser.parseObject(text.toString(), pos);
-        } finally {
-            warnings = parser.getAndClearWarnings(object);
-        }
+        return parser;
     }
 
     /**
@@ -590,7 +684,7 @@ public class WKTFormat extends CompoundF
             warnings.setRoot(object);
         }
         if (!valid) {
-            throw new ClassCastException(Errors.format(
+            throw new ClassCastException(Errors.getResources(getLocale()).getString(
                     Errors.Keys.IllegalArgumentClass_2, "object", object.getClass()));
         }
         if (buffer != toAppendTo) {

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -90,10 +90,10 @@ public final class Warnings implements L
     private final Locale errorLocale;
 
     /**
-     * {@code 0} if the warnings occurred while formatting, or
-     * {@code 1} if they occurred while parsing.
+     * {@code false} if the warnings occurred while formatting, or
+     * {@code true} if they occurred while parsing.
      */
-    private final byte operation;
+    private final boolean isParsing;
 
     /**
      * Name identifier or class name of the root object being parsed or formatted.
@@ -148,12 +148,12 @@ public final class Warnings implements L
      * Creates a new object for declaring warnings.
      *
      * @param locale The locale for reporting warning messages.
-     * @param operation {@code 0} if formatting, or {@code 1} if parsing.
+     * @param isParsing {@code false} if formatting, or {@code true} if parsing.
      * @param ignoredElements The {@link Parser#ignoredElements} map, or an empty map (can not be null).
      */
-    Warnings(final Locale locale, final byte operation, final Map<String, List<String>> ignoredElements) {
+    Warnings(final Locale locale, final boolean isParsing, final Map<String, List<String>> ignoredElements) {
         this.errorLocale     = locale;
-        this.operation       = operation;
+        this.isParsing       = isParsing;
         this.ignoredElements = ignoredElements;
     }
 
@@ -350,8 +350,8 @@ public final class Warnings implements L
         final StringBuilder buffer = new StringBuilder(250);
         final String lineSeparator = System.lineSeparator();
         final Messages resources   = Messages.getResources(locale);
-        buffer.append(resources.getString(Messages.Keys.IncompleteFormattingOrParsing_2, operation, root))
-              .append(lineSeparator);
+        buffer.append(resources.getString(isParsing ? Messages.Keys.IncompleteParsing_1
+                : Messages.Keys.NonConformFormatting_1, root)).append(lineSeparator);
         if (messages != null) {
             for (final Iterator<?> it = messages.iterator(); it.hasNext();) {
                 final InternationalString i18n = (InternationalString) it.next();

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Map;
+import java.util.HashMap;
 import java.util.Locale;
 import java.text.ParsePosition;
 import java.text.ParseException;
@@ -40,20 +42,32 @@ public final strictfp class ElementTest
     /**
      * A dummy parser to be given to the {@link Element} constructor.
      */
-    private final AbstractParser parser = new AbstractParser(Symbols.SQUARE_BRACKETS, null, null, null, Locale.ENGLISH) {
+    private final AbstractParser parser = new AbstractParser(Symbols.SQUARE_BRACKETS, new HashMap<String,Element>(2),
+            null, null, null, Locale.ENGLISH)
+    {
+        @Override String getPublicFacade() {
+            throw new UnsupportedOperationException();
+        }
+
         @Override Object parseObject(Element element) throws ParseException {
             throw new UnsupportedOperationException();
         }
     };
 
     /**
+     * The map of shared values to gives to the {@link Element} constructor.
+     * This is usually null, except for the test of WKT fragments.
+     */
+    private Map<Object,Object> sharedValues;
+
+    /**
      * Parses the given text and ensures that {@link ParsePosition} index is set at to the end of string.
      */
     private Element parse(final String text) throws ParseException {
         final ParsePosition position = new ParsePosition(0);
         final Element element;
         try {
-            element = new Element(parser, text, position);
+            element = new Element(parser, text, position, sharedValues);
         } catch (ParseException e) {
             assertEquals("index should be unchanged.", 0, position.getIndex());
             assertTrue("Error index should be set.", position.getErrorIndex() > 0);
@@ -273,4 +287,36 @@ public final strictfp class ElementTest
             assertEquals("Missing a ‘)’ character in “BracketTest” element.", e.getLocalizedMessage());
         }
     }
+
+    /**
+     * Tests the construction of {@link Element} tree from fragments.
+     *
+     * @throws ParseException if an error occurred during the parsing.
+     */
+    @Test
+    @DependsOnMethod({"testPullString", "testPullElement"})
+    public void testFragments() throws ParseException {
+        sharedValues = new HashMap<>();
+        Element frag = parse("Frag[“A”,“B”,“A”]");
+        parser.fragments.put("MyFrag", frag);
+        try {
+            frag.pullString("A");
+            fail("Element shall be unmodifiable.");
+        } catch (UnsupportedOperationException e) {
+            // This is the expected exception.
+        }
+        /*
+         * Parse a normal value. Since this is not a fragment,
+         * we should be able to pull a copy of the components.
+         */
+        sharedValues = null;
+        final Element element = parse("Foo[“C”,$MyFrag,“D”]");
+        assertEquals("C", element.pullString("C"));
+        assertEquals("D", element.pullString("D"));
+        frag = element.pullElement(AbstractParser.MANDATORY, "Frag");
+        final String a = frag.pullString("A");
+        assertEquals("A", a);
+        assertEquals("B", frag.pullString("B"));
+        assertSame(a, frag.pullString("A"));    // 'sharedValues' should have allowed to share the same instance.
+    }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -38,6 +38,7 @@ import org.opengis.referencing.crs.Geogr
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.internal.util.DefinitionURI;
@@ -258,26 +259,29 @@ public final class CRS extends Static {
      * CRS as horizontal if it is two-dimensional and comply with one of the following conditions:
      *
      * <ul>
-     *   <li>It is an instance of {@link GeographicCRS}.</li>
-     *   <li>It is an instance of {@link ProjectedCRS}.</li>
+     *   <li>is an instance of {@link GeographicCRS}, or</li>
+     *   <li>is an instance of {@link ProjectedCRS}, or</li>
+     *   <li>is an instance of {@link EngineeringCRS} (following
+     *     <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#111">ISO 19162 §16.1</a>
+     *     definition of {@literal <horizontal crs>}).</li>
      * </ul>
      *
      * In case of doubt, this method conservatively returns {@code false}.
      *
-     * @todo Future SIS implementation may extend the above condition list. For example a radar station could
+     * @todo Future SIS implementation may extend the above conditions list. For example a radar station could
      *       use a polar coordinate system in a <code>DerivedCRS</code> instance based on a projected CRS.
+     *       Conversely, a future SIS versions may impose more conditions on <code>EngineeringCRS</code>.
      *       See <a href="http://issues.apache.org/jira/browse/SIS-161">SIS-161</a>.
      *
      * @param  crs The coordinate reference system, or {@code null}.
-     * @return {@code true} if the given CRS is non-null and comply with one of the above conditions,
-     *         or {@code false} otherwise.
+     * @return {@code true} if the given CRS is non-null and likely horizontal, or {@code false} otherwise.
      *
      * @see #getHorizontalComponent(CoordinateReferenceSystem)
      *
      * @category information
      */
     public static boolean isHorizontalCRS(final CoordinateReferenceSystem crs) {
-        if (crs instanceof GeographicCRS || crs instanceof ProjectedCRS) {
+        if (crs instanceof GeographicCRS || crs instanceof ProjectedCRS || crs instanceof EngineeringCRS) {
             return crs.getCoordinateSystem().getDimension() == 2;
         }
         return false;

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -27,6 +27,11 @@ import javax.xml.bind.annotation.XmlRoot
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CompoundCRS;
+import org.opengis.referencing.crs.GeodeticCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
@@ -101,7 +106,7 @@ import static org.apache.sis.internal.re
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.6
  * @module
  */
 @XmlType(name="CompoundCRSType")
@@ -327,6 +332,73 @@ public class DefaultCompoundCRS extends
     }
 
     /**
+     * Returns {@code true} if the sequence of single components is conform to the ISO 19162 restrictions.
+     * The <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#111">WKT 2 specification at §16.1</a>
+     * restricts {@code CompoundCRS} to the following components in that order:
+     *
+     * <ul>
+     *   <li>A mandatory horizontal CRS (only one of two-dimensional {@code GeographicCRS}
+     *       or {@code ProjectedCRS} or {@code EngineeringCRS}).</li>
+     *   <li>Optionally followed by a {@code VerticalCRS} or a {@code ParametricCRS} (but not both).</li>
+     *   <li>Optionally followed by a {@code TemporalCRS}.</li>
+     * </ul>
+     *
+     * This method verifies the above criterion with the following flexibilities:
+     *
+     * <ul>
+     *   <li>Accepts three-dimensional {@code GeodeticCRS} followed by a {@code TemporalCRS}.</li>
+     * </ul>
+     *
+     * This method does not verify recursively if the component are themselves standard compliant.
+     * In particular, this method does not verify if the geographic CRS uses (latitude, longitude)
+     * axes order as requested by ISO 19162.
+     *
+     * <p>This method is not yet public because of the above-cited limitations: a {@code true} return
+     * value is not a guarantee that the CRS is really standard-compliant.</p>
+     *
+     * @return {@code true} if this CRS is "standard" compliant, except for the above-cited limitations.
+     */
+    @SuppressWarnings("fallthrough")
+    final boolean isStandardCompliant() {
+        /*
+         * 0 if we expect a horizontal CRS: Geographic2D, projected or engineering.
+         * 1 if we expect a vertical or parametric CRS (but not both).
+         * 2 if we expect a temporal CRS.
+         * 3 if we do not expect any other CRS.
+         */
+        int state = 0;
+        for (final SingleCRS crs : getSingleComponents()) {
+            switch (state) {
+                case 0: {
+                    if (crs instanceof GeodeticCRS || crs instanceof ProjectedCRS || crs instanceof EngineeringCRS) {
+                        switch (crs.getCoordinateSystem().getDimension()) {
+                            case 2: state = 1; continue;    // Next CRS can be vertical, parametric or temporal.
+                            case 3: state = 2; continue;    // Next CRS can only be temporal.
+                        }
+                    }
+                    return false;
+                }
+                case 1: {
+                    if (crs instanceof VerticalCRS) {   // TODO: accept also ParametricCRS here.
+                        state = 2; continue;    // Next CRS can only be temporal.
+                    }
+                    // Fallthrough (the current CRS may be temporal)
+                }
+                case 2: {
+                    if (crs instanceof TemporalCRS) {
+                        state = 3; continue;    // Do not expect any other CRS.
+                    }
+                    // Fallthrough (unexpected CRS).
+                }
+                default: {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
      * Computes the single CRS list on deserialization.
      *
      * @param  in The input stream from which to deserialize a compound CRS.
@@ -461,6 +533,9 @@ public class DefaultCompoundCRS extends
             formatter.append(toFormattable(element));
         }
         formatter.newLine(); // For writing the ID[…] element on its own line.
+        if (!isStandardCompliant()) {
+            formatter.setInvalidWKT(this, null);
+        }
         return isWKT1 ? WKTKeywords.Compd_CS : WKTKeywords.CompoundCRS;
     }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.Locale;
@@ -35,6 +36,7 @@ import org.apache.sis.internal.metadata.
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.datum.BursaWolfParameters;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -71,7 +73,9 @@ public final strictfp class GeodeticObje
      * Instantiates the parser to test.
      */
     private void newParser(final Convention convention) {
-        parser = new GeodeticObjectParser(Symbols.getDefault(), null, null, null, convention, Transliterator.DEFAULT, null, null);
+        parser = new GeodeticObjectParser(Symbols.getDefault(), Collections.<String,Element>emptyMap(),
+                null, null, null, convention, Transliterator.DEFAULT, null, null);
+        assertEquals(GeodeticObjectFactory.class.getCanonicalName(), parser.getPublicFacade());
     }
 
     /**
@@ -252,13 +256,13 @@ public final strictfp class GeodeticObje
     @DependsOnMethod({"testAxis", "testDatum"})
     public void testGeographicCRS() throws ParseException {
         verifyGeographicCRS(0, parse(GeographicCRS.class,
-               "  GEOGCS[“WGS 84”,\n" +
-               "    DATUM[“World Geodetic System 1984”,\n" +
-               "      SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-               "      PRIMEM[“Greenwich”, 0.0],\n" +
-               "    UNIT[“degree”, 0.017453292519943295],\n" +
-               "    AXIS[“Longitude”, EAST],\n" +
-               "    AXIS[“Latitude”, NORTH]]"));
+               "GEOGCS[“WGS 84”,\n" +
+               "  DATUM[“World Geodetic System 1984”,\n" +
+               "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
+               "    PRIMEM[“Greenwich”, 0.0],\n" +
+               "  UNIT[“degree”, 0.017453292519943295],\n" +
+               "  AXIS[“Longitude”, EAST],\n" +
+               "  AXIS[“Latitude”, NORTH]]"));
     }
 
     /**
@@ -270,13 +274,13 @@ public final strictfp class GeodeticObje
     @DependsOnMethod("testGeographicCRS")
     public void testGeographicWithLatLonAxes() throws ParseException {
         verifyGeographicCRS(1, parse(GeographicCRS.class,
-               "  GEOGCS[“WGS 84”,\n" +
-               "    DATUM[“World Geodetic System 1984”,\n" +
-               "      SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-               "      PRIMEM[“Greenwich”, 0.0],\n" +
-               "    UNIT[“degree”, 0.017453292519943295],\n" +
-               "    AXIS[“Latitude”, NORTH],\n" +
-               "    AXIS[“Longitude”, EAST]]"));
+               "GEOGCS[“WGS 84”,\n" +
+               "  DATUM[“World Geodetic System 1984”,\n" +
+               "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
+               "    PRIMEM[“Greenwich”, 0.0],\n" +
+               "  UNIT[“degree”, 0.017453292519943295],\n" +
+               "  AXIS[“Latitude”, NORTH],\n" +
+               "  AXIS[“Longitude”, EAST]]"));
     }
 
     /**
@@ -290,11 +294,11 @@ public final strictfp class GeodeticObje
     @DependsOnMethod("testGeographicCRS")
     public void testGeographicWithImplicitAxes() throws ParseException {
         verifyGeographicCRS(0, parse(GeographicCRS.class,
-               "  GEOGCS[“WGS 84”,\n" +
-               "    DATUM[“World Geodetic System 1984”,\n" +
-               "      SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-               "      PRIMEM[“Greenwich”, 0.0],\n" +
-               "    UNIT[“degree”, 0.017453292519943295]]"));
+               "GEOGCS[“WGS 84”,\n" +
+               "  DATUM[“World Geodetic System 1984”,\n" +
+               "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
+               "    PRIMEM[“Greenwich”, 0.0],\n" +
+               "  UNIT[“degree”, 0.017453292519943295]]"));
     }
 
     /**
@@ -355,13 +359,13 @@ public final strictfp class GeodeticObje
     @DependsOnMethod("testGeographicWithLatLonAxes")
     public void testGeographicWithUnorderedAxes() throws ParseException {
         verifyGeographicCRS(1, parse(GeographicCRS.class,
-               "  GEOGCS[“WGS 84”,\n" +
-               "    DATUM[“World Geodetic System 1984”,\n" +
-               "      SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-               "      PRIMEM[“Greenwich”, 0.0],\n" +
-               "    UNIT[“degree”, 0.017453292519943295],\n" +
-               "    AXIS[“Longitude”, EAST, order[2]],\n" +
-               "    AXIS[“Latitude”, NORTH, order[1]]]"));
+               "GEOGCS[“WGS 84”,\n" +
+               "  DATUM[“World Geodetic System 1984”,\n" +
+               "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
+               "    PRIMEM[“Greenwich”, 0.0],\n" +
+               "  UNIT[“degree”, 0.017453292519943295],\n" +
+               "  AXIS[“Longitude”, EAST, order[2]],\n" +
+               "  AXIS[“Latitude”, NORTH, order[1]]]"));
     }
 
     /**
@@ -755,11 +759,11 @@ public final strictfp class GeodeticObje
         newParser(Convention.DEFAULT);
         final ParsePosition position = new ParsePosition(0);
         final GeographicCRS crs = (GeographicCRS) parser.parseObject(
-               "  GEOGCS[“WGS 84”,\n" +
-               "    DATUM[“World Geodetic System 1984”,\n" +
-               "      SPHEROID[“WGS84”, 6378137.0, 298.257223563, Ext1[“foo”], Ext2[“bla”]]],\n" +
-               "      PRIMEM[“Greenwich”, 0.0, Intruder[“unknown”]],\n" +
-               "    UNIT[“degree”, 0.017453292519943295], Intruder[“foo”]]", position);
+               "GEOGCS[“WGS 84”,\n" +
+               "  DATUM[“World Geodetic System 1984”,\n" +
+               "    SPHEROID[“WGS84”, 6378137.0, 298.257223563, Ext1[“foo”], Ext2[“bla”]]],\n" +
+               "    PRIMEM[“Greenwich”, 0.0, Intruder[“unknown”]],\n" +
+               "  UNIT[“degree”, 0.017453292519943295], Intruder[“foo”]]", position);
 
         verifyGeographicCRS(0, crs);
         assertEquals("errorIndex", -1, position.getErrorIndex());

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -19,9 +19,12 @@ package org.apache.sis.io.wkt;
 import java.text.ParsePosition;
 import java.text.ParseException;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.referencing.operation.matrix.Matrix2;
 import org.apache.sis.referencing.operation.matrix.Matrix3;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -52,7 +55,8 @@ public final strictfp class MathTransfor
      */
     private MathTransform parse(final String text) throws ParseException {
         if (parser == null) {
-            parser = new MathTransformParser();
+            parser = new MathTransformParser(DefaultFactories.forBuildin(MathTransformFactory.class));
+            assertEquals(DefaultMathTransformFactory.class.getCanonicalName(), parser.getPublicFacade());
         }
         final ParsePosition position = new ParsePosition(0);
         final MathTransform mt = (MathTransform) parser.parseObject(text, position);

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -266,4 +266,30 @@ public final strictfp class WKTFormatTes
         pm = (DefaultPrimeMeridian) format.parseObject(wkt);
         assertEquals("Invalid \"$name\" here", pm.getName().getCode());
     }
+
+    /**
+     * Tests the usage of {@code WKTFormat} with WKT fragments.
+     *
+     * @throws ParseException if the parsing failed.
+     */
+    @Test
+    public void testFragments() throws ParseException {
+        format = new WKTFormat(null, null);
+        format.addFragment("deg",    "UNIT[“degree”, 0.0174532925199433]");
+        format.addFragment("Bessel", "SPHEROID[“Bessel 1841”, 6377397.155, 299.1528128, AUTHORITY[“EPSG”,“7004”]]");
+        format.addFragment("Tokyo",  "DATUM[“Tokyo”, $Bessel]");
+        format.addFragment("Lat",    "AXIS[“Lat”, NORTH, $deg]");
+        format.addFragment("Lon",    "AXIS[“Long”, EAST, $deg]");
+        final Object crs = format.parseObject("GEOGCS[“Tokyo”, $Tokyo, $Lat, $Lon]");
+        final String wkt = format.format(crs);
+        assertMultilinesEquals(
+                "GeodeticCRS[\"Tokyo\",\n" +
+                "  Datum[\"Tokyo\",\n" +
+                "    Ellipsoid[\"Bessel 1841\", 6377397.155, 299.1528128, LengthUnit[\"metre\", 1]]],\n" +
+                "    PrimeMeridian[\"Greenwich\", 0.0, AngleUnit[\"degree\", 0.017453292519943295]],\n" +
+                "  CS[ellipsoidal, 2],\n" +
+                "    Axis[\"Latitude (B)\", north, Order[1]],\n" +
+                "    Axis[\"Longitude (L)\", east, Order[2]],\n" +
+                "    AngleUnit[\"degree\", 0.017453292519943295]]", wkt);
+    }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -43,7 +43,7 @@ import static org.junit.Assert.*;
  */
 @RunWith(JUnit4.class)
 @DependsOn(GeodeticObjectParserTest.class)
-public class WKTParserTest extends CRSParserTest {
+public final strictfp class WKTParserTest extends CRSParserTest {
     /**
      * Creates a new test case using the default {@code CRSFactory} implementation.
      */
@@ -52,6 +52,22 @@ public class WKTParserTest extends CRSPa
     }
 
     /**
+     * Pre-process the WKT string before parsing. This method chooses randomly whether to replace
+     * curly quotation marks ({@code “} and {@code ”}) by straight quotation marks ({@code "}) or not.
+     * The Apache SIS parser should understand both forms transparently.
+     *
+     * @param  wkt The Well-Known Text to pre-process.
+     * @return The Well-Known Text to parse.
+     */
+    @Override
+    protected String preprocessWKT(String wkt) {
+        if (Math.random() >= 0.5) {
+            wkt = super.preprocessWKT(wkt);
+        }
+        return wkt;
+    }
+
+    /**
      * Verifies the axis names of a geographic CRS. This method is invoked when the parsed object is
      * expected to have <cite>"Geodetic latitude"</cite> and <cite>"Geodetic longitude"</cite> names.
      */

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -139,6 +139,22 @@ public final strictfp class DefaultCompo
     }
 
     /**
+     * Tests {@link DefaultCompoundCRS#isStandardCompliant()}.
+     *
+     * @since 0.6
+     */
+    @Test
+    public void testIsStandardCompliant() {
+        final DefaultCompoundCRS crs3 = new DefaultCompoundCRS(singletonMap(NAME_KEY, "3D"), HardCodedCRS.WGS84,  HEIGHT);
+        final DefaultCompoundCRS crs4 = new DefaultCompoundCRS(singletonMap(NAME_KEY, "4D"), HardCodedCRS.WGS84_3D, TIME);
+        assertTrue (crs3.isStandardCompliant());
+        assertTrue (crs4.isStandardCompliant());
+        assertTrue (new DefaultCompoundCRS(singletonMap(NAME_KEY, "4D"), crs3, TIME).isStandardCompliant());
+        assertFalse(new DefaultCompoundCRS(singletonMap(NAME_KEY, "5D"), crs4, TIME).isStandardCompliant());
+        assertFalse(new DefaultCompoundCRS(singletonMap(NAME_KEY, "4D"), TIME, crs3).isStandardCompliant());
+    }
+
+    /**
      * Tests WKT 1 formatting.
      */
     @Test

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1689674&r1=1689673&r2=1689674&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Tue Jul  7 13:56:08 2015
@@ -333,6 +333,11 @@ public final class Errors extends Indexe
         public static final short IllegalCharacterForFormat_3 = 195;
 
         /**
+         * The “{1}” character can not be used for “{0}”.
+         */
+        public static final short IllegalCharacter_2 = 197;
+
+        /**
          * Class ‘{1}’ is illegal. It must be ‘{0}’ or a derived class.
          */
         public static final short IllegalClass_2 = 34;
@@ -651,6 +656,11 @@ public final class Errors extends Indexe
         public static final short NoSuchOperationMethod_1 = 179;
 
         /**
+         * No value is associated to “{0}”.
+         */
+        public static final short NoSuchValue_1 = 196;
+
+        /**
          * No unit of measurement has been specified.
          */
         public static final short NoUnit = 72;
@@ -881,6 +891,11 @@ public final class Errors extends Indexe
         public static final short UnexpectedChange_1 = 108;
 
         /**
+         * The “{1}” characters after “{0}” was unexpected.
+         */
+        public static final short UnexpectedCharactersAfter_2 = 198;
+
+        /**
          * Unexpected end of file while reading “{0}”.
          */
         public static final short UnexpectedEndOfFile_1 = 109;



Mime
View raw message