sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1792917 [1/2] - in /sis/trunk: ./ core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ core/sis-referencing/src/main/java/org/apache/sis/referencing/...
Date Thu, 27 Apr 2017 16:17:33 GMT
Author: desruisseaux
Date: Thu Apr 27 16:17:33 2017
New Revision: 1792917

URL: http://svn.apache.org/viewvc?rev=1792917&view=rev
Log:
Merge from JDK7 branch.

Added:
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DeferredCoordinateOperation.java
      - copied unchanged from r1792916, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DeferredCoordinateOperation.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectSet.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Semaphores.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties
    sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
    sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties
    sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Apr 27 16:17:33 2017
@@ -1,5 +1,5 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394364-1758914
-/sis/branches/JDK7:1394913-1792398
-/sis/branches/JDK8:1584960-1792396
+/sis/branches/JDK7:1394913-1792916
+/sis/branches/JDK8:1584960-1792914
 /sis/branches/JDK9:1773327-1789983

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -95,7 +95,7 @@ import org.apache.sis.internal.jdk8.JDK8
  * Subclasses should select the interfaces that they choose to implement.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
  *
  * @param <DAO>  the type of factory used as Data Access Object (DAO).
  *
@@ -643,6 +643,8 @@ public abstract class ConcurrentAuthorit
      *
      * @param  factory  the Data Access Object which is about to be closed.
      * @return {@code true} if the given Data Access Object can be closed.
