Author: desruisseaux
Date: Fri Oct 13 12:00:36 2017
New Revision: 1812117

URL: http://svn.apache.org/viewvc?rev=1812117&view=rev
Log:
Parse CompoundCRS also in HTTP URL in addition of URN.

Modified:
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
    
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/DefinitionURITest.java

Modified: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java?rev=1812117&r1=1812116&r2=1812117&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
 [UTF-8] Fri Oct 13 12:00:36 2017
@@ -17,10 +17,11 @@
 package org.apache.sis.internal.util;
 
 import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
+import java.util.TreeMap;
 import java.util.Collections;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.internal.system.Loggers;
 
 import static org.apache.sis.util.CharSequences.*;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
@@ -135,6 +136,34 @@ public final class DefinitionURI {
     private static final char COMPONENT_SEPARATOR = ',';
 
     /**
+     * The separator between a URL and its first {@linkplain #components}.
+     * In URL syntax, this is the separator between URL path and the query.
+     *
+     * <div class="note"><b>Example:</b><code>
+     * http://www.opengis.net/def/crs-compound<u>?</u>1=…&amp;2=…
+     * </code></div>
+     */
+    private static final char COMPONENT_SEPARATOR_1 = '?';
+
+    /**
+     * The separator between {@linkplain #components} in a URL after the first 
component.
+     *
+     * <div class="note"><b>Example:</b><code>
+     * http://www.opengis.net/def/crs-compound?1=…<u>&amp;</u>2=…
+     * </code></div>
+     */
+    private static final char COMPONENT_SEPARATOR_2 = '&';
+
+    /**
+     * Separator between keys and values in the query part of a URL.
+     *
+     * <div class="note"><b>Example:</b><code>
+     * http://www.opengis.net/def/crs-compound?1<u>=</u>…&amp;2<u>=</u>…
+     * </code></div>
+     */
+    private static final char KEY_VALUE_SEPARATOR = '=';
+
+    /**
      * The domain of URLs in the OGC namespace.
      */
     public static final String DOMAIN = "www.opengis.net";
@@ -226,12 +255,12 @@ public final class DefinitionURI {
      * </ol></div>
      *
      * <div class="note"><b>HTTP example:</b> if the URI is
-     * {@code 
"http://www.opengis.net/def/crs-compound?1=(…)/crs/EPSG:9.1:27700&2=(…)/crs/EPSG:9.1:5701"},
+     * {@code 
"http://www.opengis.net/def/crs-compound?1=(…)/crs/EPSG/9.1/27700&2=(…)/crs/EPSG/9.1/5701"},
      * then this {@code DefinitionURI} will contain the {@code 
"http://www.opengis.net/def/crs-compound"}
      * header with two components:
      * <ol>
-     *   <li>{@code 
"http://http://www.opengis.net/def/crs/EPSG:9.1:27700"}</li>
-     *   <li>{@code "http://http://www.opengis.net/def/crs/EPSG:9.1:5701"}</li>
+     *   <li>{@code 
"http://http://www.opengis.net/def/crs/EPSG/9.1/27700"}</li>
+     *   <li>{@code "http://http://www.opengis.net/def/crs/EPSG/9.1/5701"}</li>
      * </ol></div>
      *
      * Note that this array may contain {@code null} elements if we failed to 
parse the corresponding component.
@@ -254,7 +283,7 @@ public final class DefinitionURI {
      */
     public static DefinitionURI parse(final String uri) {
         ensureNonNull("uri", uri);
-        return parse(uri, false, -1, uri.length(), SEPARATOR, 
COMPONENT_SEPARATOR);
+        return parse(uri, false, -1, uri.length());
     }
 
     /**
@@ -263,19 +292,17 @@ public final class DefinitionURI {
      *
      * This method may invoke itself recursively if the URI contains 
sub-components.
      *
-     * @param  uri                 the URI to parse.
-     * @param  prefixIsOptional    {@code true} if {@value #PREFIX} may not be 
present.
-     * @param  upper               upper index of the previous URI part, or -1 
if none.
-     * @param  stopAt              index (exclusive) where to stop parsing.
-     * @param  separator           separator character of URI parts.
-     * @param  componentSeparator  separator character of {@linkplain 
#components}.
+     * @param  uri               the URI to parse.
+     * @param  prefixIsOptional  {@code true} if {@value #PREFIX} may not be 
present.
+     * @param  upper             upper index of the previous URI part, or -1 
if none.
+     * @param  stopAt            index (exclusive) where to stop parsing.
      * @return the parse result, or {@code null} if the URI is not recognized.
      */
     @SuppressWarnings("fallthrough")
-    private static DefinitionURI parse(final String uri, boolean 
prefixIsOptional,
-            int upper, int stopAt, char separator, char componentSeparator)
-    {
-        DefinitionURI result = null;
+    private static DefinitionURI parse(final String uri, boolean 
prefixIsOptional, int upper, int stopAt) {
+        DefinitionURI result    = null;
+        char separator          = SEPARATOR;                    // Separator 
character of URI parts.
+        char componentSeparator = COMPONENT_SEPARATOR;          // Separator 
character of components.
         /*
          * Loop on all parts that we expect in the URI. Those parts are:
          *
@@ -311,11 +338,11 @@ public final class DefinitionURI {
                             return result;
                         }
                         if (!uri.regionMatches(upper, "//", 0, 2)) {
-                            return null;                // Prefix is never 
optional for HTTP.
+                            return null;                                // 
Prefix is never optional for HTTP.
                         }
                         upper++;
-                        separator = '/';                // Separator for the 
HTTP namespace.
-                        componentSeparator = '?';       // Separator for the 
query part in URL.
+                        separator = '/';                                // 
Separator for the HTTP namespace.
+                        componentSeparator = COMPONENT_SEPARATOR_1;     // 
Separator for the query part in URL.
                         prefixIsOptional = false;
                         break;
                     } else if (regionMatches("urn", uri, lower, upper)) {
@@ -364,23 +391,41 @@ public final class DefinitionURI {
                         if (stopAt < upper) {
                             upper = stopAt;
                         }
-                        if (componentSeparator == '?') {
-                            componentSeparator = '&';       // E.g. 
http://(…)/crs-compound?1=(…)&2=(…)
+                        if (componentSeparator == COMPONENT_SEPARATOR_1) {
+                            componentSeparator =  COMPONENT_SEPARATOR_2;    // 
E.g. http://(…)/crs-compound?1=(…)&2=(…)
                         }
                         if (result == null) {
                             result = new DefinitionURI();
                         }
                         final boolean isURN = !result.isHTTP;
-                        final List<DefinitionURI> cmp = new ArrayList<>(4);
+                        final Map<Integer,DefinitionURI> orderedComponents = 
new TreeMap<>();
                         boolean hasMore;
                         do {
+                            /*
+                             * Find indices of URI sub-component to parse. The 
sub-component will
+                             * go from 'splitAt' to 'next' exclusive 
('splitAt' is exclusive too).
+                             */
                             int next = uri.indexOf(componentSeparator, 
splitAt+1);
                             hasMore = next >= 0 && next < componentsEnd;
                             if (!hasMore) next = componentsEnd;
-                            cmp.add(parse(uri, isURN, splitAt, next, 
separator, componentSeparator));
+                            /*
+                             * HTTP uses key-value pairs as in 
"http://something?1=...&2=...
+                             * URN uses a comma-separated value list without 
number.
+                             * We support both forms, regardless if HTTP or 
URN.
+                             */
+                            int sequenceNumber = orderedComponents.size() + 1; 
     // Default value if no explicit key.
+                            final int s = splitKeyValue(uri, splitAt+1, next);
+                            if (s >= 0) try {
+                                sequenceNumber = 
Integer.parseInt(trimWhitespaces(uri, splitAt+1, s).toString());
+                                splitAt = s;                      // Set only 
on success.
+                            } catch (NumberFormatException e) {
+                                // Ignore. The URN is likely to be invalid, 
but we let parse(…) determines that.
+                                
Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY), 
DefinitionURI.class, "parse", e);
+                            }
+                            orderedComponents.put(sequenceNumber, parse(uri, 
isURN, splitAt, next));
                             splitAt = next;
                         } while (hasMore);
-                        result.components = cmp.toArray(new 
DefinitionURI[cmp.size()]);
+                        result.components = 
orderedComponents.values().toArray(new DefinitionURI[orderedComponents.size()]);
                     }
                     // Fall through
                 }
