sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1686268 [2/4] - in /sis/branches/JDK7: ./ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/test/java/org/apache/sis/feature/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src...
Date Thu, 18 Jun 2015 16:49:27 GMT
Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java?rev=1686268&r1=1686267&r2=1686268&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] Thu Jun 18 16:49:25 2015
@@ -20,6 +20,7 @@ import java.io.Console;
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicReference;
 import javax.xml.bind.annotation.XmlTransient;
+import org.opengis.util.InternationalString;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.util.X364;
@@ -180,9 +181,9 @@ public abstract class FormattableObject
         try {
             formatter.append(this);
             if (strict) {
-                final String message = formatter.getErrorMessage();
+                final InternationalString message = formatter.getErrorMessage();
                 if (message != null) {
-                    throw new UnformattableObjectException(message, formatter.getErrorCause());
+                    throw new UnformattableObjectException(message.toString(), formatter.getErrorCause());
                 }
             }
             wkt = formatter.toWKT();

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=1686268&r1=1686267&r2=1686268&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] Thu Jun 18 16:49:25 2015
@@ -745,7 +745,7 @@ public class Formatter implements Locali
             }
         }
         if (showRemarks) {
-            appendOnNewLine(WKTKeywords.Remarks, object.getRemarks(), ElementKind.REMARKS);
+            appendOnNewLine(WKTKeywords.Remark, object.getRemarks(), ElementKind.REMARKS);
         }
         isComplement = false;
     }
@@ -822,6 +822,10 @@ public class Formatter implements Locali
      *   <li>“{@code VerticalExtent[102, 108, LengthUnit["m", 1]]}”       (Δz =   6)</li>
      *   <li>“{@code VerticalExtent[100.2, 100.8, LengthUnit["m", 1]]}”   (Δz = 0.6)</li>
      * </ul>
