You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2016/10/27 22:53:45 UTC

svn commit: r1766907 [2/2] - in /sis/branches/JDK8: ./ core/sis-metadata/src/main/java/org/apache/sis/io/wkt/ core/sis-metadata/src/test/java/org/apache/sis/io/wkt/ core/sis-referencing/src/main/java/org/apache/sis/parameter/ core/sis-referencing/src/m...

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] Thu Oct 27 22:53:44 2016
@@ -178,7 +178,7 @@ public class AngleFormat extends Format
      * a string, this value may be specified to the {@link FieldPosition} constructor in order to
      * get the bounding index where fraction digits have been written.
      */
-    private static final int FRACTION_FIELD = 3; // Not yet implemented.
+    private static final int FRACTION_FIELD = 3;                        // Not yet implemented.
 
     /**
      * Constant for hemisphere field. When formatting a string, this value may be specified to the
@@ -346,13 +346,11 @@ public class AngleFormat extends Format
 
     /**
      * A temporary variable which may be set to the character iterator for which the
-     * attributes need to be set. IF non-null, then this is actually an instance of
-     * {@link FormattedCharacterIterator}. But we use the interface here for avoiding
-     * too early class loading.
+     * attributes need to be set.
      *
      * @see #formatToCharacterIterator(Object)
      */
