You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2015/06/18 19:38:17 UTC

svn commit: r1686282 [1/4] - in /sis/trunk: ./ 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/main/ja...

Author: desruisseaux
Date: Thu Jun 18 17:38:16 2015
New Revision: 1686282

URL: http://svn.apache.org/r1686282
Log:
Merge from the JDK6 branch Feature bug fixes and partial WKT 2 work.

Added:
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
      - copied unchanged from r1686280, sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/AbstractParser.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java
      - copied unchanged from r1686280, sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Parser.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/VerticalInfo.java
      - copied unchanged from r1686280, sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/VerticalInfo.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
      - copied unchanged from r1686280, sis/branches/JDK6/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
Removed:
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTParser.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
    sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
    sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Convention.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/package-info.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicDescription.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ElementTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
    sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/AbstractReferenceSystemTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultCompoundCRSTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultTemporalCRSTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/DefaultGeodeticDatumTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/DefaultPrimeMeridianTest.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
    sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Jun 18 17:38:16 2015
@@ -1,4 +1,4 @@
 /sis/branches/Android:1430670-1480699
-/sis/branches/JDK6:1394364-1684689
-/sis/branches/JDK7:1394913-1684688
-/sis/branches/JDK8:1584960-1684685
+/sis/branches/JDK6:1394364-1686280
+/sis/branches/JDK7:1394913-1686269
+/sis/branches/JDK8:1584960-1686266

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -41,7 +41,7 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  *
  * @see DefaultAssociationRole#newInstance()
@@ -134,7 +134,7 @@ public abstract class AbstractAssociatio
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract AbstractFeature getValue();
+    public abstract AbstractFeature getValue() throws IllegalStateException;
 
     /**
      * Returns all features, or an empty collection if none.

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -64,7 +64,7 @@ import org.apache.sis.internal.jdk7.JDK7
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  *
  * @see DefaultAttributeType#newInstance()

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -124,7 +124,7 @@ public abstract class AbstractFeature im
      *
      * @param  name The property name.
      * @return The property of the given name (never {@code null}).
-     * @throws IllegalArgumentException If the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see #getPropertyValue(String)
      * @see DefaultFeatureType#getProperty(String)
@@ -155,8 +155,9 @@ public abstract class AbstractFeature im
      * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
      *
      * @param  property The property to set.
-     * @throws IllegalArgumentException if the type of the given property is not one of the types
-     *         known to this feature, or if the property can not be set of an other reason.
+     * @throws IllegalArgumentException if the name of the given property is not a property name of this feature.
+     * @throws IllegalArgumentException if the value of the given property is not valid.
+     * @throws IllegalArgumentException if the property can not be set for another reason.
      *
      * @see #setPropertyValue(String, Object)
      */
@@ -188,10 +189,9 @@ public abstract class AbstractFeature im
      *
      * @param  name The name of the property to create.
      * @return A {@code Property} of the given name.
-     * @throws IllegalArgumentException If the given argument is not the name of an attribute or
+     * @throws IllegalArgumentException if the given argument is not the name of an attribute or
      *         feature association of this feature.
      */
-    @SuppressWarnings({"unchecked","rawtypes"})
     final Property createProperty(final String name) throws IllegalArgumentException {
         final AbstractIdentifiedType pt = type.getProperty(name);
         if (pt instanceof DefaultAttributeType<?>) {
@@ -257,7 +257,7 @@ public abstract class AbstractFeature im
      *
      * @param  name The name of the property for which to get the default value.
      * @return The default value for the {@code Property} of the given name.
-     * @throws IllegalArgumentException If the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     final Object getDefaultValue(final String name) throws IllegalArgumentException {
         final AbstractIdentifiedType pt = type.getProperty(name);
@@ -303,7 +303,7 @@ public abstract class AbstractFeature im
      *
      * @param  name The property name.
      * @return The value for the given property, or {@code null} if none.
-     * @throws IllegalArgumentException If the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      *
      * @see AbstractAttribute#getValue()
      */
@@ -320,8 +320,9 @@ public abstract class AbstractFeature im
      *
      * @param  name  The attribute name.
      * @param  value The new value for the given attribute (may be {@code null}).
-     * @throws ClassCastException If the value is not assignable to the expected value class.
-     * @throws IllegalArgumentException If the given value can not be assigned for an other reason.
+     * @throws IllegalArgumentException if the given name is not an attribute or association name of this feature.
+     * @throws ClassCastException if the value is not assignable to the expected value class.
+     * @throws IllegalArgumentException if the given value is not valid for a reason other than its type.
      *
      * @see AbstractAttribute#setValue(Object)
      */
@@ -449,9 +450,11 @@ public abstract class AbstractFeature im
             throw illegalPropertyType(base.getName(), property.getClass());
         }
         if (pt != base) {
-            throw new IllegalArgumentException(base == null
-                    ? Errors.format(Errors.Keys.PropertyNotFound_2, getName(), name)
-                    : Errors.format(Errors.Keys.MismatchedPropertyType_1, name));
+            if (base == null) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.PropertyNotFound_2, getName(), name));
+            } else {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedPropertyType_1, name));
+            }
         }
     }
 