+     *
+     * Note that according ISO 19162, heights are positive toward up and relative to an unspecified mean sea level.
+     * It is caller's responsibility to ensure that the given range complies with that specification as much as
+     * possible.
      */
     private void appendVerticalExtent(final MeasurementRange<Double> range) {
         if (range != null) {
@@ -1117,23 +1121,27 @@ public class Formatter implements Locali
      * Specialization is used in WKT 2 format except the <cite>simplified WKT 2</cite> one.
      *
      * <div class="note"><b>Example:</b>
-     * {@code append(SI.KILOMETRE)} will append "{@code LENGTHUNIT["km", 1000]}" to the WKT.</div>
+     * {@code append(SI.KILOMETRE)} will append "{@code LengthUnit["km", 1000]}" to the WKT.</div>
      *
      * @param unit The unit to append to the WKT, or {@code null} if none.
+     *
+     * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#35">WKT 2 specification §7.4</a>
      */
     public void append(final Unit<?> unit) {
         if (unit != null) {
-            String keyword = "Unit";
-            if (!convention.isSimplified()) {
-                if (Units.isLinear(unit)) {
-                    keyword = "LengthUnit";
-                } else if (Units.isAngular(unit)) {
-                    keyword = "AngleUnit";
-                } else if (Units.isScale(unit)) {
-                    keyword = "ScaleUnit";
-                } else if (Units.isTemporal(unit)) {
-                    keyword = "TimeUnit";
-                }
+            final boolean isSimplified = convention.isSimplified();
+            final Unit<?> base = unit.toSI();
+            final String keyword;
+            if (base.equals(SI.METRE)) {
+                keyword = isSimplified ? WKTKeywords.Unit : WKTKeywords.LengthUnit;
+            } else if (base.equals(SI.RADIAN)) {
+                keyword = isSimplified ? WKTKeywords.Unit : WKTKeywords.AngleUnit;
+            } else if (base.equals(Unit.ONE)) {
+                keyword = isSimplified ? WKTKeywords.Unit : WKTKeywords.ScaleUnit;
+            } else if (base.equals(SI.SECOND)) {
+                keyword = WKTKeywords.TimeUnit;  // "Unit" alone is not allowed for time units according ISO 19162.
+            } else {
+                keyword = WKTKeywords.ParametricUnit;
             }
             openElement(false, keyword);
             setColor(ElementKind.UNIT);
@@ -1143,7 +1151,7 @@ public class Formatter implements Locali
             } else if (NonSI.DEGREE_ANGLE.equals(unit)) {
                 buffer.append("degree");
             } else if (SI.METRE.equals(unit)) {
-                buffer.append(convention.usesCommonUnits() ? "meter" : "metre");
+                buffer.append(convention.usesCommonUnits ? "meter" : "metre");
             } else if (Units.PPM.equals(unit)) {
                 buffer.append("parts per million");
             } else {
@@ -1152,11 +1160,15 @@ public class Formatter implements Locali
             closeQuote(fromIndex);
             resetColor();
             final double conversion = Units.toStandardUnit(unit);
-            if (!(conversion > 0)) { // ISO 19162 requires the conversion factor to be positive.
-                setInvalidWKT(Unit.class, null);
-            }
             appendExact(conversion);
             closeElement(false);
+            /*
+             * ISO 19162 requires the conversion factor to be positive.
+             * In addition, keywords other than "Unit" are not valid in WKt 1.
+             */
+            if (!(conversion > 0) || (keyword != WKTKeywords.Unit && convention.majorVersion() == 1)) {
+                setInvalidWKT(Unit.class, null);
+            }
         }
     }
 
@@ -1321,7 +1333,7 @@ public class Formatter implements Locali
      */
     @SuppressWarnings("unchecked")
     public <Q extends Quantity> Unit<Q> addContextualUnit(final Unit<Q> unit) {
-        if (unit == null || convention.usesCommonUnits()) {
+        if (unit == null || convention.usesCommonUnits) {
             return null;
         }
         hasContextualUnit |= 1;
@@ -1353,7 +1365,7 @@ public class Formatter implements Locali
                  * However this check does not work in Convention.WKT1_COMMON_UNITS mode, since the
                  * map is always empty in that mode.
                  */
-                if (!convention.usesCommonUnits()) {
+                if (!convention.usesCommonUnits) {
                     throw new IllegalStateException();
                 }
             }
@@ -1424,7 +1436,7 @@ public class Formatter implements Locali
     }
 
     /**
-     * Marks the current WKT representation of the given object as not strictly compliant to the WKT specification.
+     * Marks the current WKT representation of the given object as not strictly compliant with the WKT specification.
      * This method can be invoked by implementations of {@link FormattableObject#formatTo(Formatter)} when the object
      * to format is more complex than what the WKT specification allows.
      * Applications can test {@link #isInvalidWKT()} later for checking WKT validity.
@@ -1449,7 +1461,7 @@ public class Formatter implements Locali
     }
 
     /**
-     * Marks the current WKT representation of the given class as not strictly compliant to the WKT specification.
+     * Marks the current WKT representation of the given class as not strictly compliant with the WKT specification.
      * This method can be used as an alternative to {@link #setInvalidWKT(IdentifiedObject, Exception)} when the
      * problematic object is not an instance of {@code IdentifiedObject}.
      *
@@ -1487,9 +1499,15 @@ public class Formatter implements Locali
     /**
      * Returns the error message {@link #isInvalidWKT()} is set, or {@code null} otherwise.
      * If non-null, a cause may be available in the {@link #getErrorCause()} method.
+     *
+     * <div class="note"><b>Note:</b> the message is returned as an {@link InternationalString}
+     * in order to defer the actual message formatting until needed.</div>
      */
-    final String getErrorMessage() {
-        return isInvalidWKT() ? Errors.format(Errors.Keys.CanNotRepresentInFormat_2, "WKT", invalidElement) : null;
+    final InternationalString getErrorMessage() {
+        if (!isInvalidWKT()) {
+            return null;
+        }
+        return Errors.formatInternational(Errors.Keys.CanNotRepresentInFormat_2, "WKT", invalidElement);
     }
 
     /**

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=1686268&r1=1686267&r2=1686268&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] Thu Jun 18 16:49:25 2015
@@ -21,12 +21,14 @@ import java.util.List;
 import java.util.Locale;
 import java.util.HashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.text.ParsePosition;
 import java.text.ParseException;
 import javax.measure.unit.Unit;
+import javax.measure.unit.UnitFormat;
 import javax.measure.unit.SI;
 import javax.measure.unit.NonSI;
 import javax.measure.quantity.Angle;
@@ -36,9 +38,9 @@ import javax.measure.quantity.Duration;
 
 import org.opengis.util.Factory;
 import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Citation;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.ObjectFactory;
 import org.opengis.util.FactoryException;
 
@@ -52,6 +54,11 @@ import org.opengis.referencing.operation
 import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.iso.extent.DefaultExtent;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription;
+import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
+import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.metadata.VerticalDatumTypes;
@@ -71,21 +78,6 @@ import static java.util.Collections.sing
  * {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform Math Transform} objects.
  * Note that math transforms are part of the WKT 1 {@code "FITTED_CS"} element.
  *
- * <div class="section">Default axis names</div>
- * The WKT 1 specification defined axis names different than the ISO 19111 ones.
- * This parser replaces the WKT 1 names by the ISO names and abbreviations when possible.
- *
- * <table class="sis">
- *   <tr><th>CRS type</th>   <th>WKT1 names</th> <th>ISO abbreviations</th></tr>
- *   <tr><td>Geographic</td> <td>Lon, Lat</td>   <td>λ, φ</td></tr>
- *   <tr><td>Vertical</td>   <td>H</td>          <td>h</td></tr>
- *   <tr><td>Projected</td>  <td>X, Y</td>       <td>x, y</td></tr>
- *   <tr><td>Geocentric</td> <td>X, Y, Z</td>    <td>X, Y, Z</td></tr>
- * </table>
- *
- * The default behavior is to use the ISO identifiers.
- * This behavior can be changed by setting the parsing conventions to {@link Convention#WKT1}.
- *
  * @author  Rémi Eve (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.6
@@ -94,6 +86,23 @@ import static java.util.Collections.sing
  */
 final class GeodeticObjectParser extends MathTransformParser {
     /**
+     * The keywords of unit elements. Most frequently used keywords should be first.
+     */
+    private static final String[] UNIT_KEYWORDS = {
+        WKTKeywords.Unit,   // Ignored since it does not allow us to know the quantity dimension.
+        WKTKeywords.LengthUnit, WKTKeywords.AngleUnit, WKTKeywords.ScaleUnit, WKTKeywords.TimeUnit,
+        WKTKeywords.ParametricUnit  // Ignored for the same reason than "Unit".
+    };
+
+    /**
+     * The base unit associated to the {@link #UNIT_KEYWORDS}, ignoring {@link WKTKeywords#Unit}.
+     * For each {@code UNIT_KEYWORDS[i]} element, the associated base unit is {@code BASE_UNIT[i]}.
+     */
+    private static final Unit<?>[] BASE_UNITS = {
+        SI.METRE, SI.RADIAN, Unit.ONE, SI.SECOND
+    };
+
+    /**
      * The names of the 7 parameters in a {@code TOWGS84[…]} element.
      * Those names are derived from the <cite>Well Known Text</cite> (WKT) version 1 specification.
      * They are not the same than the {@link org.apache.sis.referencing.datum.BursaWolfParameters}
@@ -134,31 +143,41 @@ final class GeodeticObjectParser extends
      *   <li>{@link Convention#WKT1_COMMON_UNITS} means that {@code PRIMEM} and {@code PARAMETER} angular units
      *       need to be forced to {@code NonSI.DEGREE_ANGLE} instead than inferred from the context.
      *       Note that this rule does not apply to {@code AXIS} elements.</li>
+     *
+     *   <li>{@link Convention#WKT1_IGNORE_AXES} means that axes should be parsed only for verifying the syntax,
+     *       but otherwise parsing should behave as if axes were not declared.</li>
      * </ul>
      */
     private final Convention convention;
 
     /**
-     * {@code true} if {@code AXIS[...]} elements should be ignored.
-     * This is sometime used for simulating a "force longitude first axis order" behavior.
-     * This is also used for compatibility with softwares that ignore axis elements.
+     * A map of properties to be given to the factory constructor methods.
+     * This map will be recycled for each object to be parsed.
+     */
+    private final Map<String,Object> properties = new HashMap<>(4);
+
+    /**
+     * The last vertical CRS found during the parsing, or {@code null} if none.
+     * This information is needed for creating {@link DefaultVerticalExtent} instances.
      *
-     * <p>Note that {@code AXIS} elements still need to be well formed even when this flag is set to {@code true}.
-     * Malformed axis elements will continue to cause a {@link ParseException} despite their content being ignored.</p>
+     * <p>ISO 19162 said that we should have at most one vertical CRS per WKT. Apache SIS does
+     * not enforce this constraint, but if a WKT contains more than one vertical CRS then the
+     * instance used for completing the {@link DefaultVerticalExtent} instances is unspecified.</p>
      */
-    private final boolean isAxisIgnored;
+    private transient VerticalCRS verticalCRS;
 
     /**
-     * A map of properties to be given the factory constructor methods.
-     * This map will be recycled for each object to be parsed.
+     * A chained list of temporary information needed for completing the construction of {@link DefaultVerticalExtent}
+     * instances. In particular, stores the unit of measurement until the {@link VerticalCRS} instance to associate to
+     * the extents become known.
      */
-    private final Map<String,Object> properties = new HashMap<>(4);
+    private transient VerticalInfo verticalElements;
 
     /**
      * Creates a parser using the default set of symbols and factories.
      */
     public GeodeticObjectParser() {
-        this(Symbols.getDefault(), null, null, Convention.DEFAULT, false, null, null);
+        this(Symbols.getDefault(), null, null, null, Convention.DEFAULT, null, null);
     }
 
     /**
@@ -178,14 +197,13 @@ final class GeodeticObjectParser extends
     public GeodeticObjectParser(final Map<String,?> defaultProperties,
             final ObjectFactory factories, final MathTransformFactory mtFactory)
     {
-        super(Symbols.getDefault(), null, null, mtFactory, (Locale) defaultProperties.get(Errors.LOCALE_KEY));
-        crsFactory    = (CRSFactory)   factories;
-        csFactory     = (CSFactory)    factories;
-        datumFactory  = (DatumFactory) factories;
-        referencing   = ReferencingServices.getInstance();
-        opFactory     = referencing.getCoordinateOperationFactory(defaultProperties, mtFactory);
-        convention    = Convention.DEFAULT;
-        isAxisIgnored = false;
+        super(Symbols.getDefault(), null, null, null, mtFactory, (Locale) defaultProperties.get(Errors.LOCALE_KEY));
+        crsFactory   = (CRSFactory)   factories;
+        csFactory    = (CSFactory)    factories;
+        datumFactory = (DatumFactory) factories;
+        referencing  = ReferencingServices.getInstance();
+        opFactory    = referencing.getCoordinateOperationFactory(defaultProperties, mtFactory);
+        convention   = Convention.DEFAULT;
     }
 
     /**
@@ -195,23 +213,22 @@ final class GeodeticObjectParser extends
      * @param symbols       The set of symbols to use.
      * @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 convention    The WKT convention to use.
-     * @param isAxisIgnored {@code true} if {@code AXIS} elements should be ignored.
      * @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 Convention convention, final boolean isAxisIgnored, final Locale errorLocale,
+            final UnitFormat unitFormat, final Convention convention, final Locale errorLocale,
             final Map<Class<?>,Factory> factories)
     {
-        super(symbols, numberFormat, dateFormat, getFactory(MathTransformFactory.class, factories), errorLocale);
+        super(symbols, numberFormat, dateFormat, unitFormat, getFactory(MathTransformFactory.class, factories), errorLocale);
         crsFactory   = getFactory(CRSFactory.class,   factories);
         csFactory    = getFactory(CSFactory.class,    factories);
         datumFactory = getFactory(DatumFactory.class, factories);
         referencing  = ReferencingServices.getInstance();
         opFactory    = referencing.getCoordinateOperationFactory(null, mtFactory);
         this.convention = convention;
-        this.isAxisIgnored = isAxisIgnored;
     }
 
     /**
@@ -240,11 +257,39 @@ final class GeodeticObjectParser extends
      */
     @Override
     public Object parseObject(final String text, final ParsePosition position) throws ParseException {
+        final Object object;
         try {
-            return super.parseObject(text, position);
+            object = super.parseObject(text, position);
+            /*
+             * After parsing the object, we may have been unable to set the VerticalCRS of VerticalExtent instances.
+             * First, try to set a default VerticalCRS for Mean Sea Level Height in metres. In the majority of cases
+             * that should be enough. If not (typically because the vertical extent uses other unit than metre), try
+             * to create a new CRS using the unit declared in the WKT.
+             */
+            if (verticalElements != null) {
+                Exception ex = null;
+                try {
+                    verticalElements = verticalElements.resolve(referencing.getMSLH());     // Optional operation.
+                } catch (UnsupportedOperationException e) {
+                    ex = e;
+                }
+                if (verticalElements != null) try {
+                    verticalElements = verticalElements.complete(crsFactory, csFactory);
+                } catch (FactoryException e) {
+                    if (ex == null) ex = e;
+                    else ex.addSuppressed(e);
+                }
+                if (verticalElements != null) {
+                    warning(Errors.formatInternational(Errors.Keys.CanNotAssignUnitToDimension_2,
+                            WKTKeywords.VerticalExtent, verticalElements.unit), ex);
+                }
+            }
         } finally {
+            verticalCRS = null;
+            verticalElements = null;
             properties.clear();     // for letting the garbage collector do its work.
         }
+        return object;
     }
 
     /**
@@ -264,21 +309,20 @@ final class GeodeticObjectParser extends
         if (value != null) {
             return value;
         }
-        String keyword = WKTKeywords.GeogCS;
-        final Object child = element.peek();
-        if (child instanceof Element) {
-            keyword = ((Element) child).keyword;
-            if (keyword != null) {
-                if (keyword.equalsIgnoreCase(WKTKeywords.Axis))        return parseAxis      (element, false, SI.METRE, true);
-                if (keyword.equalsIgnoreCase(WKTKeywords.PrimeM))      return parsePrimem    (element, NonSI.DEGREE_ANGLE);
-                if (keyword.equalsIgnoreCase(WKTKeywords.ToWGS84))     return parseToWGS84   (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Spheroid))    return parseSpheroid  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Vert_Datum))  return parseVertDatum (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Local_Datum)) return parseLocalDatum(element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Datum))       return parseDatum     (element, referencing.getGreenwich());
-            }
+        Object object;
+        if ((object = parseAxis             (FIRST, element, false, SI.METRE)) == null &&
+            (object = parsePrimeMeridian    (FIRST, element, NonSI.DEGREE_ANGLE)) == null &&
+            (object = parseDatum            (FIRST, element, null)) == null &&
+            (object = parseEllipsoid        (FIRST, element)) == null &&
+            (object = parseToWGS84          (FIRST, element)) == null &&
+            (object = parseVerticalDatum    (FIRST, element)) == null &&
+            (object = parseTimeDatum        (FIRST, element)) == null &&
+            (object = parseEngineeringDatum (FIRST, element)) == null &&
+            (object = parseProjection       (FIRST, element, SI.METRE, NonSI.DEGREE_ANGLE)) == null)
+        {
+            throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
         }
-        throw element.keywordNotFound(keyword, keyword == WKTKeywords.GeogCS);
+        return object;
     }
 
     /**
@@ -292,30 +336,27 @@ final class GeodeticObjectParser extends
     private CoordinateReferenceSystem parseCoordinateReferenceSystem(final Element element, final boolean mandatory)
             throws ParseException
     {
-        String keyword = WKTKeywords.GeogCS;
-        final Object child = element.peek();
-        if (child instanceof Element) {
-            keyword = ((Element) child).keyword;
-            if (keyword != null) {
-                if (keyword.equalsIgnoreCase(WKTKeywords.GeogCS))    return parseGeoGCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.ProjCS))    return parseProjCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.GeocCS))    return parseGeoCCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Vert_CS))   return parseVertCS  (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.TimeCRS))   return parseTimeCRS (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Local_CS))  return parseLocalCS (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Compd_CS))  return parseCompdCS (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Fitted_CS)) return parseFittedCS(element);
+        CoordinateReferenceSystem crs;
+        if ((crs = parseGeographicCRS  (FIRST, element)) == null &&
+            (crs = parseProjectedCRS   (FIRST, element)) == null &&
+            (crs = parseGeocentricCRS  (FIRST, element)) == null &&
+            (crs = parseVerticalCRS    (FIRST, element)) == null &&
+            (crs = parseTimeCRS        (FIRST, element)) == null &&
+            (crs = parseEngineeringCRS (FIRST, element)) == null &&
+            (crs = parseCompoundCRS    (FIRST, element)) == null &&
+            (crs = parseFittedCS       (FIRST, element)) == null)
+        {
+            if (mandatory) {
+                throw element.missingOrUnknownComponent(WKTKeywords.GeodeticCRS);
             }
         }
-        if (mandatory) {
-            throw element.keywordNotFound(keyword, keyword == WKTKeywords.GeogCS);
-        }
-        return null;
+        return crs;
     }
 
     /**
-     * Parses an <strong>optional</strong> {@code "AUTHORITY"} element.
-     * This element has the following pattern:
+     * Parses an <strong>optional</strong> metadata elements and close.
+     * This include elements like {@code "SCOPE"}, {@code "ID"} (WKT 2) or {@code "AUTHORITY"} (WKT 1).
+     * This WKT 1 element has the following pattern:
      *
      * {@preformat text
      *     AUTHORITY["<name>", "<code>"]
@@ -324,19 +365,31 @@ final class GeodeticObjectParser extends
      * @param  parent The parent element.
      * @param  name The name of the parent object being parsed, either a {@link String} or {@link Identifier} instance.
      * @return A properties map with the parent name and the optional authority code.
-     * @throws ParseException if the {@code "AUTHORITY"} can not be parsed.
+     * @throws ParseException if an element can not be parsed.
      */
-    private Map<String,Object> parseAuthorityAndClose(final Element parent, Object name) throws ParseException {
+    private Map<String,Object> parseMetadataAndClose(final Element parent, Object name) throws ParseException {
         assert (name instanceof String) || (name instanceof Identifier);
         properties.clear();
         properties.put(IdentifiedObject.NAME_KEY, name);
-        final Element element = parent.pullOptionalElement(WKTKeywords.Authority);
-        if (element != null) {
-            final String auth = element.pullString("name");
-            final String code = element.pullObject("code").toString();  // Accepts Integer as well as String.
+        Element element;
+        while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Id, WKTKeywords.Authority)) != null) {
+            final String   codeSpace = element.pullString("name");
+            final String   code      = element.pullObject("code").toString();   // Accepts Integer as well as String.
+            final Object   version   = element.pullOptional(Object.class);      // Accepts Number as well as String.
+            final Element  citation  = element.pullElement(OPTIONAL, WKTKeywords.Citation);
+            final String   authority;
+            if (citation != null) {
+                authority = citation.pullString("authority");
+                citation.close(ignoredElements);
+            } else {
+                authority = codeSpace;
+            }
+            final Element uri = element.pullElement(OPTIONAL, WKTKeywords.URI);
+            if (uri != null) {
+                uri.pullString("URI");      // TODO: not yet stored, since often redundant with other informations.
+                uri.close(ignoredElements);
+            }
             element.close(ignoredElements);
-            final Citation authority = Citations.fromName(auth);
-            properties.put(IdentifiedObject.IDENTIFIERS_KEY, new ImmutableIdentifier(authority, auth, code));
             /*
              * Note: we could be tempted to assign the authority to the name as well, like below:
              *
@@ -350,35 +403,166 @@ final class GeodeticObjectParser extends
              * (for example "WGS84" for the datum instead than "World Geodetic System 1984"),
              * so the name in WKT is often not compliant with the name actually defined by the authority.
              */
+            final ImmutableIdentifier id = new ImmutableIdentifier(Citations.fromName(authority),
+                    codeSpace, code, (version != null) ? version.toString() : null, null);
+            final Object previous = properties.put(IdentifiedObject.IDENTIFIERS_KEY, id);
+            if (previous != null) {
+                Identifier[] identifiers;
+                if (previous instanceof Identifier) {
+                    identifiers = new Identifier[] {(Identifier) previous, id};
+                } else {
+                    identifiers = (Identifier[]) previous;
+                    final int n = identifiers.length;
+                    identifiers = Arrays.copyOf(identifiers, n + 1);
+                    identifiers[n] = id;
+                }
+                properties.put(IdentifiedObject.IDENTIFIERS_KEY, identifiers);
+            }
+        }
+        /*
+         * Other metadata (SCOPE, AREA, etc.).  ISO 19162 said that at most one of each type shall be present,
+         * but our parser accepts an arbitrary amount of some kinds of metadata. They can be recognized by the
+         * 'while' loop.
+         *
+         * Most WKT do not contain any of those metadata, so we perform an 'isEmpty()' check as an optimization
+         * for those common cases.
+         */
+        if (!parent.isEmpty()) {
+            /*
+             * Example: SCOPE["Large scale topographic mapping and cadastre."]
+             */
+            element = parent.pullElement(OPTIONAL, WKTKeywords.Scope);
+            if (element != null) {
+                properties.put(ReferenceSystem.SCOPE_KEY, element.pullString("scope"));  // Other types like Datum use the same key.
+                element.close(ignoredElements);
+            }
+            /*
+             * Example: AREA["Netherlands offshore."]
+             */
+            DefaultExtent extent = null;
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.Area)) != null) {
+                final String area = element.pullString("area");
+                element.close(ignoredElements);
+                if (extent == null) extent = new DefaultExtent();
+                extent.getGeographicElements().add(new DefaultGeographicDescription(area));
+            }
+            /*
+             * Example: BBOX[51.43, 2.54, 55.77, 6.40]
+             */
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.BBox)) != null) {
+                final double southBoundLatitude = element.pullDouble("southBoundLatitude");
+                final double westBoundLongitude = element.pullDouble("westBoundLongitude");
+                final double northBoundLatitude = element.pullDouble("northBoundLatitude");
+                final double eastBoundLongitude = element.pullDouble("eastBoundLongitude");
+                element.close(ignoredElements);
+                if (extent == null) extent = new DefaultExtent();
+                extent.getGeographicElements().add(new DefaultGeographicBoundingBox(
+                        westBoundLongitude, eastBoundLongitude, southBoundLatitude, northBoundLatitude));
+            }
+            /*
+             * Example: VERTICALEXTENT[-1000, 0, LENGTHUNIT[“metre”, 1]]
+             *
+             * Units are optional, default to metres (no "contextual units" here).
+             */
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.VerticalExtent)) != null) {
+                final double minimum = element.pullDouble("minimum");
+                final double maximum = element.pullDouble("maximum");
+                Unit<Length> unit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+                element.close(ignoredElements);
+                if (unit   == null) unit   = SI.METRE;
+                if (extent == null) extent = new DefaultExtent();
+                verticalElements = new VerticalInfo(verticalElements, extent, minimum, maximum, unit).resolve(verticalCRS);
+            }
+            /*
+             * Example: TIMEEXTENT[2013-01-01, 2013-12-31]
+             *
+             * TODO: syntax like TIMEEXTENT[“Jurassic”, “Quaternary”] is not yet supported.
+             * See https://issues.apache.org/jira/browse/SIS-163
+             *
+             * This operation requires the the sis-temporal module. If not available,
+             * we will report a warning and leave the temporal extent missing.
+             */
+            while ((element = parent.pullElement(OPTIONAL, WKTKeywords.TimeExtent)) != null) {
+                final Date startTime = element.pullDate("startTime");
+                final Date endTime   = element.pullDate("endTime");
+                element.close(ignoredElements);
+                try {
+                    final DefaultTemporalExtent t = new DefaultTemporalExtent();
+                    t.setBounds(startTime, endTime);
+                    if (extent == null) extent = new DefaultExtent();
+                    extent.getTemporalElements().add(t);
+                } catch (UnsupportedOperationException e) {
+                    warning(parent, element, e);
+                }
+            }
+            /*
+             * Example: REMARK["Замечание на русском языке"]
+             */
+            element = parent.pullElement(OPTIONAL, WKTKeywords.Remark);
+            if (element != null) {
+                properties.put(IdentifiedObject.REMARKS_KEY, element.pullString("remarks"));
+                element.close(ignoredElements);
+            }
         }
         parent.close(ignoredElements);
         return properties;
     }
 
     /**
-     * Parses a {@code "UNIT"} element.
+     * Parses an optional {@code "UNIT"} element of a known dimension.
      * This element has the following pattern:
      *
      * {@preformat text
      *     UNIT["<name>", <conversion factor> {,<authority>}]
      * }
      *
-     * @param  parent The parent element.
-     * @param  unit The contextual unit, usually {@code SI.METRE} or {@code SI.RADIAN}.
-     * @return The {@code "UNIT"} element as an {@link Unit} object.
+     * Unit was a mandatory element in WKT 1, but became optional in WKT 2 because the unit may be specified
+     * in each {@code AXIS[…]} element instead than for the whole coordinate system.
+     *
+     * @param  parent   The parent element.
+     * @param  keyword  The unit keyword (e.g. {@code "LengthUnit"} or {@code "AngleUnit"}).
+     * @param  baseUnit The base unit, usually {@code SI.METRE} or {@code SI.RADIAN}.
+     * @return The {@code "UNIT"} element as an {@link Unit} object, or {@code null} if none.
      * @throws ParseException if the {@code "UNIT"} can not be parsed.
      *
      * @todo Authority code is currently ignored. We may consider to create a subclass of
      *       {@link Unit} which implements {@link IdentifiedObject} in a future version.
      */
