sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1766421 - in /sis/branches/JDK8: core/ core/sis-utility/src/main/java/org/apache/sis/measure/ core/sis-utility/src/main/java/org/apache/sis/util/ core/sis-utility/src/main/resources/org/apache/sis/measure/ core/sis-utility/src/test/java/or...
Date Mon, 24 Oct 2016 15:38:37 GMT
Author: desruisseaux
Date: Mon Oct 24 15:38:37 2016
New Revision: 1766421

URL: http://svn.apache.org/viewvc?rev=1766421&view=rev
Log:
Minimalist support of UnitFormat.parse(...) with only with label, symbol and their exponent for now.
Remove the tec.units dependency.

Added:
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties   (with props)
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties   (with props)
Modified:
    sis/branches/JDK8/core/pom.xml
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties
    sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
    sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties

Modified: sis/branches/JDK8/core/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/pom.xml?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/pom.xml (original)
+++ sis/branches/JDK8/core/pom.xml Mon Oct 24 15:38:37 2016
@@ -170,10 +170,6 @@
       <artifactId>unit-api</artifactId>
     </dependency>
     <dependency>
-      <groupId>tec.units</groupId>
-      <artifactId>unit-ri</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.opengis</groupId>
       <artifactId>geoapi-pending</artifactId>
     </dependency>

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Mon Oct 24 15:38:37 2016
@@ -16,9 +16,9 @@
  */
 package org.apache.sis.measure;
 
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.ResourceBundle;
 import java.util.MissingResourceException;
 import java.io.ObjectStreamException;
 import java.io.Serializable;
@@ -116,11 +116,13 @@ abstract class AbstractUnit<Q extends Qu
      * If this unit exists in the EPSG database, then this method should return the name as specified in the database.
      *
      * @return the unit name, or {@code null} if this unit has no specific name.
+     *
+     * @see UnitFormat#format(Unit, Appendable)
      */
     @Override
     public final String getName() {
         try {
-            return ResourceBundle.getBundle(UnitFormat.RESOURCES).getString(symbol);
+            return UnitFormat.getBundle(Locale.getDefault()).getString(symbol);
         } catch (MissingResourceException e) {
             return null;
         }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java [UTF-8] Mon Oct 24 15:38:37 2016
@@ -337,7 +337,7 @@ final class UnitDimension implements Dim
     public String toString() {
         final StringBuilder buffer = new StringBuilder(8);
         try {
-            UnitFormat.formatComponents(components, buffer);
+            UnitFormat.formatComponents(components, UnitFormat.Style.SYMBOL, buffer);
         } catch (IOException e) {
             throw new AssertionError(e);      // Should never happen since we are writting to a StringBuilder.
         }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] Mon Oct 24 15:38:37 2016
@@ -25,6 +25,8 @@ import java.util.Locale;
 import java.text.Format;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
+import java.util.ResourceBundle;
+import java.util.MissingResourceException;
 import java.io.IOException;
 import javax.measure.Dimension;
 import javax.measure.Unit;
@@ -33,14 +35,13 @@ import org.apache.sis.internal.util.Cons
 import org.apache.sis.internal.util.DefinitionURI;
 import org.apache.sis.internal.util.XPaths;
 import org.apache.sis.math.Fraction;
-import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters;
-import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CorruptedObjectException;
+import org.apache.sis.util.collection.WeakValueHashMap;
 
 
 /**
@@ -78,18 +79,6 @@ public class UnitFormat extends Format i
     private static final long serialVersionUID = -3064428584419360693L;
 
     /**
-     * The resource bundles for {@linkplain AbstractUnit#getName() unit names}.
-     */
-    static final String RESOURCES = "org.apache.sis.measure.UnitNames";
-
-    /**
-     * The suffixes that NetCDF files sometime put after the "degrees" unit.
-     * Suffix at even index are for axes having the standard geometric direction,
-     * while suffix at odd index are for axes having the reverse direction.
-     */
-    private static final String[] CARDINAL_DIRECTIONS = {"east", "west", "north", "south"};
-
-    /**
      * The default instance used by {@link Units#valueOf(String)} for parsing units of measurement.
      * While {@code UnitFormat} is generally not thread-safe, this particular instance is safe if
      * we never invoke any setter method.
@@ -135,8 +124,10 @@ public class UnitFormat extends Format i
          * they exist (e.g. U+212A “K” for Kelvin sign), <i>etc.</i>
          *
          * <p>This is the default style of {@link UnitFormat}.</p>
+         *
+         * @see Unit#getSymbol()
          */
-        SYMBOL,
+        SYMBOL('⋅', '∕'),
 
         /**
          * Format unit symbols using the Unified Code for Units of Measure (UCUM) syntax.
@@ -144,37 +135,124 @@ public class UnitFormat extends Format i
          *
          * @see org.apache.sis.util.CharSequences#toASCII(CharSequence)
          */
-        UCUM,
+        UCUM('.', '/') {
+            /** Replace non-ASCII characters on a "best effort" basis. */
+            @Override Appendable appendSymbol(final Appendable toAppendTo, final String value) throws IOException {
+                return toAppendTo.append(CharSequences.toASCII(value));
+            }
+
+            /** Formats the power for a unit symbol. */
+            @Override void appendPower(final Appendable toAppendTo, final int power) throws IOException {
+                toAppendTo.append(String.valueOf(power));
+            }
+
+            /** Actually illegal for UCUM, but at least ensure that it contains only ASCII characters. */
+            @Override void appendPower(final Appendable toAppendTo, final Fraction power) throws IOException {
+                toAppendTo.append("^(").append(String.valueOf(power.numerator))
+                           .append('/').append(String.valueOf(power.denominator)).append(')');
+            }
+        },
 
         /**
          * Format unit symbols as localized long names if known, or Unicode symbols otherwise.
+         *
+         * @see Unit#getName()
+         */
+        NAME('⋅', '∕');
+
+        /**
+         * Symbols to use for unit multiplications or divisions.
+         */
+        final char multiply, divide;
+
+        /**
+         * Creates a new style using the given symbols.
+         */
+        private Style(final char multiply, final char divide) {
+            this.multiply = multiply;
+            this.divide   = divide;
+        }
+
+        /**
+         * Appends a string that may contains Unicode characters. The enumeration is responsible
+         * for converting the Unicode characters into ASCII ones if needed.
+         */
+        Appendable appendSymbol(final Appendable toAppendTo, final String value) throws IOException {
+            return toAppendTo.append(value);
+        }
+
+        /**
+         * Appends an integer power. The power may be added as an exponent if allowed by the format style.
+         */
+        void appendPower(final Appendable toAppendTo, final int power) throws IOException {
+            if (power >= 0 && power <= 9) {
+                toAppendTo.append(Characters.toSuperScript((char) (power + '0')));
+            } else {
+                toAppendTo.append(String.valueOf(power));
+            }
+        }
+
+        /**
+         * Appends a rational power.
          */
-        NAME
+        void appendPower(final Appendable toAppendTo, final Fraction power) throws IOException {
+            final String value = power.toString();
+            if (value.length() == 1) {
+                toAppendTo.append('^').append(value);
+            } else {
+                toAppendTo.append("^(").append(value).append(')');
+            }
+        }
     }
 
     /**
-     * Symbols or names to use for formatting unit in replacement of the default unit symbols or names.
+     * Symbols or names to use for formatting unit in replacement to the default unit symbols or names.
      *
      * @see #label(Unit, String)
      */