@@ -431,6 +476,23 @@ public final class DefinitionURI {
     }
 
     /**
+     * Returns the index of the {@code '='} character in the given sub-string, 
provided that all characters
+     * before it are spaces or decimal digits. Returns -1 if the key-value 
separator character is not found.
+     * Note that a positive return value does not guarantee that the number is 
parsable.
+     */
+    private static int splitKeyValue(final String uri, int lower, final int 
upper) {
+        while (lower < upper) {
+            int c = uri.codePointAt(lower);
+            if ((c < '0' || c > '9') && !Character.isWhitespace(c)) {
+                if (c == KEY_VALUE_SEPARATOR) return lower;
+                break;
+            }
+            lower += Character.charCount(c);
+        }
+        return -1;
+    }
+
+    /**
      * Returns the substring of the given URN, ignoring whitespaces and 
version number if present.
      * The substring is expected to contains at most one {@code ':'} 
character. If such separator
      * character is present, then that character and everything before it are 
ignored. The ignored
@@ -726,16 +788,24 @@ loop:   for (int p=0; ; p++) {
          * of "urn:ogc:def:crs:::"). This happen with URN defining compound 
CRS for instance.
          */
         int length = buffer.length();
-        while (--length >= 0 && buffer.charAt(length) == separator) {
+        n = isHTTP ? 2 : 1;
+        while ((length -= n) >= 0 && buffer.charAt(length) == separator) {
+            if (isHTTP && buffer.charAt(length + 1) != NO_VERSION.charAt(0)) 
break;
             buffer.setLength(length);
         }
         /*
          * If there is components, format them recursively. Note that the 
format is different depending if
          * we are formatting URN or HTTP.  Example: 
"urn:ogc:def:crs,crs:EPSG:9.1:27700,crs:EPSG:9.1:5701"
-         * and 
"http://www.opengis.net/def/crs-compound?1=(…)/crs/EPSG:9.1:27700&2=(…)/crs/EPSG:9.1:5701".
+         * and 
"http://www.opengis.net/def/crs-compound?1=(…)/crs/EPSG/9.1/27700&2=(…)/crs/EPSG/9.1/5701".
          */
         if (components != null) {
-            for (final DefinitionURI c : components) {
+            for (int i=0; i<components.length;) {
+                final DefinitionURI c = components[i++];
+                if (isHTTP) {
+                    buffer.append(i == 1 ? COMPONENT_SEPARATOR_1
+                                         : COMPONENT_SEPARATOR_2)
+                          .append(i).append(KEY_VALUE_SEPARATOR);
+                }
                 c.toString(buffer, COMPONENT_SEPARATOR);
             }
         }

Modified: 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/DefinitionURITest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/DefinitionURITest.java?rev=1812117&r1=1812116&r2=1812117&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/DefinitionURITest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/util/DefinitionURITest.java
 [UTF-8] Fri Oct 13 12:00:36 2017
@@ -147,13 +147,44 @@ public final strictfp class DefinitionUR
      * Example: {@code "urn:ogc:def:crs,crs:EPSG:6.3:27700,crs:EPSG:6.3:5701"}.
      */
     @Test
+    @DependsOnMethod("testParse")
     public void testCompoundURN() {
-        final DefinitionURI parsed = DefinitionURI.parse("urn:ogc:def:crs, crs 
:EPSG:9.1:27700, crs:EPSG: 9.1 :5701");
-        assertNotNull("components", parsed.components);
-        assertEquals("components.length", 2, parsed.components.length);
+        DefinitionURI parsed = DefinitionURI.parse("urn:ogc:def:crs, crs 
:EPSG:9.1:27700, crs:EPSG: 9.1 :5701");
+        assertNotNull("components",                    parsed.components);
+        assertEquals("components.length", 2,           
parsed.components.length);
         assertEquals("urn:ogc:def:crs:EPSG:9.1:27700", 
parsed.components[0].toString());
-        assertEquals("urn:ogc:def:crs:EPSG:9.1:5701", 
parsed.components[1].toString());
+        assertEquals("urn:ogc:def:crs:EPSG:9.1:5701",  
parsed.components[1].toString());
         assertEquals("urn:ogc:def:crs,crs:EPSG:9.1:27700,crs:EPSG:9.1:5701", 
parsed.toString());
+        /*
+         * The following URN is malformed, but Apache SIS should be tolerant 
to some errors.
+         *
+         *   - the "urn:ogc:def" prefix should be omitted in components, but 
SIS should be able to skip them.
+         *   - the "ogc:crs" parts should not be accepted because "def" is 
missing between "ogc" and "crs".
+         */
+        parsed = 
DefinitionURI.parse("urn:ogc:def:crs,urn:ogc:def:crs:EPSG:9.1:27700,ogc:crs:EPSG:9.1:5701,def:crs:OGC::AnsiDate");
+        assertNotNull("components",                    parsed.components);
+        assertEquals("components.length", 3,           
parsed.components.length);
+        assertEquals("urn:ogc:def:crs:EPSG:9.1:27700", 
parsed.components[0].toString());
+        assertNull  ("urn:ogc:def:crs:EPSG:9.1:5701",  parsed.components[1]);
+        assertEquals("urn:ogc:def:crs:OGC::AnsiDate",  
parsed.components[2].toString());
+    }
+
+    /**
+     * Tests compound CRS in HTTP URL.
+     */
+    @Test
+    @DependsOnMethod("testParseHTTP")
+    public void testCompoundHTTP() {
+        DefinitionURI parsed = 
DefinitionURI.parse("http://www.opengis.net/def/crs-compound?";
+                + "1=http://www.opengis.net/def/crs/EPSG/9.1/27700&";
+                + "2=http://www.opengis.net/def/crs/EPSG/9.1/5701";);
+        assertEquals("type", "crs-compound", parsed.type);
+        assertEquals("components.length", 2, parsed.components.length);
+        assertEquals("http://www.opengis.net/def/crs/EPSG/9.1/27700";, 
parsed.components[0].toString());
+        assertEquals("http://www.opengis.net/def/crs/EPSG/9.1/5701";,  
parsed.components[1].toString());
+        assertEquals("http://www.opengis.net/def/crs-compound?";
+                 + "1=http://www.opengis.net/def/crs/EPSG/9.1/27700&";
+                 + "2=http://www.opengis.net/def/crs/EPSG/9.1/5701";, 
parsed.toString());
     }
 
     /**


Reply via email to