-    private <Q extends Quantity> Unit<Q> parseUnit(final Element parent, final Unit<Q> unit)
-            throws ParseException
+    private <Q extends Quantity> Unit<Q> parseDerivedUnit(final Element parent,
+            final String keyword, final Unit<Q> baseUnit) throws ParseException
     {
-        final Element element = parent.pullElement("Unit");
-        final String  name    = element.pullString("name");
-        final double  factor  = element.pullDouble("factor");
-        parseAuthorityAndClose(element, name);
-        return Units.multiply(unit, factor);
+        final Element element = parent.pullElement(OPTIONAL, keyword, WKTKeywords.Unit);
+        if (element == null) {
+            return null;
+        }
+        final String name   = element.pullString("name");
+        final double factor = element.pullDouble("factor");
+        parseMetadataAndClose(element, name);
+        return Units.multiply(baseUnit, factor);
+    }
+
+    /**
+     * Parses an optional {@code "UNIT"} element of unknown dimension.
+     * This method tries to infer the quantity dimension from the unit keyword.
+     *
+     * @param  parent The parent element.
+     * @return The {@code "UNIT"} element, or {@code null} if none.
+     * @throws ParseException if the {@code "UNIT"} can not be parsed.
+     */
+    private Unit<?> parseUnit(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(OPTIONAL, UNIT_KEYWORDS);
+        if (element == null) {
+            return null;
+        }
+        final String name   = element.pullString("name");
+        final double factor = element.pullDouble("factor");
+        final int    index  = element.getKeywordIndex() - 1;
+        parseMetadataAndClose(element, name);
+        if (index >= 0 && index < BASE_UNITS.length) {
+            return Units.multiply(BASE_UNITS[index - 1], factor);
+        }
+        // If we can not infer the base type, we have to rely on the name.
+        return parseUnit(name);
     }
 
     /**
@@ -394,25 +578,20 @@ final class GeodeticObjectParser extends
      * to make the parser more tolerant to non-100% compliant WKT. Note that AXIS is really the only element without
      * such AUTHORITY clause and the EPSG database provides authority code for all axis.</div>
      *
+     * @param  mode         {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent       The parent element.
      * @param  isGeographic {@code true} if the parent element is a geodetic CRS having an ellipsoidal coordinate system.
-     * @param  unit         The contextual unit, usually {@code SI.METRE} or {@code SI.RADIAN}.
-     * @param  mandatory    {@code true} if the axis is mandatory, or {@code false} if it is optional.
+     * @param  defaultUnit  The contextual unit (usually {@code SI.METRE} or {@code SI.RADIAN}), or {@code null} if unknown.
      * @return The {@code "AXIS"} element as a {@link CoordinateSystemAxis} object, or {@code null}
      *         if the axis was not required and there is no axis object.
      * @throws ParseException if the {@code "AXIS"} element can not be parsed.
      */