+     *
+     * @see #close()
      */
     protected boolean canClose(DAO factory) {
         return true;
@@ -1635,9 +1637,9 @@ public abstract class ConcurrentAuthorit
      * creation is delegated to the {@linkplain #getDataAccess() Data Access Object}.
      * The result is then stored in the cache and returned.
      *
-     * @param  <T>   The type of the object to be returned.
-     * @param  proxy The proxy to use for creating the object.
-     * @param  code  The code of the object to create.
+     * @param  <T>    the type of the object to be returned.
+     * @param  proxy  the proxy to use for creating the object.
+     * @param  code   the code of the object to create.
      * @return the object extracted from the cache or created.
      * @throws FactoryException if an error occurred while creating the object.
      */
@@ -1658,7 +1660,9 @@ public abstract class ConcurrentAuthorit
                     } finally {
                         release(null, type, code);
                     }
-                    value = result;                                     // For the finally block below.
+                    if (isCacheable(code, result)) {
+                        value = result;                                 // For the finally block below.
+                    }
                     return result;
                 }
             } finally {
@@ -1938,11 +1942,36 @@ public abstract class ConcurrentAuthorit
     }
 
     /**
+     * Returns whether the given object can be cached. This method is invoked after the
+     * {@linkplain #newDataAccess() Data Access Object} created a new object not previously in the cache.
+     * If this {@code isCacheable(…)} method returns {@code true}, then the newly created object will be cached so
+     * that next calls to the same {@code createFoo(String)} method with the same code may return the same object.
+     * If this method returns {@code false}, then the newly created object will not be cached and next call to
+     * the {@code createFoo(String)} method with the same code will return a new object.
+     *
+     * <p>The default implementation always returns {@code true}.
+     * Subclasses can override this method for filtering the objects to store in the cache.</p>
+     *
+     * @param  code    the authority code specified by the caller for creating an object.
+     * @param  object  the object created for the given authority code.
+     * @return whether the given object should be cached.
+     *
+     * @see #printCacheContent(PrintWriter)
+     *
+     * @since 0.8
+     */
+    protected boolean isCacheable(String code, Object object) {
+        return true;
+    }
+
+    /**
      * Prints the cache content to the given writer.
      * Keys are sorted by numerical order if possible, or alphabetical order otherwise.
      * This method is used for debugging purpose only.
      *
      * @param  out  the output printer, or {@code null} for the {@linkplain System#out standard output stream}.
+     *
+     * @see #isCacheable(String, Object)
      */
     @Debug
     public void printCacheContent(final PrintWriter out) {
@@ -2060,6 +2089,8 @@ public abstract class ConcurrentAuthorit
      * depending which event happen first.</p>
      *
      * @throws FactoryException if an error occurred while closing the Data Access Objects.
+     *
+     * @see #canClose(GeodeticAuthorityFactory)
      */
     @Override
     public void close() throws FactoryException {
@@ -2084,6 +2115,8 @@ public abstract class ConcurrentAuthorit
      * The string returned by this method may change in any future SIS version.
      *
      * @return a string representation for debugging purpose.
+     *
+     * @see #printCacheContent(PrintWriter)
      */
     @Debug
     @Override

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectSet.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectSet.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectSet.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectSet.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -316,12 +316,10 @@ public class IdentifiedObjectSet<T exten
                      * object but we do not put it in this IdentifiedObjectSet. This behavior is as if this method
                      * has been invoked before the concurrent removal happened.
                      */
-                    if (objects.containsKey(code)) {
+                    if (objects.containsKey(code)) {        // Needed because code may be associated to null value.
                         final T c = JDK8.putIfAbsent(objects, code, object);
                         if (c != null) {
                             object = c;                     // The object has been created concurrently.
-                        } else {
-                            codes = null;
                         }
                     }
                 } else if (JDK8.remove(objects, code, null)) {    // Do not remove if a concurrent thread succeeded.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -71,6 +71,7 @@ import org.apache.sis.internal.metadata.
 import org.apache.sis.internal.metadata.TransformationAccuracy;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.metadata.sql.SQLUtilities;
+import org.apache.sis.internal.referencing.DeferredCoordinateOperation;
 import org.apache.sis.internal.referencing.DeprecatedCode;
 import org.apache.sis.internal.referencing.EPSGParameterDomain;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
@@ -2825,12 +2826,10 @@ next:               while (r.next()) {
                      * (it was checked by getInteger(code, result, …) above in this method) but optional
                      * for concatenated operations. Fetching parameter values is part of this block.
                      */
-                    OperationMethod method;
-                    ParameterValueGroup parameters;
-                    if (methodCode == null) {
-                        method      = null;
-                        parameters  = null;
-                    } else {
+                    final boolean       isDeferred = Semaphores.query(Semaphores.METADATA_ONLY);
+                    ParameterValueGroup parameters = null;
+                    OperationMethod     method     = null;
+                    if (methodCode != null && !isDeferred) {
                         method = owner.createOperationMethod(methodCode.toString());
                         if (isDimensionKnown) {
                             method = DefaultOperationMethod.redimension(method, sourceDimensions, targetDimensions);
@@ -2861,7 +2860,9 @@ next:               while (r.next()) {
                      */
                     final CoordinateOperation operation;
                     final CoordinateOperationFactory copFactory = owner.copFactory;
-                    if (isConversion && (sourceCRS == null || targetCRS == null)) {
+                    if (isDeferred) {
+                        operation = new DeferredCoordinateOperation(opProperties, sourceCRS, targetCRS, owner);
+                    } else if (isConversion && (sourceCRS == null || targetCRS == null)) {
                         operation = copFactory.createDefiningConversion(opProperties, method, parameters);
                     } else if (isConcatenated) {
                         /*
@@ -3034,7 +3035,9 @@ next:               while (r.next()) {
          * Before to return the set, tests the creation of 1 object in order to report early (i.e. now)
          * any problems with SQL statements. Remaining operations will be created only when first needed.
          */
-        set.resolve(1);
+        if (!Semaphores.query(Semaphores.METADATA_ONLY)) {
+            set.resolve(1);
+        }
         return set;
     }
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -38,6 +38,7 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.apache.sis.internal.metadata.sql.Initializer;
+import org.apache.sis.internal.referencing.DeferredCoordinateOperation;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.util.Constants;
@@ -507,4 +508,17 @@ public class EPSGFactory extends Concurr
     protected boolean canClose(final EPSGDataAccess factory) {
         return factory.canClose();
     }
+
+    /**
+     * Returns whether the given object can be cached.
+     * This method is invoked after {@link EPSGDataAccess} created a new object not previously in the cache.
+     *
+     * @return whether the given object should be cached.
+     *
+     * @since 0.8
+     */
+    @Override
+    protected boolean isCacheable(String code, Object object) {
+        return !(object instanceof DeferredCoordinateOperation);
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -18,10 +18,10 @@ package org.apache.sis.referencing.opera
 
 import java.util.Map;
 import java.util.HashMap;
-import java.util.Set;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.Objects;
 import java.util.logging.Level;
@@ -59,11 +59,13 @@ import org.apache.sis.referencing.factor
 import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.internal.referencing.CoordinateOperations;
+import org.apache.sis.internal.referencing.DeferredCoordinateOperation;
 import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.referencing.provider.Affine;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.internal.system.Semaphores;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.Citations;
 import org.apache.sis.util.ArgumentChecks;
@@ -386,20 +388,25 @@ class CoordinateOperationRegistry {
             return null;
         }
         final boolean inverse;
-        Set<CoordinateOperation> operations;
+        Collection<CoordinateOperation> operations;
+        Semaphores.queryAndSet(Semaphores.METADATA_ONLY);           // See comment for the same call inside the loop.
         try {
-            operations = registry.createFromCoordinateReferenceSystemCodes(sourceID, targetID);
-            inverse = Containers.isNullOrEmpty(operations);
-            if (inverse) {
-                /*
-                 * No operation from 'source' to 'target' available. But maybe there is an inverse operation.
-                 * This is typically the case when the user wants to convert from a projected to a geographic CRS.
-                 * The EPSG database usually contains transformation paths for geographic to projected CRS only.
-                 */
-                operations = registry.createFromCoordinateReferenceSystemCodes(targetID, sourceID);
-                if (Containers.isNullOrEmpty(operations)) {
-                    return null;
+            try {
+                operations = registry.createFromCoordinateReferenceSystemCodes(sourceID, targetID);
+                inverse = Containers.isNullOrEmpty(operations);
+                if (inverse) {
+                    /*
+                     * No operation from 'source' to 'target' available. But maybe there is an inverse operation.
+                     * This is typically the case when the user wants to convert from a projected to a geographic CRS.
+                     * The EPSG database usually contains transformation paths for geographic to projected CRS only.
+                     */
+                    operations = registry.createFromCoordinateReferenceSystemCodes(targetID, sourceID);
+                    if (Containers.isNullOrEmpty(operations)) {
+                        return null;
+                    }
                 }
+            } finally {
+                Semaphores.clear(Semaphores.METADATA_ONLY);
             }
         } catch (NoSuchAuthorityCodeException | MissingFactoryResourceException exception) {
             /*
@@ -411,72 +418,56 @@ class CoordinateOperationRegistry {
             return null;
         }
         /*
-         * We will loop over all coordinate operations and select the one having the largest intersection
-         * with the area of interest. Note that if the user did not specified an area of interest himself,
-         * then we need to get one from the CRS. This is necessary for preventing the transformation from
-         * NAD27 to NAD83 in Idaho to select the transform for Alaska (since the later has a larger area).
+         * This outer loop is executed exactly once in most case. It may be executed more than once if an
+         * ignoreable error occurred while creating the CoordinateOperation, in which case we will fallback
+         * on the next best choice until we succeed.
          */
-        double largestArea = 0;
-        double finestAccuracy = Double.POSITIVE_INFINITY;
-        CoordinateOperation bestChoice = null;
-        boolean stopAtFirstDeprecated = false;
-        for (final Iterator<CoordinateOperation> it=operations.iterator(); it.hasNext();) {
-            CoordinateOperation candidate;
-            try {
-                candidate = it.next();
-            } catch (BackingStoreException exception) {
-                FactoryException cause = exception.unwrapOrRethrow(FactoryException.class);
-                if (cause instanceof MissingFactoryResourceException) {
-                    log(cause);
-                    continue;
-                }
-                throw cause;
-            }
-            if (candidate != null) {
+        CoordinateOperation bestChoice;
+        while (true) {
+            /*
+             * We will loop over all coordinate operations and select the one having the largest intersection
+             * with the area of interest. Note that if the user did not specified an area of interest himself,
+             * then we need to get one from the CRS. This is necessary for preventing the transformation from
+             * NAD27 to NAD83 in Idaho to select the transform for Alaska (since the later has a larger area).
+             */
+            bestChoice = null;
+            double largestArea = 0;
+            double finestAccuracy = Double.POSITIVE_INFINITY;
+            boolean stopAtFirstDeprecated = false;
+            for (final Iterator<CoordinateOperation> it=operations.iterator();;) {
+                CoordinateOperation candidate;
                 /*
-                 * If we found at least one non-deprecated operation, we will stop the search at
-                 * the first deprecated one (assuming that deprecated operations are sorted last).
+                 * Some pair of CRS have a lot of coordinate operations backed by datum shift grids.
+                 * We do not want to load all of them until we found the right coordinate operation.
+                 * The non-public Semaphores.METADATA_ONLY mechanism instructs EPSGDataAccess to
+                 * instantiate DeferredCoordinateOperation instead of full coordinate operations.
                  */
-                final boolean isDeprecated = (candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated();
-                if (isDeprecated && stopAtFirstDeprecated) {
-                    break;
+                Semaphores.queryAndSet(Semaphores.METADATA_ONLY);
+                try {
+                    try {
+                        if (!it.hasNext()) break;
+                        candidate = it.next();
+                    } finally {
+                        Semaphores.clear(Semaphores.METADATA_ONLY);
+                    }
+                } catch (BackingStoreException exception) {
+                    throw exception.unwrapOrRethrow(FactoryException.class);
                 }
-                final double area = Extents.area(Extents.intersection(
-                        Extents.getGeographicBoundingBox(areaOfInterest),
-                        Extents.getGeographicBoundingBox(candidate.getDomainOfValidity())));
-                if (bestChoice == null || area >= largestArea) {
-                    final double accuracy = CRS.getLinearAccuracy(candidate);
-                    if (bestChoice == null || area != largestArea || accuracy < finestAccuracy) {
-                        /*
-                         * Inverse the operation only after we verified the metadata (domain of validity,
-                         * accuracy, etc.) since the creation of inverse operation is not guaranteed to
-                         * preserve all metadata.
-                         */
-                        if (inverse) try {
-                            candidate = inverse(candidate);
-                        } catch (NoninvertibleTransformException exception) {
-                            // It may be a normal failure - the operation is not required to be invertible.
-                            Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
-                                    CoordinateOperationRegistry.class, "createOperation", exception);
-                            continue;
-                        } catch (MissingFactoryResourceException e) {
-                            log(e);
-                            continue;
-                        }
-                        /*
-                         * It is possible that the CRS given to this method were not quite right.  For example the user
-                         * may have created his CRS from a WKT using a different axis order than the order specified by
-                         * the authority and still (wrongly) call those CRS "EPSG:xxxx".  So we check if the source and
-                         * target CRS for the operation we just created are equivalent to the CRS specified by the user.
-                         *
-                         * NOTE: FactoryException may be thrown if we failed to create a transform from the user-provided
-                         * CRS to the authority-provided CRS. That transform should have been only an identity transform,
-                         * or a simple affine transform if the user specified wrong CRS as explained in above paragraph.
-                         * If we failed here, we are likely to fail for all other transforms. So we are better to let
-                         * the FactoryException propagate.
-                         */
-                        candidate = complete(candidate, sourceCRS, targetCRS);
-                        if (filter == null || filter.test(candidate)) {
+                if (candidate != null) {
+                    /*
+                     * If we found at least one non-deprecated operation, we will stop the search at
+                     * the first deprecated one (assuming that deprecated operations are sorted last).
+                     */
+                    final boolean isDeprecated = (candidate instanceof Deprecable) && ((Deprecable) candidate).isDeprecated();
+                    if (isDeprecated && stopAtFirstDeprecated) {
+                        break;
+                    }
+                    final double area = Extents.area(Extents.intersection(
+                            Extents.getGeographicBoundingBox(areaOfInterest),
+                            Extents.getGeographicBoundingBox(candidate.getDomainOfValidity())));
+                    if (bestChoice == null || area >= largestArea) {
+                        final double accuracy = CRS.getLinearAccuracy(candidate);
+                        if (bestChoice == null || area != largestArea || accuracy < finestAccuracy) {
                             bestChoice = candidate;
                             if (!Double.isNaN(area)) {
                                 largestArea = area;
@@ -487,6 +478,59 @@ class CoordinateOperationRegistry {
                     }
                 }
             }
+            /*
+             * At this point we filtered a CoordinateOperation by looking only at its metadata.
+             * Code following this point will need the full coordinate operation, including its
+             * MathTransform. So if we got a deferred operation, we need to resolve it now.
+             * Conversely, we should not use metadata below this point because the call to
+             * inverse(CoordinateOperation) is not guaranteed to preserve all metadata.
+             */
+            if (bestChoice == null) break;
+            final CoordinateOperation deferred = bestChoice;
+            try {
+                if (bestChoice instanceof DeferredCoordinateOperation) {
+                    bestChoice = ((DeferredCoordinateOperation) bestChoice).create();
+                }
+                if (inverse) {
+                    bestChoice = inverse(bestChoice);
+                }
+            } catch (NoninvertibleTransformException | MissingFactoryResourceException e) {
+                /*
+                 * If we failed to get the real CoordinateOperation instance, remove it from the collection
+                 * and try again in order to get the next best choice. The Apache SIS implementation allows
+                 * to remove directly from the Set<CoordinateOperation>, but that removal may fail with non-SIS
+                 * implementations, in which case we copy the CoordinateOperation in a temporary list before
+                 * removal. We do not perform that copy unconditionally in order to avoid lazy initialization
+                 * of unneeded CoordinateOperations like the deprecated ones.
+                 */
+                boolean removed;
+                try {
+                    removed = operations.remove(deferred);
+                } catch (UnsupportedOperationException ignored) {
+                    operations = new ArrayList<>(operations);
+                    removed = operations.remove(deferred);
+                }
+                if (removed) {
+                    log(e);
+                    continue;                                   // Try again with the next best case.
+                }
+                // Should never happen, but if happen anyway we should fail for avoiding never-ending loop.
+                throw (e instanceof FactoryException) ? (FactoryException) e : new FactoryException(e);
+            }
+            /*
+             * It is possible that the CRS given to this method were not quite right.  For example the user
+             * may have created his CRS from a WKT using a different axis order than the order specified by
+             * the authority and still (wrongly) call those CRS "EPSG:xxxx".  So we check if the source and
+             * target CRS for the operation we just created are equivalent to the CRS specified by the user.
+             *
+             * NOTE: FactoryException may be thrown if we fail to create a transform from the user-provided
+             * CRS to the authority-provided CRS. That transform should have been only an identity transform,
+             * or a simple affine transform if the user specified wrong CRS as explained in above paragraph.
+             * If we fail here, we are likely to fail for all other transforms. So we are better to let the
+             * FactoryException propagate.
+             */
+            bestChoice = complete(bestChoice, sourceCRS, targetCRS);
+            if (filter == null || filter.test(bestChoice)) break;
         }
         return bestChoice;
     }
@@ -1080,9 +1124,18 @@ class CoordinateOperationRegistry {
      *
      * @param exception  the exception which occurred.
      */
-    private static void log(final FactoryException exception) {
+    private static void log(final Exception exception) {
         final LogRecord record = new LogRecord(Level.WARNING, exception.getLocalizedMessage());
         record.setLoggerName(Loggers.COORDINATE_OPERATION);
+        /*
+         * We usually do not log the stack trace since this method should be invoked only for exceptions
+         * like NoSuchAuthorityCodeException or MissingFactoryResourceException, for which the message
+         * is descriptive enough. But we make a special case for NoninvertibleTransformException since
+         * its cause may have deeper root.
+         */
+        if (exception instanceof NoninvertibleTransformException) {
+            record.setThrown(exception);
+        }
         Logging.log(CoordinateOperationFinder.class, "createOperation", record);
     }
 }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Semaphores.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Semaphores.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Semaphores.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Semaphores.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -21,37 +21,46 @@ import org.apache.sis.util.Workaround;
 
 /**
  * Thread-local booleans that need to be shared across different packages. Each thread has its own set of booleans.
- * The {@link #clear(byte)} method <strong>must</strong> be invoked after the {@link #queryAndSet(byte)} method in
+ * The {@link #clear(int)} method <strong>must</strong> be invoked after the {@link #queryAndSet(int)} method in
  * a {@code try ... finally} block.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.5
  * @module
  */
 public final class Semaphores {
     /**
+     * A flag to indicate that empty collections should be returned as {@code null}. Returning null
+     * collections is not a recommended practice, but is useful in some situations like marshalling
+     * a XML document with JAXB, when we want to omit empty XML blocks.
+     */
+    public static final int NULL_COLLECTION = 1;
+
+    /**
+     * A flag to indicate that only metadata are desired and that there is no need to create costly objects.
+     * This flag is used during iteration over many coordinate operations before to select a single one by
+     * inspecting only their metadata.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-327">SIS-327</a>
+     */
+    public static final int METADATA_ONLY = 2;
+
+    /**
      * A lock for avoiding never-ending recursivity in the {@code equals} method of {@code AbstractDerivedCRS}
      * and {@link org.apache.sis.referencing.operation.AbstractCoordinateOperation}.
      * It is set to {@code true} when a comparison is in progress. This lock is necessary because
      * {@code AbstractDerivedCRS} objects contain a {@code conversionFromBase} field, which contains a
      * {@code DefaultConversion.targetCRS} field referencing back the {@code AbstractDerivedCRS} object.
      */
-    public static final byte CONVERSION_AND_CRS = 1;
+    public static final int CONVERSION_AND_CRS = 4;
 
     /**
      * A flag to indicate that {@link org.apache.sis.referencing.operation.AbstractCoordinateOperation}
      * is querying parameters of a {@code MathTransform} enclosed in the operation. This is often in the
      * intend to format WKT of a {@code "ProjectedCRS"} element.
      */
-    public static final byte ENCLOSED_IN_OPERATION = 2;
-
-    /**
-     * A flag to indicate that empty collections should be returned as {@code null}. Returning null
-     * collections is not a recommended practice, but is useful in some situations like marshalling
-     * a XML document with JAXB, when we want to omit empty XML blocks.
-     */
-    public static final byte NULL_COLLECTION = 4;
+    public static final int ENCLOSED_IN_OPERATION = 8;
 
     /**
      * A flag to indicate that a parameter value outside its domain of validity should not cause an exception
@@ -62,7 +71,7 @@ public final class Semaphores {
      * <p><b>Example:</b> EPSG:3752 was a Mercator (variant A) projection but set the latitude of origin to 41°S.</p>
      */
     @Workaround(library = "EPSG:3752", version = "8.9")        // Deprecated in 2007 but still present in 2016.
-    public static final byte SUSPEND_PARAMETER_CHECK = 8;
+    public static final int SUSPEND_PARAMETER_CHECK = 16;
 
     /**
      * The flags per running thread.
@@ -72,7 +81,7 @@ public final class Semaphores {
     /**
      * The bit flags.
      */
-    private byte flags;
+    private int flags;
 
     /**
      * For internal use only.
@@ -86,7 +95,7 @@ public final class Semaphores {
      * @param  flag  one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
      * @return {@code true} if the given flag is set.
      */
-    public static boolean query(final byte flag) {
+    public static boolean query(final int flag) {
         final Semaphores s = FLAGS.get();
         return (s != null) && (s.flags & flag) != 0;
     }
@@ -97,7 +106,7 @@ public final class Semaphores {
      * @param  flag  one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
      * @return {@code true} if the given flag was already set.
      */
-    public static boolean queryAndSet(final byte flag) {
+    public static boolean queryAndSet(final int flag) {
         Semaphores s = FLAGS.get();
         if (s == null) {
             s = new Semaphores();
@@ -113,7 +122,7 @@ public final class Semaphores {
      *
      * @param  flag  one of {@link #CONVERSION_AND_CRS}, {@link #ENCLOSED_IN_OPERATION} or other constants.
      */
-    public static void clear(final byte flag) {
+    public static void clear(final int flag) {
         final Semaphores s = FLAGS.get();
         if (s != null) {
             s.flags &= ~flag;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -127,6 +127,13 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Returns {@code true} if the use of SI prefixes is allowed for this unit.
+     */
+    final boolean isPrefixable() {
+        return (scope & UnitRegistry.PREFIXABLE) != 0;
+    }
+
+    /**
      * Returns the symbol (if any) of this unit. A unit may have no symbol, in which case
      * the {@link #toString()} method is responsible for creating a string representation.
      *
@@ -173,6 +180,15 @@ abstract class AbstractUnit<Q extends Qu
     public abstract SystemUnit<Q> getSystemUnit();
 
     /**
+     * Returns the base units and their exponent whose product is the system unit,
+     * or {@code null} if the system unit is a base unit (not a product of existing units).
+     *
+     * @return the base units and their exponent making up the system unit.
+     */
+    @Override
+    public abstract Map<SystemUnit<?>, Integer> getBaseUnits();
+
+    /**
      * Returns the base units used by Apache SIS implementations.
      * Contrarily to {@link #getBaseUnits()}, this method never returns {@code null}.
      */
@@ -283,6 +299,14 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Returns units for the same quantity but with scale factors that are not the SI one, or {@code null} if none.
+     * This method returns a direct reference to the internal field; caller shall not modify.
+     */
+    ConventionalUnit<Q>[] related() {
+        return null;
+    }
+
+    /**
      * Compares this unit with the given object for equality.
      *
      * @param  other  the other object to compare with this unit, or {@code null}.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -62,9 +62,11 @@ final class ConventionalUnit<Q extends Q
 
     /**
      * The base, derived or alternate units to which this {@code ConventionalUnit} is related.
-     * This is called "preferred unit" in GML.
+     * This is called "preferred unit" in GML. This is usually an instance of {@link SystemUnit},
+     * but may also be another {@link ConventionalUnit} in some rare cases where this conventional
+     * unit can be prefixed like a SI units (e.g. litre: L, cl, mL, µL).
      */
-    final SystemUnit<Q> target;
+    private final AbstractUnit<Q> target;
 
     /**
      * The conversion from this unit to the {@linkplain #target} unit.
@@ -80,7 +82,7 @@ final class ConventionalUnit<Q extends Q
      * @param  scope     {@link UnitRegistry#SI}, {@link UnitRegistry#ACCEPTED}, other constants or 0 if unknown.
      * @param  epsg      the EPSG code, or 0 if this unit has no EPSG code.
      */
-    ConventionalUnit(final SystemUnit<Q> target, final UnitConverter toTarget, final String symbol, final byte scope, final short epsg) {
+    ConventionalUnit(final AbstractUnit<Q> target, final UnitConverter toTarget, final String symbol, final byte scope, final short epsg) {
         super(symbol, scope, epsg);
         this.target   = target;
         this.toTarget = toTarget;
@@ -88,9 +90,12 @@ final class ConventionalUnit<Q extends Q
 
     /**
      * Creates a new unit with default name and symbol for the given converter.
+     *
+     * @param  target    the base or derived units to which the new unit will be related.
+     * @param  toTarget  the conversion from the new unit to the {@code target} unit.
      */
     @SuppressWarnings("unchecked")
-    static <Q extends Quantity<Q>> AbstractUnit<Q> create(final SystemUnit<Q> target, final UnitConverter toTarget) {
+    static <Q extends Quantity<Q>> AbstractUnit<Q> create(final AbstractUnit<Q> target, final UnitConverter toTarget) {
         if (toTarget.isIdentity()) {
             return target;
         }
@@ -115,7 +120,7 @@ final class ConventionalUnit<Q extends Q
          * unit instances.
          */
         String symbol = null;
-        if (target.scope == UnitRegistry.SI) {
+        if (target.isPrefixable()) {
             final String ts = target.getSymbol();
             if (ts != null && !ts.isEmpty() && toTarget.isLinear()) {
                 final int power = power(ts);
@@ -251,7 +256,7 @@ final class ConventionalUnit<Q extends Q
      */
     @Override
     public Dimension getDimension() {
-        return target.dimension;
+        return target.getDimension();
     }
 
     /**
@@ -259,7 +264,7 @@ final class ConventionalUnit<Q extends Q
      */
     @Override
     public SystemUnit<Q> getSystemUnit() {
-        return target;
+        return target.getSystemUnit();
     }
 
     /**
@@ -317,11 +322,12 @@ final class ConventionalUnit<Q extends Q
         UnitConverter c = toTarget;
         if (target != that) {                           // Optimization for a common case.
             final Unit<Q> step = that.getSystemUnit();
-            if (target != step && !target.equalsIgnoreMetadata(step)) {
+            if (target != step && !target.isCompatible(step)) {
                 // Should never occur unless parameterized type has been compromised.
                 throw new UnconvertibleException(incompatible(that));
             }
-            c = step.getConverterTo(that).concatenate(c);
+            c = target.getConverterTo(step).concatenate(c);         // Usually leave 'c' unchanged.
+            c =   step.getConverterTo(that).concatenate(c);
         }
         return c;
     }
@@ -349,7 +355,8 @@ final class ConventionalUnit<Q extends Q
             if (target != step && !target.isCompatible(step)) {
                 throw new IncommensurableException(incompatible(that));
             }
-            c = step.getConverterToAny(that).concatenate(c);
+            c = target.getConverterToAny(step).concatenate(c);      // Usually leave 'c' unchanged.
+            c =   step.getConverterToAny(that).concatenate(c);
         }
         return c;
     }
@@ -435,9 +442,14 @@ final class ConventionalUnit<Q extends Q
      * @return the unit after the specified transformation.
      */
     @Override
-    public Unit<Q> transform(final UnitConverter operation) {
+    public Unit<Q> transform(UnitConverter operation) {
         ArgumentChecks.ensureNonNull("operation", operation);
-        return create(target, toTarget.concatenate(operation));
+        AbstractUnit<Q> base = this;
+        if (!isPrefixable()) {
+            base = target;
+            operation = toTarget.concatenate(operation);
+        }
+        return create(base, operation);
     }
 
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -487,9 +487,10 @@ final class SystemUnit<Q extends Quantit
     }
 
     /**
-     * Returns units for the same quantity but with scale factors that are not the SI one.
+     * Returns units for the same quantity but with scale factors that are not the SI one, or {@code null} if none.
      * This method returns a direct reference to the internal field; caller shall not modify.
      */
+    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     final ConventionalUnit<Q>[] related() {
         return related;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -1246,7 +1246,7 @@ search:     while ((i = CharSequences.sk
                 s = 2;
             }
             unit = Units.get(uom.substring(s));
-            if (unit instanceof SystemUnit<?> && ((SystemUnit<?>) unit).scope == UnitRegistry.SI) {
+            if (unit instanceof AbstractUnit<?> && ((AbstractUnit<?>) unit).isPrefixable()) {
                 final LinearConverter c = LinearConverter.forPrefix(prefix);
                 if (c != null) {
                     String symbol = unit.getSymbol();
@@ -1255,7 +1255,7 @@ search:     while ((i = CharSequences.sk
                     } else {
                         symbol = prefix + symbol;
                     }
-                    return new ConventionalUnit<>((SystemUnit<?>) unit, c, symbol.intern(), (byte) 0, (short) 0);
+                    return new ConventionalUnit<>((AbstractUnit<?>) unit, c, symbol.intern(), (byte) 0, (short) 0);
                 }
             }
             unit = null;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitRegistry.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -48,25 +48,32 @@ final class UnitRegistry implements Syst
     private static final long serialVersionUID = -84557361079506390L;
 
     /**
+     * A bitmask specifying that the unit symbol can be combined with a SI prefix.
+     * This is usually combined only with {@link #SI}, not {@link #ACCEPTED} except
+     * the litre unit (cL, mL, etc).
+     */
+    static final byte PREFIXABLE = 1;
+
+    /**
      * Identifies units defined by the SI system.
      * All {@link SystemUnit} instances with this code can have a SI prefix.
      */
-    static final byte SI = 1;
+    static final byte SI = 2;
 
     /**
      * Identifies units defined outside the SI system but accepted for use with SI.
      */
-    static final byte ACCEPTED = 2;
+    static final byte ACCEPTED = 4;
 
     /**
      * Identifies units defined for use in British imperial system.
      */
-    static final byte IMPERIAL = 4;
+    static final byte IMPERIAL = 8;
 
     /**
      * Identifies units defined in another system than the above.
      */
-    static final byte OTHER = 8;
+    static final byte OTHER = 16;
 
     /**
      * All {@link UnitDimension}, {@link SystemUnit} or {@link ConventionalUnit} that are hard-coded in Apache SIS.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -33,6 +33,11 @@ import org.apache.sis.internal.util.Cons
 
 import static java.lang.Math.PI;
 import static java.lang.Math.abs;
+import static org.apache.sis.measure.UnitRegistry.SI;
+import static org.apache.sis.measure.UnitRegistry.ACCEPTED;
+import static org.apache.sis.measure.UnitRegistry.IMPERIAL;
+import static org.apache.sis.measure.UnitRegistry.OTHER;
+import static org.apache.sis.measure.UnitRegistry.PREFIXABLE;
 import static org.apache.sis.measure.SexagesimalConverter.EPS;
 
 
@@ -324,6 +329,14 @@ public final class Units extends Static
     public static final Unit<Volume> CUBIC_METRE;
 
     /**
+     * The unit for litre volume (L, l or ℓ).
+     * The unlocalized name is “litre”.
+     *
+     * @since 0.8
+     */
+    public static final Unit<Volume> LITRE;
+
+    /**
      * The SI unit for solid angles (sr).
      * The unlocalized name is “steradian”.
      *
@@ -942,6 +955,14 @@ public final class Units extends Static
     public static final Unit<Illuminance> LUX;
 
     /**
+     * A SI conventional unit for mass (g).
+     * The unlocalized name is “gram”.
+     *
+     * @since 0.8
+     */
+    public static final Unit<Mass> GRAM;
+
+    /**
      * The SI base unit for mass (kg).
      * The unlocalized name is “kilogram”.
      *
@@ -1063,13 +1084,15 @@ public final class Units extends Static
          * Note: JDK8 branch uses much more compact method references instead than following inner classes.
          */
         ScalarFactory<Dimensionless> dimensionlessFactory =            new ScalarFactory<Dimensionless>() {@Override public Dimensionless create(double value, Unit<Dimensionless> unit) {return new Scalar.Dimensionless(value, unit);}};
-        final SystemUnit<Length>        m   = add(Length.class,        new ScalarFactory<Length>       () {@Override public Length        create(double value, Unit<Length>        unit) {return new Scalar.Length       (value, unit);}}, length,        "m",   UnitRegistry.SI, Constants.EPSG_METRE);
-        final SystemUnit<Area>          m2  = add(Area.class,          new ScalarFactory<Area>         () {@Override public Area          create(double value, Unit<Area>          unit) {return new Scalar.Area         (value, unit);}}, area,          "m²",  UnitRegistry.SI, (short) 0);
-        final SystemUnit<Time>          s   = add(Time.class,          new ScalarFactory<Time>         () {@Override public Time          create(double value, Unit<Time>          unit) {return new Scalar.Time         (value, unit);}}, time,          "s",   UnitRegistry.SI, (short) 1040);
-        final SystemUnit<Temperature>   K   = add(Temperature.class,   new ScalarFactory<Temperature>  () {@Override public Temperature   create(double value, Unit<Temperature>   unit) {return new Scalar.Temperature  (value, unit);}}, temperature,   "K",   UnitRegistry.SI, (short) 0);
-        final SystemUnit<Speed>         mps = add(Speed.class,         new ScalarFactory<Speed>        () {@Override public Speed         create(double value, Unit<Speed>         unit) {return new Scalar.Speed        (value, unit);}}, speed,         "m∕s", UnitRegistry.SI, (short) 1026);
-        final SystemUnit<Pressure>      Pa  = add(Pressure.class,      new ScalarFactory<Pressure>     () {@Override public Pressure      create(double value, Unit<Pressure>      unit) {return new Scalar.Pressure     (value, unit);}}, pressure,      "Pa",  UnitRegistry.SI, (short) 0);
-        final SystemUnit<Angle>         rad = add(Angle.class,         new ScalarFactory<Angle>        () {@Override public Angle         create(double value, Unit<Angle>         unit) {return new Scalar.Angle        (value, unit);}}, dimensionless, "rad", UnitRegistry.SI, (short) 9101);
+        final SystemUnit<Length>        m   = add(Length.class,        new ScalarFactory<Length>       () {@Override public Length        create(double value, Unit<Length>        unit) {return new Scalar.Length       (value, unit);}}, length,        "m",   (byte) (SI | PREFIXABLE), Constants.EPSG_METRE);
+        final SystemUnit<Area>          m2  = add(Area.class,          new ScalarFactory<Area>         () {@Override public Area          create(double value, Unit<Area>          unit) {return new Scalar.Area         (value, unit);}}, area,          "m²",  (byte) (SI | PREFIXABLE), (short) 0);
+        final SystemUnit<Volume>        m3  = add(Volume.class,        new ScalarFactory<Volume>       () {@Override public Volume        create(double value, Unit<Volume>        unit) {return new Scalar.Volume       (value, unit);}}, length.pow(3), "m³",  (byte) (SI | PREFIXABLE), (short) 0);
+        final SystemUnit<Time>          s   = add(Time.class,          new ScalarFactory<Time>         () {@Override public Time          create(double value, Unit<Time>          unit) {return new Scalar.Time         (value, unit);}}, time,          "s",   (byte) (SI | PREFIXABLE), (short) 1040);
+        final SystemUnit<Temperature>   K   = add(Temperature.class,   new ScalarFactory<Temperature>  () {@Override public Temperature   create(double value, Unit<Temperature>   unit) {return new Scalar.Temperature  (value, unit);}}, temperature,   "K",   (byte) (SI | PREFIXABLE), (short) 0);
+        final SystemUnit<Speed>         mps = add(Speed.class,         new ScalarFactory<Speed>        () {@Override public Speed         create(double value, Unit<Speed>         unit) {return new Scalar.Speed        (value, unit);}}, speed,         "m∕s", (byte) (SI | PREFIXABLE), (short) 1026);
+        final SystemUnit<Pressure>      Pa  = add(Pressure.class,      new ScalarFactory<Pressure>     () {@Override public Pressure      create(double value, Unit<Pressure>      unit) {return new Scalar.Pressure     (value, unit);}}, pressure,      "Pa",  (byte) (SI | PREFIXABLE), (short) 0);
+        final SystemUnit<Angle>         rad = add(Angle.class,         new ScalarFactory<Angle>        () {@Override public Angle         create(double value, Unit<Angle>         unit) {return new Scalar.Angle        (value, unit);}}, dimensionless, "rad", (byte) (SI | PREFIXABLE), (short) 9101);
+        final SystemUnit<Mass>          kg  = add(Mass.class,          new ScalarFactory<Mass>         () {@Override public Mass          create(double value, Unit<Mass>          unit) {return new Scalar.Mass         (value, unit);}}, mass,          "kg",          SI,               (short) 0);
         final SystemUnit<Dimensionless> one = add(Dimensionless.class, dimensionlessFactory, dimensionless, "", UnitRegistry.SI, (short) 9201);
         /*
          * All SI prefix to be used below, with additional converters to be used more than once.
@@ -1089,98 +1112,100 @@ public final class Units extends Static
          */
         rad.related(4);
         RADIAN      = rad;
-        GRAD        = add(rad, LinearConverter.scale(Math.PI / 20, 200       / 20), "grad", UnitRegistry.OTHER,    (short) 9105);
-        DEGREE      = add(rad, LinearConverter.scale(Math.PI / 20, 180       / 20), "°",    UnitRegistry.ACCEPTED, Constants.EPSG_PARAM_DEGREES);
-        ARC_MINUTE  = add(rad, LinearConverter.scale(Math.PI / 20, 180*60    / 20), "′",    UnitRegistry.ACCEPTED, (short) 9103);
-        ARC_SECOND  = add(rad, LinearConverter.scale(Math.PI / 20, 180*60*60 / 20), "″",    UnitRegistry.ACCEPTED, (short) 9104);
-        MICRORADIAN = add(rad, micro,                                               "µrad", UnitRegistry.SI,       (short) 9109);
+        GRAD        = add(rad, LinearConverter.scale(Math.PI / 20, 200       / 20), "grad", OTHER,    (short) 9105);
+        DEGREE      = add(rad, LinearConverter.scale(Math.PI / 20, 180       / 20), "°",    ACCEPTED, Constants.EPSG_PARAM_DEGREES);
+        ARC_MINUTE  = add(rad, LinearConverter.scale(Math.PI / 20, 180*60    / 20), "′",    ACCEPTED, (short) 9103);
+        ARC_SECOND  = add(rad, LinearConverter.scale(Math.PI / 20, 180*60*60 / 20), "″",    ACCEPTED, (short) 9104);
+        MICRORADIAN = add(rad, micro,                                               "µrad", SI,       (short) 9109);
         /*
          * All Unit<Length>.
          */
         m.related(7);
         METRE          = m;
-        NANOMETRE      = add(m, nano,                                     "nm",    UnitRegistry.SI,       (short) 0);
-        MILLIMETRE     = add(m, milli,                                    "mm",    UnitRegistry.SI,       (short) 1025);
-        CENTIMETRE     = add(m, centi,                                    "cm",    UnitRegistry.SI,       (short) 1033);
-        KILOMETRE      = add(m, kilo,                                     "km",    UnitRegistry.SI,       (short) 9036);
-        NAUTICAL_MILE  = add(m, LinearConverter.scale(   1852,        1), "M",     UnitRegistry.OTHER,    (short) 9030);
-        STATUTE_MILE   = add(m, LinearConverter.scale(1609344,      100), "mi",    UnitRegistry.IMPERIAL, (short) 9093);
-        US_SURVEY_FOOT = add(m, LinearConverter.scale(   1200,     3937), "ftUS",  UnitRegistry.OTHER,    (short) 9003);
-        CLARKE_FOOT    = add(m, LinearConverter.scale(3047972654d, 1E10), "ftCla", UnitRegistry.OTHER,    (short) 9005);
-        FOOT           = add(m, LinearConverter.scale(   3048,    10000), "ft",    UnitRegistry.IMPERIAL, (short) 9002);
-        INCH           = add(m, LinearConverter.scale(    254,    10000), "in",    UnitRegistry.IMPERIAL, (short) 0);
-        POINT          = add(m, LinearConverter.scale( 996264, 72000000), "pt",    UnitRegistry.OTHER,    (short) 0);
+        NANOMETRE      = add(m, nano,                                     "nm",    SI,       (short) 0);
+        MILLIMETRE     = add(m, milli,                                    "mm",    SI,       (short) 1025);
+        CENTIMETRE     = add(m, centi,                                    "cm",    SI,       (short) 1033);
+        KILOMETRE      = add(m, kilo,                                     "km",    SI,       (short) 9036);
+        NAUTICAL_MILE  = add(m, LinearConverter.scale(   1852,        1), "M",     OTHER,    (short) 9030);
+        STATUTE_MILE   = add(m, LinearConverter.scale(1609344,      100), "mi",    IMPERIAL, (short) 9093);
+        US_SURVEY_FOOT = add(m, LinearConverter.scale(   1200,     3937), "ftUS",  OTHER,    (short) 9003);
+        CLARKE_FOOT    = add(m, LinearConverter.scale(3047972654d, 1E10), "ftCla", OTHER,    (short) 9005);
+        FOOT           = add(m, LinearConverter.scale(   3048,    10000), "ft",    IMPERIAL, (short) 9002);
+        INCH           = add(m, LinearConverter.scale(    254,    10000), "in",    IMPERIAL, (short) 0);
+        POINT          = add(m, LinearConverter.scale( 996264, 72000000), "pt",    OTHER,    (short) 0);
         /*
          * All Unit<Time>.
          */
         s.related(5);
         SECOND         = s;
-        MILLISECOND    = add(s, milli, "ms", UnitRegistry.SI, (short) 0);
-        MINUTE         = add(s, LinearConverter.scale(         60,      1), "min", UnitRegistry.ACCEPTED, (short) 0);
-        HOUR           = add(s, LinearConverter.scale(      60*60,      1), "h",   UnitRegistry.ACCEPTED, (short) 0);
-        DAY            = add(s, LinearConverter.scale(   24*60*60,      1), "d",   UnitRegistry.ACCEPTED, (short) 0);
-        WEEK           = add(s, LinearConverter.scale( 7*24*60*60,      1), "wk",  UnitRegistry.OTHER,    (short) 0);
-        TROPICAL_YEAR  = add(s, LinearConverter.scale(31556925445.0, 1000), "a",   UnitRegistry.OTHER,    (short) 1029);
+        MILLISECOND    = add(s, milli,                                      "ms",  SI,       (short) 0);
+        MINUTE         = add(s, LinearConverter.scale(         60,      1), "min", ACCEPTED, (short) 0);
+        HOUR           = add(s, LinearConverter.scale(      60*60,      1), "h",   ACCEPTED, (short) 0);
+        DAY            = add(s, LinearConverter.scale(   24*60*60,      1), "d",   ACCEPTED, (short) 0);
+        WEEK           = add(s, LinearConverter.scale( 7*24*60*60,      1), "wk",  OTHER,    (short) 0);
+        TROPICAL_YEAR  = add(s, LinearConverter.scale(31556925445.0, 1000), "a",   OTHER,    (short) 1029);
         /*
          * All Unit<Speed>.
          */
         mps.related(1);
         METRES_PER_SECOND   = mps;
-        KILOMETRES_PER_HOUR = add(mps, LinearConverter.scale(6, 100), "km∕h", UnitRegistry.ACCEPTED, (short) 0);
+        KILOMETRES_PER_HOUR = add(mps, LinearConverter.scale(6, 100), "km∕h", ACCEPTED, (short) 0);
         /*
          * All Unit<Pressure>.
          */
         Pa.related(3);
         PASCAL      = Pa;
-        HECTOPASCAL = add(Pa,  hecto,                            "hPa",  UnitRegistry.SI,    (short) 0);
-        DECIBAR     = add(Pa,  ten4,                             "dbar", UnitRegistry.OTHER, (short) 0);
-        BAR         = add(Pa,  LinearConverter.scale(100000, 1), "bar",  UnitRegistry.OTHER, (short) 0);
-        ATMOSPHERE  = add(Pa,  LinearConverter.scale(101325, 1), "atm",  UnitRegistry.OTHER, (short) 0);
+        HECTOPASCAL = add(Pa,  hecto,                            "hPa",  SI,    (short) 0);
+        DECIBAR     = add(Pa,  ten4,                             "dbar", OTHER, (short) 0);
+        BAR         = add(Pa,  LinearConverter.scale(100000, 1), "bar",  OTHER, (short) 0);
+        ATMOSPHERE  = add(Pa,  LinearConverter.scale(101325, 1), "atm",  OTHER, (short) 0);
         /*
          * All Unit<Temperature>.
          */
         K.related(1);
         KELVIN     = K;
-        CELSIUS    = add(K, LinearConverter.offset(  27315, 100), "°C", UnitRegistry.SI,    (short) 0);
-        FAHRENHEIT = add(K, new LinearConverter(100, 45967, 180), "°F", UnitRegistry.OTHER, (short) 0);
+        CELSIUS    = add(K, LinearConverter.offset(  27315, 100), "°C", SI,    (short) 0);
+        FAHRENHEIT = add(K, new LinearConverter(100, 45967, 180), "°F", OTHER, (short) 0);
         /*
          * Electricity and magnetism.
          */
-        AMPERE  = add(ElectricCurrent.class,     null, current,                      "A",  UnitRegistry.SI, (short) 0);
-        COULOMB = add(ElectricCharge.class,      null, charge,                       "C",  UnitRegistry.SI, (short) 0);
-        VOLT    = add(ElectricPotential.class,   null, potential,                    "V",  UnitRegistry.SI, (short) 0);
-        FARAD   = add(ElectricCapacitance.class, null, charge.divide(potential),     "F",  UnitRegistry.SI, (short) 0);
-        SIEMENS = add(ElectricConductance.class, null, current.divide(potential),    "S",  UnitRegistry.SI, (short) 0);
-        OHM     = add(ElectricResistance.class,  null, potential.divide(current),    "Ω",  UnitRegistry.SI, (short) 0);
-        WEBER   = add(MagneticFlux.class,        null, magneticFlux,                 "Wb", UnitRegistry.SI, (short) 0);
-        TESLA   = add(MagneticFluxDensity.class, null, magneticFlux.divide(area),    "T",  UnitRegistry.SI, (short) 0);
-        HENRY   = add(ElectricInductance.class,  null, magneticFlux.divide(current), "H",  UnitRegistry.SI, (short) 0);
+        AMPERE  = add(ElectricCurrent.class,     null, current,                      "A",  (byte) (SI | PREFIXABLE), (short) 0);
+        COULOMB = add(ElectricCharge.class,      null, charge,                       "C",  (byte) (SI | PREFIXABLE), (short) 0);
+        VOLT    = add(ElectricPotential.class,   null, potential,                    "V",  (byte) (SI | PREFIXABLE), (short) 0);
+        FARAD   = add(ElectricCapacitance.class, null, charge.divide(potential),     "F",  (byte) (SI | PREFIXABLE), (short) 0);
+        SIEMENS = add(ElectricConductance.class, null, current.divide(potential),    "S",  (byte) (SI | PREFIXABLE), (short) 0);
+        OHM     = add(ElectricResistance.class,  null, potential.divide(current),    "Ω",  (byte) (SI | PREFIXABLE), (short) 0);
+        WEBER   = add(MagneticFlux.class,        null, magneticFlux,                 "Wb", (byte) (SI | PREFIXABLE), (short) 0);
+        TESLA   = add(MagneticFluxDensity.class, null, magneticFlux.divide(area),    "T",  (byte) (SI | PREFIXABLE), (short) 0);
+        HENRY   = add(ElectricInductance.class,  null, magneticFlux.divide(current), "H",  (byte) (SI | PREFIXABLE), (short) 0);
         /*
          * Other units.
          * Note: JDK8 branch uses method references instead than inner classes.
          */
         SQUARE_METRE = m2;
-        HECTARE      = add(m2, ten4, "ha",  UnitRegistry.ACCEPTED, (short) 0);
-        CUBIC_METRE  = add(Volume.class,    new ScalarFactory<Volume>   () {@Override public Volume    create(double value, Unit<Volume>    unit) {return new Scalar.Volume   (value, unit);}}, length.pow(3), "m³", UnitRegistry.SI, (short) 0);
-        HERTZ        = add(Frequency.class, new ScalarFactory<Frequency>() {@Override public Frequency create(double value, Unit<Frequency> unit) {return new Scalar.Frequency(value, unit);}}, time.pow(-1),  "Hz", UnitRegistry.SI, (short) 0);
-        KILOGRAM     = add(Mass.class,      new ScalarFactory<Mass>     () {@Override public Mass      create(double value, Unit<Mass>      unit) {return new Scalar.Mass     (value, unit);}}, mass,          "kg", UnitRegistry.SI, (short) 0);
-        NEWTON       = add(Force.class,     new ScalarFactory<Force>    () {@Override public Force     create(double value, Unit<Force>     unit) {return new Scalar.Force    (value, unit);}}, force,         "N",  UnitRegistry.SI, (short) 0);
-        JOULE        = add(Energy.class,    new ScalarFactory<Energy>   () {@Override public Energy    create(double value, Unit<Energy>    unit) {return new Scalar.Energy   (value, unit);}}, energy,        "J",  UnitRegistry.SI, (short) 0);
-        WATT         = add(Power.class,     new ScalarFactory<Power>    () {@Override public Power     create(double value, Unit<Power>     unit) {return new Scalar.Power    (value, unit);}}, power,         "W",  UnitRegistry.SI, (short) 0);
-        LUX          = add(Illuminance.class,       null, luminous.divide(area), "lx",  UnitRegistry.SI, (short) 0);
-        LUMEN        = add(LuminousFlux.class,      null, luminous,              "lm",  UnitRegistry.SI, (short) 0);
-        CANDELA      = add(LuminousIntensity.class, null, luminous,              "cd",  UnitRegistry.SI, (short) 0);    // Must be after Lumen.
-        MOLE         = add(AmountOfSubstance.class, null, amount,                "mol", UnitRegistry.SI, (short) 0);
-        STERADIAN    = add(SolidAngle.class,        null, dimensionless,         "sr",  UnitRegistry.SI, (short) 0);
+        CUBIC_METRE  = m3;
+        KILOGRAM     = kg;
+        HECTARE      = add(m2, ten4,  "ha",        ACCEPTED,               (short) 0);
+        LITRE        = add(m3, milli, "L", (byte) (ACCEPTED | PREFIXABLE), (short) 0);
+        GRAM         = add(kg, milli, "g", (byte) (ACCEPTED | PREFIXABLE), (short) 0);
+        HERTZ        = add(Frequency.class, new ScalarFactory<Frequency>() {@Override public Frequency create(double value, Unit<Frequency> unit) {return new Scalar.Frequency(value, unit);}}, time.pow(-1), "Hz", (byte) (SI | PREFIXABLE), (short) 0);
+        NEWTON       = add(Force.class,     new ScalarFactory<Force>    () {@Override public Force     create(double value, Unit<Force>     unit) {return new Scalar.Force    (value, unit);}}, force,        "N",  (byte) (SI | PREFIXABLE), (short) 0);
+        JOULE        = add(Energy.class,    new ScalarFactory<Energy>   () {@Override public Energy    create(double value, Unit<Energy>    unit) {return new Scalar.Energy   (value, unit);}}, energy,       "J",  (byte) (SI | PREFIXABLE), (short) 0);
+        WATT         = add(Power.class,     new ScalarFactory<Power>    () {@Override public Power     create(double value, Unit<Power>     unit) {return new Scalar.Power    (value, unit);}}, power,        "W",  (byte) (SI | PREFIXABLE), (short) 0);
+        LUX          = add(Illuminance.class,       null, luminous.divide(area), "lx",  (byte) (SI | PREFIXABLE), (short) 0);
+        LUMEN        = add(LuminousFlux.class,      null, luminous,              "lm",  (byte) (SI | PREFIXABLE), (short) 0);
+        CANDELA      = add(LuminousIntensity.class, null, luminous,              "cd",  (byte) (SI | PREFIXABLE), (short) 0);    // Must be after Lumen.
+        MOLE         = add(AmountOfSubstance.class, null, amount,                "mol", (byte) (SI | PREFIXABLE), (short) 0);
+        STERADIAN    = add(SolidAngle.class,        null, dimensionless,         "sr",  (byte) (SI | PREFIXABLE), (short) 0);
         /*
          * All Unit<Dimensionless>.
          * Note: JDK8 branch uses method references instead than inner classes.
          */
-        PERCENT = add(one, centi,                                               "%",     UnitRegistry.OTHER, (short) 0);
-        PPM     = add(one, micro,                                               "ppm",   UnitRegistry.OTHER, (short) 9202);
-        PSU     = add(Dimensionless.class, dimensionlessFactory, dimensionless, "psu",   UnitRegistry.OTHER, (short) 0);
-        SIGMA   = add(Dimensionless.class, dimensionlessFactory, dimensionless, "sigma", UnitRegistry.OTHER, (short) 0);
-        PIXEL   = add(Dimensionless.class, dimensionlessFactory, dimensionless, "px",    UnitRegistry.OTHER, (short) 0);
+        PERCENT = add(one, centi,                                               "%",     OTHER, (short) 0);
+        PPM     = add(one, micro,                                               "ppm",   OTHER, (short) 9202);
+        PSU     = add(Dimensionless.class, dimensionlessFactory, dimensionless, "psu",   OTHER, (short) 0);
+        SIGMA   = add(Dimensionless.class, dimensionlessFactory, dimensionless, "sigma", OTHER, (short) 0);
+        PIXEL   = add(Dimensionless.class, dimensionlessFactory, dimensionless, "px",    OTHER, (short) 0);
         UNITY   = UnitRegistry.init(one);  // Must be last in order to take precedence over all other units associated to UnitDimension.NONE.
 
         UnitRegistry.alias(UNITY,       Short.valueOf((short) 9203));

Modified: sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties [UTF-8] Thu Apr 27 16:17:33 2017
@@ -12,12 +12,17 @@ degree\ north=�
 degree\ south=�
 degree\ west=�
 feet=ft
+kilograms=kg
 grade=grad
 grades=grad
 gradian=grad
 gradians=grad
 grads=grad
+grams=g
 hours=h
+l=L
+liters=L
+litres=L
 mbar=hPa
 millibar=hPa
 millibars=hPa

Modified: sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties [ISO-8859-1] Thu Apr 27 16:17:33 2017
@@ -11,6 +11,7 @@ F=farad
 ft=foot
 ftCla=Clarke\u2019s foot
 ftUS=US survey foot
+g=gram
 grad=grad
 h=hour
 ha=hectare
@@ -23,12 +24,13 @@ J=joule
 kg=kilogram
 km=kilometre
 km\u2215h=kilometres per hour
+L=litre
 lm=lumen
 lx=lux
 M=nautical mile
 m=metre
-m\u00b2=square metre
-m\u00b3=cubic metre
+m²=square metre
+m³=cubic metre
 m\u2215s=metres per second
 mi=statute mile
 min=minute
@@ -52,11 +54,11 @@ V=volt
 W=watt
 Wb=weber
 wk=week
-\u00b5rad=microradian
+µrad=microradian
 \u03a9=ohm
 %=percentage
-\u00b0=degree
+°=degree
 \u2032=arc-minute
 \u2033=arc-second
-\u00b0C=Celsius
-\u00b0F=Fahrenheit
+°C=Celsius
+°F=Fahrenheit

Modified: sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties [ISO-8859-1] Thu Apr 27 16:17:33 2017
@@ -1,6 +1,7 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
 cm=centimeter
 km=kilometer
+L=liter
 m=meter
 m²=square meter
 m³=cubic meter

Modified: sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties [ISO-8859-1] Thu Apr 27 16:17:33 2017
@@ -1,33 +1,34 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
-A=amp\u00e8re
-a=ann\u00e9e
-atm=atmosph\u00e8re
-cm=centim\u00e8tre
+A=ampère
+a=année
+atm=atmosphère
+cm=centimètre
 d=jour
 ft=pied
 ftCla=pied de Clarke
-ftUS=pied am\u00e9ricain
+ftUS=pied américain
+g=gramme
 grad=grade
 h=heure
 in=pouce
 kg=kilogramme
-km=kilom\u00e8tre
-km\u2215h=kilom\u00e8tres par heure
+km=kilomètre
+km\u2215h=kilomètres par heure
 M=mille marin international
-m=m\u00e8tre
-m\u00b2=m\u00e8tre carr\u00e9
-m\u00b3=m\u00e8tre cube
-m\u2215s=m\u00e8tres par seconde
+m=mètre
+m²=mètre carré
+m³=mètre cube
+m\u2215s=mètres par seconde
 mi=mille terrestre international
-mm=millim\u00e8tre
+mm=millimètre
 ms=milliseconde
-nm=nanom\u00e8tre
+nm=nanomètre
 ppm=parties par million
 s=seconde
-sr=st\u00e9radian
-unity=unit\u00e9
+sr=stéradian
+unity=unité
 wk=semaine
 %=pourcentage
-\u00b0=degr\u00e9e
+°=degrée
 \u2032=arc-minute
 \u2033=arc-seconde

Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -17,8 +17,10 @@
 package org.apache.sis.measure;
 
 import java.lang.reflect.Field;
+import javax.measure.IncommensurableException;
 import javax.measure.Unit;
 import javax.measure.UnitConverter;
+import javax.measure.quantity.Volume;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
@@ -30,7 +32,7 @@ import static org.apache.sis.test.Assert
 /**
  * Tests the {@link ConventionalUnit} class. This class tests also the {@link SystemUnit#multiply(double)} and
  * {@link SystemUnit#divide(double)} methods since they are used for creating {@code ConventionalUnit} instances,
- * but those methods just delegate to {@link ConventionalUnit#create(SystemUnit, UnitConverter)}.
+ * but those methods just delegate to {@link ConventionalUnit#create(AbstractUnit, UnitConverter)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
@@ -70,7 +72,9 @@ public final strictfp class Conventional
         verify(Units.PASCAL,            Units.PASCAL,                "Pa",     1);
         verify(Units.PASCAL,            Units.HECTOPASCAL,          "hPa",   100);
         verify(Units.METRES_PER_SECOND, Units.KILOMETRES_PER_HOUR, "km∕h",  0.06);
+        verify(Units.CUBIC_METRE,       Units.LITRE,                  "L",  1E-3);
         verify(Units.KILOGRAM,          Units.KILOGRAM,              "kg",     1);
+        verify(Units.KILOGRAM,          Units.GRAM,                   "g",  1E-3);
         verify(Units.UNITY,             Units.UNITY,                   "",     1);
         verify(Units.UNITY,             Units.PERCENT,                "%",  1E-2);
         verify(Units.UNITY,             Units.PPM,                  "ppm",  1E-6);
@@ -240,6 +244,68 @@ public final strictfp class Conventional
     }
 
     /**
+     * Verifies that the given units derived from litres ({@code u1}) is equivalent to the given units derived
+     * from cubic metres ({@code u2}). The conversion between those two units is expected to be identity.
+     */
+    private static void assertEquivalent(final String s1, final Unit<Volume> u1,
+                                         final String s2, final Unit<Volume> u2)
+            throws IncommensurableException
+    {
+        assertEquals("unit1.symbol", s1, u1.getSymbol());
+        assertEquals("unit2.symbol", s2, u2.getSymbol());
+        assertTrue("getConverterTo(…).isIdentity", u1.getConverterTo(u2).isIdentity());
+        assertTrue("getConverterTo(…).isIdentity", u2.getConverterTo(u1).isIdentity());
+        assertTrue("getConverterTo(…).isIdentity", u1.getConverterToAny(u2).isIdentity());
+        assertTrue("getConverterTo(…).isIdentity", u2.getConverterToAny(u1).isIdentity());
+    }
+
+    /**
+     * Tests the equivalence between litres and cubic metres.
+     * The litre unit is handled as a special case, since it is not a SI unit but can have SI prefix.
+     *
+     * @throws IncommensurableException if {@link Unit#getConverterToAny(Unit)} failed.
+     */
+    @Test
+    @DependsOnMethod("verifyPrefixes")
+    public void testVolumeEquivalences() throws IncommensurableException {
+        assertEquivalent(  "L", Units.LITRE.divide  (1E+00),  "dm³", Units.CUBIC_METRE.divide  (1E+03));
+        assertEquivalent( "mL", Units.LITRE.divide  (1E+03),  "cm³", Units.CUBIC_METRE.divide  (1E+06));
+        assertEquivalent( "µL", Units.LITRE.divide  (1E+06),  "mm³", Units.CUBIC_METRE.divide  (1E+09));
+        assertEquivalent( "fL", Units.LITRE.divide  (1E+15),  "µm³", Units.CUBIC_METRE.divide  (1E+18));
+        assertEquivalent( "yL", Units.LITRE.divide  (1E+24),  "nm³", Units.CUBIC_METRE.divide  (1E+27));
+        assertEquivalent( "kL", Units.LITRE.multiply(1E+03),   "m³", Units.CUBIC_METRE.divide  (1E+00));
+        assertEquivalent( "ML", Units.LITRE.multiply(1E+06), "dam³", Units.CUBIC_METRE.multiply(1E+03));
+        assertEquivalent( "GL", Units.LITRE.multiply(1E+09),  "hm³", Units.CUBIC_METRE.multiply(1E+06));
+        assertEquivalent( "TL", Units.LITRE.multiply(1E+12),  "km³", Units.CUBIC_METRE.multiply(1E+09));
+        assertEquivalent( "ZL", Units.LITRE.multiply(1E+21),  "Mm³", Units.CUBIC_METRE.multiply(1E+18));
+        assertEquals    ( "dL", Units.LITRE.divide  (1E+01).getSymbol());
+        assertEquals    ( "cL", Units.LITRE.divide  (1E+02).getSymbol());
+        assertEquals    ( "nL", Units.LITRE.divide  (1E+09).getSymbol());
+        assertEquals    ( "pL", Units.LITRE.divide  (1E+12).getSymbol());
+        assertEquals    ( "aL", Units.LITRE.divide  (1E+18).getSymbol());
+        assertEquals    ( "zL", Units.LITRE.divide  (1E+21).getSymbol());
+        assertEquals    ("daL", Units.LITRE.multiply(1E+01).getSymbol());
+        assertEquals    ( "hL", Units.LITRE.multiply(1E+02).getSymbol());
+        assertEquals    ( "PL", Units.LITRE.multiply(1E+15).getSymbol());
+        assertEquals    ( "EL", Units.LITRE.multiply(1E+18).getSymbol());
+        assertEquals    ( "YL", Units.LITRE.multiply(1E+24).getSymbol());
+    }
+
+    /**
+     * Tests conversion between litres and cubic metres.
+     */
+    @Test
+    @DependsOnMethod("testVolumeEquivalences")
+    public void testVolumeConversions() {
+        final Unit<Volume>  l  = Units.LITRE;
+        final Unit<Volume> cl  = Units.LITRE.divide(100);
+        final Unit<Volume> ml  = Units.LITRE.divide(1000);
+        final Unit<Volume> cm3 = Units.CUBIC_METRE.divide(1E+06);
+        assertEquals("4 L to ml", 4000,  l.getConverterTo(ml) .convert(4), STRICT);
+        assertEquals("4 cL to cm³", 40, cl.getConverterTo(cm3).convert(4), STRICT);
+    }
+
+    /**
      * Serializes some units, deserializes them and verifies that we get the same instance.
      */
     @Test

Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java?rev=1792917&r1=1792916&r2=1792917&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java [UTF-8] Thu Apr 27 16:17:33 2017
@@ -53,7 +53,7 @@ public final strictfp class SystemUnitTe
      *
      * @throws ReflectiveOperationException if an error occurred while iterating over the field values.
      *
-     * @see ConventionalUnit#create(SystemUnit, UnitConverter)
+     * @see ConventionalUnit#create(AbstractUnit, UnitConverter)
      */
     @Test
     public void verifyRelatedUnits() throws ReflectiveOperationException {



Mime
View raw message