-    private transient AttributedCharacterIterator characterIterator;
+    private transient FormattedCharacterIterator characterIterator;
 
     /**
      * Returns the number format, created when first needed.
@@ -377,7 +375,7 @@ public class AngleFormat extends Format
     /**
      * Constructs a new {@code AngleFormat} for the default pattern and the current default locale.
      *
-     * @return An angle format for the current default locale.
+     * @return an angle format for the current default locale.
      */
     public static AngleFormat getInstance() {
         return new AngleFormat();
@@ -386,8 +384,8 @@ public class AngleFormat extends Format
     /**
      * Constructs a new {@code AngleFormat} for the default pattern and the specified locale.
      *
-     * @param  locale The locale to use.
-     * @return An angle format for the given locale.
+     * @param  locale  the locale to use.
+     * @return an angle format for the given locale.
      */
     public static AngleFormat getInstance(final Locale locale) {
         return new AngleFormat(locale);
@@ -403,7 +401,7 @@ public class AngleFormat extends Format
     /**
      * Constructs a new {@code AngleFormat} for the default pattern and the specified locale.
      *
-     * @param  locale The locale to use.
+     * @param  locale  the locale to use.
      */
     @SuppressWarnings("PointlessBitwiseExpression")  // We rely on the compiler for simplifying the expression.
     public AngleFormat(final Locale locale) {
@@ -423,9 +421,9 @@ public class AngleFormat extends Format
     /**
      * Constructs a new {@code AngleFormat} for the specified pattern and the current default locale.
      *
-     * @param  pattern Pattern to use for parsing and formatting angles.
+     * @param  pattern  the pattern to use for parsing and formatting angles.
      *         See class description for an explanation of pattern syntax.
-     * @throws IllegalArgumentException If the specified pattern is illegal.
+     * @throws IllegalArgumentException if the specified pattern is illegal.
      */
     public AngleFormat(final String pattern) throws IllegalArgumentException {
         this(pattern, Locale.getDefault(Locale.Category.FORMAT));
@@ -434,10 +432,10 @@ public class AngleFormat extends Format
     /**
      * Constructs a new {@code AngleFormat} using the specified pattern and locale.
      *
-     * @param  pattern Pattern to use for parsing and formatting angles.
+     * @param  pattern  the pattern to use for parsing and formatting angles.
      *         See class description for an explanation of pattern syntax.
      * @param  locale Locale to use.
-     * @throws IllegalArgumentException If the specified pattern is illegal.
+     * @throws IllegalArgumentException if the specified pattern is illegal.
      */
     public AngleFormat(final String pattern, final Locale locale) throws IllegalArgumentException {
         ArgumentChecks.ensureNonEmpty("pattern", pattern);
@@ -450,8 +448,8 @@ public class AngleFormat extends Format
      * Sets the pattern to use for parsing and formatting angles.
      * See class description for a description of pattern syntax.
      *
-     * @param  pattern Pattern to use for parsing and formatting angle.
-     * @throws IllegalArgumentException If the specified pattern is not legal.
+     * @param  pattern  the pattern to use for parsing and formatting angle.
+     * @throws IllegalArgumentException if the specified pattern is not legal.
      *
      * @see #setMinimumFractionDigits(int)
      * @see #setMaximumFractionDigits(int)
@@ -477,9 +475,9 @@ public class AngleFormat extends Format
      * Actual implementation of {@link #applyPattern(String)}, as a private method for use by the constructor.
      * All fields related to the pattern shall be set to 0 or null before this method call.
      *
-     * @param symbols An array of code points containing the reserved symbols as upper-case letters.
-     *        This is always the {@link #SYMBOLS} array, unless we apply localized patterns.
-     * @param decimalSeparator The code point which represent decimal separator in the pattern.
+     * @param  symbols  an array of code points containing the reserved symbols as upper-case letters.
+     *                  This is always the {@link #SYMBOLS} array, unless we apply localized patterns.
+     * @param  decimalSeparator  the code point which represent decimal separator in the pattern.
      */
     @SuppressWarnings("fallthrough")
     private void applyPattern(final String pattern, final int[] symbols, final int decimalSeparator) {
@@ -611,9 +609,9 @@ public class AngleFormat extends Format
     /**
      * Returns the field index for the given upper case character, or -1 if none.
      *
-     * @param  symbols An array of code points containing the reserved symbols as upper-case letters.
-     * @param  c The symbol to search, as an upper-case character (code point actually).
-     * @return The index of the given character, or -1 if not found.
+     * @param  symbols  an array of code points containing the reserved symbols as upper-case letters.
+     * @param  c  the symbol to search, as an upper-case character (code point actually).
+     * @return the index of the given character, or -1 if not found.
      */
     private static int fieldForSymbol(final int[] symbols, final int c) {
         for (int field=DEGREES_FIELD; field<=FRACTION_FIELD; field++) {
@@ -636,7 +634,7 @@ public class AngleFormat extends Format
      * Returns the pattern used for parsing and formatting angles.
      * See class description for an explanation of how patterns work.
      *
-     * @return The formatting pattern.
+     * @return the formatting pattern.
      *
      * @see #getMinimumFractionDigits()
      * @see #getMaximumFractionDigits()
@@ -649,9 +647,9 @@ public class AngleFormat extends Format
      * Actual implementation of {@link #toPattern()} and {@code toLocalizedPattern()}
      * (the later method may be provided in a future SIS version).
      *
-     * @param symbols An array of code points containing the reserved symbols as upper-case letters.
-     *        This is always the {@link #SYMBOLS} array, unless we apply localized patterns.
-     * @param decimalSeparator The code point which represent decimal separator in the pattern.
+     * @param  symbols  an array of code points containing the reserved symbols as upper-case letters.
+     *                  this is always the {@link #SYMBOLS} array, unless we apply localized patterns.
+     * @param  decimalSeparator  the code point which represent decimal separator in the pattern.
      */
     private String toPattern(final int[] symbols, final int decimalSeparator) {
         int symbol = 0;
@@ -729,7 +727,7 @@ public class AngleFormat extends Format
      * This value can be set by the repetition of {@code 'd'}, {@code 'm'} or {@code 's'} symbol
      * in the pattern.
      *
-     * @return The minimum number of digits allowed in the fraction portion.
+     * @return the minimum number of digits allowed in the fraction portion.
      *
      * @see DecimalFormat#getMinimumFractionDigits()
      */
@@ -742,7 +740,7 @@ public class AngleFormat extends Format
      * If the given value is greater than the {@linkplain #getMaximumFractionDigits() maximum
      * number of fraction digits}, then that maximum number will be set to the given value too.
      *
-     * @param count The minimum number of digits allowed in the fraction portion.
+     * @param  count  the minimum number of digits allowed in the fraction portion.
      *
      * @see DecimalFormat#setMinimumFractionDigits(int)
      */
@@ -762,7 +760,7 @@ public class AngleFormat extends Format
      * Returns the maximum number of digits allowed in the fraction portion of the last field.
      * This value can be set by the repetition of {@code '#'} symbol in the pattern.
      *
-     * @return The maximum number of digits allowed in the fraction portion.
+     * @return the maximum number of digits allowed in the fraction portion.
      *
      * @see DecimalFormat#getMaximumFractionDigits()
      */
@@ -775,7 +773,7 @@ public class AngleFormat extends Format
      * If the given value is smaller than the {@linkplain #getMinimumFractionDigits() minimum
      * number of fraction digits}, then that minimum number will be set to the given value too.
      *
-     * @param count The maximum number of digits allowed in the fraction portion.
+     * @param  count  the maximum number of digits allowed in the fraction portion.
      *
      * @see DecimalFormat#setMaximumFractionDigits(int)
      */
@@ -811,7 +809,7 @@ public class AngleFormat extends Format
      * <p>This method does not take into account the space needed for the hemisphere symbol when
      * formatting {@link Latitude} or {@link Longitude} objects.</p>
      *
-     * @param width The maximum total width of formatted angle.
+     * @param  width  the maximum total width of formatted angle.
      */
     @SuppressWarnings("fallthrough")
     public void setMaximumWidth(int width) {
@@ -841,8 +839,8 @@ public class AngleFormat extends Format
              */
             if (width < 0) {
                 switch (field) {
-                    default:  width += (degreesFieldWidth-1); degreesFieldWidth = 1; // Fall through
-                    case MINUTES_FIELD: minutesSuffix = null; minutesFieldWidth = 0; // Fall through
+                    default:  width += (degreesFieldWidth-1); degreesFieldWidth = 1;    // Fall through
+                    case MINUTES_FIELD: minutesSuffix = null; minutesFieldWidth = 0;    // Fall through
                     case SECONDS_FIELD: secondsSuffix = null; secondsFieldWidth = 0;
                 }
                 if (field >= MINUTES_FIELD) {
@@ -881,8 +879,8 @@ public class AngleFormat extends Format
      * Formats an angle. The angle will be formatted according the pattern given to the last call
      * of {@link #applyPattern(String)}.
      *
-     * @param  angle Angle to format, in decimal degrees.
-     * @return The formatted string.
+     * @param  angle  angle to format, in decimal degrees.
+     * @return the formatted string.
      */
     public final String format(final double angle) {
         return format(angle, new StringBuffer(), null).toString();
@@ -892,16 +890,12 @@ public class AngleFormat extends Format
      * Formats an angle in the given buffer. The angle will be formatted according
      * the pattern given to the last call of {@link #applyPattern(String)}.
      *
-     * @param angle
-     *          Angle to format, in decimal degrees.
-     * @param toAppendTo
-     *          The buffer where to append the formatted angle.
-     * @param pos
-     *          An optional object where to store the position of the field in the formatted
-     *          text, or {@code null} if this information is not wanted. This field position
-     *          shall be created with one of the {@link Field} constants.
-     *
-     * @return The {@code toAppendTo} buffer, returned for method calls chaining.
+     * @param  angle       angle to format, in decimal degrees.
+     * @param  toAppendTo  the buffer where to append the formatted angle.
+     * @param  pos         an optional object where to store the position of the field in the formatted text,
+     *                     or {@code null} if this information is not wanted. This field position shall be
+     *                     created with one of the {@link Field} constants.
+     * @return the {@code toAppendTo} buffer, returned for method calls chaining.
      */
     @SuppressWarnings("PointlessBitwiseExpression")  // We rely on the compiler for simplifying the expression.
     public StringBuffer format(final double angle, StringBuffer toAppendTo, final FieldPosition pos) {
@@ -936,15 +930,15 @@ public class AngleFormat extends Format
             final double p = pow10(maximumFractionDigits);
             if (secondsFieldWidth != 0) {
                 seconds = (minutes - (minutes = truncate(minutes))) * 60;
-                seconds = rint(seconds * p) / p; // Correction for rounding errors.
-                if (seconds >= 60) { // We do not expect > 60 (only == 60), but let be safe.
+                seconds = rint(seconds * p) / p;                            // Correction for rounding errors.
+                if (seconds >= 60) {                    // We do not expect > 60 (only == 60), but let be safe.
                     seconds = 0;
                     minutes++;
                 }
             } else {
-                minutes = rint(minutes * p) / p; // Correction for rounding errors.
+                minutes = rint(minutes * p) / p;        // Correction for rounding errors.
             }
-            if (minutes >= 60) { // We do not expect > 60 (only == 60), but let be safe.
+            if (minutes >= 60) {                        // We do not expect > 60 (only == 60), but let be safe.
                 minutes = 0;
                 degrees += Math.signum(angle);
             }
@@ -1037,7 +1031,7 @@ public class AngleFormat extends Format
              */
             final int startPosition = toAppendTo.length();
             if (characterIterator != null) {
-                final FormattedCharacterIterator it = (FormattedCharacterIterator) characterIterator;
+                final FormattedCharacterIterator it = characterIterator;
                 it.append(numberFormat.formatToCharacterIterator(value), toAppendTo);
                 if (suffix != null) {
                     toAppendTo.append(suffix);
@@ -1082,16 +1076,12 @@ public class AngleFormat extends Format
      *       method.</li>
      * </ul>
      *
-     * @param value
-     *          {@link Angle} object to format.
-     * @param toAppendTo
-     *          The buffer where to append the formatted angle.
-     * @param pos
-     *          An optional object where to store the position of the field in the formatted
-     *          text, or {@code null} if this information is not wanted. This field position
-     *          shall be created with one of the {@link Field} constants.
-     *
-     * @return The {@code toAppendTo} buffer, returned for method calls chaining.
+     * @param  value       {@link Angle} object to format.
+     * @param  toAppendTo  the buffer where to append the formatted angle.
+     * @param  pos         an optional object where to store the position of the field in the formatted text,
+     *                     or {@code null} if this information is not wanted. This field position shall be
+     *                     created with one of the {@link Field} constants.
+     * @return the {@code toAppendTo} buffer, returned for method calls chaining.
      * @throws IllegalArgumentException if {@code value} if not an instance of {@link Angle}.
      */
     @Override
@@ -1135,8 +1125,7 @@ public class AngleFormat extends Format
             pos.setEndIndex(toAppendTo.length());
         }
         if (characterIterator != null) {
-            ((FormattedCharacterIterator) characterIterator).addFieldLimit(
-                    Field.HEMISPHERE, suffix, startPosition);
+            characterIterator.addFieldLimit(Field.HEMISPHERE, suffix, startPosition);
         }
         return toAppendTo;
     }
@@ -1180,8 +1169,8 @@ public class AngleFormat extends Format
      * In Apache SIS implementation, the returned character iterator also implements the
      * {@link CharSequence} interface for convenience.
      *
-     * @param  value {@link Angle} object to format.
-     * @return A character iterator together with the attributes describing the formatted value.
+     * @param  value  the {@link Angle} object to format.
+     * @return a character iterator together with the attributes describing the formatted value.
      * @throws IllegalArgumentException if {@code value} if not an instance of {@link Angle}.
      */
     @Override
@@ -1208,16 +1197,13 @@ public class AngleFormat extends Format
      * been recognized, then this method will compares against the standard ', � and "
      * ASCII symbols.</p>
      *
-     * @param source
-     *          The string being parsed.
-     * @param pos
-     *          On input, index of the first {@code source} character to read.
-     *          On output, index after the last suffix character.
-     * @param expectedField
-     *          First field to verify. For example a value of {@link #MINUTES_FIELD} means that
-     *          the suffix for minute and seconds shall be verified before degrees.
-     * @return The {@code *_FIELD} constant for the suffix which has been found, or a value
-     *         outside those constants if no suffix matched.
+     * @param source         the string being parsed.
+     * @param pos            on input, index of the first {@code source} character to read.
+     *                       On output, index after the last suffix character.
+     * @param expectedField  first field to verify. For example a value of {@link #MINUTES_FIELD} means that
+     *                       the suffix for minute and seconds shall be verified before degrees.
+     * @return the {@code *_FIELD} constant for the suffix which has been found,
+     *         or a value outside those constants if no suffix matched.
      */
     private int skipSuffix(final String source, final ParsePosition pos, final int expectedField) {
         int field = expectedField;
@@ -1245,7 +1231,7 @@ public class AngleFormat extends Format
                     c = source.codePointAt(index);
                     index += Character.charCount(c);
                 }
-                while (Character.isSpaceChar(c)); // Method shall be consistent with skipSpaces(\u2026)
+                while (Character.isSpaceChar(c));       // Method shall be consistent with skipSpaces(\u2026)
             }
             if (++field > SECONDS_FIELD) {
                 field = PREFIX_FIELD;
@@ -1264,7 +1250,7 @@ public class AngleFormat extends Format
                 c = source.codePointAt(start);
                 start += Character.charCount(c);
             }
-            while (Character.isSpaceChar(c)); // Method shall be consistent with skipSpaces(\u2026)
+            while (Character.isSpaceChar(c));           // Method shall be consistent with skipSpaces(\u2026)
             switch (c) {
                 case '�' :            pos.setIndex(start); return DEGREES_FIELD;
                 case '\u2032' : case '\'': pos.setIndex(start); return MINUTES_FIELD;
@@ -1284,10 +1270,10 @@ public class AngleFormat extends Format
      * not want to skip tabulations or line feeds, since they are unlikely to be part of
      * the angle to parse.
      *
-     * @param  source The string being parsed.
-     * @param  index  Index of the first {@code source} character to read.
-     * @param  length The length of {@code source}.
-     * @return Index of the first non-space character, or the end of string if none.
+     * @param  source  the string being parsed.
+     * @param  index   index of the first {@code source} character to read.
+     * @param  length  the length of {@code source}.
+     * @return index of the first non-space character, or the end of string if none.
      */
     private static int skipSpaces(final String source, int index, final int length) {
         while (index < length) {
@@ -1314,10 +1300,10 @@ public class AngleFormat extends Format
      * used as a separator for other kinds of values. If the string is known to contain only
      * an angle value, use {@code parse(String)} instead.</p>
      *
-     * @param  source The string to parse.
-     * @param  pos    On input, index of the first {@code source} character to read.
-     *                On output, index after the last parsed character.
-     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     * @param  source  the string to parse.
+     * @param  pos     on input, index of the first {@code source} character to read.
+     *                 On output, index after the last parsed character.
+     * @return the parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
      *
      * @see #isFallbackAllowed()
      */
@@ -1651,7 +1637,7 @@ BigBoss:    switch (skipSuffix(source, p
                 case EAST : pos.setIndex(index); return new Longitude( degrees);
                 case WEST : pos.setIndex(index); return new Longitude(-degrees);
             }
-            if (!Character.isSpaceChar(c)) { // Method shall be consistent with skipSpaces(\u2026)
+            if (!Character.isSpaceChar(c)) {                // Method shall be consistent with skipSpaces(\u2026)
                 break;
             }
         }
@@ -1664,9 +1650,9 @@ BigBoss:    switch (skipSuffix(source, p
      * {@link #parse(String, ParsePosition)} regarding white spaces between degrees, minutes
      * and seconds fields.
      *
-     * @param  source The string to parse.
-     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
-     * @throws ParseException If the string can not be fully parsed.
+     * @param  source  the string to parse.
+     * @return the parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     * @throws ParseException if the string can not be fully parsed.
      *
      * @see #isFallbackAllowed()
      */
@@ -1685,9 +1671,9 @@ BigBoss:    switch (skipSuffix(source, p
      * Parses a substring as an object.
      * The default implementation delegates to {@link #parse(String, ParsePosition)}.
      *
-     * @param  source The string to parse.
-     * @param  pos The position where to start parsing.
-     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     * @param  source  the string to parse.
+     * @param  pos     the position where to start parsing.
+     * @return the parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
      */
     @Override
     public Object parseObject(final String source, final ParsePosition pos) {
@@ -1698,9 +1684,9 @@ BigBoss:    switch (skipSuffix(source, p
      * Parses the given string as an object.
      * The default implementation delegates to {@link #parse(String)}.
      *
-     * @param  source The string to parse.
-     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
-     * @throws ParseException If the string can not been fully parsed.
+     * @param  source  the string to parse.
+     * @return the parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     * @throws ParseException if the string can not been fully parsed.
      */
     @Override
     public Object parseObject(final String source) throws ParseException {
@@ -1744,7 +1730,7 @@ BigBoss:    switch (skipSuffix(source, p
      * Returns this formatter locale. This is the locale specified at construction time if any,
      * or the {@linkplain Locale#getDefault() default locale} at construction time otherwise.
      *
-     * @return This formatter locale (never {@code null}).
+     * @return this formatter locale (never {@code null}).
      */
     @Override
     public Locale getLocale() {
@@ -1754,7 +1740,7 @@ BigBoss:    switch (skipSuffix(source, p
     /**
      * Returns a clone of this {@code AngleFormat}.
      *
-     * @return A clone of this format.
+     * @return a clone of this format.
      */
     @Override
     public AngleFormat clone() {
@@ -1777,7 +1763,7 @@ BigBoss:    switch (skipSuffix(source, p
     /**
      * Compares this format with the specified object for equality.
      *
-     * @param object The object to compare with this angle format for equality.
+     * @param  object  the object to compare with this angle format for equality.
      */
     @Override
     public boolean equals(final Object object) {

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] Thu Oct 27 22:53:44 2016
@@ -93,6 +93,19 @@ final class ConventionalUnit<Q extends Q
             return target;
         }
         /*
+         * Verifies if an instance already exists for the given converter.
+         */
+        final ConventionalUnit<Q>[] related = target.related;
+        if (related != null && toTarget instanceof LinearConverter) {
+            final LinearConverter c = (LinearConverter) toTarget;
+            for (final ConventionalUnit<Q> existing : related) {
+                // Units.add(\u2026) verified the UnitConverter class.
+                if (c.equivalent((LinearConverter) existing.toTarget)) {
+                    return existing;
+                }
+            }
+        }
+        /*
          * If the unit is a SI unit, try to create the SI symbol by the concatenation of the SI prefix
          * with the system unit symbol. The unit symbol are used later as a key for searching existing
          * unit instances.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java [UTF-8] Thu Oct 27 22:53:44 2016
@@ -308,7 +308,7 @@ public class RangeFormat extends Format
      * Creates a new format for parsing and formatting {@linkplain NumberRange number ranges}
      * using the given locale.
      *
-     * @param  locale The locale for parsing and formatting range components.
+     * @param  locale  the locale for parsing and formatting range components.
      */
     public RangeFormat(final Locale locale) {
         this(locale, Number.class);
@@ -318,8 +318,8 @@ public class RangeFormat extends Format
      * Creates a new format for parsing and formatting {@code Range<Date>}
      * using the given locale and timezone.
      *
-     * @param locale   The locale for parsing and formatting range components.
-     * @param timezone The timezone for the date to be formatted.
+     * @param locale    the locale for parsing and formatting range components.
+     * @param timezone  the timezone for the date to be formatted.
      */
     public RangeFormat(final Locale locale, final TimeZone timezone) {
         this(locale, Date.class);
@@ -331,9 +331,9 @@ public class RangeFormat extends Format
      * the given element type using the given locale. The element type is typically
      * {@code Date.class} or some subclass of {@code Number.class}.
      *
-     * @param  locale The locale for parsing and formatting range components.
-     * @param  elementType The type of range components.
-     * @throws IllegalArgumentException If the given type is not recognized by this constructor.
+     * @param  locale       the locale for parsing and formatting range components.
+     * @param  elementType  the type of range components.
+     * @throws IllegalArgumentException if the given type is not recognized by this constructor.
      */
     public RangeFormat(final Locale locale, final Class<?> elementType) throws IllegalArgumentException {
         this.elementType = elementType;
@@ -358,8 +358,8 @@ public class RangeFormat extends Format
         minusSign         = ds.getMinusSign();
         infinity          = ds.getInfinity();
         openSet           = '{';
-        openInclusive     = '['; // Future SIS version may determine those characters from the locale.
-        openExclusive     = '('; // We may also provide an 'applyPattern(String)' method for setting those char.
+        openInclusive     = '[';        // Future SIS version may determine those characters from the locale.
+        openExclusive     = '(';        // We may also provide an 'applyPattern(String)' method for setting those char.
         openExclusiveAlt  = ']';
         closeSet          = '}';
         closeInclusive    = ']';
@@ -386,9 +386,8 @@ public class RangeFormat extends Format
      * Returns the pattern used by {@link #elementFormat} for formatting the minimum and
      * maximum values. If the element format does not use pattern, returns {@code null}.
      *
-     * @param  localized {@code true} for returning the localized pattern, or {@code false}
-     *         for the unlocalized one.
-     * @return The pattern, or {@code null} if the {@link #elementFormat} doesn't use pattern.
+     * @param  localized {@code true} for returning the localized pattern, or {@code false} for the unlocalized one.
+     * @return the pattern, or {@code null} if the {@link #elementFormat} doesn't use pattern.
      *
      * @see DecimalFormat#toPattern()
      * @see SimpleDateFormat#toPattern()
@@ -414,9 +413,9 @@ public class RangeFormat extends Format
      * Sets the pattern to be used by {@link #elementFormat} for formatting the minimum and
      * maximum values.
      *
-     * @param  pattern The new pattern.
-     * @param  localized {@code true} if the given pattern is localized.
-     * @throws IllegalStateException If the {@link #elementFormat} does not use pattern.
+     * @param  pattern    the new pattern.
+     * @param  localized  {@code true} if the given pattern is localized.
+     * @throws IllegalStateException if the {@link #elementFormat} does not use pattern.
      *
      * @see DecimalFormat#applyPattern(String)
      * @see SimpleDateFormat#applyPattern(String)
@@ -461,8 +460,7 @@ public class RangeFormat extends Format
     /**
      * Sets whether this {@code RangeFormat} shall use the alternate form at formatting time.
      *
-     * @param alternateForm {@code true} for using the alternate format, or {@code false} for
-     *        using the default format.
+     * @param alternateForm {@code true} for using the alternate format, or {@code false} for using the default format.
      */
     public void setAlternateForm(final boolean alternateForm) {
         this.alternateForm = alternateForm;
@@ -520,11 +518,11 @@ public class RangeFormat extends Format
      * Formats a {@link Range} and appends the resulting text to a given string buffer.
      * See the <a href="#skip-navbar_top">class javadoc</a> for a description of the format.
      *
-     * @param  range      The {@link Range} object to format.
-     * @param  toAppendTo Where the text is to be appended.
-     * @param  pos        Identifies a field in the formatted text, or {@code null} if none.
-     * @return The string buffer passed in as {@code toAppendTo}, with formatted text appended.
-     * @throws IllegalArgumentException If this formatter can not format the given object.
+     * @param  range       the {@link Range} object to format.
+     * @param  toAppendTo  where the text is to be appended.
+     * @param  pos         identifies a field in the formatted text, or {@code null} if none.
+     * @return the string buffer passed in as {@code toAppendTo}, with formatted text appended.
+     * @throws IllegalArgumentException if this formatter can not format the given object.
      */
     @Override
     public StringBuffer format(final Object range, final StringBuffer toAppendTo, final FieldPosition pos) {
@@ -535,16 +533,14 @@ public class RangeFormat extends Format
     /**
      * Implementation of the format methods.
      *
-     * @param range      The range to format.
-     * @param toAppendTo Where the text is to be appended.
-     * @param pos        Identifies a field in the formatted text, or {@code null} if none.
-     * @param characterIterator The character iterator for which the attributes need to be set,
-     *        or null if none. This is actually an instance of {@link FormattedCharacterIterator},
-     *        but we use the interface here for avoiding too early class loading.
+     * @param  range              the range to format.
+     * @param  toAppendTo         where the text is to be appended.
+     * @param  pos                identifies a field in the formatted text, or {@code null} if none.
+     * @param  characterIterator  the character iterator for which the attributes need to be set, or null if none.
      */
     @SuppressWarnings("fallthrough")
     private void format(final Range<?> range, final StringBuffer toAppendTo, final FieldPosition pos,
-            final AttributedCharacterIterator characterIterator)
+            final FormattedCharacterIterator characterIterator)
     {
         /*
          * Special case for an empty range. This is typically formatted as "{}". The field
@@ -555,8 +551,8 @@ public class RangeFormat extends Format
             toAppendTo.appendCodePoint(openSet);
             if (fieldPos >= MIN_VALUE_FIELD && fieldPos <= UNIT_FIELD) {
                 final int p = toAppendTo.length();
-                pos.setBeginIndex(p); // First index, inclusive.
-                pos.setEndIndex  (p); // Last index, exclusive
+                pos.setBeginIndex(p);                                   // First index, inclusive.
+                pos.setEndIndex  (p);                                   // Last index, exclusive
             }
             toAppendTo.appendCodePoint(closeSet);
             return;
@@ -607,8 +603,7 @@ public class RangeFormat extends Format
                     format = elementFormat;
                 }
                 if (characterIterator != null) {
-                    ((FormattedCharacterIterator) characterIterator)
-                            .append(format.formatToCharacterIterator(value), toAppendTo);
+                    characterIterator.append(format.formatToCharacterIterator(value), toAppendTo);
                 } else {
                     format.format(value, toAppendTo, new FieldPosition(-1));
                 }
@@ -618,8 +613,7 @@ public class RangeFormat extends Format
              * then append the separator between this field and the next one.
              */
             if (characterIterator != null) {
-                ((FormattedCharacterIterator) characterIterator)
-                        .addFieldLimit(Field.forCode(field), value, startPosition);
+                characterIterator.addFieldLimit(Field.forCode(field), value, startPosition);
             }
             if (field == fieldPos) {
                 pos.setBeginIndex(startPosition);
@@ -671,8 +665,8 @@ public class RangeFormat extends Format
      * <p>In Apache SIS implementation, the returned character iterator also implements the
      * {@link CharSequence} interface for convenience.</p>
      *
-     * @param  range {@link Range} object to format.
-     * @return A character iterator together with the attributes describing the formatted value.
+     * @param  range  the {@link Range} object to format.
+     * @return a character iterator together with the attributes describing the formatted value.
      * @throws IllegalArgumentException if {@code value} if not an instance of {@link Range}.
      */
     @Override
@@ -687,9 +681,9 @@ public class RangeFormat extends Format
      * Parses text from a string to produce a range. The default implementation delegates to
      * {@link #parse(String)} with no additional work.
      *
-     * @param  source The text, part of which should be parsed.
-     * @return A range parsed from the string, or {@code null} in case of error.
-     * @throws ParseException If the given string can not be fully parsed.
+     * @param  source  the text, part of which should be parsed.
+     * @return a range parsed from the string, or {@code null} in case of error.
+     * @throws ParseException if the given string can not be fully parsed.
      */
     @Override
     public Object parseObject(final String source) throws ParseException {
@@ -700,9 +694,9 @@ public class RangeFormat extends Format
      * Parses text from a string to produce a range. The default implementation delegates to
      * {@link #parse(String, ParsePosition)} with no additional work.
      *
-     * @param  source The text, part of which should be parsed.
-     * @param  pos    Index and error index information as described above.
-     * @return A range parsed from the string, or {@code null} in case of error.
+     * @param  source  the text, part of which should be parsed.
+     * @param  pos     index and error index information as described above.
+     * @return a range parsed from the string, or {@code null} in case of error.
      */
     @Override
     public Object parseObject(final String source, final ParsePosition pos) {
@@ -714,9 +708,9 @@ public class RangeFormat extends Format
      * If there is some unparsed characters after the parsed range, then this method thrown an
      * exception.
      *
-     * @param  source The text to parse.
-     * @return The parsed range (never {@code null}).
-     * @throws ParseException If the given string can not be fully parsed.
+     * @param  source  the text to parse.
+     * @return the parsed range (never {@code null}).
+     * @throws ParseException if the given string can not be fully parsed.
      */
     public Range<?> parse(final String source) throws ParseException {
         final ParsePosition pos = new ParsePosition(0);
@@ -743,9 +737,9 @@ public class RangeFormat extends Format
      * an error occurs, then the index of {@code pos} is not changed, the error index of {@code pos}
      * is set to the index of the character where the error occurred, and {@code null} is returned.
      *
-     * @param  source The text, part of which should be parsed.
-     * @param  pos    Index and error index information as described above.
-     * @return A range parsed from the string, or {@code null} in case of error.
+     * @param  source  the text, part of which should be parsed.
+     * @param  pos     index and error index information as described above.
+     * @return a range parsed from the string, or {@code null} in case of error.
      */
     public Range<?> parse(final String source, final ParsePosition pos) {
         final int origin = pos.getIndex();
@@ -820,7 +814,7 @@ public class RangeFormat extends Format
                 if (value == null) {
                     return null;
                 }
-                pos.setErrorIndex(index); // In case of failure during the conversion.
+                pos.setErrorIndex(index);                   // In case of failure during the conversion.
                 minValue = maxValue = convert(value);
                 index = pos.getIndex();
                 isMinIncluded = isMaxIncluded = true;
@@ -859,7 +853,7 @@ public class RangeFormat extends Format
                 c = source.codePointAt(index);
             } while (Character.isWhitespace(c));
             if (isClose(c)) {
-                pos.setErrorIndex(index);  // In case of failure during the conversion.
+                pos.setErrorIndex(index);                   // In case of failure during the conversion.
                 minValue = maxValue = valueOfNil();
                 isMaxIncluded = false;
                 index += Character.charCount(c);
@@ -882,7 +876,7 @@ public class RangeFormat extends Format
                     }
                     pos.setIndex(index += infinity.length());
                 }
-                pos.setErrorIndex(savedIndex); // In case of failure during the conversion.
+                pos.setErrorIndex(savedIndex);              // In case of failure during the conversion.
                 minValue = convert(value);
                 /*
                  * Parsing of minimal value succeed and its type is valid. Now look for the
@@ -918,7 +912,7 @@ public class RangeFormat extends Format
                         }
                         pos.setIndex(index += infinity.length());
                     }
-                    pos.setErrorIndex(index); // In case of failure during the conversion.
+                    pos.setErrorIndex(index);               // In case of failure during the conversion.
                     maxValue = convert(value);
                     /*
                      * Skip one last time the whitespaces. The check for the closing bracket

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] Thu Oct 27 22:53:44 2016
@@ -63,6 +63,13 @@ final class SystemUnit<Q extends Quantit
     final UnitDimension dimension;
 
     /**
+     * Units for the same quantity but with scale factors that are not the SI one.
+     * This is initialized by {@link Units} only and shall not change anymore after.
+     * All units in this array shall use an instance of {@link LinearConverter}.
+     */
+    transient ConventionalUnit<Q>[] related;
+
+    /**
      * Creates a new unit having the given symbol and EPSG code.
      *
      * @param  quantity   the type of quantity that uses this unit, or {@code null} if unknown.

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=1766907&r1=1766906&r2=1766907&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] Thu Oct 27 22:53:44 2016
@@ -52,7 +52,7 @@ import org.apache.sis.util.collection.We
  *
  * <div class="section">Parsing authority codes</div>
  * As a special case, if a character sequence given to the {@link #parse(CharSequence)} method is of the
- * {@code "EPSG:####"} or {@code "urn:ogc:def:uom:EPSG:####"} form (ignoring case and whitespaces),
+ * {@code "EPSG:####"} or {@code "urn:ogc:def:uom:EPSG::####"} form (ignoring case and whitespaces),
  * then {@code "####"} is parsed as an integer and forwarded to the {@link Units#valueOfEPSG(int)} method.
  *
  * <div class="section">NetCDF unit symbols</div>
@@ -140,15 +140,42 @@ public class UnitFormat extends Format i
         SYMBOL('\u22c5', '\u2215'),
 
         /**
-         * Format unit symbols using the Unified Code for Units of Measure (UCUM) syntax.
-         * Those symbols are restricted to the ASCII character set.
+         * Format unit symbols using a syntax close to the Unified Code for Units of Measure (UCUM) one.
+         * The character set is restricted to ASCII. The multiplication operator is the period (\u201c.\u201d).
+         *
+         * <div class="section">Modification to UCUM syntax rules</div>
+         * UCUM does not allow floating point numbers in unit terms, so the use of period as an operator
+         * should not be ambiguous. However Apache SIS relaxes this restriction in order to support the
+         * scale factors commonly found in angular units (e.g. \u03c0/180). The meaning of a period in a string
+         * is resolved with two SIS-specific rules:
+         *
+         * <ul>
+         *   <li>Unit symbols shall not begin or end with a decimal digit or a superscript.</li>
+         *   <li>A period between two decimal digits is interpreted as a decimal separator.</li>
+         * </ul>
          *
          * @see org.apache.sis.util.CharSequences#toASCII(CharSequence)
          */
         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));
+                if (value.startsWith("�")) {
+                    final int length = value.length();
+                    if (length == 2) {
+                        switch (value.charAt(1)) {
+                            case 'C': return toAppendTo.append("Cel");
+                            case '\u212a': // U+212A (Kelvin symbol)
+                            case 'K': return toAppendTo.append('K');
+                        }
+                    }
+                    return toAppendTo.append("deg").append(value, 1, length);
+                }
+                final CharSequence cs = CharSequences.toASCII(value);
+                final int length = cs.length();
+                for (int i=0; i<length; i++) {
+                    toAppendTo.append(Characters.toNormalScript(cs.charAt(i)));
+                }
+                return toAppendTo;
             }
 
             /** Formats the power for a unit symbol. */
@@ -336,14 +363,22 @@ public class UnitFormat extends Format i
     }
 
     /**
-     * Attaches a label to the specified unit.
+     * Attaches a label to the specified unit. A <cite>label</cite> can be a substitute to either the
+     * {@linkplain AbstractUnit#getSymbol() unit symbol} or the {@link AbstractUnit#getName() unit name},
+     * depending on the {@linkplain #getStyle() format style}.
      * 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. Current implementation does not put additional restrictions. However if the label will
-     * be used as a {@linkplain AbstractUnit#getSymbol() unit symbol} (as opposed to {@link AbstractUnit#getName()
-     * unit name}), then we recommend to restrict the characters to {@linkplain Character#isLetter(int) letters}
-     * and {@linkplain Characters#isSubScript(int) subscripts}.</p>
+     * <div class="section">Restriction on character set</div>
+     * Current implementation accepts only {@linkplain Character#isLetter(int) letters},
+     * {@linkplain Characters#isSubScript(int) subscripts} and {@linkplain Character#isWhitespace(int) whitespaces},
+     * but the set of legal characters may be expanded in future Apache SIS versions.
+     * However the following restrictions are likely to remain:
+     *
+     * <ul>
+     *   <li>The following characters are reserved since they have special meaning in UCUM format, in URI
+     *       or in Apache SIS parser: <blockquote>" # ( ) * + - . / : = ? [ ] { } ^ \u22c5 \u2215</blockquote></li>
+     *   <li>The symbol can not begin or end with digits, since such digits would be confused with unit power.</li>
+     * </ul>
      *
      * @param  unit   the unit being labeled.
      * @param  label  the new label for the given unit.
@@ -354,12 +389,12 @@ public class UnitFormat extends Format i
         ArgumentChecks.ensureNonNull ("unit", unit);
         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));
+        for (int i=0; i < label.length();) {
+            final int c = label.codePointAt(i);
+            if (!AbstractUnit.isSymbolChar(c) && !Character.isWhitespace(c)) {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "label", label));
+            }
+            i += Character.charCount(c);
         }
         final Unit<?> unitForOldLabel = labelToUnit.remove(unitToLabel.put(unit, label));
         final Unit<?> oldUnitForLabel = labelToUnit.put(label, unit);
@@ -714,11 +749,50 @@ public class UnitFormat extends Format i
     }
 
     /**
+     * Returns {@code true} if the {@code '*'} character at the given index is surrounded by digits
+     * or a sign on its right side. For example this method returns {@code true} for "10*-6", which
+     * means 1E-6 in UCUM syntax. This check is used for heuristic rules at parsing time.
+     */
+    private static boolean isExponentOperator(final CharSequence symbols, int i, final int length) {
+        char c;
+        return (i != 0) && isDigit(symbols.charAt(i-1)) &&
+               (++i < length) && (isDigit(c = symbols.charAt(i)) || isSign(c));
+    }
+
+    /**
+     * Returns {@code true} if the {@code '.'} character at the given index is surrounded by digits or
+     * is at the beginning or the end of the character sequences. This check is used for heuristic rules.
+     *
+     * @see Style#UCUM
+     */
+    private static boolean isDecimalSeparator(final CharSequence symbols, int i, final int length) {
+        return (i   == 0      || isDigit(symbols.charAt(i-1)) &&
+               (++i >= length || isDigit(symbols.charAt(i))));
+    }
+
+    /**
+     * Returns {@code true} if the given character is a digit in the sense of the {@code UnitFormat} parser.
+     * Note that "digit" is taken here in a much more restrictive way than {@link Character#isDigit(int)}.
+     */
+    private static boolean isDigit(final int c) {
+        return c >= '0' && c <= '9';
+    }
+
+    /**
+     * Returns {@code true} if the given character is the sign of a number according the {@code UnitFormat} parser.
+     */
+    private static boolean isSign(final int c) {
+        return c == '+' || c == '-';
+    }
+
+    /**
      * 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 \u201cm\u2215s\u201d, this method accepts also authority codes like
-     * {@code "urn:ogc:def:uom:EPSG:####"}. See class javadoc for more information.</p>
+     * <p>The parsing is lenient: symbols can be products or quotients of units like \u201cm\u2215s\u201d,
+     * words like \u201cmeters per second\u201d, or authority codes like {@code "urn:ogc:def:uom:EPSG::1026"}.
+     * The product operator can be either {@code '.'} (ASCII) or {@code '\u22c5'} (Unicode) character.
+     * Exponent after symbol can be decimal digits as in \u201cm2\u201d or a superscript as in \u201cm�\u201d.</p>
      *
      * @param  symbols  the unit symbols or URI to parse.
      * @return the unit parsed from the specified symbols.
@@ -727,9 +801,8 @@ public class UnitFormat extends Format i
      * @see Units#valueOf(String)
      */
     @Override
-    @SuppressWarnings("null")
-    public Unit<?> parse(final CharSequence symbols) throws ParserException {
-        String uom = CharSequences.trimWhitespaces(symbols).toString();
+    @SuppressWarnings( {"null", "fallthrough"})
+    public Unit<?> parse(CharSequence symbols) throws ParserException {
         /*
          * 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
@@ -740,7 +813,8 @@ public class UnitFormat extends Format i
          * This is the intended behavior for AuthorityFactory, but in the particular case of this method
          * we want to try to parse as a xpointer before to give up.
          */
-        if (isURI(uom)) {
+        if (isURI(symbols)) {
+            final String uom = symbols.toString();
             String code = DefinitionURI.codeOf("uom", Constants.EPSG, uom);
             if (code != null && code != uom) try {              // Really identity check, see above comment.
                 return Units.valueOfEPSG(Integer.parseInt(code));
@@ -751,57 +825,77 @@ public class UnitFormat extends Format i
             }
             code = XPaths.xpointer("uom", uom);
             if (code != null) {
-                uom = code;
+                symbols = code;
             }
         }
         /*
          * Split the unit around the multiplication and division operators and parse each term individually.
          */
         int offset    = 0;                  // Index of the first character of the next unit term to parse.
-        int operation = 0;                  // 1 = multiplication,  2 = division.
+        int operation = NOOP;               // Enumeration value: MULTIPLY, DIVIDE.
         Unit<?> unit  = null;
-        final int length = uom.length();
+        final int length = symbols.length();
         for (int i=0; i<length; i++) {
-            final char c = uom.charAt(i);   // No need to use code points because we search characters in BMP.
+            final char c = symbols.charAt(i);   // No need to use code points because we search characters in BMP.
             final int next;
             switch (c) {
-                case '.':
-                case '\u22c5': next = 1; break;
+                /*
+                 * Star is for exponentiation in UCUM syntax, but some symbols may use it for unit multiplications.
+                 * We interpret
+                 */
+                case '*': if (isExponentOperator(symbols, i, length)) continue;
+                          next = MULTIPLY; break;
+                /*
+                 * The period is the multiplication operator in UCUM format. According UCUM there is no ambiguity
+                 * with the decimal separator since unit terms should not contain floating point numbers. However
+                 * we relax this rule in order to support scale factor of angular units (e.g. \u03c0/180).  The period
+                 * is interpreted as a decimal separator if there is a decimal digit before and after it.
+                 */
+                case '.': if (isDecimalSeparator(symbols, i, length)) continue;
+                case '\u22c5': // Fallthrough
+                case '�': next = MULTIPLY; break;
+                case '�':
                 case '/':
-                case '\u2215': next = 2; break;
+                case '\u2215': next = DIVIDE; break;
                 default:  continue;
             }
-            final Unit<?> term = parseSymbol(CharSequences.trimWhitespaces(uom, offset, i).toString(), symbols, offset);
+            final Unit<?> term = parseSymbol(symbols, offset, i);
             switch (operation) {
-                case 0:  unit = term; break;
-                case 1:  unit = unit.multiply(term); break;
-                case 2:  unit = unit.divide(term); break;
+                case NOOP:     unit = term; break;
+                case MULTIPLY: unit = unit.multiply(term); break;
+                case DIVIDE:   unit = unit.divide(term); break;
                 default: throw new AssertionError(operation);
             }
             operation = next;
             offset = i+1;
         }
-        final Unit<?> term = parseSymbol(CharSequences.trimWhitespaces(uom, offset, length).toString(), symbols, offset);
+        final Unit<?> term = parseSymbol(symbols, offset, length);
         switch (operation) {
-            case 0:  unit = term; break;
-            case 1:  unit = unit.multiply(term); break;
-            case 2:  unit = unit.divide(term); break;
+            case NOOP:     unit = term; break;
+            case MULTIPLY: unit = unit.multiply(term); break;
+            case DIVIDE:   unit = unit.divide(term); break;
             default: throw new AssertionError(operation);
         }
         return unit;
     }
 
     /**
+     * Meaning of some characters parsed by {@link #parse(CharSequence)}.
+     */
+    private static final int NOOP = 0, MULTIPLY = 1, DIVIDE = 2;
+
+    /**
      * Parses a single unit symbol with its exponent.
      * The given symbol shall not contain multiplication or division operator.
      *
-     * @param  uom      the single unit symbol to parse.
-     * @param  symbols  the complete string specified by the user, used for error reporting.
-     * @param  offset   index of {@code uom} in the {@code symbols} string, used for error reporting.
+     * @param  symbols  the complete string specified by the user.
+     * @param  lower    index where to begin parsing in the {@code symbols} string.
+     * @param  upper    index after the last character to parse in the {@code symbols} string.
      * @return the parsed unit symbol (never {@code null}).
      * @throws ParserException if a problem occurred while parsing the given symbols.
      */
-    private Unit<?> parseSymbol(final String uom, final CharSequence symbols, final int offset) throws ParserException {
+    private Unit<?> parseSymbol(final CharSequence symbols, final int lower, final int upper) throws ParserException {
+        final String uom = CharSequences.trimWhitespaces(symbols, lower, upper).toString();
         /*
          * 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.
@@ -813,6 +907,25 @@ public class UnitFormat extends Format i
                 final int length = uom.length();
                 if (length == 0) {
                     return Units.UNITY;
+                } else {
+                    /*
+                     * If the first character is a digit, presume that the term is a multiplication factor.
+                     * The "*" character is used for raising the number on the left to the power on the right.
+                     * Example: "10*6" is equal to one million.
+                     */
+                    final char c = uom.charAt(0);
+                    if (isDigit(c) || isSign(c)) {
+                        final double multiplier;
+                        final int s = uom.lastIndexOf('*');
+                        if (s >= 0) {
+                            final int base = Integer.parseInt(uom.substring(0, s));
+                            final int exp  = Integer.parseInt(uom.substring(s+1));
+                            multiplier = Math.pow(base, exp);
+                        } else {
+                            multiplier = Double.parseDouble(uom);
+                        }
+                        return Units.UNITY.multiply(multiplier);
+                    }
                 }
                 if (length >= 2) {
                     /*
@@ -826,21 +939,21 @@ public class UnitFormat extends Format i
                     boolean canApply = false;
                     if (Characters.isSuperScript(c)) {
                         c = Characters.toNormalScript(c);
-                        if (c >= '0' && c <= '9') {
+                        if (isDigit(c)) {
                             power = c - '0';
                             canApply = true;
                         }
-                    } else if (c >= '0' && c <= '9') {
+                    } else if (isDigit(c)) {
                         do {
                             c = uom.charAt(--i);
-                            if (c < '0' || c > '9') {
-                                if (c != '+' && c != '-') i++;
+                            if (!isDigit(c)) {
+                                if (!isSign(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, offset+i).initCause(e);
+                                            Errors.Keys.UnknownUnit_1, uom), symbols, lower+i).initCause(e);
                                 }
                                 canApply = true;
                                 break;
@@ -885,7 +998,7 @@ public class UnitFormat extends Format i
                         "degrees", "degree").toString();
                 unit = nameToUnit().get(lc);
                 if (unit == null) {
-                    throw new ParserException(Errors.format(Errors.Keys.UnknownUnit_1, uom), symbols, offset);
+                    throw new ParserException(Errors.format(Errors.Keys.UnknownUnit_1, uom), symbols, lower);
                 }
             }
         }

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=1766907&r1=1766906&r2=1766907&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] Thu Oct 27 22:53:44 2016
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.measure;
 
+import java.util.Arrays;
 import javax.measure.Dimension;
 import javax.measure.Unit;
 import javax.measure.UnitConverter;
@@ -708,11 +709,11 @@ public final class Units extends Static
          * All Unit<Angle>
          */
         RADIAN      = rad;
-        GRAD        = add(rad, LinearConverter.scale(Math.PI, 200),    "grad", UnitRegistry.OTHER,    (short) 9105);
-        DEGREE      = add(rad, LinearConverter.scale(Math.PI, 180),       "�", UnitRegistry.ACCEPTED, Constants.EPSG_PARAM_DEGREES);
-        ARC_MINUTE  = add(rad, LinearConverter.scale(Math.PI, 180*60),    "\u2032", UnitRegistry.ACCEPTED, (short) 9103);
-        ARC_SECOND  = add(rad, LinearConverter.scale(Math.PI, 180*60*60), "\u2033", UnitRegistry.ACCEPTED, (short) 9104);
-        MICRORADIAN = add(rad, micro,                                  "�rad", UnitRegistry.SI,       (short) 9109);
+        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), "\u2032",    UnitRegistry.ACCEPTED, (short) 9103);
+        ARC_SECOND  = add(rad, LinearConverter.scale(Math.PI / 20, 180*60*60 / 20), "\u2033",    UnitRegistry.ACCEPTED, (short) 9104);
+        MICRORADIAN = add(rad, micro,                                         "�rad", UnitRegistry.SI,       (short) 9109);
         /*
          * All Unit<Length>
          */
@@ -793,8 +794,20 @@ public final class Units extends Static
      * Invoked by {@code Units} static class initializer for registering SI conventional units.
      * This method shall be invoked in a single thread by the {@code Units} class initializer only.
      */
+    @SuppressWarnings({"unchecked", "rawtypes"})
     private static <Q extends Quantity<Q>> ConventionalUnit<Q> add(SystemUnit<Q> target, UnitConverter toTarget, String symbol, byte scope, short epsg) {
-        return UnitRegistry.init(new ConventionalUnit<>(target, toTarget, symbol, scope, epsg));
+        final ConventionalUnit<Q> unit = UnitRegistry.init(new ConventionalUnit<>(target, toTarget, symbol, scope, epsg));
+        if (unit.scope != UnitRegistry.SI && toTarget instanceof LinearConverter) {
+            ConventionalUnit<Q>[] related = target.related;
+            if (related == null) {
+                related = new ConventionalUnit[1];
+            } else {
+                related = Arrays.copyOf(related, related.length + 1);
+            }
+            related[related.length - 1] = unit;
+            target.related = related;
+        }
+        return unit;
     }
 
     /**

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java [UTF-8] Thu Oct 27 22:53:44 2016
@@ -180,6 +180,8 @@ public final strictfp class Conventional
         assertSame(Units.CUBIC_METRE,  Units.CUBIC_METRE .multiply(   1));
         verify    (Units.CUBIC_METRE,  Units.CUBIC_METRE .multiply(1E+9), "km�", 1E+9);
         verify    (Units.CUBIC_METRE,  Units.CUBIC_METRE .divide  (1E+9), "mm�", 1E-9);
+
+        assertSame(Units.HOUR, Units.SECOND.multiply(3600));
     }
 
     /**
@@ -201,6 +203,17 @@ public final strictfp class Conventional
     }
 
     /**
+     * Tests conversion of an angular value between two conventional units.
+     * The use of angular units is of special interest because of rounding errors.
+     */
+    @Test
+    public void testConvertAngle() {
+        final UnitConverter c = Units.GRAD.getConverterTo(Units.DEGREE);
+        assertEquals(180,        c.convert(200),       STRICT);
+        assertEquals(2.33722917, c.convert(2.5969213), STRICT);
+    }
+
+    /**
      * Serializes some units, deserializes them and verifies that we get the same instance.
      */
     @Test

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=1766907&r1=1766906&r2=1766907&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] Thu Oct 27 22:53:44 2016
@@ -169,15 +169,32 @@ public final strictfp class UnitFormatTe
     }
 
     /**
+     * Tests unit formatting with {@link UnitFormat.Style#UCUM}.
+     */
+    @Test
+    public void testFormatUCUM() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        f.setStyle(UnitFormat.Style.UCUM);
+        assertEquals("m",   f.format(Units.METRE));
+        assertEquals("km",  f.format(Units.KILOMETRE));
+        assertEquals("s",   f.format(Units.SECOND));
+        assertEquals("min", f.format(Units.MINUTE));
+        assertEquals("m2",  f.format(Units.SQUARE_METRE));
+        assertEquals("Cel", f.format(Units.CELSIUS));
+        assertEquals("K",   f.format(Units.KELVIN));
+    }
+
+    /**
      * Tests unit formatting with {@link UnitFormat.Style#NAME}.
      */
     @Test
-    public void testNameFormatting() {
+    public void testFormatName() {
         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("minute",       f.format(Units.MINUTE));
         assertEquals("square metre", f.format(Units.SQUARE_METRE));
         assertEquals("Celsius",      f.format(Units.CELSIUS));          // Really upper-case "C" - this is a SI exception.
 
@@ -200,7 +217,7 @@ public final strictfp class UnitFormatTe
      * Tests parsing of names.
      */
     @Test
-    public void testNameParsing() {
+    public void testParseName() {
         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"));
@@ -242,10 +259,22 @@ public final strictfp class UnitFormatTe
     }
 
     /**
+     * Tests parsing a unit defined by a URI in OGC namespace.
+     * Example: {@code "urn:ogc:def:uom:EPSG::1026"} is for metres per second.
+     */
+    @Test
+    public void testParseEPSG() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        assertSame(Units.METRE,             f.parse("urn:ogc:def:uom:EPSG::9001"));
+        assertSame(Units.METRES_PER_SECOND, f.parse("urn:ogc:def:uom:EPSG::1026"));
+        assertSame(Units.METRE, f.parse("http://schemas.opengis.net/iso/19139/20070417/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])"));
+    }
+
+    /**
      * Tests parsing of symbols without arithmetic operations other than exponent.
      */
     @Test
-    public void testSymbolParsing() {
+    public void testParseSymbol() {
         final UnitFormat f = new UnitFormat(Locale.UK);
         assertSame(Units.METRE,         f.parse("m"));
         assertSame(Units.UNITY,         f.parse("m\u2070"));
@@ -264,8 +293,8 @@ public final strictfp class UnitFormatTe
      * Note that the "da" prefix needs to be handled in a special way because it is the only two-letters long prefix.
      */
     @Test
-    @DependsOnMethod("testSymbolParsing")
-    public void testPrefixParsing() {
+    @DependsOnMethod("testParseSymbol")
+    public void testParsePrefix() {
         final UnitFormat f = new UnitFormat(Locale.UK);
         ConventionalUnitTest.verify(Units.JOULE,  f.parse("kJ"),   "kJ",  1E+3);
         ConventionalUnitTest.verify(Units.HERTZ,  f.parse("MHz"),  "MHz", 1E+6);
@@ -289,8 +318,8 @@ public final strictfp class UnitFormatTe
      * Tests parsing of symbols composed of terms combined by arithmetic operations (e.g. "m/s").
      */
     @Test
-    @DependsOnMethod("testPrefixParsing")
-    public void testTermsParsing() {
+    @DependsOnMethod("testParsePrefix")
+    public void testParseTerms() {
         final UnitFormat f = new UnitFormat(Locale.UK);
         assertSame(Units.SQUARE_METRE,      f.parse("m\u22c5m"));
         assertSame(Units.CUBIC_METRE,       f.parse("m\u22c5m\u22c5m"));
@@ -299,4 +328,18 @@ public final strictfp class UnitFormatTe
         assertSame(Units.METRES_PER_SECOND, f.parse("m\u2215s"));
         assertSame(Units.HERTZ,             f.parse("1/s"));
     }
+
+    /**
+     * Tests parsing of symbols composed of terms combined by arithmetic operations (e.g. "m/s").
+     */
+    @Test
+    @DependsOnMethod("testParseTerms")
+    public void testParseMultiplier() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        assertSame(Units.MILLIMETRE, f.parse("m/1000"));
+        ConventionalUnitTest.verify(Units.METRE, f.parse("10*-6\u22c5m"),   "�m", 1E-6);
+        ConventionalUnitTest.verify(Units.METRE, f.parse("10*-6.m"),   "�m", 1E-6);
+        ConventionalUnitTest.verify(Units.METRE, f.parse( "1000*m"),   "km", 1E+3);
+        ConventionalUnitTest.verify(Units.METRE, f.parse( "1000.0*m"), "km", 1E+3);
+    }
 }

Modified: sis/branches/JDK8/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/pom.xml?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/pom.xml (original)
+++ sis/branches/JDK8/pom.xml Thu Oct 27 22:53:44 2016
@@ -555,7 +555,6 @@ Apache SIS is a free software, Java lang
           <includes>
             <include>**/*TestSuite.java</include>
           </includes>
-          <skipTests>true</skipTests>   <!-- Temporary, while we migrate from JSR-275 to JSR-363. -->
           <systemProperties>
             <property>
               <name>derby.stream.error.file</name>

Modified: sis/branches/JDK8/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java?rev=1766907&r1=1766906&r2=1766907&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java [UTF-8] Thu Oct 27 22:53:44 2016
@@ -144,7 +144,7 @@ public final strictfp class MetadataRead
             "��\u2502���\u2514\u2500Attribute group\n" +
             "��\u2502�������\u2514\u2500Attribute\n" +
             "��\u2502�����������\u251c\u2500Sequence identifier\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026 SST\n" +
-            "��\u2502�����������\u251c\u2500Units\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026 K\n" +
+            "��\u2502�����������\u251c\u2500Units\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026 \u212a\n" +
             "��\u2502�����������\u2514\u2500Description\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026\u2026 Sea temperature\n" +
             "��\u251c\u2500Data quality info\n" +
             "��\u2502���\u2514\u2500Lineage\n" +