@@ -560,9 +563,9 @@ public abstract class AbstractFeature im
     }
 
     /**
-     * Returns the exception for a property type which neither an attribute or an association.
+     * Returns the exception for a property type which is neither an attribute or an association.
      * This method is invoked after a {@link PropertyType} has been found for the user-supplied name,
-     * but that property can not be stored in a {@link Property} instance.
+     * but that property can not be stored in or extracted from a {@link Property} instance.
      */
     static IllegalArgumentException unsupportedPropertyType(final GenericName name) {
         return new IllegalArgumentException(Errors.format(Errors.Keys.CanNotInstantiate_1, name));

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -42,7 +42,7 @@ import org.apache.sis.internal.jdk7.Obje
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 public class AbstractIdentifiedType implements Serializable {
@@ -169,9 +169,7 @@ public class AbstractIdentifiedType impl
      * @param  identification The name and other information to be given to this identified type.
      * @throws IllegalArgumentException if a property has an invalid value.
      */
-    protected AbstractIdentifiedType(final Map<String,?> identification)
-            throws IllegalArgumentException
-    {
+    protected AbstractIdentifiedType(final Map<String,?> identification) throws IllegalArgumentException {
         ensureNonNull("identification", identification);
         Object value = identification.get(NAME_KEY);
         if (value == null) {

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -31,7 +31,7 @@ import org.apache.sis.internal.util.Abst
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> implements Cloneable {

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -79,7 +79,7 @@ final class DenseFeature extends Abstrac
      * @param  name The property name.
      * @return The index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws IllegalArgumentException If the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     private int getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
@@ -127,7 +127,7 @@ final class DenseFeature extends Abstrac
      *
      * @param  property The property to set.
      * @throws IllegalArgumentException if the type of the given property is not one of the types
-     *         known to this feature.
+     *         known to this feature, or if the property can not be set or another reason.
      */
     @Override
     public void setProperty(final Object property) throws IllegalArgumentException {
@@ -202,7 +202,7 @@ final class DenseFeature extends Abstrac
      * @param  name  The attribute name.
      * @param  value The new value for the given attribute (may be {@code null}).
      * @throws ClassCastException If the value is not assignable to the expected value class.
-     * @throws IllegalArgumentException If the given value can not be assigned for an other reason.
+     * @throws IllegalArgumentException If the given value can not be assigned for another reason.
      */
     @Override
     public void setPropertyValue(final String name, Object value) throws IllegalArgumentException {
@@ -285,12 +285,36 @@ final class DenseFeature extends Abstrac
 
     /**
      * Returns a hash code value for this feature.
+     * This implementation computes the hash code using only the property values, not the {@code Property} instances,
+     * in order to keep the hash code value stable before and after the {@code properties} array is promoted from the
+     * {@code Object[]} type to the {@code Property[]} type.
      *
      * @return A hash code value.
      */
     @Override
     public int hashCode() {
-        return type.hashCode() + 37 * Arrays.hashCode(properties);
+        int code = 1;
+        if (properties != null) {
+            if (properties instanceof Property[]) {
+                for (final Property p : (Property[]) properties) {
+                    code = 31 * code;
+                    final Object value;
+                    if (p instanceof AbstractAttribute<?>) {
+                        value = getAttributeValue((AbstractAttribute<?>) p);
+                    } else if (p instanceof AbstractAssociation) {
+                        value = getAssociationValue((AbstractAssociation) p);
+                    } else {
+                        continue;
+                    }
+                    if (value != null) {
+                        code += value.hashCode();
+                    }
+                }
+            } else {
+                code = Arrays.hashCode(properties);
+            }
+        }
+        return type.hashCode() + code;
     }
 
     /**
@@ -305,7 +329,17 @@ final class DenseFeature extends Abstrac
         }
         if (obj instanceof DenseFeature) {
             final DenseFeature that = (DenseFeature) obj;
-            return type.equals(that.type) && Arrays.equals(properties, that.properties);
+            if (type.equals(that.type)) {
+                final boolean asProperties = (properties instanceof Property[]);
+                if (asProperties != (that.properties instanceof Property[])) {
+                    if (asProperties) {
+                        that.wrapValuesInProperties();
+                    } else {
+                        wrapValuesInProperties();
+                    }
+                }
+                return Arrays.equals(properties, that.properties);
+            }
         }
         return false;
     }

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.Locale;
 import java.util.TimeZone;
 import java.io.IOException;
@@ -60,7 +63,7 @@ import org.apache.sis.referencing.Identi
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 public class FeatureFormat extends TabularFormat<Object> {
@@ -267,19 +270,28 @@ header: for (int i=0; ; i++) {
              * Column 3 - Value or default value.
              */
             if (value != null) {
-                final Format format = getFormat(valueClass);
-                if (format != null) {
-                    value = format.format(value, buffer, dummyFP);
-                } else if (value instanceof AbstractFeature && propertyType instanceof DefaultAssociationRole) {
-                    final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
-                    if (p != null) {
-                        value = ((AbstractFeature) value).getPropertyValue(p);
+                final boolean isInstance = valueClass != null && valueClass.isInstance(value);
+                final Format format = isInstance ? getFormat(valueClass) : null;
+                final Iterator<?> it = (!isInstance && (value instanceof Collection<?>)
+                        ? (Collection<?>) value : Collections.singleton(value)).iterator();
+                String separator = "";
+                while (it.hasNext()) {
+                    value = it.next();
+                    if (value != null) {
+                        if (format != null) {
+                            value = format.format(value, buffer, dummyFP);
+                        } else if (value instanceof AbstractFeature && propertyType instanceof DefaultAssociationRole) {
+                            final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
+                            if (p != null) {
+                                value = ((AbstractFeature) value).getPropertyValue(p);
+                                if (value == null) continue;
+                            }
+                        }
+                        table.append(separator).append(formatValue(value));
+                        buffer.setLength(0);
+                        separator = ", ";
                     }
                 }
-                if (value != null) {
-                    table.append(formatValue(value));
-                }
-                buffer.setLength(0);
             }
             /*
              * Column 4 - Characteristics.

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -28,7 +28,7 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 abstract class Field<V> extends Property {

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -39,7 +39,7 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  *
  * @see DefaultAssociationRole

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -42,7 +42,7 @@ import org.apache.sis.util.resources.Err
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  *
  * @see DefaultAttributeType
@@ -86,10 +86,13 @@ final class MultiValuedAttribute<V> exte
         final Class<V> valueClass = type.getValueClass();
         if (values == null) {
             this.values = new CheckedArrayList<V>(valueClass);
-        } else if (((CheckedArrayList<?>) values).getElementType() == valueClass) {
-            this.values = (CheckedArrayList<V>) values;
         } else {
-            throw new ClassCastException();
+            final Class<?> actual = ((CheckedArrayList<?>) values).getElementType();
+            if (actual == valueClass) {
+                this.values = (CheckedArrayList<V>) values;
+            } else {
+                throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3, "values", valueClass, actual));
+            }
         }
     }
 

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -27,7 +27,7 @@ import org.opengis.util.GenericName;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 final class NamedFeatureType implements FeatureType, Serializable {

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -34,7 +34,7 @@ import org.apache.sis.internal.jdk7.Obje
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  *
  * @see DefaultAssociationRole
@@ -92,7 +92,7 @@ final class SingletonAssociation extends
      * @throws IllegalArgumentException If the given feature is not valid for this association.
      */
     @Override
-    public void setValue(final AbstractFeature value) {
+    public void setValue(final AbstractFeature value) throws IllegalArgumentException {
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
         }

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -26,6 +26,9 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.CorruptedObjectException;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk7.Objects;
+
 
 /**
  * A feature in which only a small fraction of properties are expected to be provided. This implementation uses
@@ -197,7 +200,7 @@ final class SparseFeature extends Abstra
      *
      * @param  property The property to set.
      * @throws IllegalArgumentException if the type of the given property is not one of the types
-     *         known to this feature.
+     *         known to this feature, or if the property can not be set for another reason.
      */
     @Override
     public void setProperty(final Object property) throws IllegalArgumentException {
@@ -252,7 +255,7 @@ final class SparseFeature extends Abstra
      * @param  name  The attribute name.
      * @param  value The new value for the given attribute (may be {@code null}).
      * @throws ClassCastException If the value is not assignable to the expected value class.
-     * @throws IllegalArgumentException If the given value can not be assigned for an other reason.
+     * @throws IllegalArgumentException If the given value can not be assigned for another reason.
      */
     @Override
     public void setPropertyValue(final String name, final Object value) throws IllegalArgumentException {
@@ -355,12 +358,32 @@ final class SparseFeature extends Abstra
 
     /**
      * Returns a hash code value for this feature.
+     * This implementation computes the hash code using only the property values, not the {@code Property} instances,
+     * in order to keep the hash code value stable before and after the {@code properties} map is (conceptually)
+     * promoted from the {@code Map<Integer,Object>} type to the {@code Map<Integer,Property>} type.
      *
      * @return A hash code value.
      */
     @Override
     public int hashCode() {
-        return type.hashCode() + 37 * properties.hashCode();
+        int code = type.hashCode() * 37;
+        if (valuesKind == PROPERTIES) {
+            for (final Map.Entry<Integer,Object> entry : properties.entrySet()) {
+                final Object p = entry.getValue();
+                final Object value;
+                if (p instanceof AbstractAttribute<?>) {
+                    value = getAttributeValue((AbstractAttribute<?>) p);
+                } else if (p instanceof AbstractAssociation) {
+                    value = getAssociationValue((AbstractAssociation) p);
+                } else {
+                    value = null;
+                }
+                code += Objects.hashCode(entry.getKey()) ^ Objects.hashCode(value);
+            }
+        } else {
+            code += properties.hashCode();
+        }
+        return code;
     }
 
     /**
@@ -375,7 +398,17 @@ final class SparseFeature extends Abstra
         }
         if (obj instanceof SparseFeature) {
             final SparseFeature that = (SparseFeature) obj;
-            return type.equals(that.type) && properties.equals(that.properties);
+            if (type.equals(that.type)) {
+                final boolean asProperties = (valuesKind == PROPERTIES);
+                if (asProperties != (that.valuesKind == PROPERTIES)) {
+                    if (asProperties) {
+                        that.requireMapOfProperties();
+                    } else {
+                        requireMapOfProperties();
+                    }
+                }
+                return properties.equals(that.properties);
+            }
         }
         return false;
     }

Modified: sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Arrays;
 import java.util.Locale;
 import java.util.Random;
 import org.apache.sis.test.DependsOn;
@@ -31,7 +32,7 @@ import static org.apache.sis.test.Assert
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 @DependsOn({
@@ -66,7 +67,47 @@ public final strictfp class FeatureForma
     @Test
     public void testFeature() {
         final Random random = TestUtilities.createRandomNumberGenerator();
-        final AbstractFeature feature = FeatureTestCase.twinTown(random.nextBoolean());
+        final boolean isSparse = random.nextBoolean();
+
+        final DefaultFeatureType type = DefaultFeatureTypeTest.worldMetropolis();
+        final AbstractFeature feature = isSparse ? new SparseFeature(type) : new DenseFeature(type);
+        feature.setPropertyValue("city", "Tokyo");
+        feature.setPropertyValue("population", 13185502); // In 2011.
+        feature.setPropertyValue("universities", Arrays.asList("Waseda", "Keio"));
+
+        final FeatureFormat format = new FeatureFormat(Locale.US, null);
+        final String text = format.format(feature);
+        assertMultilinesEquals("World metropolis\n" +
+                "┌──────────────┬─────────────────────┬─────────────┬──────────────┬─────────────────┐\n" +
+                "│ Name         │ Type                │ Cardinality │ Value        │ Characteristics │\n" +
+                "├──────────────┼─────────────────────┼─────────────┼──────────────┼─────────────────┤\n" +
+                "│ city         │ String              │ [1 … 1]     │ Tokyo        │                 │\n" +
+                "│ population   │ Integer             │ [1 … 1]     │ 13,185,502   │                 │\n" +
+                "│ region       │ InternationalString │ [1 … 1]     │              │                 │\n" +
+                "│ isGlobal     │ Boolean             │ [1 … 1]     │              │                 │\n" +
+                "│ universities │ String              │ [0 … ∞]     │ Waseda, Keio │                 │\n" +
+                "│ temperature  │ Float               │ [1 … 1]     │              │ accuracy, units │\n" +
+                "└──────────────┴─────────────────────┴─────────────┴──────────────┴─────────────────┘\n", text);
+    }
+
+    /**
+     * Tests the formatting of an {@link AbstractFeature} with an association to another feature of the same type.
+     */
+    @Test
+    public void testFeatureWithAssociation() {
+        final Random random = TestUtilities.createRandomNumberGenerator();
+        final boolean isSparse = random.nextBoolean();
+
+        final DefaultFeatureType type = DefaultAssociationRoleTest.twinTownCity(false);
+        final AbstractFeature twinTown = isSparse ? new SparseFeature(type) : new DenseFeature(type);
+        twinTown.setPropertyValue("city", "Le Mans");
+        twinTown.setPropertyValue("population", 143240); // In 2011.
+
+        final AbstractFeature feature = isSparse ? new SparseFeature(type) : new DenseFeature(type);
+        feature.setPropertyValue("city", "Paderborn");
+        feature.setPropertyValue("population", 143174); // December 31th, 2011
+        feature.setPropertyValue("twin town", twinTown);
+
         final FeatureFormat format = new FeatureFormat(Locale.US, null);
         final String text = format.format(feature);
         assertMultilinesEquals("Twin town\n" +

Modified: sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -38,7 +38,7 @@ import static org.apache.sis.test.Assert
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Marc le Bihan
  * @since   0.5
- * @version 0.5
+ * @version 0.6
  * @module
  */
 public abstract strictfp class FeatureTestCase extends TestCase {
@@ -60,23 +60,6 @@ public abstract strictfp class FeatureTe
     }
 
     /**
-     * Creates a feature for twin towns.
-     */
-    static AbstractFeature twinTown(final boolean isSparse) {
-        final DefaultFeatureType twinTown = DefaultAssociationRoleTest.twinTownCity(false);
-
-        final AbstractFeature leMans = isSparse ? new SparseFeature(twinTown) : new DenseFeature(twinTown);
-        leMans.setPropertyValue("city", "Le Mans");
-        leMans.setPropertyValue("population", 143240); // In 2011.
-
-        final AbstractFeature paderborn = isSparse ? new SparseFeature(twinTown) : new DenseFeature(twinTown);
-        paderborn.setPropertyValue("city", "Paderborn");
-        paderborn.setPropertyValue("population", 143174); // December 31th, 2011
-        paderborn.setPropertyValue("twin town", leMans);
-        return paderborn;
-    }
-
-    /**
      * Creates a new feature for the given type.
      */
     abstract AbstractFeature createFeature(final DefaultFeatureType type);
@@ -370,4 +353,31 @@ public abstract strictfp class FeatureTe
     private void testSerialization() {
         assertNotSame(feature, assertSerializedEquals(feature));
     }
+
+    /**
+     * Tests {@code equals(Object)}.
+     *
+     * @throws CloneNotSupportedException Should never happen.
+     */
+    @Test
+    @DependsOnMethod("testSimpleProperties")
+    public void testEquals() throws CloneNotSupportedException {
+        feature = createFeature(DefaultFeatureTypeTest.city());
+        feature.setPropertyValue("city", "Tokyo");
+        final AbstractFeature clone = cloneFeature();
+        assertEquals(feature, clone);
+        /*
+         * Force the conversion of a property value into a full Property object on one and only one of
+         * the Features to be compared. The implementation shall be able to wrap or unwrap the values.
+         */
+        assertEquals("Tokyo", ((AbstractAttribute) clone.getProperty("city")).getValue());
+        assertEquals("hashCode", feature.hashCode(), clone.hashCode());
+        assertEquals("equals", feature, clone);
+        /*
+         * For the other Feature instance to contain full Property object and test again.
+         */
+        assertEquals("Tokyo", ((AbstractAttribute) feature.getProperty("city")).getValue());
+        assertEquals("hashCode", feature.hashCode(), clone.hashCode());
+        assertEquals("equals", feature, clone);
+    }
 }

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ReferencingServices.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -25,6 +25,7 @@ import org.opengis.referencing.Identifie
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.DerivedCRS;
+import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -310,6 +311,17 @@ public class ReferencingServices extends
     ///////////////////////////////////////////////////////////////////////////////////////
 
     /**
+     * Returns a coordinate reference system for heights above the mean seal level.
+     *
+     * @return The "Mean Seal Level (MSL) height" coordinate reference system, or {@code null}.
+     *
+     * @since 0.6
+     */
+    public VerticalCRS getMSLH() {
+        throw moduleNotFound();
+    }
+
+    /**
      * Returns the Greenwich prime meridian.
      *
      * @return The Greenwich prime meridian.

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/WKTKeywords.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -60,7 +60,18 @@ public final class WKTKeywords extends S
             Anchor    = "Anchor",
             Scope     = "Scope",
             Area      = "Area",
-            Remarks   = "Remarks";
+            Remark    = "Remark";
+
+    /**
+     * Related to unit of measurements.
+     */
+    public static final String
+            Unit           = "Unit",
+            LengthUnit     = "LengthUnit",
+            AngleUnit      = "AngleUnit",
+            ScaleUnit      = "ScaleUnit",
+            TimeUnit       = "TimeUnit",
+            ParametricUnit = "ParametricUnit";
 
     /**
      * Related to {@link org.apache.sis.referencing.cs.AbstractCS}
@@ -86,6 +97,7 @@ public final class WKTKeywords extends S
             Datum       = "Datum",
             GeodeticCRS = "GeodeticCRS",
             BaseGeodCRS = "BaseGeodCRS",
+            GeodCRS     = "GeodCRS",
             GeogCS      = "GeogCS",
             GeocCS      = "GeocCS";
 

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Convention.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Convention.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Convention.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Convention.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -38,15 +38,19 @@ import org.apache.sis.metadata.iso.citat
  *       according the OGC 01-009 (<cite>Coordinate transformation services</cite>) specification.</li>
  *   <li>An older specification — <cite>Simple Features</cite> — was unclear on this matter and has been
  *       interpreted by many softwares as fixing the unit to decimal degrees.</li>
+ *   <li>Some softwares support only (<var>longitude</var>, <var>latitude</var>) axis order
+ *       and ignore completely all {@code AXIS[…]} elements in the WKT.</li>
  * </ul>
  *
  * Despite the first interpretation being specified by both OGC 01-009 and ISO 19162 standards, the second
  * interpretation appears to be in wide use for WKT 1. Apache SIS uses the standard interpretation by default,
  * but the {@link #WKT1_COMMON_UNITS} enumeration allows parsing and formatting using the older interpretation.
+ * The {@link #WKT1_IGNORE_AXES} enumeration mimics the most minimalist WKT 1 parsers,
+ * but should be avoided when not imposed by compatibility reasons.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.6
  * @module
  *
  * @see WKTFormat#getConvention()
@@ -66,7 +70,7 @@ public enum Convention {
      *
      * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html">WKT 2 specification</a>
      */
-    WKT2(false),
+    WKT2(false, false),
 
     /**
      * The ISO 19162 format with omission of some optional elements. This convention is identical
@@ -91,7 +95,7 @@ public enum Convention {
      *
      * <p>This is the default convention used by {@link FormattableObject#toString()}.</p>
      */
-    WKT2_SIMPLIFIED(false),
+    WKT2_SIMPLIFIED(false, false),
 
     /**
      * The OGC 01-009 format, also known as “WKT 1”.
@@ -103,12 +107,14 @@ public enum Convention {
      * names when available.</p>
      *
      * <div class="section">Differences compared to WKT 2</div>
-     * WKT 1 and WKT 2 differ in their keywords and syntax, but also in more subtle ways regarding parameter
-     * and code list values. For {@link GeocentricCRS}, WKT 1 uses a legacy set of Cartesian axes which were
-     * defined in OGC 01-009. Those axes use the <var>Other</var>, <var>Easting</var> and <var>Northing</var>
-     * {@linkplain org.opengis.referencing.cs.AxisDirection axis directions} instead than the geocentric ones,
-     * as shown in the following table:
+     * WKT 1 and WKT 2 differ in their keywords and syntax, but also in more subtle ways regarding axis names,
+     * parameter and code list values. For example in  {@link GeocentricCRS}, WKT 1 uses a legacy set of Cartesian axes
+     * which were defined in OGC 01-009. Those axes use the <var>Other</var>, <var>Easting</var> and <var>Northing</var>
+     * {@linkplain org.opengis.referencing.cs.AxisDirection axis directions} instead than the geocentric ones.
+     * For more uniform handling of CRS objects in client code, SIS parser replaces some WKT 1 conventions by
+     * the ISO ones when possible.
      *
+     * <table class="compact" summary="Differences between current and legacy specifications"><tr><td>
      * <table class="sis">
      *   <caption>Geocentric axis directions</caption>
      *   <tr><th>ISO 19111</th>    <th>OGC 01-009</th> <th>Description</th></tr>
@@ -116,10 +122,20 @@ public enum Convention {
      *   <tr><td>Geocentric Y</td> <td>Easting</td>    <td>Toward 90°E longitude</td></tr>
      *   <tr><td>Geocentric Z</td> <td>Northing</td>   <td>Toward north pole</td></tr>
      * </table>
+     * </td><td>
+     * <table class="sis">
+     *   <caption>Coordinate system axis names</caption>
+     *   <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><var>H</var></td>                             <td><var>H</var> or <var>h</var></td></tr>
+     *   <tr><td>Projected</td>  <td><var>X</var>, <var>Y</var></td>               <td><var>E</var>, <var>N</var></td></tr>
+     *   <tr><td>Geocentric</td> <td><var>X</var>, <var>Y</var>, <var>Z</var></td> <td><var>X</var>, <var>Y</var>, <var>Z</var></td></tr>
+     * </table>
+     * </td></tr></table>
      *
      * @see <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">Legacy WKT 1</a>
      */
-    WKT1(true),
+    WKT1(true, false),
 
     /**
      * The <cite>Simple Feature</cite> format, also known as “WKT 1”.
@@ -136,7 +152,24 @@ public enum Convention {
      *       (e.g. <cite>"meter"</cite> instead than <cite>"metre"</cite>).</li>
      * </ul>
      */
-    WKT1_COMMON_UNITS(true),
+    WKT1_COMMON_UNITS(true, true),
+
+    /**
+     * The <cite>Simple Feature</cite> format without parsing of axis elements.
+     * This convention is identical to {@link #WKT1_COMMON_UNITS} except that all {@code AXIS[…]} elements are ignored.
+     * Since the WKT 1 specification said that the default axis order shall be (<var>x</var>,<var>y</var>) or
+     * (<var>longitude</var>, <var>latitude</var>), ignoring {@code AXIS[…]} elements is equivalent to forcing
+     * the coordinate systems to that default order.
+     *
+     * <p>Note that {@code AXIS[…]} elements still need to be well formed even when parsing a text with this convention.
+     * Malformed axis elements will continue to cause a {@link java.text.ParseException} despite their content being ignored.</p>
+     *
+     * <p>This convention may be useful for compatibility with some other softwares that do not handle axis order correctly.
+     * But except when imposed by such compatibility reasons, this convention should be avoided as much as possible.</p>
+     *
+     * @since 0.6
+     */
+    WKT1_IGNORE_AXES(true, true),
 
     /**
      * A special convention for formatting objects as stored internally by Apache SIS.
@@ -167,7 +200,7 @@ public enum Convention {
      * @see org.apache.sis.referencing.operation.projection.NormalizedProjection#getParameterValues()
      */
     @Debug
-    INTERNAL(false);
+    INTERNAL(false, false);
 
     /**
      * The default conventions.
@@ -180,10 +213,24 @@ public enum Convention {
     private final boolean isWKT1;
 
     /**
+     * {@code true} for a frequently-used convention about units instead than the standard one.
+     * <ul>
+     *   <li>If {@code true}, forces {@code PRIMEM} and {@code PARAMETER} angular units to degrees
+     *       instead than inferring the unit from the context. The standard value is {@code false},
+     *       which means that the angular units are inferred from the context as required by the
+     *       WKT 1 specification.</li>
+     *   <li>If {@code true}, uses US unit names instead of the international names.
+     *       For example Americans said {@code "meter"} instead of {@code "metre"}.</li>
+     * </ul>
+     */
+    final boolean usesCommonUnits;
+
+    /**
      * Creates a new enumeration value.
      */
-    private Convention(final boolean isWKT1) {
+    private Convention(final boolean isWKT1, final boolean usesCommonUnits) {
         this.isWKT1 = isWKT1;
+        this.usesCommonUnits = usesCommonUnits;
     }
 
     /**
@@ -217,21 +264,6 @@ public enum Convention {
     }
 
     /**
-     * {@code true} for a frequently-used convention about units instead than the standard one.
-     * <ul>
-     *   <li>If {@code true}, forces {@code PRIMEM} and {@code PARAMETER} angular units to degrees
-     *       instead than inferring the unit from the context. The standard value is {@code false},
-     *       which means that the angular units are inferred from the context as required by the
-     *       WKT 1 specification.</li>
-     *   <li>If {@code true}, uses US unit names instead of the international names.
-     *       For example Americans said {@code "meter"} instead of {@code "metre"}.</li>
-     * </ul>
-     */
-    final boolean usesCommonUnits() {
-        return this == WKT1_COMMON_UNITS;
-    }
-
-    /**
      * Returns the default authority to look for when fetching identified object names and identifiers.
      * The difference between various authorities are most easily seen in projection and parameter names.
      * The value returned by this method can be overwritten by {@link WKTFormat#setNameAuthority(Citation)}.

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Element.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -80,6 +80,14 @@ final class Element {
     final int offset;
 
     /**
+     * Index of the keyword in the array given to the {@link #pullElement(String...)}
+     * or {@link #pullOptionalElement(String...)} method.
+     *
+     * @see #getKeywordIndex()
+     */
+    private byte keywordIndex;
+
+    /**
      * Keyword of this entity. For example: {@code "PrimeMeridian"}.
      */
     public final String keyword;
@@ -116,7 +124,7 @@ final class Element {
      * @param position On input, the position where to start parsing from.
      *                 On output, the first character after the separator.
      */
-    Element(final Parser parser, final String text, final ParsePosition position) throws ParseException {
+    Element(final AbstractParser parser, final String text, final ParsePosition position) throws ParseException {
         /*
          * Find the first keyword in the specified string. If a keyword is found, then
          * the position is set to the index of the first character after the keyword.
@@ -284,20 +292,6 @@ final class Element {
     ////////////////////////////////////////////////////////////////////////////////////////
 
     /**
-     * Returns a {@link ParseException} for a child keyword which is either missing or unknown.
-     *
-     * @param  child The missing or unknown child keyword.
-     * @return {@code true} if the given keyword is missing, or {@code false} if it is unknown.
-     * @return The exception to be thrown.
-     */
-    final ParseException keywordNotFound(final String child, final boolean missing) {
-        return new LocalizedParseException(locale,
-                missing ? Errors.Keys.MissingComponentInElement_2
-                        : Errors.Keys.UnknownKeywordInElement_2,
-                new String[] {keyword, child}, offset);
-    }
-
-    /**
      * Returns a {@link ParseException} with the specified cause. A localized string
      * <code>"Error in &lt;{@link #keyword}&gt;"</code> will be prepend to the message.
      * The error index will be the starting index of this {@code Element}.
@@ -348,11 +342,11 @@ final class Element {
     }
 
     /**
-     * Returns an exception saying that a component is missing.
+     * Returns an exception saying that a sub-element is missing.
      *
-     * @param key The name of the missing component.
+     * @param key The name of the missing sub-element.
      */
-    private ParseException missingParameter(final String key) {
+    final ParseException missingComponent(final String key) {
         int error = offset;
         if (keyword != null) {
             error += keyword.length();
@@ -361,6 +355,34 @@ final class Element {
                 new String[] {keyword, key}, error);
     }
 
+    /**
+     * Returns a {@link ParseException} for a child keyword which is unknown.
+     *
+     * @param  expected Keyword of a typical element. Used only if this element contains no child element.
+     * @return The exception to be thrown.
+     */
+    final ParseException missingOrUnknownComponent(final String expected) {
+        String name = null;
+        for (final Object child : list) {
+            if (child instanceof Element) {
+                name = ((Element) child).keyword;
+                if (name != null) {
+                    break;
+                }
+            }
+        }
+        final short res;
+        final String[] args;
+        if (name != null) {
+            res  = Errors.Keys.UnknownKeyword_1;
+            args = new String[] {name};
+        } else {
+            res  = Errors.Keys.MissingComponentInElement_2;
+            args = new String[] {keyword, expected};
+        }
+        return new LocalizedParseException(locale, res, args, offset);
+    }
+
 
 
 
@@ -386,7 +408,7 @@ final class Element {
                 return (Date) object;
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
@@ -405,7 +427,7 @@ final class Element {
                 return ((Number) object).doubleValue();
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
@@ -429,7 +451,7 @@ final class Element {
                 return number.intValue();
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
@@ -448,7 +470,7 @@ final class Element {
                 return (Boolean) object;
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
@@ -467,7 +489,7 @@ final class Element {
                 return (String) object;
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
@@ -486,43 +508,51 @@ final class Element {
                 return object;
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
-     * Removes the next {@link Element} from the list and returns it.
+     * Removes the next {@link Element} of the given name from the list and returns it.
+     * If the element was mandatory but is missing, then the first entry in the given {@code keys}
+     * array will be taken as the name of the missing element to report in the exception message.
      *
-     * @param  key The element name (e.g. {@code "PrimeMeridian"}).
-     * @return The next {@link Element} on the list.
-     * @throws ParseException if no more element is available.
-     */
-    public Element pullElement(final String key) throws ParseException {
-        final Element element = pullOptionalElement(key);
-        if (element != null) {
-            return element;
-        }
-        throw missingParameter(key);
-    }
-
-    /**
-     * Removes the next {@link Element} from the list and returns it.
+     * <p>The given {@code mode} argument can be one of the following constants:</p>
+     * <ul>
+     *   <li>{@link AbstractParser#MANDATORY} throw an exception if no matching element is found.</li>
+     *   <li>{@link AbstractParser#OPTIONAL} return {@code null} if no matching element is found.</li>
+     *   <li>{@link AbstractParser#FIRST} return {@code null} if the first element (ignoring all others)
+     *       does not match.</li>
+     * </ul>
      *
-     * @param  key The element name (e.g. {@code "PrimeMeridian"}).
-     * @return The next {@link Element} on the list, or {@code null} if no more element is available.
+     * @param  mode {@link AbstractParser#FIRST}, {@link AbstractParser#OPTIONAL} or {@link AbstractParser#MANDATORY}.
+     * @param  keys The element names (e.g. {@code "PrimeMeridian"}).
+     * @return The next {@link Element} of the given names found on the list, or {@code null} if none.
+     * @throws ParseException if {@code mode} is {@code MANDATORY} and no element of the given names was found.
      */
-    public Element pullOptionalElement(final String key) {
+    public Element pullElement(final int mode, final String... keys) throws ParseException {
         final Iterator<Object> iterator = list.iterator();
         while (iterator.hasNext()) {
             final Object object = iterator.next();
             if (object instanceof Element) {
                 final Element element = (Element) object;
-                if (element.list != null && key.equalsIgnoreCase(element.keyword)) {
-                    iterator.remove();
-                    return element;
+                if (element.list != null) {
+                    for (int i=0; i<keys.length; i++) {
+                        if (element.keyword.equalsIgnoreCase(keys[i])) {
+                            keywordIndex = (byte) i;
+                            iterator.remove();
+                            return element;
+                        }
+                    }
+                    if (mode == AbstractParser.FIRST) {
+                        return null;
+                    }
                 }
             }
         }
-        return null;
+        if (mode != AbstractParser.MANDATORY) {
+            return null;
+        }
+        throw missingComponent(keys[0]);
     }
 
     /**
@@ -545,17 +575,43 @@ final class Element {
                 }
             }
         }
-        throw missingParameter(key);
+        throw missingComponent(key);
     }
 
     /**
-     * Returns the next element, or {@code null} if there is no more element.
-     * The element is <strong>not</strong> removed from the list.
+     * Removes the next object of the given type from the list and returns it, if presents.
      *
-     * @return The next element, or {@code null} if there is no more elements.
+     * @param  type The object type.
+     * @return The next object on the list, or {@code null} if none.
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T pullOptional(final Class<T> type) {
+        final Iterator<Object> iterator = list.iterator();
+        while (iterator.hasNext()) {
+            final Object object = iterator.next();
+            if (type.isInstance(object)) {
+                iterator.remove();
+                return (T) object;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if this element does not contains any remaining child.
+     *
+     * @return {@code true} if there is no child remaining.
+     */
+    public boolean isEmpty() {
+        return list.isEmpty();
+    }
+
+    /**
+     * Returns the index of the keyword in the array given to the {@link #pullElement(String...)}
+     * or {@link #pullOptionalElement(String...)} method.
      */
-    public Object peek() {
-        return list.isEmpty() ? null : list.getFirst();
+    final int getKeywordIndex() {
+        return keywordIndex;
     }
 
     /**
@@ -565,8 +621,9 @@ final class Element {
      * If the given {@code ignored} map is non-null, then this method will add the keywords
      * of ignored elements in that map as below:
      * <ul>
-     *   <li>Keyword of ignored elements are the keys. Note that a key may be null.</li>
-     *   <li>Keywords of the elements that contained ignored elements are the values.</li>
+     *   <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}.

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] Thu Jun 18 17:38:16 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/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1686282&r1=1686281&r2=1686282&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Thu Jun 18 17:38:16 2015
@@ -746,7 +746,7 @@ public class Formatter implements Locali
             }
         }
         if (showRemarks) {
-            appendOnNewLine(WKTKeywords.Remarks, object.getRemarks(), ElementKind.REMARKS);
+            appendOnNewLine(WKTKeywords.Remark, object.getRemarks(), ElementKind.REMARKS);
         }
         isComplement = false;
     }
@@ -823,6 +823,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) {
@@ -1118,23 +1122,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);
@@ -1144,7 +1152,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 {
@@ -1153,11 +1161,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);
+            }
         }
     }
 
@@ -1322,7 +1334,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;
@@ -1354,7 +1366,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();
                 }
             }
@@ -1425,7 +1437,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.
@@ -1450,7 +1462,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}.
      *
@@ -1488,9 +1500,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);
     }
 
     /**