sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1773452 - in /sis/branches/JDK8/core/sis-utility/src: main/java/org/apache/sis/internal/util/ main/java/org/apache/sis/measure/ main/java/org/apache/sis/util/resources/ test/java/org/apache/sis/internal/util/ test/java/org/apache/sis/measure/
Date Fri, 09 Dec 2016 18:42:10 GMT
Author: desruisseaux
Date: Fri Dec  9 18:42:09 2016
New Revision: 1773452

URL: http://svn.apache.org/viewvc?rev=1773452&view=rev
Log:
Better detection about where the parsing of unit should end (i.e. remove the previous hack).
Initial support of parenthesis.

Modified:
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java
    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/AngleFormat.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.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/resources/Errors.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/RangeFormatTest.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java
[UTF-8] Fri Dec  9 18:42:09 2016
@@ -27,7 +27,7 @@ import static org.apache.sis.internal.ut
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.8
  * @module
  */
 public final class XPaths extends Static {
@@ -38,6 +38,51 @@ public final class XPaths extends Static
     }
 
     /**
+     * If the given character sequences seems to be a URI, returns the presumed end of that
URN.
+     * Otherwise returns -1.
+     * 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>
+     *
+     * @param  uri     the URI candidate to verify.
+     * @param  offset  index of the first character to verify.
+     * @return index after the last character of the presumed URI, or -1 if this
+     *         method thinks that the given character sequence is not a URI.
+     *
+     * @since 0.8
+     */
+    public static int endOfURI(final CharSequence uri, int offset) {
+        boolean isURI = false;
+        int parenthesis = 0;
+        final int length = uri.length();
+scan:   while (offset < length) {
+            final int c = Character.codePointAt(uri, offset);
+            if (!Character.isLetterOrDigit(c)) {
+                switch (c) {
+                    case '#':                                           // Anchor in URL,
presumed followed by xpointer.
+                    case ':': isURI |= (parenthesis == 0); break;       // Scheme or URN
separator.
+                    case '_':
+                    case '-':                                           // Valid character
in URL.
+                    case '%':                                           // Encoded character
in URL.
+                    case '.':                                           // Domain name separator
in URL.
+                    case '/': break;                                    // Path separator,
but could also be division as in "m/s".
+                    case '(': parenthesis++; break;
+                    case ')': parenthesis--; break;
+                    default: {
+                        if (Character.isWhitespace(c)) break;           // Not supposed to
be valid, but be lenient.
+                        if (parenthesis != 0) break;
+                        break scan;                                     // Non-valid character
outside parenthesis.
+                    }
+                }
+            }
+            offset += Character.charCount(c);
+        }
+        return isURI ? offset : -1;
+    }
+
+    /**
      * Parses a URL which contains a pointer to a XML fragment.
      * The current implementation recognizes the following types:
      *
@@ -69,7 +114,7 @@ public final class XPaths extends Static
                     if (i >= 0 && regionMatches("xpointer", url, f+1, i)) {
                         i = url.indexOf("@gml:id=", i+1);
                         if (i >= 0) {
-                            i = skipLeadingWhitespaces(url, i+8, url.length()); // 8 is the
length of "@gml:id="
+                            i = skipLeadingWhitespaces(url, i+8, url.length());     // 8
is the length of "@gml:id="
                             final int c = url.charAt(i);
                             if (c == '\'' || c == '"') {
                                 final int s = url.indexOf(c, ++i);

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=1773452&r1=1773451&r2=1773452&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] Fri Dec  9 18:42:09 2016
@@ -307,15 +307,16 @@ abstract class AbstractUnit<Q extends Qu
 
     /**
      * Returns {@code true} if the given Unicode code point is a valid character for a unit
symbol.
-     * Current implementation accepts only letters, subscripts and the degree sign, but the
set of
-     * legal characters may be expanded in any future SIS version.
-     * The most important goal is to avoid confusion with exponents.
+     * Current implementation accepts letters, subscripts and the degree sign, but the set
of legal
+     * characters may be expanded in any future SIS version. The most important goal is to
avoid
+     * confusion with exponents and to detect where a unit symbol ends.
      *
-     * <p>Note that some units defined in the {@link Units} class break this rule.
But the hard-coded
-     * symbols in that class are known to be consistent with SI usage or with {@link UnitFormat}
work.</p>
+     * <p>Note that some units defined in the {@link Units} class break this rule.
In particular,
+     * some of those units contains superscripts or division sign. But the hard-coded symbols
in
+     * that class are known to be consistent with SI usage or with {@link UnitFormat} work.</p>
      */
     static boolean isSymbolChar(final int c) {
-        return Character.isLetter(c) || Characters.isSubScript(c) || c == '°';
+        return Character.isLetter(c) || Characters.isSubScript(c) || "°'′’\"″%‰-_".indexOf(c)
>= 0;
     }
 
     /**

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=1773452&r1=1773451&r2=1773452&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] Fri Dec  9 18:42:09 2016
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.measure;
 
+import java.util.Objects;
 import java.util.Locale;
 import java.text.Format;
 import java.text.FieldPosition;
@@ -41,9 +42,6 @@ import static org.apache.sis.math.MathFu
 import static org.apache.sis.math.MathFunctions.isNegative;
 import static org.apache.sis.math.DecimalFunctions.fractionDigitsForDelta;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * Parses and formats angles according a specified pattern. The pattern is a string

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=1773452&r1=1773451&r2=1773452&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] Fri Dec  9 18:42:09 2016
@@ -98,7 +98,7 @@ final class ConventionalUnit<Q extends Q
          * The SystemUnitTest.verifyRelatedUnits() method verified that the array does
          * not contain null element and that all 'toTarget' are instances of LinearConverter.
          */