-    private final Map<Unit<?>,String> labels;
+    private final Map<Unit<?>,String> unitToLabel;
 
     /**
      * Units associated to a given label (in addition to the system-wide {@link UnitRegistry}).
-     * This map is the converse of {@link #labels}.
+     * This map is the converse of {@link #unitToLabel}.
      *
      * @see #label(Unit, String)
      */
-    private final Map<String,Unit<?>> units;
+    private final Map<String,Unit<?>> labelToUnit;
+
+    /**
+     * The mapping from unit symbols to long localized names.
+     * Those resources are locale-dependent and loaded when first needed.
+     *
+     * @see #symbolToName()
+     */
+    private transient ResourceBundle symbolToName;
+
+    /**
+     * Mapping from long localized and unlocalized names to unit instances.
+     * This map is used only for parsing and created when first needed.
+     *
+     * @see #nameToUnit()
+     */
+    private transient volatile Map<String,Unit<?>> nameToUnit;
+
+    /**
+     * Cached values of {@link #nameToUnit}, for avoiding to load the same information many time and for saving memory
+     * if the user create many {@code UnitFormat} instances. Note that we do not cache {@link #symbolToName} because
+     * {@link ResourceBundle} already provides its own caching mechanism.
+     *
+     * @see #nameToUnit()
+     */
+    private static final WeakValueHashMap<Locale, Map<String,Unit<?>>> SHARED = new WeakValueHashMap<>(Locale.class);
 
     /**
      * Creates the unique {@link #INSTANCE}.
      */
     private UnitFormat() {
-        locale = Locale.ROOT;
-        style  = Style.SYMBOL;
-        labels = Collections.emptyMap();
-        units  = Collections.emptyMap();
+        locale      = Locale.ROOT;
+        style       = Style.SYMBOL;
+        unitToLabel = Collections.emptyMap();
+        labelToUnit = Collections.emptyMap();
     }
 
     /**
@@ -185,9 +263,9 @@ public class UnitFormat extends Format i
     public UnitFormat(final Locale locale) {
         ArgumentChecks.ensureNonNull("locale", locale);
         this.locale = locale;
-        style  = Style.SYMBOL;
-        labels = new HashMap<>();
-        units  = new HashMap<>();
+        style       = Style.SYMBOL;
+        unitToLabel = new HashMap<>();
+        labelToUnit = new HashMap<>();
     }
 
     /**
@@ -209,14 +287,9 @@ public class UnitFormat extends Format i
      */
     public void setLocale(final Locale locale) {
         ArgumentChecks.ensureNonNull("locale", locale);
-        this.locale = locale;
-    }
-
-    /**
-     * Returns {@code true} if the locale is the US one.
-     */
-    private boolean isLocaleUS() {
-        return locale.getCountry().equalsIgnoreCase("US");
+        this.locale  = locale;
+        symbolToName = null;            // Force reloading for the new locale.
+        nameToUnit   = null;
     }
 
     /**
@@ -254,29 +327,136 @@ public class UnitFormat extends Format i
      * Attaches a label to the specified unit.
      * If the specified label is already associated to another unit, then the previous association is discarded.
      *
+     * <p>The given label must be non-empty and can not ends with a digit, since such digit would be confused
+     * with unit power.</p>
+     *
      * @param  unit   the unit being labeled.
      * @param  label  the new label for the given unit.
+     * @throws IllegalArgumentException if the given label is not a valid unit name.
      */
     @Override