-    private CoordinateSystemAxis parseAxis(final Element parent, final boolean isGeographic,
-            final Unit<?> unit, final boolean mandatory) throws ParseException
+    private CoordinateSystemAxis parseAxis(final int mode, final Element parent, final boolean isGeographic,
+            final Unit<?> defaultUnit) throws ParseException
     {
-        final Element element;
-        if (mandatory) {
-            element = parent.pullElement(WKTKeywords.Axis);
-        } else {
-            element = parent.pullOptionalElement(WKTKeywords.Axis);
-            if (element == null) {
-                return null;
-            }
+        final Element element = parent.pullElement(mode, WKTKeywords.Axis);
+        if (element == null) {
+            return null;
         }
         String name = CharSequences.trimWhitespaces(element.pullString("name"));
         if (isGeographic) {
@@ -429,7 +608,14 @@ final class GeodeticObjectParser extends
             }
         }
         final Element orientation = element.pullVoidElement("orientation");
-        final AxisDirection direction = Types.forCodeName(AxisDirection.class, orientation.keyword, mandatory);
+        Unit<?> unit = parseUnit(element);
+        if (unit == null) {
+            if (defaultUnit == null) {
+                throw element.missingComponent(WKTKeywords.Unit);
+            }
+            unit = defaultUnit;
+        }
+        final AxisDirection direction = Types.forCodeName(AxisDirection.class, orientation.keyword, mode == MANDATORY);
         /*
          * According ISO 19162, the abbreviation should be inserted between parenthesis in the name.
          * Example: "Easting (E)", "Longitude (L)". If we do not find an abbreviation, then we will
@@ -447,7 +633,7 @@ final class GeodeticObjectParser extends
             abbreviation = referencing.suggestAbbreviation(name, direction, unit);
         }
         try {
-            return csFactory.createCoordinateSystemAxis(parseAuthorityAndClose(element, name), abbreviation, direction, unit);
+            return csFactory.createCoordinateSystemAxis(parseMetadataAndClose(element, name), abbreviation, direction, unit);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -464,26 +650,45 @@ final class GeodeticObjectParser extends
     }
 
     /**
+     * Returns {@code true} if axes should be ignored.
+     *
+     * @param  axis The first parsed axis (can be {@code null}).
+     * @return {@code true} for ignoring the given axis and all other ones in the current coordinate system.
+     */
+    private boolean isAxisIgnored(final CoordinateSystemAxis axis) {
+        return (axis == null) || (convention == Convention.WKT1_IGNORE_AXES);
+    }
+
+    /**
      * Parses a {@code "PRIMEM"} element. This element has the following pattern:
      *
      * {@preformat text
      *     PRIMEM["<name>", <longitude> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @param  angularUnit The contextual unit.
      * @return The {@code "PRIMEM"} element as a {@link PrimeMeridian} object.
      * @throws ParseException if the {@code "PRIMEM"} element can not be parsed.
      */
-    private PrimeMeridian parsePrimem(final Element parent, Unit<Angle> angularUnit) throws ParseException {
-        if (convention == Convention.WKT1_COMMON_UNITS) {
+    private PrimeMeridian parsePrimeMeridian(final int mode, final Element parent, Unit<Angle> angularUnit)
+            throws ParseException
+    {
+        if (convention.usesCommonUnits) {
             angularUnit = NonSI.DEGREE_ANGLE;
         }
-        final Element element   = parent.pullElement(WKTKeywords.PrimeM);
-        final String  name      = element.pullString("name");
-        final double  longitude = element.pullDouble("longitude");
+        final Element element = parent.pullElement(mode, WKTKeywords.PrimeMeridian, WKTKeywords.PrimeM);
+        if (element == null) {
+            return null;
+        }
+        final String name      = element.pullString("name");
+        final double longitude = element.pullDouble("longitude");
+        if (angularUnit == null) {
+            throw element.missingComponent(WKTKeywords.AngleUnit);
+        }
         try {
-            return datumFactory.createPrimeMeridian(parseAuthorityAndClose(element, name), longitude, angularUnit);
+            return datumFactory.createPrimeMeridian(parseMetadataAndClose(element, name), longitude, angularUnit);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -497,20 +702,21 @@ final class GeodeticObjectParser extends
      *     TOWGS84[<dx>, <dy>, <dz>, <ex>, <ey>, <ez>, <ppm>]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "TOWGS84"} element as a {@link org.apache.sis.referencing.datum.BursaWolfParameters} object,
      *         or {@code null} if no {@code "TOWGS84"} has been found.
      * @throws ParseException if the {@code "TOWGS84"} can not be parsed.
      */
-    private Object parseToWGS84(final Element parent) throws ParseException {
-        final Element element = parent.pullOptionalElement(WKTKeywords.ToWGS84);
+    private Object parseToWGS84(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.ToWGS84);
         if (element == null) {
             return null;
         }
         final double[] values = new double[ToWGS84.length];
         for (int i=0; i<values.length;) {
             values[i] = element.pullDouble(ToWGS84[i]);
-            if ((++i % 3) == 0 && element.peek() == null) {
+            if ((++i % 3) == 0 && element.isEmpty()) {
                 break;  // It is legal to have only 3 or 6 elements.
             }
         }
@@ -525,12 +731,16 @@ final class GeodeticObjectParser extends
      *     SPHEROID["<name>", <semi-major axis>, <inverse flattening> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "SPHEROID"} element as an {@link Ellipsoid} object.
      * @throws ParseException if the {@code "SPHEROID"} element can not be parsed.
      */
-    private Ellipsoid parseSpheroid(final Element parent) throws ParseException {
-        final Element element      = parent.pullElement(WKTKeywords.Spheroid);
+    private Ellipsoid parseEllipsoid(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.Ellipsoid, WKTKeywords.Spheroid);
+        if (element == null) {
+            return null;
+        }
         final String name          = element.pullString("name");
         final double semiMajorAxis = element.pullDouble("semiMajorAxis");
         double inverseFlattening   = element.pullDouble("inverseFlattening");
@@ -538,7 +748,7 @@ final class GeodeticObjectParser extends
             inverseFlattening = Double.POSITIVE_INFINITY;
         }
         try {
-            return datumFactory.createFlattenedSphere(parseAuthorityAndClose(element, name),
+            return datumFactory.createFlattenedSphere(parseMetadataAndClose(element, name),
                     semiMajorAxis, inverseFlattening, SI.METRE);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -552,18 +762,20 @@ final class GeodeticObjectParser extends
      *     PROJECTION["<name>" {,<authority>}]
      * }
      *
+     * @param  mode        {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent      The parent element.
      * @param  linearUnit  The linear unit of the parent {@code PROJCS} element, or {@code null}.
      * @param  angularUnit The angular unit of the parent {@code GEOCS} element, or {@code null}.
      * @return The {@code "PROJECTION"} element as a defining conversion.
      * @throws ParseException if the {@code "PROJECTION"} element can not be parsed.
      */
-    private Conversion parseProjection(final Element      parent,
-                                       final Unit<Length> linearUnit,
-                                       final Unit<Angle>  angularUnit)
-            throws ParseException
+    private Conversion parseProjection(final int mode, final Element parent,
+            final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException
     {
-        final Element element = parent.pullElement(WKTKeywords.Projection);
+        final Element element = parent.pullElement(mode, WKTKeywords.Projection);
+        if (element == null) {
+            return null;
+        }
         final String classification = element.pullString("name");
         /*
          * Set the list of parameters. NOTE: Parameters are defined in the parent
@@ -573,7 +785,7 @@ final class GeodeticObjectParser extends
             final OperationMethod method = opFactory.getOperationMethod(classification);
             final ParameterValueGroup parameters = method.getParameters().createValue();
             parseParameters(parent, parameters, linearUnit, angularUnit);
-            return opFactory.createDefiningConversion(parseAuthorityAndClose(element, classification), method, parameters);
+            return opFactory.createDefiningConversion(parseMetadataAndClose(element, classification), method, parameters);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -586,17 +798,24 @@ final class GeodeticObjectParser extends
      *     DATUM["<name>", <spheroid> {,<to wgs84>} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
-     * @param  meridian the prime meridian.
+     * @param  meridian the prime meridian, or {@code null} for Greenwich.
      * @return The {@code "DATUM"} element as a {@link GeodeticDatum} object.
      * @throws ParseException if the {@code "DATUM"} element can not be parsed.
      */
-    private GeodeticDatum parseDatum(final Element parent, final PrimeMeridian meridian) throws ParseException {
-        final Element             element    = parent.pullElement(WKTKeywords.Datum);
-        final String              name       = element.pullString("name");
-        final Ellipsoid           ellipsoid  = parseSpheroid(element);
-        final Object              toWGS84    = parseToWGS84(element);     // Optional; may be null.
-        final Map<String,Object>  properties = parseAuthorityAndClose(element, name);
+    private GeodeticDatum parseDatum(final int mode, final Element parent, PrimeMeridian meridian) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.Datum);
+        if (element == null) {
+            return null;
+        }
+        final String             name       = element.pullString("name");
+        final Ellipsoid          ellipsoid  = parseEllipsoid(MANDATORY, element);
+        final Object             toWGS84    = parseToWGS84(OPTIONAL, element);
+        final Map<String,Object> properties = parseMetadataAndClose(element, name);
+        if (meridian == null) {
+            meridian = referencing.getGreenwich();
+        }
         if (toWGS84 != null) {
             properties.put(ReferencingServices.BURSA_WOLF_KEY, toWGS84);
         }
@@ -614,18 +833,22 @@ final class GeodeticObjectParser extends
      *     VERT_DATUM["<name>", <datum type> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "VERT_DATUM"} element as a {@link VerticalDatum} object.
      * @throws ParseException if the {@code "VERT_DATUM"} element can not be parsed.
      */
-    private VerticalDatum parseVertDatum(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Vert_Datum);
-        final String  name    = element.pullString ("name");
-        final int     datum   = element.pullInteger("datum");
+    private VerticalDatum parseVerticalDatum(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.VerticalDatum, WKTKeywords.Vert_Datum);
+        if (element == null) {
+            return null;
+        }
+        final String  name  = element.pullString ("name");
+        final int     datum = element.pullInteger("datum");
         final VerticalDatumType type = VerticalDatumTypes.fromLegacy(datum);
         // TODO: need to find a default value if null.
         try {
-            return datumFactory.createVerticalDatum(parseAuthorityAndClose(element, name), type);
+            return datumFactory.createVerticalDatum(parseMetadataAndClose(element, name), type);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -638,18 +861,22 @@ final class GeodeticObjectParser extends
      *     TimeDatum["<name>", TimeOrigin[<time origin>] {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "TimeDatum"} element as a {@link TemporalDatum} object.
      * @throws ParseException if the {@code "TimeDatum"} element can not be parsed.
      */
-    private TemporalDatum parseTimeDatum(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.TimeDatum);
-        final String  name    = element.pullString ("name");
-        final Element origin  = element.pullElement(WKTKeywords.TimeOrigin);
-        final Date    epoch   = origin .pullDate("origin");
+    private TemporalDatum parseTimeDatum(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.TimeDatum);
+        if (element == null) {
+            return null;
+        }
+        final String  name   = element.pullString ("name");
+        final Element origin = element.pullElement(MANDATORY, WKTKeywords.TimeOrigin);
+        final Date    epoch  = origin .pullDate("origin");
         origin.close(ignoredElements);
         try {
-            return datumFactory.createTemporalDatum(parseAuthorityAndClose(element, name), epoch);
+            return datumFactory.createTemporalDatum(parseMetadataAndClose(element, name), epoch);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -662,18 +889,22 @@ final class GeodeticObjectParser extends
      *     LOCAL_DATUM["<name>", <datum type> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "LOCAL_DATUM"} element as an {@link EngineeringDatum} object.
      * @throws ParseException if the {@code "LOCAL_DATUM"} element can not be parsed.
      *
      * @todo The vertical datum type is currently ignored.
      */
-    private EngineeringDatum parseLocalDatum(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Local_Datum);
-        final String  name    = element.pullString ("name");
-        final int     datum   = element.pullInteger("datum");   // Ignored for now.
+    private EngineeringDatum parseEngineeringDatum(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.EngineeringDatum, WKTKeywords.Local_Datum);
+        if (element == null) {
+            return null;
+        }
+        final String name  = element.pullString ("name");
+        final int    datum = element.pullInteger("datum");   // Ignored for now.
         try {
-            return datumFactory.createEngineeringDatum(parseAuthorityAndClose(element, name));
+            return datumFactory.createEngineeringDatum(parseMetadataAndClose(element, name));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -687,6 +918,7 @@ final class GeodeticObjectParser extends
      *     LOCAL_CS["<name>", <local datum>, <unit>, <axis>, {,<axis>}* {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "LOCAL_CS"} element as an {@link EngineeringCRS} object.
      * @throws ParseException if the {@code "LOCAL_CS"} element can not be parsed.
@@ -695,20 +927,23 @@ final class GeodeticObjectParser extends
      *       know which method to invokes in the {@link CSFactory} (is it a Cartesian
      *       coordinate system? a spherical one? etc.).
      */
-    private EngineeringCRS parseLocalCS(final Element parent) throws ParseException {
-        final Element          element    = parent.pullElement(WKTKeywords.Local_CS);
+    private EngineeringCRS parseEngineeringCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.EngineeringCRS, WKTKeywords.Local_CS);
+        if (element == null) {
+            return null;
+        }
         final String           name       = element.pullString("name");
-        final EngineeringDatum datum      = parseLocalDatum(element);
-        final Unit<Length>     linearUnit = parseUnit(element, SI.METRE);
-        CoordinateSystemAxis   axis       = parseAxis(element, false, linearUnit, true);
+        final EngineeringDatum datum      = parseEngineeringDatum(MANDATORY, element);
+        final Unit<Length>     linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        CoordinateSystemAxis   axis       = parseAxis(MANDATORY, element, false, linearUnit);
         final List<CoordinateSystemAxis> list = new ArrayList<>();
         do {
             list.add(axis);
-            axis = parseAxis(element, false, linearUnit, false);
+            axis = parseAxis(OPTIONAL, element, false, linearUnit);
         } while (axis != null);
         final CoordinateSystem cs = referencing.createAbstractCS(list.toArray(new CoordinateSystemAxis[list.size()]));
         try {
-            return crsFactory.createEngineeringCRS(parseAuthorityAndClose(element, name), datum, cs);
+            return crsFactory.createEngineeringCRS(parseMetadataAndClose(element, name), datum, cs);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -723,29 +958,36 @@ final class GeodeticObjectParser extends
      *            {,<axis> ,<axis> ,<axis>} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "GEOCCS"} element as a {@link GeocentricCRS} object.
      * @throws ParseException if the {@code "GEOCCS"} element can not be parsed.
      */
-    private GeocentricCRS parseGeoCCS(final Element parent) throws ParseException {
-        final Element       element    = parent.pullElement(WKTKeywords.GeocCS);
+    private GeocentricCRS parseGeocentricCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.GeocCS);
+        if (element == null) {
+            return null;
+        }
         final String        name       = element.pullString("name");
-        final PrimeMeridian meridian   = parsePrimem(element, NonSI.DEGREE_ANGLE);
-        final GeodeticDatum datum      = parseDatum (element, meridian);
-        final Unit<Length>  linearUnit = parseUnit  (element, SI.METRE);
+        final PrimeMeridian meridian   = parsePrimeMeridian(MANDATORY, element, NonSI.DEGREE_ANGLE);
+        final GeodeticDatum datum      = parseDatum (MANDATORY, element, meridian);
+        final Unit<Length>  linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
         CartesianCS cs;
         CoordinateSystemAxis axis0, axis1 = null, axis2 = null;
-        axis0 = parseAxis(element, false, linearUnit, false);
+        axis0 = parseAxis(OPTIONAL, element, false, linearUnit);
         try {
             if (axis0 != null) {
-                axis1 = parseAxis(element, false, linearUnit, true);
-                axis2 = parseAxis(element, false, linearUnit, true);
+                axis1 = parseAxis(MANDATORY, element, false, linearUnit);
+                axis2 = parseAxis(MANDATORY, element, false, linearUnit);
             }
-            final Map<String,?> properties = parseAuthorityAndClose(element, name);
-            if (axis0 != null && !isAxisIgnored) {
+            final Map<String,?> properties = parseMetadataAndClose(element, name);
+            if (!isAxisIgnored(axis0)) {
                 cs = csFactory.createCartesianCS(properties, axis0, axis1, axis2);
                 cs = referencing.upgradeGeocentricCS(cs);
             } else {
+                if (linearUnit == null) {
+                    throw element.missingComponent(WKTKeywords.Unit);
+                }
                 cs = referencing.getGeocentricCS(linearUnit);
             }
             return crsFactory.createGeocentricCRS(properties, datum, cs);
@@ -762,18 +1004,25 @@ final class GeodeticObjectParser extends
      *     VERT_CS["<name>", <vert datum>, <linear unit>, {<axis>,} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "VERT_CS"} element as a {@link VerticalCRS} object.
      * @throws ParseException if the {@code "VERT_CS"} element can not be parsed.
      */
-    private VerticalCRS parseVertCS(final Element parent) throws ParseException {
-        final Element        element    = parent.pullElement(WKTKeywords.Vert_CS);
+    private VerticalCRS parseVerticalCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.VerticalCRS, WKTKeywords.Vert_CS);
+        if (element == null) {
+            return null;
+        }
         final String         name       = element.pullString("name");
-        final VerticalDatum  datum      = parseVertDatum(element);
-        final Unit<Length>   linearUnit = parseUnit(element, SI.METRE);
-        CoordinateSystemAxis axis       = parseAxis(element, false, linearUnit, false);
+        final VerticalDatum  datum      = parseVerticalDatum(MANDATORY, element);
+        final Unit<Length>   linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        CoordinateSystemAxis axis       = parseAxis(OPTIONAL, element, false, linearUnit);
         try {
-            if (axis == null || isAxisIgnored) {
+            if (isAxisIgnored(axis)) {
+                if (linearUnit == null) {
+                    throw element.missingComponent(WKTKeywords.Unit);
+                }
                 String sn = "Height", abbreviation = "h";
                 AxisDirection direction = AxisDirection.UP;
                 final VerticalDatumType type = datum.getVerticalDatumType();
@@ -789,8 +1038,16 @@ final class GeodeticObjectParser extends
                 }
                 axis = createAxis(sn, abbreviation, direction, linearUnit);
             }
-            return crsFactory.createVerticalCRS(parseAuthorityAndClose(element, name), datum,
-                    csFactory.createVerticalCS(singletonMap("name", name), axis));
+            verticalCRS = crsFactory.createVerticalCRS(parseMetadataAndClose(element, name), datum,
+                           csFactory.createVerticalCS(singletonMap("name", name), axis));
+            /*
+             * Some DefaultVerticalExtent objects may be waiting for the VerticalCRS before to complete
+             * their construction. If this is the case, try to complete them now.
+             */
+            if (verticalElements != null) {
+                verticalElements = verticalElements.resolve(verticalCRS);
+            }
+            return verticalCRS;
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -799,21 +1056,28 @@ final class GeodeticObjectParser extends
     /**
      * Parses an <strong>optional</strong> {@code "TIMECRS"} element.
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "TIMECRS"} element as a {@link TemporalCRS} object.
      * @throws ParseException if the {@code "TIMECRS"} element can not be parsed.
      */
-    private TemporalCRS parseTimeCRS(final Element parent) throws ParseException {
-        final Element        element  = parent.pullElement(WKTKeywords.TimeCRS);
+    private TemporalCRS parseTimeCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.TimeCRS);
+        if (element == null) {
+            return null;
+        }
         final String         name     = element.pullString("name");
-        final TemporalDatum  datum    = parseTimeDatum(element);
-        final Unit<Duration> timeUnit = parseUnit(element, SI.SECOND);
-        CoordinateSystemAxis axis     = parseAxis(element, false, timeUnit, false);
+        final TemporalDatum  datum    = parseTimeDatum(MANDATORY, element);
+        final Unit<Duration> timeUnit = parseDerivedUnit(element, WKTKeywords.TimeUnit, SI.SECOND);
+        CoordinateSystemAxis axis     = parseAxis(OPTIONAL, element, false, timeUnit);
         try {
-            if (axis == null || isAxisIgnored) {
+            if (isAxisIgnored(axis)) {
+                if (timeUnit == null) {
+                    throw element.missingComponent(WKTKeywords.TimeUnit);
+                }
                 axis = createAxis("Time", "t", AxisDirection.FUTURE, timeUnit);
             }
-            return crsFactory.createTemporalCRS(parseAuthorityAndClose(element, name), datum,
+            return crsFactory.createTemporalCRS(parseMetadataAndClose(element, name), datum,
                     csFactory.createTimeCS(singletonMap("name", name), axis));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -827,16 +1091,20 @@ final class GeodeticObjectParser extends
      *     GEOGCS["<name>", <datum>, <prime meridian>, <angular unit>  {,<twin axes>} {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "GEOGCS"} element as a {@link GeographicCRS} object.
      * @throws ParseException if the {@code "GEOGCS"} element can not be parsed.
      */
-    private GeographicCRS parseGeoGCS(final Element parent) throws ParseException {
-        final Element       element     = parent.pullElement(WKTKeywords.GeogCS);
-              Object        name        = element.pullString("name");
-        final Unit<Angle>   angularUnit = parseUnit  (element, SI.RADIAN);
-        final PrimeMeridian meridian    = parsePrimem(element, angularUnit);
-        final GeodeticDatum datum       = parseDatum (element, meridian);
+    private GeographicCRS parseGeographicCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.GeodeticCRS, WKTKeywords.GeodCRS, WKTKeywords.GeogCS);
+        if (element == null) {
+            return null;
+        }
+        Object              name        = element.pullString("name");
+        final Unit<Angle>   angularUnit = parseDerivedUnit(element, WKTKeywords.AngleUnit, SI.RADIAN);
+        final PrimeMeridian meridian    = parsePrimeMeridian(MANDATORY, element, angularUnit);
+        final GeodeticDatum datum       = parseDatum(MANDATORY, element, meridian);
         if (((String) name).isEmpty()) {
             /*
              * GeographicCRS name is a mandatory property, but some invalid WKT with an empty string exist.
@@ -845,17 +1113,20 @@ final class GeodeticObjectParser extends
              */
             name = datum.getName();
         }
-        CoordinateSystemAxis axis0 = parseAxis(element, true, angularUnit, false);
+        CoordinateSystemAxis axis0 = parseAxis(OPTIONAL, element, true, angularUnit);
         CoordinateSystemAxis axis1 = null;
         try {
             if (axis0 != null) {
-                axis1 = parseAxis(element, true, angularUnit, true);
+                axis1 = parseAxis(MANDATORY, element, true, angularUnit);
             }
-            if (axis0 == null || isAxisIgnored) {
+            if (isAxisIgnored(axis0)) {
+                if (angularUnit == null) {
+                    throw element.missingComponent(WKTKeywords.AngleUnit);
+                }
                 axis0 = createAxis(AxisNames.GEODETIC_LONGITUDE, "λ", AxisDirection.EAST,  angularUnit);
                 axis1 = createAxis(AxisNames.GEODETIC_LATITUDE,  "φ", AxisDirection.NORTH, angularUnit);
             }
-            final Map<String,?> properties = parseAuthorityAndClose(element, name);
+            final Map<String,?> properties = parseMetadataAndClose(element, name);
             return crsFactory.createGeographicCRS(properties, datum,
                     csFactory.createEllipsoidalCS(properties, axis0, axis1));
         } catch (FactoryException exception) {
@@ -872,30 +1143,37 @@ final class GeodeticObjectParser extends
      *            <linear unit> {,<twin axes>}{,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "PROJCS"} element as a {@link ProjectedCRS} object.
      * @throws ParseException if the {@code "GEOGCS"} element can not be parsed.
      */
-    private ProjectedCRS parseProjCS(final Element parent) throws ParseException {
-        final Element       element    = parent.pullElement(WKTKeywords.ProjCS);
+    private ProjectedCRS parseProjectedCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.ProjCS);
+        if (element == null) {
+            return null;
+        }
         final String        name       = element.pullString("name");
-        final GeographicCRS geoCRS     = parseGeoGCS(element);
-        final Unit<Length>  linearUnit = parseUnit(element, SI.METRE);
-        final boolean  usesCommonUnits = convention.usesCommonUnits();
-        final Conversion    conversion = parseProjection(element,
+        final GeographicCRS geoCRS     = parseGeographicCRS(MANDATORY, element);
+        final Unit<Length>  linearUnit = parseDerivedUnit(element, WKTKeywords.LengthUnit, SI.METRE);
+        final boolean  usesCommonUnits = convention.usesCommonUnits;
+        final Conversion    conversion = parseProjection(MANDATORY, element,
                 usesCommonUnits ? SI.METRE : linearUnit,
                 usesCommonUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class));
-        CoordinateSystemAxis axis0 = parseAxis(element, false, linearUnit, false);
+        CoordinateSystemAxis axis0 = parseAxis(OPTIONAL, element, false, linearUnit);
         CoordinateSystemAxis axis1 = null;
         try {
             if (axis0 != null) {
-                axis1 = parseAxis(element, false, linearUnit, true);
+                axis1 = parseAxis(MANDATORY, element, false, linearUnit);
             }
-            if (axis0 == null || isAxisIgnored) {
+            if (isAxisIgnored(axis0)) {
+                if (linearUnit == null) {
+                    throw element.missingComponent(WKTKeywords.LengthUnit);
+                }
                 axis0 = createAxis(AxisNames.EASTING,  "E", AxisDirection.EAST,  linearUnit);
                 axis1 = createAxis(AxisNames.NORTHING, "N", AxisDirection.NORTH, linearUnit);
             }
-            final Map<String,?> properties = parseAuthorityAndClose(element, name);
+            final Map<String,?> properties = parseMetadataAndClose(element, name);
             return crsFactory.createProjectedCRS(properties, geoCRS, conversion,
                     csFactory.createCartesianCS(properties, axis0, axis1));
         } catch (FactoryException exception) {
@@ -911,20 +1189,24 @@ final class GeodeticObjectParser extends
      *     COMPD_CS["<name>", <head cs>, <tail cs> {,<authority>}]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "COMPD_CS"} element as a {@link CompoundCRS} object.
      * @throws ParseException if the {@code "COMPD_CS"} element can not be parsed.
      */
-    private CompoundCRS parseCompdCS(final Element parent) throws ParseException {
-        final List<CoordinateReferenceSystem> components = new ArrayList<>(4);
-        final Element element = parent.pullElement(WKTKeywords.Compd_CS);
-        final String  name    = element.pullString("name");
+    private CompoundCRS parseCompoundCRS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.CompoundCRS, WKTKeywords.Compd_CS);
+        if (element == null) {
+            return null;
+        }
+        final String  name = element.pullString("name");
         CoordinateReferenceSystem crs;
+        final List<CoordinateReferenceSystem> components = new ArrayList<>(4);
         while ((crs = parseCoordinateReferenceSystem(element, components.size() < 2)) != null) {
             components.add(crs);
         }
         try {
-            return crsFactory.createCompoundCRS(parseAuthorityAndClose(element, name),
+            return crsFactory.createCompoundCRS(parseMetadataAndClose(element, name),
                     components.toArray(new CoordinateReferenceSystem[components.size()]));
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
@@ -939,12 +1221,16 @@ final class GeodeticObjectParser extends
      *     FITTED_CS["<name>", <to base>, <base cs>]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "FITTED_CS"} element as a {@link CompoundCRS} object.
      * @throws ParseException if the {@code "COMPD_CS"} element can not be parsed.
      */
-    private DerivedCRS parseFittedCS(final Element parent) throws ParseException {
-        final Element                   element = parent.pullElement(WKTKeywords.Fitted_CS);
+    private DerivedCRS parseFittedCS(final int mode, final Element parent) throws ParseException {
+        final Element element = parent.pullElement(mode, WKTKeywords.Fitted_CS);
+        if (element == null) {
+            return null;
+        }
         final String                    name    = element.pullString("name");
         final MathTransform             toBase  = parseMathTransform(element, true);
         final OperationMethod           method  = getOperationMethod();
@@ -970,7 +1256,7 @@ final class GeodeticObjectParser extends
                         singletonMap(IdentifiedObject.NAME_KEY, buffer.toString()),
                         number, AxisDirection.OTHER, Unit.ONE);
             }
-            final Map<String,Object> properties = parseAuthorityAndClose(element, name);
+            final Map<String,Object> properties = parseMetadataAndClose(element, name);
             final CoordinateSystem derivedCS = referencing.createAbstractCS(axes);
             /*
              * We do not know which name to give to the conversion method.

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=1686268&r1=1686267&r2=1686268&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] Thu Jun 18 16:49:25 2015
@@ -21,6 +21,7 @@ import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import javax.measure.unit.Unit;
+import javax.measure.unit.UnitFormat;
 import javax.measure.quantity.Angle;
 import javax.measure.quantity.Length;
 import org.opengis.util.FactoryException;
@@ -58,7 +59,7 @@ import static org.apache.sis.util.Argume
  *
  * @see <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
  */
-class MathTransformParser extends Parser {
+class MathTransformParser extends AbstractParser {
     /**
      * The factory to use for creating math transforms.
      */
@@ -94,7 +95,7 @@ class MathTransformParser extends Parser
      * @param mtFactory The factory to use to create {@link MathTransform} objects.
      */
     public MathTransformParser(final MathTransformFactory mtFactory) {
-        this(Symbols.getDefault(), null, null, mtFactory, null);
+        this(Symbols.getDefault(), null, null, null, mtFactory, null);
     }
 
     /**
@@ -103,13 +104,14 @@ class MathTransformParser extends Parser
      * @param symbols       The set of symbols to use.
      * @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 MathTransformFactory mtFactory, final Locale errorLocale)
+            final UnitFormat unitFormat, final MathTransformFactory mtFactory, final Locale errorLocale)
     {
-        super(symbols, numberFormat, dateFormat, errorLocale);
+        super(symbols, numberFormat, dateFormat, unitFormat, errorLocale);
         this.mtFactory = mtFactory;
         ensureNonNull("mtFactory", mtFactory);
     }
@@ -137,21 +139,17 @@ class MathTransformParser extends Parser
     final MathTransform parseMathTransform(final Element element, final boolean mandatory) throws ParseException {
         lastMethod = null;
         classification = null;
-        String keyword = WKTKeywords.Param_MT;
-        final Object child = element.peek();
-        if (child instanceof Element) {
-            keyword = ((Element) child).keyword;
-            if (keyword != null) {
-                if (keyword.equalsIgnoreCase(WKTKeywords.Param_MT))       return parseParamMT      (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Concat_MT))      return parseConcatMT     (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.Inverse_MT))     return parseInverseMT    (element);
-                if (keyword.equalsIgnoreCase(WKTKeywords.PassThrough_MT)) return parsePassThroughMT(element);
+        MathTransform tr;
+        if ((tr = parseParamMT       (element)) == null &&
+            (tr = parseConcatMT      (element)) == null &&
+            (tr = parseInverseMT     (element)) == null &&
+            (tr = parsePassThroughMT (element)) == null)
+        {
+            if (mandatory) {
+                throw element.missingOrUnknownComponent(WKTKeywords.Param_MT);
             }
         }
-        if (mandatory) {
-            throw element.keywordNotFound(keyword, keyword == WKTKeywords.Param_MT);
-        }
-        return null;
+        return tr;
     }
 
     /**
@@ -168,7 +166,7 @@ class MathTransformParser extends Parser
     {
         Element param = element;
         try {
-            while ((param = element.pullOptionalElement(WKTKeywords.Parameter)) != null) {
+            while ((param = element.pullElement(OPTIONAL, WKTKeywords.Parameter)) != null) {
                 final String                 name       = param.pullString("name");
                 final ParameterValue<?>      parameter  = parameters.parameter(name);
                 final ParameterDescriptor<?> descriptor = parameter.getDescriptor();
@@ -211,8 +209,11 @@ class MathTransformParser extends Parser
      * @return The {@code "PARAM_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "PARAM_MT"} element can not be parsed.
      */
-    final MathTransform parseParamMT(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Param_MT);
+    private MathTransform parseParamMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.Param_MT);
+        if (element == null) {
+            return null;
+        }
         classification = element.pullString("classification");
         final ParameterValueGroup parameters;
         try {
@@ -250,8 +251,11 @@ class MathTransformParser extends Parser
      * @return The {@code "INVERSE_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "INVERSE_MT"} element can not be parsed.
      */
-    final MathTransform parseInverseMT(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Inverse_MT);
+    private MathTransform parseInverseMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.Inverse_MT);
+        if (element == null) {
+            return null;
+        }
         MathTransform transform = parseMathTransform(element, true);
         try {
             transform = transform.inverse();
@@ -273,8 +277,11 @@ class MathTransformParser extends Parser
      * @return The {@code "PASSTHROUGH_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "PASSTHROUGH_MT"} element can not be parsed.
      */
-    final MathTransform parsePassThroughMT(final Element parent) throws ParseException {
-        final Element element           = parent.pullElement(WKTKeywords.PassThrough_MT);
+    private MathTransform parsePassThroughMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.PassThrough_MT);
+        if (element == null) {
+            return null;
+        }
         final int firstAffectedOrdinate = parent.pullInteger("firstAffectedOrdinate");
         final MathTransform transform   = parseMathTransform(element, true);
         element.close(ignoredElements);
@@ -292,12 +299,16 @@ class MathTransformParser extends Parser
      *     CONCAT_MT[<math transform> {,<math transform>}*]
      * }
      *
+     * @param  mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent The parent element.
      * @return The {@code "CONCAT_MT"} element as an {@link MathTransform} object.
      * @throws ParseException if the {@code "CONCAT_MT"} element can not be parsed.
      */
-    final MathTransform parseConcatMT(final Element parent) throws ParseException {
-        final Element element = parent.pullElement(WKTKeywords.Concat_MT);
+    private MathTransform parseConcatMT(final Element parent) throws ParseException {
+        final Element element = parent.pullElement(FIRST, WKTKeywords.Concat_MT);
+        if (element == null) {
+            return null;
+        }
         MathTransform transform = parseMathTransform(element, true);
         MathTransform optionalTransform;
         while ((optionalTransform = parseMathTransform(element, false)) != null) {
@@ -313,7 +324,7 @@ class MathTransformParser extends Parser
 
     /**
      * Returns the operation method for the last math transform parsed. This is used by
-     * {@link Parser} in order to built {@link org.opengis.referencing.crs.DerivedCRS}.
+     * {@link GeodeticObjectParser} in order to built {@link org.opengis.referencing.crs.DerivedCRS}.
      */
     final OperationMethod getOperationMethod() {
         if (lastMethod == null) {



Mime
View raw message