-        final ConventionalUnit<Q>[] related = target.related;
+        final ConventionalUnit<Q>[] related = target.related();
         if (related != null && toTarget instanceof LinearConverter) {
             final LinearConverter c = (LinearConverter) toTarget;
             for (final ConventionalUnit<Q> existing : related) {

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
[UTF-8] Fri Dec  9 18:42:09 2016
@@ -16,15 +16,13 @@
  */
 package org.apache.sis.measure;
 
+import java.util.Objects;
 import javax.measure.Unit;
 import javax.measure.UnitConverter;
 import javax.measure.IncommensurableException;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * A range of numbers associated with a unit of measurement. All operations performed by
this
@@ -47,7 +45,7 @@ import java.util.Objects;
  * Subclasses may or may not be immutable, at implementation choice. But implementors are
  * encouraged to make sure that subclasses remain immutable for more predictable behavior.
  *
- * @param <E> The type of range elements as a subclass of {@link Number}.
+ * @param  <E>  the type of range elements as a subclass of {@link Number}.
  *
  * @author  Martin Desruisseaux (IRD)
  * @since   0.3

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=1773452&r1=1773451&r2=1773452&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] Fri Dec  9 18:42:09 2016
@@ -69,7 +69,7 @@ final class SystemUnit<Q extends Quantit
      *
      * @see #related(int)
      */
-    transient ConventionalUnit<Q>[] related;
+    private transient ConventionalUnit<Q>[] related;
 
     /**
      * Creates a new unit having the given symbol and EPSG code.
@@ -465,10 +465,22 @@ final class SystemUnit<Q extends Quantit
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
     final void related(final int n) {
+        if (related != null) {
+            throw new IllegalStateException();
+        }
         related = new ConventionalUnit[n];
     }
 
     /**
+     * Returns units for the same quantity but with scale factors that are not the SI one.
+     * This method returns a direct reference to the internal field; caller shall not modify.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    final ConventionalUnit<Q>[] related() {
+        return related;
+    }
+
+    /**
      * Compares this unit with the given object for equality.
      *
      * @param  other  the other object to compares with this unit, or {@code null}.

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=1773452&r1=1773451&r2=1773452&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] Fri Dec  9 18:42:09 2016
@@ -43,6 +43,7 @@ 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;
+import org.apache.sis.util.iso.DefaultNameSpace;
 
 
 /**
@@ -83,7 +84,12 @@ public class UnitFormat extends Format i
     private static final long serialVersionUID = -3064428584419360693L;
 
     /**
-     * The SI "“deca” prefix. This is the only SI prefix encoded on two letters instead
than one.
+     * The unit name for dimensionless unit.
+     */
+    static final String UNITY = "unity";
+
+    /**
+     * The SI “deca” prefix. This is the only SI prefix encoded on two letters instead
than one.
      * It can be represented by the CJK compatibility character “㍲”, but use of those
characters
      * is generally not recommended outside of Chinese, Japanese or Korean texts.
      */
@@ -186,8 +192,8 @@ public class UnitFormat extends Format i
 
             /** 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(')');
+                toAppendTo.append(EXPONENT).append(OPEN).append(String.valueOf(power.numerator))
+                           .append('/').append(String.valueOf(power.denominator)).append(CLOSE);
             }
         },
 
@@ -199,6 +205,11 @@ public class UnitFormat extends Format i
         NAME('⋅', '∕');
 
         /**
+         * Other symbols not in the {@link Style} enumeration because common to all.
+         */
+        static final char EXPONENT_OR_MULTIPLY = '*', EXPONENT = '^', OPEN = '(', CLOSE =
')';
+
+        /**
          * Symbols to use for unit multiplications or divisions.
          */
         final char multiply, divide;
@@ -234,11 +245,12 @@ public class UnitFormat extends Format i
          * Appends a rational power.
          */
         void appendPower(final Appendable toAppendTo, final Fraction power) throws IOException
{
+            toAppendTo.append(EXPONENT);
             final String value = power.toString();
             if (value.length() == 1) {
-                toAppendTo.append('^').append(value);
+                toAppendTo.append(value);
             } else {
-                toAppendTo.append("^(").append(value).append(')');
+                toAppendTo.append(OPEN).append(value).append(CLOSE);
             }
         }
     }