-    public void label(final Unit<?> unit, final String label) {
+    public void label(final Unit<?> unit, String label) {
         ArgumentChecks.ensureNonNull("unit",  unit);
-        ArgumentChecks.ensureNonNull("label", label);
-        final Unit<?> unitForOldLabel = units.remove(labels.put(unit, label));
-        final Unit<?> oldUnitForLabel = units.put(label, unit);
-        if (!unit.equals(oldUnitForLabel) && !label.equals(labels.remove(oldUnitForLabel))) {
+        label = CharSequences.trimWhitespaces(label);
+        ArgumentChecks.ensureNonEmpty("label", label);
+        int c = Character.codePointBefore(label, label.length());
+        if (Character.isBmpCodePoint(c)) {
+            c = Characters.toNormalScript((char) c);
+        }
+        if (Character.isDigit(c)) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "label", label));
+        }
+        final Unit<?> unitForOldLabel = labelToUnit.remove(unitToLabel.put(unit, label));
+        final Unit<?> oldUnitForLabel = labelToUnit.put(label, unit);
+        if (oldUnitForLabel != null && !oldUnitForLabel.equals(unit) && !label.equals(unitToLabel.remove(oldUnitForLabel))) {
             // Assuming there is no bug in our algorithm, this exception should never happen
             // unless this UnitFormat has been modified concurrently in another thread.
-            throw new CorruptedObjectException("labels");
+            throw new CorruptedObjectException("unitToLabel");
         }
         if (unitForOldLabel != null && !unitForOldLabel.equals(unit)) {
             // Assuming there is no bug in our algorithm, this exception should never happen
             // unless this UnitFormat has been modified concurrently in another thread.
-            throw new CorruptedObjectException("units");
+            throw new CorruptedObjectException("labelToUnit");
+        }
+    }
+
+    /**
+     * Loads the {@code UnitNames} resource bundle for the given locale.
+     */
+    static ResourceBundle getBundle(final Locale locale) {
+        return ResourceBundle.getBundle("org.apache.sis.measure.UnitNames", locale, UnitFormat.class.getClassLoader());
+    }
+
+    /**
+     * Returns the mapping from unit symbols to long localized names.
+     * This mapping is loaded when first needed and memorized as long as the locale does not change.
+     */
+    private ResourceBundle symbolToName() {
+        ResourceBundle r = symbolToName;
+        if (r == null) {
+            symbolToName = r = getBundle(locale);
+        }
+        return r;
+    }
+
+    /**
+     * Returns the mapping from long localized and unlocalized names to unit instances.
+     * This mapping is somewhat the converse of {@link #symbolToName()}, but includes
+     * international and American spelling of unit names in addition of localized names.
+     * The intend is to recognize "meter" as well as "metre" (together with, for example,
+     * "mètre" if and only if the locale language is French).
+     *
+     * <p>While we said that {@code UnitFormat} is not thread safe, we make an exception for this method
+     * for allowing the singleton {@link #INSTANCE} to parse symbols in a multi-threads environment.</p>
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    private Map<String,Unit<?>> nameToUnit() {
+        Map<String,Unit<?>> map = nameToUnit;
+        if (map == null) {
+            map = SHARED.get(locale);
+            if (map == null) {
+                map = new HashMap<>(128);
+                copy(locale, symbolToName(), map);
+                if (!locale.equals(Locale.US))   copy(Locale.US,   getBundle(Locale.US),   map);
+                if (!locale.equals(Locale.ROOT)) copy(Locale.ROOT, getBundle(Locale.ROOT), map);
+                /*
+                 * The UnitAliases file contains names that are not unit symbols and are not included in the UnitNames
+                 * property files neither. It contains longer names somtime used (for example "decimal degree" instead
+                 * of "degree"), some plural forms (for example "feet" instead of "foot") and a few common misspellings
+                 * (for exemple "Celcius" instead of "Celsius").
+                 */
+                final ResourceBundle r = ResourceBundle.getBundle("org.apache.sis.measure.UnitAliases", locale, UnitFormat.class.getClassLoader());
+                for (final String name : r.keySet()) {
+                    map.put(name, Units.get(r.getString(name)));
+                }
+                map = Collections.unmodifiableMap(map);
+                /*
+                 * Cache the map so we can share it with other UnitFormat instances.
+                 * Sharing is safe if the map is unmodifiable.
+                 */
+                synchronized (SHARED) {
+                    for (final Map<String,Unit<?>> existing : SHARED.values()) {
+                        if (map.equals(existing)) {
+                            map = existing;
+                            break;
+                        }
+                    }
+                    SHARED.put(locale, map);
+                }
+            }
+            nameToUnit = map;
+        }
+        return map;
+    }
+
+    /**
+     * Copies all entries from the given "symbols to names" mapping to the given "names to units" mapping.
+     * During this copy, keys are converted from symbols to names and values are converted from symbols to
+     * {@code Unit} instance. We use {@code Unit} values instead of their symbols because all {@code Unit}
+     * instances are created at {@link Units} class initialization anyway (so we do not create new instance
+     * here), and it avoid to retain references to the {@link String} instances loaded by the resource bundle.
+     */
+    private static void copy(final Locale locale, final ResourceBundle symbolToName, final Map<String,Unit<?>> nameToUnit) {
+        for (final String symbol : symbolToName.keySet()) {
+            nameToUnit.put(symbolToName.getString(symbol).toLowerCase(locale), Units.get(symbol));
         }
     }
 
     /**
      * Formats the specified unit.
+     * This method performs the first of the following actions that can be done.
+     *
+     * <ol>
+     *   <li>If a {@linkplain #label(Unit, String) label has been specified} for the given unit,
+     *       then that label is appended unconditionally.</li>
+     *   <li>Otherwise if the formatting style is {@link Style#NAME} and the {@link Unit#getName()} method
+     *       returns a non-null value, then that value is appended. {@code Unit} instances implemented by
+     *       Apache SIS are handled in a special way for localizing the name according the
+     *       {@linkplain #setLocale(Locale) locale specified to this format}.</li>
+     *   <li>Otherwise if the {@link Unit#getSymbol()} method returns a non-null value,
+     *       then that value is appended.</li>
+     *   <li>Otherwise a default symbol is created from the entries returned by {@link Unit#getBaseUnits()}.</li>
+     * </ol>
      *
      * @param  unit        the unit to format.
      * @param  toAppendTo  where to format the unit.
@@ -285,47 +465,83 @@ public class UnitFormat extends Format i
      */
     @Override
     public Appendable format(final Unit<?> unit, final Appendable toAppendTo) throws IOException {
+        ArgumentChecks.ensureNonNull("unit", unit);
+        ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
+        /*
+         * Choice 1: label specified by a call to label(Unit, String).
+         */
+        String label = unitToLabel.get(unit);
+        if (label != null) {
+            return toAppendTo.append(label);
+        }
         if (style == Style.NAME) {
             /*
-             * Following are specific to the WKT format, which is currently the only user of this method.
-             * If we invoke this method for other purposes, then we would need to provide more control on
-             * what kind of formatting is desired.
+             * Choice 2: value specified by Unit.getName(). We skip this check if the given Unit is an instance
+             * implemented by Apache SIS because  AbstractUnit.getName()  delegates to the same resource bundle
+             * than the one used by this block. We are better to use the resource bundle of the UnitFormat both
+             * for performance reasons and because the locale may not be the same.
              */
-            if (Units.UNITY.equals(unit)) {
-                return toAppendTo.append("unity");
-            } else if (Units.DEGREE.equals(unit)) {
-                return toAppendTo.append("degree");
-            } else if (Units.METRE.equals(unit)) {
-                return toAppendTo.append(isLocaleUS() ? "meter" : "metre");
-            } else if (Units.US_SURVEY_FOOT.equals(unit)) {
-                return toAppendTo.append("US survey foot");
-            } else if (Units.PPM.equals(unit)) {
-                return toAppendTo.append("parts per million");
+            if (!(unit instanceof AbstractUnit)) {
+                label = unit.getName();
+                if (label != null) {
+                    return toAppendTo.append(label);
+                }
+            } else {
+                label = unit.getSymbol();
+                if (label != null) {
+                    if (label.isEmpty()) {
+                        label = "unity";
+                    }
+                    // Following is not thread-safe, but it is okay since we do not use INSTANCE for unit names.
+                    final ResourceBundle names = symbolToName();
+                    try {
+                        label = names.getString(label);
+                    } catch (MissingResourceException e) {
+                        // Name not found; use the symbol as a fallback.
+                    }
+                    return toAppendTo.append(label);
+                }
             }
         }
-        String symbol = unit.getSymbol();
-        if (symbol != null) {
-            return toAppendTo.append(symbol);
+        /*
+         * Choice 3: if the unit has a specific symbol, appends that symbol.
+         */
+        label = unit.getSymbol();
+        if (label != null) {
+            return style.appendSymbol(toAppendTo, label);
         }
+        /*
+         * Choice 4: if all the above failed, fallback on a symbol created from the base units and their power.
+         * Note that this may produce more verbose symbols than needed since derived units like Volt or Watt are
+         * decomposed into their base SI units.
+         */
         Map<? extends Unit<?>, ? extends Number> components;
         if (unit instanceof AbstractUnit<?>) {
+            // In Apache SIS implementation, the powers may be ratios.
             components = ((AbstractUnit<?>) unit).getBaseSystemUnits();
         } else {
-            // Fallback for foreigner implementations.
+            // Fallback for foreigner implementations (powers restricted to integers).
             components = unit.getBaseUnits();
             if (components == null) {
                 components = Collections.singletonMap(unit, 1);
             }
         }
-        formatComponents(components, toAppendTo);
+        formatComponents(components, style, toAppendTo);
         return toAppendTo;
     }
 
     /**
      * Creates a new symbol (e.g. "m/s") from the given symbols and factors.
      * Keys in the given map can be either {@link Unit} or {@link Dimension} instances.
+     * Values in the given map are either {@link Integer} or {@link Fraction} instances.
+     *
+     * @param  components  the components of the symbol to format.
+     * @param  style       whether to allow Unicode characters.
+     * @param  toAppendTo  where to write the symbol.
      */
-    static void formatComponents(final Map<?, ? extends Number> components, final Appendable toAppendTo) throws IOException {
+    static void formatComponents(final Map<?, ? extends Number> components, final Style style, final Appendable toAppendTo)
+            throws IOException
+    {
         boolean isFirst = true;
         final List<Map.Entry<?,? extends Number>> deferred = new ArrayList<>(components.size());
         for (final Map.Entry<?,? extends Number> entry : components.entrySet()) {
@@ -333,23 +549,31 @@ public class UnitFormat extends Format i
             final int n = (power instanceof Fraction) ? ((Fraction) power).numerator : power.intValue();
             if (n > 0) {
                 if (!isFirst) {
-                    toAppendTo.append('⋅');
+                    toAppendTo.append(style.multiply);
                 }
                 isFirst = false;
-                formatComponent(entry, false, toAppendTo);
+                formatComponent(entry, false, style, toAppendTo);
             } else if (n != 0) {
                 deferred.add(entry);
             }
         }
-        if (!isFirst && deferred.size() == 1) {
-            formatComponent(deferred.get(0), true, toAppendTo.append('∕'));
-        } else {
+        // At this point, all numerators have been appended. Now append the denominators together.
+        if (!deferred.isEmpty()) {
+            toAppendTo.append(style.divide);
+            final boolean useParenthesis = (deferred.size() > 1);
+            if (useParenthesis) {
+                toAppendTo.append('(');
+            }
+            isFirst = true;
             for (final Map.Entry<?,? extends Number> entry : deferred) {
                 if (!isFirst) {
-                    toAppendTo.append('⋅');
+                    toAppendTo.append(style.multiply);
                 }
                 isFirst = false;
-                formatComponent(entry, false, toAppendTo);
+                formatComponent(entry, true, style, toAppendTo);
+            }
+            if (useParenthesis) {
+                toAppendTo.append(')');
             }
         }
     }
@@ -359,11 +583,12 @@ public class UnitFormat extends Format i
      *
      * @param  entry    the base unit or base dimension to format, together with its power.
      * @param  inverse  {@code true} for inverting the power sign.
+     * @param  style    whether to allow Unicode characters.
      */
-    private static void formatComponent(final Map.Entry<?,? extends Number> entry,
-            final boolean inverse, final Appendable toAppendTo) throws IOException
+    private static void formatComponent(final Map.Entry<?,? extends Number> entry, final boolean inverse,
+            final Style style, final Appendable toAppendTo) throws IOException
     {
-        formatSymbol(entry.getKey(), toAppendTo);
+        formatSymbol(entry.getKey(), style, toAppendTo);
         final Number power = entry.getValue();
         int n;
         if (power instanceof Fraction) {
@@ -372,12 +597,7 @@ public class UnitFormat extends Format i
                 if (inverse) {
                     f = f.negate();
                 }
-                final String t = f.toString();
-                if (t.length() == 1) {
-                    toAppendTo.append('^').append(t);
-                } else {
-                    toAppendTo.append("^(").append(t).append(')');
-                }
+                style.appendPower(toAppendTo, f);
                 return;
             }
             n = f.numerator;
@@ -386,10 +606,7 @@ public class UnitFormat extends Format i
         }
         if (inverse) n = -n;
         if (n != 1) {
-            final String t = String.valueOf(n);
-            for (int i=0; i<t.length(); i++) {
-                toAppendTo.append(Characters.toSuperScript(t.charAt(i)));
-            }
+            style.appendPower(toAppendTo, n);
         }
     }
 
@@ -397,9 +614,10 @@ public class UnitFormat extends Format i
      * Appends the symbol for the given base unit of base dimension, or "?" if no symbol was found.
      *
      * @param  base        the base unit or base dimension to format.
+     * @param  style       whether to allow Unicode characters.
      * @param  toAppendTo  where to append the symbol.
      */
-    private static void formatSymbol(final Object base, final Appendable toAppendTo) throws IOException {
+    private static void formatSymbol(final Object base, final Style style, final Appendable toAppendTo) throws IOException {
         if (base instanceof UnitDimension) {
             final char symbol = ((UnitDimension) base).symbol;
             if (symbol != 0) {
@@ -410,7 +628,7 @@ public class UnitFormat extends Format i
         if (base instanceof Unit<?>) {
             final String symbol = ((Unit<?>) base).getSymbol();
             if (symbol != null) {
-                toAppendTo.append(symbol);
+                style.appendSymbol(toAppendTo, symbol);
                 return;
             }
         }
@@ -418,7 +636,8 @@ public class UnitFormat extends Format i
     }
 
     /**
-     * Formats the specified unit.
+     * Formats the specified unit in the given buffer.
+     * This method delegates to {@link #format(Unit, Appendable)}.
      *
      * @param  unit        the unit to format.
      * @param  toAppendTo  where to format the unit.
@@ -436,6 +655,7 @@ public class UnitFormat extends Format i
 
     /**
      * Formats the given unit.
+     * This method delegates to {@link #format(Unit, Appendable)}.
      *
      * @param  unit  the unit to format.
      * @return the formatted unit.
@@ -450,9 +670,30 @@ public class UnitFormat extends Format i
     }
 
     /**
+     * Returns {@code true} if the given unit seems to be an URI.
+     * Examples:
+     * <ul>
+     *   <li>{@code "urn:ogc:def:uom:EPSG::9001"}</li>
+     *   <li>{@code "http://schemas.opengis.net/iso/19139/20070417/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])"}</li>
+     * </ul>
+     */
+    private static boolean isURI(final CharSequence uom) {
+        for (int i=uom.length(); --i>=0;) {
+            final char c = uom.charAt(i);
+            if (c == ':' || c == '#') {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Parses the given text as an instance of {@code Unit}.
      * If the parse completes without reading the entire length of the text, an exception is thrown.
      *
+     * <p>In addition to unit symbols like “m∕s”, this method accepts also authority codes like
+     * {@code "urn:ogc:def:uom:EPSG:####"}. See class javadoc for more information.</p>
+     *
      * @param  symbols  the unit symbols or URI to parse.
      * @return the unit parsed from the specified symbols.
      * @throws ParserException if a problem occurred while parsing the given symbols.
@@ -461,8 +702,7 @@ public class UnitFormat extends Format i
      */
     @Override
     public Unit<?> parse(final CharSequence symbols) throws ParserException {
-        String uom = CharSequences.trimWhitespaces(CharSequences.toASCII(symbols)).toString();
-        final int length = uom.length();
+        String uom = CharSequences.trimWhitespaces(symbols).toString();
         /*
          * Check for authority codes (currently only EPSG, but more could be added later).
          * If the unit is not an authority code (which is the most common case), then we
@@ -488,117 +728,97 @@ public class UnitFormat extends Format i
             }
         }
         /*
-         * Check for degrees units. Note that "deg" could be both angular and Celsius degrees.
-         * We try to resolve this ambiguity in the code below by looking for the "Celsius" suffix.
-         * Other suffixes commonly found in NetCDF files are "west", "east", "north" or "south".
-         * Those suffixes are ignored.
-         */
-        if (uom.regionMatches(true, 0, "deg", 0, 3)) {
-            switch (length) {
-                case 3: return Units.DEGREE;                // Exactly "deg"
-                case 4: {
-                    if (uom.charAt(3) == 'K') {             // Exactly "degK"
-                        return Units.KELVIN;
+         * Check for labels explicitly given by users. Those labels have precedence over the Apache SIS hard-coded
+         * symbols. If no explicit label was found, check for symbols and names known to this UnitFormat instance.
+         */
+        Unit<?> unit = labelToUnit.get(uom);
+        if (unit == null) {
+            unit = Units.get(uom);
+            if (unit == null) {
+                final int length = uom.length();
+                if (length == 0) {
+                    return Units.UNITY;
+                }
+                if (length >= 2) {
+                    /*
+                     * If the symbol ends with a digit (normal script or superscript), presume that this is the unit
+                     * exponent.  That exponent can be a Unicode character (only one character in current UnitFormat
+                     * implementation) or a number parseable with Integer.parseInt(String).
+                     */
+                    int  power = 1;
+                    int  i = length;
+                    char c = uom.charAt(--i);
+                    boolean canApply = false;
+                    if (Characters.isSuperScript(c)) {
+                        c = Characters.toNormalScript(c);
+                        if (c >= '0' && c <= '9') {
+                            power = c - '0';
+                            canApply = true;
+                        }
+                    } else if (c >= '0' && c <= '9') {
+                        do {
+                            c = uom.charAt(--i);
+                            if (c < '0' || c > '9') {
+                                if (c != '+' && c != '-') i++;
+                                try {
+                                    power = Integer.parseInt(uom.substring(i));
+                                } catch (NumberFormatException e) {
+                                    // Should never happen unless the number is larger than 'int' capacity.
+                                    throw (ParserException) new ParserException(Errors.format(
+                                            Errors.Keys.UnknownUnit_1, uom), symbols, i).initCause(e);
+                                }
+                                canApply = true;
+                                break;
+                            }
+                        } while (i != 0);
+                    }
+                    if (canApply) {
+                        uom = CharSequences.trimWhitespaces(uom.substring(0, i));
+                        unit = Units.get(uom);
+                        if (unit != null) {
+                            return unit.pow(power);
+                        }
                     }
-                    break;
                 }
-            }
-            String prefix = uom;
-            boolean isTemperature = false;
-            final int s = Math.max(uom.lastIndexOf(' '), uom.lastIndexOf('_'));
-            if (s >= 1) {
-                final String suffix = (String) CharSequences.trimWhitespaces(uom, s+1, length);
-                if (ArraysExt.containsIgnoreCase(CARDINAL_DIRECTIONS, suffix) || (isTemperature = isCelsius(suffix))) {
-                    prefix = (String) CharSequences.trimWhitespaces(uom, 0, s);       // Remove the suffix only if we recognized it.
+                /*
+                 * Check for degrees units. Note that "deg" could be both angular and Celsius degrees.
+                 * We try to resolve this ambiguity in the code below by looking for the "C" suffix.
+                 * We perform a special case for those checks because the above check for unit symbol
+                 * is case-sentive, the check for unit name (later) is case-insensitive, while this
+                 * check for "deg" is a mix of both.
+                 */
+                if (uom.regionMatches(true, 0, "deg", 0, 3)) {
+                    switch (length) {
+                        case 3: return Units.DEGREE;                    // Exactly "deg"  (ignoring case)
+                        case 4: switch (uom.charAt(3)) {
+                                    case 'K':                           // Unicode U+212A
+                                    case 'K': return Units.KELVIN;      // Exactly "degK" (ignoring case except for 'K')
+                                    case 'C': return Units.CELSIUS;
+                                }
+                    }
+                }
+                /*
+                 * At this point, we have determined that the label is not a known unit symbol.
+                 * It may be a unit name, in which case the label is not case-sensitive anymore.
+                 * The 'nameToUnit' map contains plural forms (declared in UnitAliases.properties),
+                 * but we make a special case for "degrees", "metres" and "meters" because they
+                 * appear in numerous places.
+                 */
+                String lc = uom.replace('_', ' ').toLowerCase(locale);
+                lc = CharSequences.replace(CharSequences.replace(CharSequences.replace(lc,
+                        "meters",  "meter"),
+                        "metres",  "metre"),
+                        "degrees", "degree").toString();
+                unit = nameToUnit().get(lc);
+                if (unit == null) {
+                    throw new ParserException(Errors.format(Errors.Keys.UnknownUnit_1, uom), symbols, 0);
                 }
             }
-            if (equalsIgnorePlural(prefix, "degree")) {
-                return isTemperature ? Units.CELSIUS : Units.DEGREE;
-            }
-        } else {
-            /*
-             * Check for unit symbols that do not begin with "deg". If a symbol begins
-             * with "deg", then the check should be put in the above block instead.
-             */
-            if (uom.equals("°")                      || equalsIgnorePlural(uom, "decimal_degree")) return Units.DEGREE;
-            if (uom.equalsIgnoreCase("arcsec"))                                                    return Units.ARC_SECOND;
-            if (uom.equalsIgnoreCase("rad")          || equalsIgnorePlural(uom, "radian"))         return Units.RADIAN;
-            if (equalsIgnorePlural(uom, "meter")     || equalsIgnorePlural(uom, "metre"))          return Units.METRE;
-            if (equalsIgnorePlural(uom, "kilometer") || equalsIgnorePlural(uom, "kilometre"))      return Units.KILOMETRE;
-            if (equalsIgnorePlural(uom, "week"))        return Units.WEEK;
-            if (equalsIgnorePlural(uom, "day"))         return Units.DAY;
-            if (equalsIgnorePlural(uom, "hour"))        return Units.HOUR;
-            if (equalsIgnorePlural(uom, "minute"))      return Units.MINUTE;
-            if (equalsIgnorePlural(uom, "second"))      return Units.SECOND;
-            if (equalsIgnorePlural(uom, "grade"))       return Units.GRAD;
-            if (equalsIgnorePlural(uom, "grad"))        return Units.GRAD;
-            if (isCelsius(uom))                         return Units.CELSIUS;
-            if (uom.isEmpty())                          return Units.UNITY;
-            if (uom.equalsIgnoreCase("US survey foot")) return Units.US_SURVEY_FOOT;
-            if (uom.equalsIgnoreCase("ppm"))            return Units.PPM;
-            if (uom.equalsIgnoreCase("psu"))            return Units.PSU;
-            if (uom.equalsIgnoreCase("sigma"))          return Units.SIGMA;
-            if (equalsIgnorePlural(uom, "pixel"))       return Units.PIXEL;
-        }
-        final Unit<?> unit;
-        try {
-            unit = tec.units.ri.format.SimpleUnitFormat.getInstance().parse(symbols);
-        } catch (ParserException e) {
-            // Provides a better error message than the default JSR-275 0.9.4 implementation.
-            throw Exceptions.setMessage(e, Errors.format(Errors.Keys.IllegalArgumentValue_2, "uom", uom), true);
-        }
-        /*
-         * Special case: JSR-275 version 0.6.1 parses "1/s" and "s-1" as "Baud", which is not what
-         * we use in geoscience. Replace "Baud" by "Hertz" if the symbol was not explicitely "Bd".
-         */
-        if (unit.isCompatible(Units.HERTZ) && !uom.equals("Bd")) {
-            return Units.HERTZ;
         }
         return unit;
     }
 
     /**
-     * Returns {@code true} if the given {@code uom} is equals to the given expected string,
-     * ignoring trailing {@code 's'} character (if any).
-     */
-    @SuppressWarnings("fallthrough")
-    private static boolean equalsIgnorePlural(final String uom, final String expected) {
-        final int length = expected.length();
-        switch (uom.length() - length) {
-            case 0:  break;                                                         // uom has exactly the expected length.
-            case 1:  if (Character.toLowerCase(uom.charAt(length)) == 's') break;   // else fallthrough.
-            default: return false;
-        }
-        return uom.regionMatches(true, 0, expected, 0, length);
-    }
-
-    /**
-     * Returns {@code true} if the given {@code uom} is equals to {@code "Celsius"} or {@code "Celcius"}.
-     * The later is a common misspelling.
-     */
-    private static boolean isCelsius(final String uom) {
-        return uom.equalsIgnoreCase("Celsius") || uom.equalsIgnoreCase("Celcius");
-    }
-
-    /**
-     * Returns {@code true} if the given unit seems to be an URI.
-     * Examples:
-     * <ul>
-     *   <li>{@code "urn:ogc:def:uom:EPSG::9001"}</li>
-     *   <li>{@code "http://schemas.opengis.net/iso/19139/20070417/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])"}</li>
-     * </ul>
-     */
-    private static boolean isURI(final CharSequence uom) {
-        for (int i=uom.length(); --i>=0;) {
-            final char c = uom.charAt(i);
-            if (c == ':' || c == '#') {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Parses the given text as an instance of {@code Unit}.
      *
      * @param  symbols  the unit symbols to parse.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8] Mon Oct 24 15:38:37 2016
@@ -23,6 +23,7 @@ import javax.measure.format.ParserExcept
 import javax.measure.Quantity;
 import javax.measure.quantity.*;
 import javax.measure.quantity.Angle;            // Because of name collision with Angle in this SIS package.
+import org.opengis.geometry.DirectPosition;     // For javadoc
 
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Workaround;
@@ -722,12 +723,12 @@ public final class Units extends Static
 
         UnitRegistry.alias(UNITY,       Short.valueOf((short) 9203));
         UnitRegistry.alias(DEGREE,      Short.valueOf(Constants.EPSG_AXIS_DEGREES));
-        UnitRegistry.alias(DEGREE,    "deg");
         UnitRegistry.alias(ARC_MINUTE,  "'");
         UnitRegistry.alias(ARC_SECOND, "\"");
         UnitRegistry.alias(KELVIN,      "K");       // Ordinary "K" letter (not the dedicated Unicode character).
         UnitRegistry.alias(CELSIUS,    "°C");
         UnitRegistry.alias(CELSIUS,   "Cel");
+        UnitRegistry.alias(GRAD,      "gon");
 
         initialized = true;
     }
@@ -1022,6 +1023,7 @@ public final class Units extends Static
      *
      * @since 0.8
      */
+    @SuppressWarnings("fallthrough")
     public static Number[] coefficients(final UnitConverter converter) {
         if (converter != null) {
             if (converter instanceof AbstractConverter) {
@@ -1035,7 +1037,7 @@ public final class Units extends Static
                 final double scale  = converter.convert(1) - offset;
                 final Number[] c = new Number[(scale != 1) ? 2 : (offset != 0) ? 1 : 0];
                 switch (c.length) {
-                    case 2: c[1] = scale;
+                    case 2: c[1] = scale;       // Fall through
                     case 1: c[0] = offset;
                     case 0: break;
                 }
@@ -1052,6 +1054,8 @@ public final class Units extends Static
      * @param  converter  the converter for which we want the derivative at a given point, or {@code null}.
      * @param  value      the point at which to compute the derivative.
      * @return the derivative at the given point, or {@code NaN} if unknown.
+     *
+     * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#derivative(DirectPosition)
      */
     public static double derivative(final UnitConverter converter, final double value) {
         return AbstractConverter.derivative(converter, value);

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java [UTF-8] Mon Oct 24 15:38:37 2016
@@ -86,7 +86,7 @@ public final class Characters extends St
      * They are ASCII codes 32 to 125 inclusive except ! (33), # (35), $ (36), @ (64) and ` (96),
      * plus the addition of ° (176) despite being formally outside the ASCII character set.
      *
-     * @param  c The code point to test.
+     * @param  c  the code point to test.
      * @return {@code true} if the given code point is a valid WKT character.
      *
      * @see org.apache.sis.io.wkt.Transliterator
@@ -110,7 +110,7 @@ public final class Characters extends St
      * line separator}, a {@linkplain Character#PARAGRAPH_SEPARATOR paragraph separator} or one
      * of the {@code '\r'} or {@code '\n'} control characters.
      *
-     * @param  c The code point to test.
+     * @param  c  the code point to test.
      * @return {@code true} if the given code point is a line or paragraph separator.
      *
      * @see #LINE_SEPARATOR
@@ -130,7 +130,7 @@ public final class Characters extends St
      * This method returns {@code true} if {@code c} is between {@code '0'} and {@code '9'} inclusive,
      * or between {@code 'A'} and {@code 'F'} inclusive, or between {@code 'a'} and {@code 'f'} inclusive.
      *
-     * @param  c The character to test.
+     * @param  c  the character to test.
      * @return {@code true} if the given character is an hexadecimal digit.
      *
      * @since 0.5
@@ -152,16 +152,16 @@ public final class Characters extends St
      *   ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ
      * }
      *
-     * @param  c The character to test.
+     * @param  c  the character to test.
      * @return {@code true} if the given character is a superscript.
      */
     public static boolean isSuperScript(final int c) {
         switch (c) {
             case '¹':      // Legacy values in "Latin-1 supplement" space: 00B9, 00B2 and 00B3.
-            case '²':      // Those values are outside the normal [2070 … 207F] range.
+            case '²':      // Those values are outside the usual [2070 … 207F] range.
             case '³':      return true;
-            case '\u2071': // Would be the '¹', '²' and '³' values if they were declared in the
-            case '\u2072': // normal range. Since they are not, those values are unassigned.
+            case '\u2071': // Would be the '¹', '²' and '³' values if they were declared in the usual range.
+            case '\u2072': // Since they are not, those values are unassigned.
             case '\u2073': return false;
             default:       return (c >= '⁰' && c <= 'ⁿ');
         }
@@ -175,7 +175,7 @@ public final class Characters extends St
      *   ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎
      * }
      *
-     * @param  c The character to test.
+     * @param  c  the character to test.
      * @return {@code true} if the given character is a subscript.
      */
     public static boolean isSubScript(final int c) {
@@ -190,9 +190,8 @@ public final class Characters extends St
      *     0 1 2 3 4 5 6 7 8 9 + - = ( ) n
      * }
      *
-     * @param  c The character to convert.
-     * @return The given character as a superscript, or {@code c}
-     *         if the given character can not be converted.
+     * @param  c  the character to convert.
+     * @return the given character as a superscript, or {@code c} if the given character can not be converted.
      */
     public static char toSuperScript(char c) {
         switch (c) {
@@ -223,9 +222,8 @@ public final class Characters extends St
      *     0 1 2 3 4 5 6 7 8 9 + - = ( )
      * }
      *
-     * @param  c The character to convert.
-     * @return The given character as a subscript, or {@code c}
-     *         if the given character can not be converted.
+     * @param  c  the character to convert.
+     * @return the given character as a subscript, or {@code c} if the given character can not be converted.
      */
     public static char toSubScript(char c) {
         switch (c) {
@@ -247,8 +245,8 @@ public final class Characters extends St
     /**
      * Converts the given character argument to normal script.
      *
-     * @param  c The character to convert.
-     * @return The given character as a normal script, or {@code c} if the
+     * @param  c  the character to convert.
+     * @return the given character as a normal script, or {@code c} if the
      *         given character was not a superscript or a subscript.
      */
     public static char toNormalScript(char c) {
@@ -369,8 +367,8 @@ public final class Characters extends St
         /**
          * Creates a new subset of the given name.
          *
-         * @param name  The subset name.
-         * @param types A bitmask of character types.
+         * @param  name   the subset name.
+         * @param  types  a bitmask of character types.
          */
         Filter(final String name, final long types) {
             super(name);
@@ -380,7 +378,7 @@ public final class Characters extends St
         /**
          * Returns {@code true} if this subset contains the given Unicode character.
          *
-         * @param  codePoint The Unicode character, as a code point value.
+         * @param  codePoint  the Unicode character, as a code point value.
          * @return {@code true} if this subset contains the given character.
          */
         public boolean contains(final int codePoint) {
@@ -395,7 +393,7 @@ public final class Characters extends St
          * {@link Character#DECIMAL_DIGIT_NUMBER DECIMAL_DIGIT_NUMBER} or
          * {@link Character#SPACE_SEPARATOR      SPACE_SEPARATOR}.
          *
-         * @param  type One of the {@link Character} constants.
+         * @param  type  one of the {@link Character} constants.
          * @return {@code true} if this subset contains the characters of the given type.
          *
          * @see Character#getType(int)
@@ -407,8 +405,8 @@ public final class Characters extends St
         /**
          * Returns a subset representing the union of all Unicode characters of the given types.
          *
-         * @param  types The character types, as {@link Character} constants.
-         * @return The subset of Unicode characters of the given type.
+         * @param  types  the character types, as {@link Character} constants.
+         * @return the subset of Unicode characters of the given type.
          *
          * @see Character#LOWERCASE_LETTER
          * @see Character#UPPERCASE_LETTER

Added: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties?rev=1766421&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties (added)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties [UTF-8] Mon Oct 24 15:38:37 2016
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
+
+# "degrees", "metres" and "meters" are handled as special cases.
+arcsec=\u2033
+days=d
+decimal\ degree=\u00b0
+degree\ celsius=\u2103
+degree\ kelvin=\u212a
+degree\ east=\u00b0
+degree\ north=\u00b0
+degree\ south=\u00b0
+degree\ west=\u00b0
+feet=ft
+grade=grad
+grades=grad
+gradian=grad
+gradians=grad
+grads=grad
+hours=h
+minutes=min
+radians=rad
+seconds=s
+weeks=wk
+
+# Common misspellings
+celcius=\u2103
+degree\ celcius=\u2103

Propchange: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitAliases.properties
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames.properties [ISO-8859-1] Mon Oct 24 15:38:37 2016
@@ -1,38 +1,39 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
-rad=radian
-grad=grad
-\u00b0=degree
-\u2032=arc-minute
-\u2033=arc-second
-m=metre
-nm=nanometre
-mm=millimetre
+a=year
 cm=centimetre
-km=kilometre
-M=nautical mile
-mi=statute mile
-ft_US=US survey foot
+d=day
 ft=foot
-in=inch
-pt=point
-px=pixel
-%=percentage
-ppm=parts per million
-s=second
-ms=millisecond
-min=minute
+ft_US=US survey foot
+grad=grad
 h=hour
-d=day
-wk=week
-a=year
-Pa=pascal
 hPa=hectopascal
+Hz=hertz
+in=inch
+J=joule
+\u212a=kelvin
+kg=kilogram
+km=kilometre
+M=nautical mile
+m=metre
 m\u00b2=square metre
 m\u00b3=cubic metre
-kg=kilogram
+mi=statute mile
+min=minute
+mm=millimetre
+ms=millisecond
 N=newton
-J=joule
+nm=nanometre
+Pa=pascal
+ppm=parts per million
+pt=point
+px=pixel
+rad=radian
+s=second
+unity=unity
 W=watt
-\u212a=kelvin
-\u2103=celsius
-Hz=hertz
+wk=week
+%=percentage
+\u00b0=degree
+\u2032=arc-minute
+\u2033=arc-second
+\u2103=Celsius

Added: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties?rev=1766421&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties (added)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties [ISO-8859-1] Mon Oct 24 15:38:37 2016
@@ -0,0 +1 @@
+# Inherit all definitions from UnitNames.properties.

Propchange: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en.properties
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=ISO-8859-1

Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_en_US.properties [ISO-8859-1] Mon Oct 24 15:38:37 2016
@@ -1,2 +1,9 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
+cm=centimeter
+km=kilometer
+m=meter
+m\u00b2=square meter
+m\u00b3=cubic meter
 mi=international mile
+mm=millimeter
+nm=nanometer

Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/org/apache/sis/measure/UnitNames_fr.properties [ISO-8859-1] Mon Oct 24 15:38:37 2016
@@ -1,3 +1,30 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
-mi=mille terrestre international
+a=ann\u00e9e
+cm=centim\u00e8tre
+d=jour
+ft=pied
+grad=grade
+h=heure
+in=pouce
+kg=kilogramme
+km=kilom\u00e8tre
 M=mille marin international
+m=m\u00e8tre
+m\u00b2=m\u00e8tre carr\u00e9
+m\u00b3=m\u00e8tre cube
+mi=mille terrestre international
+min=minute
+mm=millim\u00e8tre
+ms=milliseconde
+nm=nanom\u00e8tre
+ppm=parties par million
+pt=point
+px=pixel
+rad=radian
+s=seconde
+unity=unit\u00e9
+wk=semaine
+%=pourcentage
+\u00b0=degr\u00e9e
+\u2032=arc-minute
+\u2033=arc-seconde

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java [UTF-8] Mon Oct 24 15:38:37 2016
@@ -18,8 +18,10 @@ package org.apache.sis.measure;
 
 import java.util.Set;
 import java.util.HashSet;
+import java.util.Locale;
 import java.lang.reflect.Field;
 import javax.measure.Unit;
+import javax.measure.format.ParserException;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
@@ -79,8 +81,8 @@ public final strictfp class UnitFormatTe
         verify(declared, "DAY",                 "T",        "d",     Units.DAY);
         verify(declared, "WEEK",                "T",        "wk",    Units.WEEK);
         verify(declared, "TROPICAL_YEAR",       "T",        "a",     Units.TROPICAL_YEAR);
-        verify(declared, "PASCAL",              "M⋅L⁻¹⋅T⁻²", "Pa",    Units.PASCAL);
-        verify(declared, "HECTOPASCAL",         "M⋅L⁻¹⋅T⁻²", "hPa",   Units.HECTOPASCAL);
+        verify(declared, "PASCAL",              "M∕(L⋅T²)", "Pa",    Units.PASCAL);
+        verify(declared, "HECTOPASCAL",         "M∕(L⋅T²)", "hPa",   Units.HECTOPASCAL);
         verify(declared, "SQUARE_METRE",        "L²",       "m²",    Units.SQUARE_METRE);
         verify(declared, "CUBIC_METRE",         "L³",       "m³",    Units.CUBIC_METRE);
         verify(declared, "METRES_PER_SECOND",   "L∕T",      "m∕s",   Units.METRES_PER_SECOND);
@@ -91,7 +93,7 @@ public final strictfp class UnitFormatTe
         verify(declared, "WATT",                "M⋅L²∕T³",  "W",     Units.WATT);
         verify(declared, "KELVIN",              "Θ",        "K",     Units.KELVIN);
         verify(declared, "CELSIUS",             "Θ",        "℃",     Units.CELSIUS);
-        verify(declared, "HERTZ",               "T⁻¹",      "Hz",    Units.HERTZ);
+        verify(declared, "HERTZ",               "∕T",       "Hz",    Units.HERTZ);
         verify(declared, "UNITY",               "",         "",      Units.UNITY);
         verify(declared, "PERCENT",             "",         "%",     Units.PERCENT);
         verify(declared, "PPM",                 "",         "ppm",   Units.PPM);
@@ -121,6 +123,135 @@ public final strictfp class UnitFormatTe
     @Test
     @DependsOnMethod("verifyUnitConstants")
     public void testRationalPower() {
-        assertEquals("T^(5⁄2)⋅M⁻¹⋅L⁻¹", UnitDimensionTest.specificDetectivity().toString());
+        assertEquals("T^(5⁄2)∕(M⋅L)", UnitDimensionTest.specificDetectivity().toString());
+    }
+
+    /**
+     * Tests {@link UnitFormat#label(Unit, String)}.
+     */
+    @Test
+    public void testLabel() {
+        final UnitFormat f = new UnitFormat(Locale.ENGLISH);
+        f.label(Units.METRE,  "mFoo");
+        f.label(Units.SECOND, "sFoo");
+        assertEquals("mFoo", f.format(Units.METRE));
+        assertEquals("sFoo", f.format(Units.SECOND));
+        assertSame(Units.METRE,  f.parse("mFoo"));
+        assertSame(Units.SECOND, f.parse("sFoo"));
+        /*
+         * Overwriting previous value should remove the assignment from "mFoo" to Units.METRE.
+         */
+        f.label(Units.METRE, "mètre");
+        assertEquals("mètre", f.format(Units.METRE));
+        assertEquals("sFoo",  f.format(Units.SECOND));
+        assertSame(Units.METRE, f.parse("mètre"));
+        try {
+            f.parse("mFoo");
+            fail("“mFoo” should not be assigned to unit anymore.");
+        } catch (ParserException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("mFoo"));
+        }
+        /*
+         * Verify that we can not specify invalid unit label.
+         */
+        try {
+            f.label(Units.METRE, "m¹");
+            fail("Should not accept labels ending with a digit.");
+        } catch (IllegalArgumentException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("m¹"));
+        }
+    }
+
+    /**
+     * Tests unit formatting with {@link UnitFormat.Style#NAME}.
+     */
+    @Test
+    public void testNameFormatting() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        f.setStyle(UnitFormat.Style.NAME);
+        assertEquals("metre",        f.format(Units.METRE));
+        assertEquals("kilometre",    f.format(Units.KILOMETRE));
+        assertEquals("second",       f.format(Units.SECOND));
+        assertEquals("square metre", f.format(Units.SQUARE_METRE));
+        assertEquals("Celsius",      f.format(Units.CELSIUS));          // Really upper-case "C" - this is a SI exception.
+
+        f.setLocale(Locale.US);
+        assertEquals("meter",        f.format(Units.METRE));
+        assertEquals("kilometer",    f.format(Units.KILOMETRE));
+        assertEquals("second",       f.format(Units.SECOND));
+        assertEquals("square meter", f.format(Units.SQUARE_METRE));
+        assertEquals("Celsius",      f.format(Units.CELSIUS));
+
+        f.setLocale(Locale.FRANCE);
+        assertEquals("mètre",        f.format(Units.METRE));
+        assertEquals("kilomètre",    f.format(Units.KILOMETRE));
+        assertEquals("seconde",      f.format(Units.SECOND));
+        assertEquals("mètre carré",  f.format(Units.SQUARE_METRE));
+        assertEquals("Celsius",      f.format(Units.CELSIUS));
+    }
+
+    /**
+     * Tests parsing of names.
+     */
+    @Test
+    public void testNameParsing() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        f.setStyle(UnitFormat.Style.NAME);                          // As a matter of principle, but actually ignored.
+        assertSame(Units.METRE,         f.parse("metre"));
+        assertSame(Units.METRE,         f.parse("metres"));
+        assertSame(Units.METRE,         f.parse("meter"));
+        assertSame(Units.METRE,         f.parse("meters"));
+        assertSame(Units.KILOMETRE,     f.parse("kilometre"));
+        assertSame(Units.KILOMETRE,     f.parse("kilometer"));
+        assertSame(Units.KILOMETRE,     f.parse("kilometres"));
+        assertSame(Units.KILOMETRE,     f.parse("kilometers"));
+        assertSame(Units.SQUARE_METRE,  f.parse("square metre"));
+        assertSame(Units.SQUARE_METRE,  f.parse("square_meters"));
+        assertSame(Units.DEGREE,        f.parse("degree"));
+        assertSame(Units.DEGREE,        f.parse("degrees"));
+        assertSame(Units.DEGREE,        f.parse("decimal degrees"));
+        assertSame(Units.DEGREE,        f.parse("degree north"));
+        assertSame(Units.DEGREE,        f.parse("degree_east"));
+        assertSame(Units.DEGREE,        f.parse("Degree West"));
+        assertSame(Units.KELVIN,        f.parse("degree Kelvin"));
+        assertSame(Units.CELSIUS,       f.parse("degree Celsius"));
+        assertSame(Units.WATT,          f.parse("watt"));
+        try {
+            f.parse("degree foo");
+            fail("Should not accept unknown unit.");
+        } catch (ParserException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("foo"));
+        }
+        // Tests with localisation.
+        try {
+            f.parse("mètre cube");
+            fail("Should not accept localized unit unless requested.");
+        } catch (ParserException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("mètre cube"));
+        }
+        f.setLocale(Locale.FRANCE);
+        assertSame(Units.CUBIC_METRE, f.parse("mètre cube"));
+    }
+
+    /**
+     * Tests parsing of symbols without arithmetic operations other than exponent.
+     */
+    @Test
+    public void testSymbolParsing() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        assertSame(Units.METRE,         f.parse("m"));
+        assertSame(Units.UNITY,         f.parse("m⁰"));
+        assertSame(Units.METRE,         f.parse("m¹"));
+        assertSame(Units.SQUARE_METRE,  f.parse("m²"));
+        assertSame(Units.CUBIC_METRE,   f.parse("m³"));
+        assertSame(Units.UNITY,         f.parse("m-0"));
+        assertSame(Units.METRE,         f.parse("m01"));
+        assertSame(Units.SQUARE_METRE,  f.parse("m2"));
+        assertSame(Units.CUBIC_METRE,   f.parse("m3"));
+        assertSame(Units.HERTZ,         f.parse("s-1"));
     }
 }

Modified: sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties?rev=1766421&r1=1766420&r2=1766421&view=diff
==============================================================================
--- sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/ide-project/NetBeans/nbproject/project.properties [ISO-8859-1] Mon Oct 24 15:38:37 2016
@@ -111,7 +111,6 @@ endorsed.classpath=
 javac.classpath=\
     ${project.GeoAPI}/dist/geoapi.jar:\
     ${maven.repository}/javax/measure/unit-api/${jsr363.version}/unit-api-${jsr363.version}.jar:\
-    ${maven.repository}/tec/units/unit-ri/${jsr363-ri.version}/unit-ri-${jsr363-ri.version}.jar:\
     ${maven.repository}/com/esri/geometry/esri-geometry-api/${geometry.version}/esri-geometry-api-${geometry.version}.jar:\
     ${maven.repository}/org/geonames/georss-rome/${georss.version}/georss-rome-${georss.version}.jar:\
     ${maven.repository}/rome/rome/${rome.version}/rome-${rome.version}.jar:\



Mime
View raw message