@@ -452,7 +464,7 @@ public class UnitFormat extends Format i
                 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
+                 * property files neither. It contains longer names sometime 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").
                  */
@@ -541,7 +553,7 @@ public class UnitFormat extends Format i
                 label = unit.getSymbol();
                 if (label != null) {
                     if (label.isEmpty()) {
-                        label = "unity";
+                        label = UNITY;
                     }
                     // Following is not thread-safe, but it is okay since we do not use INSTANCE
for unit names.
                     final ResourceBundle names = symbolToName();
@@ -569,7 +581,8 @@ public class UnitFormat extends Format i
         final double scale = Units.toStandardUnit(unit);
         if (scale != 1) {
             if (Double.isNaN(scale)) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.NonRatioUnit_1,
"?(" + unit.getSystemUnit() + ')'));
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.NonRatioUnit_1,
+                        "?⋅" + Style.OPEN + unit.getSystemUnit() + Style.CLOSE));
             }
             final String text = Double.toString(scale);
             int length = text.length();
@@ -620,12 +633,16 @@ public class UnitFormat extends Format i
                 deferred.add(entry);
             }
         }
-        // At this point, all numerators have been appended. Now append the denominators
together.
+        /*
+         * At this point, all numerators have been appended. Now append the denominators
together.
+         * For example pressure dimension is formatted as M∕(L⋅T²) no matter if 'M'
was the first
+         * dimension in the given 'components' map or not.
+         */
         if (!deferred.isEmpty()) {
             toAppendTo.append(style.divide);
             final boolean useParenthesis = (deferred.size() > 1);
             if (useParenthesis) {
-                toAppendTo.append('(');
+                toAppendTo.append(Style.OPEN);
             }
             isFirst = true;
             for (final Map.Entry<?,? extends Number> entry : deferred) {
@@ -636,7 +653,7 @@ public class UnitFormat extends Format i
                 formatComponent(entry, true, style, toAppendTo);
             }
             if (useParenthesis) {
-                toAppendTo.append(')');
+                toAppendTo.append(Style.CLOSE);
             }
         }
     }
@@ -733,24 +750,6 @@ 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;
-    }
-
-    /**
      * 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.
@@ -796,6 +795,10 @@ public class UnitFormat extends Format i
      * The product operator can be either {@code '.'} (ASCII) or {@code '⋅'} (Unicode)
character.
      * Exponent after symbol can be decimal digits as in “m2” or a superscript as in
“m²”.</p>
      *
+     * <p>The default implementation delegates to
+     * <code>{@linkplain #parse(CharSequence, ParsePosition) parse}(symbols, new ParsePosition(0))</code>
+     * and verifies that all non-white characters have been parsed.</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.
@@ -803,8 +806,43 @@ public class UnitFormat extends Format i
      * @see Units#valueOf(String)
      */
     @Override
-    @SuppressWarnings( {"null", "fallthrough"})
-    public Unit<?> parse(CharSequence symbols) throws ParserException {
+    public Unit<?> parse(final CharSequence symbols) throws ParserException {
+        final ParsePosition position = new ParsePosition(0);
+        final Unit<?> unit = parse(symbols, position);
+        final int length = symbols.length();
+        final int unrecognized = CharSequences.skipTrailingWhitespaces(symbols, position.getIndex(),
length);
+        if (unrecognized < length) {
+            throw new ParserException(Errors.format(Errors.Keys.UnexpectedCharactersAfter_2,
+                    CharSequences.trimWhitespaces(symbols, 0, unrecognized),
+                    CharSequences.trimWhitespaces(symbols, unrecognized, length)),
+                    symbols, unrecognized);
+        }
+        return unit;
+    }
+
+    /**
+     * Parses a portion of the given text as an instance of {@code Unit}.
+     * Parsing begins at the index given by {@link ParsePosition#getIndex()}.
+     * After parsing, the above-cited index is updated to the first unparsed character.
+     *
+     * <p>The parsing is lenient: symbols can be products or quotients of units like
“m∕s”,
+     * words like “meters per second”, or authority codes like {@code "urn:ogc:def:uom:EPSG::1026"}.
+     * The product operator can be either {@code '.'} (ASCII) or {@code '⋅'} (Unicode)
character.
+     * Exponent after symbol can be decimal digits as in “m2” or a superscript as in
“m²”.</p>
+     *
+     * <p>Note that contrarily to {@link #parseObject(String, ParsePosition)}, this
method never return {@code null}.
+     * If an error occurs at parsing time, an unchecked {@link ParserException} is thrown.</p>
+     *
+     * @param  symbols  the unit symbols to parse.
+     * @param  position on input, index of the first character to parse.
+     *                  On output, index after the last parsed character.
+     * @return the unit parsed from the specified symbols.
+     * @throws ParserException if a problem occurred while parsing the given symbols.
+     */
+    @SuppressWarnings({"null", "fallthrough"})
+    public Unit<?> parse(CharSequence symbols, final ParsePosition position) throws
ParserException {
+        ArgumentChecks.ensureNonNull("symbols",  symbols);
+        ArgumentChecks.ensureNonNull("position", position);
         /*
          * 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
@@ -815,77 +853,161 @@ 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(symbols)) {
-            final String uom = symbols.toString();
+        int start = CharSequences.skipLeadingWhitespaces(symbols, position.getIndex(), symbols.length());
+        int end = XPaths.endOfURI(symbols, start);
+        if (end >= 0) {
+            final String uom = symbols.subSequence(start, end).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));
-            } catch (NumberFormatException e) {
-                throw (ParserException) new ParserException(
-                        Errors.format(Errors.Keys.IllegalArgumentValue_2, "symbols", symbols),
-                        uom, Math.max(0, uom.indexOf(code))).initCause(e);
+            if (code != null && code != uom) {                  // Really identity
check, see above comment.
+                NumberFormatException failure = null;
+                try {
+                    final Unit<?> unit = Units.valueOfEPSG(Integer.parseInt(code));
+                    if (unit != null) {
+                        position.setIndex(end);
+                        return unit;
+                    }
+                } catch (NumberFormatException e) {
+                    failure = e;
+                }
+                throw (ParserException) new ParserException(Errors.format(Errors.Keys.UnknownUnit_1,
+                        Constants.EPSG + DefaultNameSpace.DEFAULT_SEPARATOR + code),
+                        symbols, start + Math.max(0, uom.indexOf(code))).initCause(failure);
             }
             code = XPaths.xpointer("uom", uom);
             if (code != null) {
                 symbols = code;
+                start = 0;
             }
         }
         /*
          * Split the unit around the multiplication and division operators and parse each
term individually.
+         * Note that exponentation need to be kept as part of a single unit symbol.
+         *
+         * The 'start' variable is the index of the first character of the next unit term
to parse.
          */
-        int offset    = 0;                  // Index of the first character of the next unit
term to parse.
-        int operation = NOOP;               // Enumeration value: MULTIPLY, DIVIDE.
-        Unit<?> unit  = null;
-        final int length = symbols.length();
-        for (int i=0; i<length; i++) {
-            final char c = symbols.charAt(i);   // No need to use code points because we
search characters in BMP.
+        int operation = NOOP;            // Enumeration value: IMPLICIT, MULTIPLY, DIVIDE.
+        Unit<?> unit = null;
+        end = symbols.length();
+        int i = start;
+scan:   for (int n; i < end; i += n) {
+            final int c = Character.codePointAt(symbols, i);
+            n = Character.charCount(c);
             final int next;
             switch (c) {
                 /*
-                 * Star is for exponentiation in UCUM syntax, but some symbols may use it
for unit multiplications.
-                 * We interpret
+                 * For any character that are is not an operator or parenthesis, either continue
the scanning of
+                 * character or stop it, depending on whether the character is valid for
a unit symbol or not.
+                 * In the later case, we consider that we reached the end of a unit symbol.
+                 */
+                default:  {
+                    if (AbstractUnit.isSymbolChar(c)) {
+                        if (operation == IMPLICIT) {
+                            operation = MULTIPLY;
+                        }
+                        continue;
+                    }
+                    if (Character.isWhitespace(c) || Character.isDigit(c) || Characters.isSuperScript(c))
{
+                        continue;
+                    }
+                    break scan;
+                }
+                /*
+                 * Star is for exponentiation in UCUM syntax, but some symbols may use it
for unit multiplication.
+                 * We interpret the symbol as a multiplication if the characters before or
after it seem to be for
+                 * a unit symbol.
                  */
-                case '*': if (isExponentOperator(symbols, i, length)) continue;
-                          next = MULTIPLY; break;
+                case Style.EXPONENT_OR_MULTIPLY: {
+                    if (!isExponentOperator(symbols, i, end)) {
+                        next = MULTIPLY;
+                        break;
+                    }
+                    // else fall through.
+                }
+                case Style.EXPONENT: {
+                    if (operation == IMPLICIT) {
+                        // Support of exponentiation after parenthesis is not yet supported.
+                        break scan;
+                    }
+                    continue;
+                }
                 /*
                  * 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.
π/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 '⋅': // Fallthrough
+                case '.': if (isDecimalSeparator(symbols, i, end)) continue;
+                case '⋅': // Fall through
                 case '×': next = MULTIPLY; break;
                 case '÷':
                 case '⁄': // Fraction slash
                 case '/':
                 case '∕': next = DIVIDE; break;
-                default:  continue;
+                /*
+                 * If we find an '(' parenthesis, invoke recursively this method for the
part inside parenthesis.
+                 * The parsing should end at the ')' parenthesis since it is not a valid
unit symbol. If we do not
+                 * find that closing parenthesis, this will be considered an error.
+                 */
+                case Style.OPEN: {
+                    final ParsePosition sub = new ParsePosition(i + Character.charCount(c));
+                    final Unit<?> term = parse(symbols, sub);
+                    i = CharSequences.skipLeadingWhitespaces(symbols, sub.getIndex(), end);
+                    if (i >= end || Character.codePointAt(symbols, i) != Style.CLOSE)
{
+                        throw new ParserException(Errors.format(Errors.Keys.NonEquilibratedParenthesis_2,
+                               symbols.subSequence(start, i), Style.CLOSE), symbols, start);
+                    }
+                    unit = apply(operation, unit, term);
+                    operation = IMPLICIT;       // Default operation if there is no × or
/ symbols after parenthesis.
+                    start = i + (n = 1);        // Skip the number of characters in the '('
Unicode code point.
+                    continue;
+                }
             }
-            final Unit<?> term = parseSymbol(symbols, offset, i);
-            switch (operation) {
-                case NOOP:     unit = term; break;
-                case MULTIPLY: unit = unit.multiply(term); break;
-                case DIVIDE:   unit = unit.divide(term); break;
-                default: throw new AssertionError(operation);
+            /*
+             * At this point, we have either a first unit to parse (NOOP), or a multiplication
or division to apply
+             * between the previously parsed units and the next unit to parse. A special
case is IMPLICIT, which is
+             * a multiplication without explicit × symbol after the parenthesis. The implicit
multiplication can be
+             * overridden by an explicit × or / symbol, which is what happened if we reach
this point (tip: look in
+             * the above 'switch' statement all cases that end with 'break', not 'break scan'
or 'continue').
+             */
+            if (operation == IMPLICIT) {
+                operation = next;
+                continue;
             }
+            unit = apply(operation, unit, parseSymbol(symbols, start, i));
             operation = next;
-            offset = i+1;
-        }
-        final Unit<?> term = parseSymbol(symbols, offset, length);
-        switch (operation) {
-            case NOOP:     unit = term; break;
-            case MULTIPLY: unit = unit.multiply(term); break;
-            case DIVIDE:   unit = unit.divide(term); break;
-            default: throw new AssertionError(operation);
+            start = i + n;
         }
+        /*
+         * At this point we either found an unrecognized character or reached the end of
string. Parse the
+         * remaining characters as a unit and apply the pending unit operation (multiplication
or division).
+         */
+        unit = apply(operation, unit, parseSymbol(symbols, start, i));
+        position.setIndex(i);
         return unit;
     }
 
     /**
-     * Meaning of some characters parsed by {@link #parse(CharSequence)}.
+     * Meaning of some characters parsed by {@link #parse(CharSequence)}. The {@code IMPLICIT}
case
+     * is a multiplication without symbol, which can be overridden by an explicit × or /
symbol.
      */
-    private static final int NOOP = 0, MULTIPLY = 1, DIVIDE = 2;
+    private static final int NOOP = 0, IMPLICIT = 1, MULTIPLY = 2, DIVIDE = 3;
+
+    /**
+     * Applies a multiplication or division operation between the given units.
+     *
+     * @param  operation  one of {@link #NOOP}, {@link #IMPLICIT}, {@link #MULTIPLY} or {@link
#DIVIDE}.
+     * @param  unit       the left operand, which is the unit parsed so far.
+     * @param  term       the right operation, which is the newly parsed unit.
+     */
+    private static Unit<?> apply(final int operation, final Unit<?> unit, final
Unit<?> term) {
+        switch (operation) {
+            case NOOP:     return term;
+            case IMPLICIT:
+            case MULTIPLY: return unit.multiply(term);
+            case DIVIDE:   return unit.divide(term);
+            default: throw new AssertionError(operation);
+        }
+    }
 
     /**
      * Parses a single unit symbol with its exponent.
@@ -933,7 +1055,7 @@ public class UnitFormat extends Format i
                                     return parseSymbol(uom, s, length).multiply(multiplier);
                                 }
                             }
-                            s = uom.lastIndexOf('*');
+                            s = uom.lastIndexOf(Style.EXPONENT_OR_MULTIPLY);
                             if (s >= 0) {
                                 final int base = Integer.parseInt(uom.substring(0, s));
                                 final int exp  = Integer.parseInt(uom.substring(s+1));
@@ -1022,6 +1144,9 @@ public class UnitFormat extends Format i
                         "degrees", "degree").toString();
                 unit = nameToUnit().get(lc);
                 if (unit == null) {
+                    if (CharSequences.regionMatches(symbols, lower, UNITY, true)) {
+                        return Units.UNITY;
+                    }
                     throw new ParserException(Errors.format(Errors.Keys.UnknownUnit_1, uom),
symbols, lower);
                 }
             }
@@ -1062,33 +1187,6 @@ public class UnitFormat extends Format i
     }
 
     /**
-     * Parses the given text as an instance of {@code Unit}.
-     *
-     * @param  symbols  the unit symbols to parse.
-     * @param  pos      on input, index of the first character to parse.
-     *                  On output, index after the last parsed character.
-     * @return the unit parsed from the specified symbols.
-     * @throws ParserException if a problem occurred while parsing the given symbols.
-     */
-    public Unit<?> parse(final CharSequence symbols, final ParsePosition pos) {
-        final int start = pos.getIndex();
-        int stop = start;
-        while (stop < symbols.length()) {
-            final int c = Character.codePointAt(symbols, stop);
-            if (Character.isWhitespace(c) || c == ']') break;       // Temporary hack before
we complete the JSR-275 replacement.
-            stop += Character.charCount(c);
-        }
-        try {
-            final Unit<?> unit = parse(symbols.subSequence(start, stop));
-            pos.setIndex(stop);
-            return unit;
-        } catch (ParserException e) {
-            pos.setErrorIndex(start);
-            return null;
-        }
-    }
-
-    /**
      * Parses text from a string to produce a unit. The default implementation delegates
to {@link #parse(CharSequence)}
      * and wraps the {@link ParserException} into a {@link ParseException} for compatibility
with {@code java.text} API.
      *
@@ -1106,8 +1204,9 @@ public class UnitFormat extends Format i
     }
 
     /**
-     * Parses text from a string to produce a unit. The default implementation delegates
to
-     * {@link #parse(CharSequence, ParsePosition)} with no additional work.
+     * Parses text from a string to produce a unit, or returns {@code null} if the parsing
failed.
+     * The default implementation delegates to {@link #parse(CharSequence, ParsePosition)}
and catches
+     * the {@link ParserException}.
      *
      * @param  source  the text, part of which should be parsed.
      * @param  pos     index and error index information as described above.
@@ -1115,6 +1214,11 @@ public class UnitFormat extends Format i
      */
     @Override
     public Object parseObject(final String source, final ParsePosition pos) {
-        return parse(source, pos);
+        try {
+            return parse(source, pos);
+        } catch (ParserException e) {
+            pos.setErrorIndex(e.getPosition());
+            return null;
+        }
     }
 }

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=1773452&r1=1773451&r2=1773452&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]
Fri Dec  9 18:42:09 2016
@@ -1208,9 +1208,9 @@ public final class Units extends Static
      */
     private static <Q extends Quantity<Q>> ConventionalUnit<Q> add(SystemUnit<Q>
target, UnitConverter toTarget, String symbol, byte scope, short epsg) {
         final ConventionalUnit<Q> unit = UnitRegistry.init(new ConventionalUnit<>(target,
toTarget, symbol, scope, epsg));
-        final ConventionalUnit<Q>[] related = target.related;
+        final ConventionalUnit<Q>[] related = target.related();
         if (related != null && unit.scope != UnitRegistry.SI) {
-            // Search first empty slot. This algorithm is inefficient, but the length of
those arrays is small (<= 6).
+            // Search first empty slot. This algorithm is inefficient, but the length of
those arrays is small (<= 7).
             int i = 0;
             while (related[i] != null) i++;
             related[i] = unit;

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
[UTF-8] Fri Dec  9 18:42:09 2016
@@ -735,7 +735,7 @@ public final class Errors extends Indexe
         public static final short UnexpectedChange_1 = 128;
 
         /**
-         * The “{1}” characters after “{0}” was unexpected.
+         * The “{1}” characters after “{0}” were unexpected.
          */
         public static final short UnexpectedCharactersAfter_2 = 129;
 

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
[ISO-8859-1] Fri Dec  9 18:42:09 2016
@@ -158,7 +158,7 @@ TreeDepthExceedsMaximum           = Tree
 UndefinedOrderingForElements_2    = Ordering between \u201c{0}\u201d and \u201c{1}\u201d
elements is undefined.
 UnexpectedArrayLength_2           = Expected an array of length {0}, but got {1}.
 UnexpectedChange_1                = Unexpected change in \u2018{0}\u2019.
-UnexpectedCharactersAfter_2       = The \u201c{1}\u201d characters after \u201c{0}\u201d
was unexpected.
+UnexpectedCharactersAfter_2       = The \u201c{1}\u201d characters after \u201c{0}\u201d
were unexpected.
 UnexpectedCharactersAtBound_4     = Text for \u2018{0}\u2019 was expected to {1,choice,0#begin|1#end}
with \u201c{2}\u201d, but found \u201c{3}\u201d.
 UnexpectedEndOfFile_1             = Unexpected end of file while reading \u201c{0}\u201d.
 UnexpectedEndOfString_1           = More characters were expected at the end of \u201c{0}\u201d.

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java
[UTF-8] Fri Dec  9 18:42:09 2016
@@ -27,11 +27,24 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.8
  * @module
  */
 public final strictfp class XPathsTest extends TestCase {
     /**
+     * Tests the {@link XPaths#endOfURI(CharSequence, int)} method.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testEndOfURI() {
+        assertEquals(26, XPaths.endOfURI("urn:ogc:def:uom:EPSG::9001", 0));
+        assertEquals(97, XPaths.endOfURI("http://schemas.opengis.net/iso/19139/20070417/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])",
0));
+        assertEquals(-1, XPaths.endOfURI("m/s", 0));
+        assertEquals(-1, XPaths.endOfURI("m.s", 0));
+    }
+
+    /**
      * Tests {@link XPaths#xpointer(String, String)}.
      */
     @Test

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/RangeFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/RangeFormatTest.java?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/RangeFormatTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/RangeFormatTest.java
[UTF-8] Fri Dec  9 18:42:09 2016
@@ -225,9 +225,9 @@ public final strictfp class RangeFormatT
 
         assertEquals(NumberRange.create(-10, true,   20, true ), parse("[-10 … 20]" ));
         assertEquals(NumberRange.create( -3, false,   4, false), parse("( -3 …  4) "));
-        assertEquals(NumberRange.create(  2, true,    8, false), parse("  [2 …  8) _"));
-        assertEquals(NumberRange.create( 40, false,  90, true ), parse(" (40 … 90]_"));
-        assertEquals(NumberRange.create(300, true,  300, true ), parse(" 300_"));
+        assertEquals(NumberRange.create(  2, true,    8, false), parse("  [2 …  8) "));
+        assertEquals(NumberRange.create( 40, false,  90, true ), parse(" (40 … 90]"));
+        assertEquals(NumberRange.create(300, true,  300, true ), parse(" 300 "));
         assertEquals(NumberRange.create(300, true,  300, true ), parse("[300]"));
         assertEquals(NumberRange.create(300, false, 300, false), parse("(300)"));
         assertEquals(NumberRange.create(300, true,  300, true ), parse("{300}"));

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java?rev=1773452&r1=1773451&r2=1773452&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/SystemUnitTest.java
[UTF-8] Fri Dec  9 18:42:09 2016
@@ -60,7 +60,7 @@ public final strictfp class SystemUnitTe
         for (final Field f : Units.class.getFields()) {
             final Object value = f.get(null);
             if (value instanceof SystemUnit<?>) {
-                final ConventionalUnit<?>[] related = ((SystemUnit<?>) value).related;
+                final ConventionalUnit<?>[] related = ((SystemUnit<?>) value).related();
                 if (related != null) {
                     final String symbol = ((SystemUnit<?>) value).getSymbol();
                     for (final ConventionalUnit<?> r : related) {

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=1773452&r1=1773451&r2=1773452&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] Fri Dec  9 18:42:09 2016
@@ -22,6 +22,7 @@ import java.util.Locale;
 import java.lang.reflect.Field;
 import javax.measure.Unit;
 import javax.measure.format.ParserException;
+import org.apache.sis.util.Characters;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
@@ -137,6 +138,16 @@ public final strictfp class UnitFormatTe
         assertEquals(field, symbol,    UnitFormat.INSTANCE.format(unit));
         if (name != null) {
             assertEquals(field, name, UnitFormat.getBundle(Locale.UK).getString(symbol));
+            for (int i=0; i<name.length();) {
+                final int c = name.codePointAt(i);
+                assertTrue(name, AbstractUnit.isSymbolChar(c) || Character.isWhitespace(c));
+                i += Character.charCount(c);
+            }
+        }
+        for (int i=0; i<symbol.length();) {
+            final int c = symbol.codePointAt(i);
+            assertTrue(symbol, AbstractUnit.isSymbolChar(c) || Characters.isSuperScript(c)
|| c == '∕');
+            i += Character.charCount(c);
         }
         declared.remove(field);
     }
@@ -264,6 +275,7 @@ public final strictfp class UnitFormatTe
         assertSame(Units.CELSIUS,       f.parse("degC"));
         assertSame(Units.CELSIUS,       f.parse("deg C"));
         assertSame(Units.WATT,          f.parse("watt"));
+        assertSame(Units.UNITY,         f.parse("unity"));
         try {
             f.parse("degree foo");
             fail("Should not accept unknown unit.");




Mime
View raw message