Author: desruisseaux
Date: Thu Oct 12 21:38:33 2017
New Revision: 1812053
URL: http://svn.apache.org/viewvc?rev=1812053&view=rev
Log:
Partial implementation of compound CRS in URN. For now only the URN parser has
been adapted (not yet the CRS factory).
Modified:
sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameMeaning.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java
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-metadata/src/main/java/org/apache/sis/internal/metadata/NameMeaning.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameMeaning.java?rev=1812053&r1=1812052&r2=1812053&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameMeaning.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameMeaning.java
[UTF-8] Thu Oct 12 21:38:33 2017
@@ -35,7 +35,7 @@ import org.apache.sis.metadata.iso.citat
/**
- * The meaning of some part of URN in the {@code "ogc"} namespace.
+ * The meaning of some parts of URN in the {@code "ogc"} namespace.
* The meaning are defined by <cite>OGC Naming Authority</cite> (OGCNA) or
other OGC sources.
*
* @author Martin Desruisseaux (IRD, Geomatys)
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java?rev=1812053&r1=1812052&r2=1812053&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java
[UTF-8] Thu Oct 12 21:38:33 2017
@@ -32,7 +32,7 @@ import org.opengis.referencing.operation
* <li>If the expected mathematical value is infinite (for example the
Mercator projection at ±90° of latitude),
* then the map projection should return a {@link
Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY},
* depending on the sign of the correct mathematical answer.</li>
- * <li>If no real number is expected to exist for the input coordinate (for
example the root of a negative value),
+ * <li>If no real number is expected to exist for the input coordinate (for
example at a latitude greater than 90°),
* then the map projection should return {@link Double#NaN}.</li>
* <li>If a real number is expected to exist but the map projection fails to
compute it (for example because an
* iterative algorithm does not converge), then the projection should
throw {@code ProjectionException}.</li>
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=1812053&r1=1812052&r2=1812053&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] Thu Oct 12 21:38:33 2017
@@ -17,6 +17,8 @@
package org.apache.sis.internal.util;
import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
import java.util.Collections;
import org.apache.sis.util.CharSequences;
@@ -46,7 +48,7 @@ import static org.apache.sis.internal.ut
* <li>{@code http://www.opengis.net/def/uom/SI/0/m%2Fs}</li>
* </ul>
*
- * <div class="section">Components or URN</div>
+ * <div class="section">Parts of URN</div>
* URN begins with {@code "urn:ogc:def:"} (formerly {@code "urn:x-ogc:def:"})
followed by:
* <ul>
* <li>an object {@linkplain #type}</li>
@@ -103,7 +105,7 @@ import static org.apache.sis.internal.ut
* {@code "urn:ogc:def:crs,crs:EPSG:6.3:27700,crs:EPSG:6.3:5701"}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
*
* @see org.apache.sis.internal.metadata.NameMeaning
* @see <a
href="http://portal.opengeospatial.org/files/?artifact_id=24045">Definition
identifier URNs in OGC namespace</a>
@@ -124,6 +126,15 @@ public final class DefinitionURI {
public static final char SEPARATOR = ':';
/**
+ * The separator between {@linkplain #components} in a URN.
+ *
+ * <div class="note"><b>Example:</b>
+ * in {@code "urn:ogc:def:crs,crs:EPSG:9.1:27700,crs:EPSG:9.1:5701"}, the
components are
+ * {@code "crs:EPSG:9.1:27700"} and {@code "crs:EPSG:9.1:5701"}.</div>
+ */
+ private static final char COMPONENT_SEPARATOR = ',';
+
+ /**
* The domain of URLs in the OGC namespace.
*/
public static final String DOMAIN = "www.opengis.net";
@@ -204,6 +215,30 @@ public final class DefinitionURI {
public String[] parameters;
/**
+ * If the URI contains sub-components, those sub-components. Otherwise
{@code null}.
+ *
+ * <div class="note"><b>URN example:</b>
+ * if the URI is {@code
"urn:ogc:def:crs,crs:EPSG:9.1:27700,crs:EPSG:9.1:5701"}, then this
+ * {@code DefinitionURI} will contain the {@code "urn:ogc:def:crs"} header
with two components:
+ * <ol>
+ * <li>{@code "urn:ogc:def:crs:EPSG:9.1:27700"}</li>
+ * <li>{@code "urn:ogc:def:crs:EPSG:9.1:5701"}</li>
+ * </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"},
+ * 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>
+ * </ol></div>
+ *
+ * Note that this array may contain {@code null} elements if we failed to
parse the corresponding component.
+ */
+ public DefinitionURI[] components;
+
+ /**
* For {@link #parse(String)} usage only.
*/
private DefinitionURI() {
@@ -219,9 +254,28 @@ public final class DefinitionURI {
*/
public static DefinitionURI parse(final String uri) {
ensureNonNull("uri", uri);
+ return parse(uri, false, -1, uri.length(), SEPARATOR,
COMPONENT_SEPARATOR);
+ }
+
+ /**
+ * Parses a sub-region of the given URI. This method can start parsing for
an arbitrary URI part,
+ * no necessarily the root. The first URI part is identified by an ordinal
number:
+ *
+ * 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}.
+ * @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;
- char separator = SEPARATOR;
- int upper = -1;
/*
* Loop on all parts that we expect in the URI. Those parts are:
*
@@ -234,19 +288,19 @@ public final class DefinitionURI {
* 6: code
* 7: parameters, or null if none.
*/
- for (int p=0; p<=6; p++) {
+ for (int part = 0; part <= 6; part++) {
final int lower = upper + 1;
upper = uri.indexOf(separator, lower);
- if (upper < 0) {
- upper = uri.length();
+ if (upper < 0 || upper >= stopAt) {
+ upper = stopAt;
if (lower > upper) {
- return result; // Happen if a component is missing.
+ return result; // Happen if a part is
missing.
}
}
- switch (p) {
+ switch (part) {
/*
- * Verifies that the 3 first components are "urn:ogc:def:" or
"http://www.opengis.net/def/"
- * without storing them. In the particular case of second
component, we also accept "x-ogc"
+ * Verifies that the 3 first parts are "urn:ogc:def:" or
"http://www.opengis.net/def/"
+ * without storing them. In the particular case of second
part, we also accept "x-ogc"
* in addition to "ogc" in URN.
*/
case 0: {
@@ -257,74 +311,123 @@ public final class DefinitionURI {
return result;
}
if (!uri.regionMatches(upper, "//", 0, 2)) {
- return null;
+ return null; // Prefix is never
optional for HTTP.
}
upper++;
- separator = '/'; // Separator for the HTTP
namespace.
- } else if (!regionMatches("urn", uri, lower, upper)) {
+ separator = '/'; // Separator for the
HTTP namespace.
+ componentSeparator = '?'; // Separator for the
query part in URL.
+ prefixIsOptional = false;
+ break;
+ } else if (regionMatches("urn", uri, lower, upper)) {
+ prefixIsOptional = false;
+ break;
+ } else if (!prefixIsOptional) {
return null;
}
- break;
+ part++;
+ // Part is not "urn" but its presence was optional. Maybe
it is "ogc". Fall through for checking.
}
case 1: {
final boolean isHTTP = (separator != SEPARATOR);
- if (!regionMatches(isHTTP ? DOMAIN : "ogc", uri, lower,
upper)) {
- if (isHTTP || !regionMatches("x-ogc", uri, lower,
upper)) {
- return null;
- }
+ if (regionMatches(isHTTP ? DOMAIN : "ogc", uri, lower,
upper) ||
+ (!isHTTP && regionMatches("x-ogc", uri, lower,
upper)))
+ {
+ prefixIsOptional = false;
+ break;
+ } else if (!prefixIsOptional) {
+ return null;
}
- break;
+ part++;
+ // Part is not "ogc" but its presence was optional. Maybe
it is "def". Fall through for checking.
}
case 2: {
- if (!regionMatches("def", uri, lower, upper)) {
+ if (regionMatches("def", uri, lower, upper)) {
+ prefixIsOptional = false;
+ break;
+ } else if (!prefixIsOptional) {
return null;
}
- break;
+ part++;
+ // Part is not "def" but its presence was optional. Maybe
it is "crs". Fall through for checking.
}
/*
- * For all components after the first 3 ones, trim whitespaces
and store non-empty values.
+ * The forth part is the first one that we want to remember;
all cases before this one were
+ * only verification. This case is also the first part where
component separator may appear,
+ * for example as in
"urn:ogc:def:crs,crs:EPSG:9.1:27700,crs:EPSG:9.1:5701". We verify here
+ * if such components exist, and if so we parse them
recursively.
+ */
+ case 3: {
+ int splitAt = uri.indexOf(componentSeparator, lower);
+ if (splitAt >= 0 && splitAt < stopAt) {
+ final int componentsEnd = stopAt;
+ stopAt = splitAt; // Upper limit of
the DefinitionURI created in this method call.
+ if (stopAt < upper) {
+ upper = stopAt;
+ }
+ if (componentSeparator == '?') {
+ componentSeparator = '&'; // 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);
+ boolean hasMore;
+ do {
+ 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));
+ splitAt = next;
+ } while (hasMore);
+ result.components = cmp.toArray(new
DefinitionURI[cmp.size()]);
+ }
+ // Fall through
+ }
+ /*
+ * For all parts after the first 3 ones, trim whitespaces and
store non-empty values.
*/
default: {
final String value = trimWhitespaces(uri, lower,
upper).toString();
- if (!value.isEmpty() && (p != 5 ||
!NO_VERSION.equals(value))) {
+ if (!value.isEmpty() && (part != 5 ||
!NO_VERSION.equals(value))) {
if (result == null) {
result = new DefinitionURI();
}
- switch (p) {
+ switch (part) {
case 3: result.type = value; break;
case 4: result.authority = value; break;
case 5: result.version = value; break;
case 6: result.code = value; break;
- default: throw new AssertionError(p);
+ default: throw new AssertionError(part);
}
}
}
}
}
/*
- * Take every remaining components as parameters.
+ * Take every remaining parts as parameters.
*/
- if (result != null && ++upper < uri.length()) {
+ if (result != null && ++upper < stopAt) {
result.parameters = (String[]) split(uri.substring(upper),
separator);
}
return result;
}
/**
- * Returns {@code true} if a sub-region of {@code urn} matches the given
{@code component},
+ * Returns {@code true} if a sub-region of {@code urn} matches the given
{@code part},
* ignoring case, leading and trailing whitespaces.
*
- * @param component the expected component ({@code "urn"}, {@code
"ogc"}, {@code "def"}, <i>etc.</i>)
+ * @param part the expected part ({@code "urn"}, {@code "ogc"},
{@code "def"}, <i>etc.</i>)
* @param urn the URN for which to test a subregion.
* @param lower index of the first character in {@code urn} to
compare, after skipping whitespaces.
* @param upper index after the last character in {@code urn} to
compare, ignoring whitespaces.
- * @return {@code true} if the given sub-region of {@code urn} match the
given component.
+ * @return {@code true} if the given sub-region of {@code urn} match the
given part.
*/
- static boolean regionMatches(final String component, final String urn, int
lower, int upper) {
+ static boolean regionMatches(final String part, final String urn, int
lower, int upper) {
lower = skipLeadingWhitespaces (urn, lower, upper);
upper = skipTrailingWhitespaces(urn, lower, upper);
final int length = upper - lower;
- return (length == component.length()) && urn.regionMatches(true,
lower, component, 0, length);
+ return (length == part.length()) && urn.regionMatches(true, lower,
part, 0, length);
}
/**
@@ -406,38 +509,38 @@ public final class DefinitionURI {
* Check for supported protocols: only "urn" and "http" at this time.
* All other protocols are rejected as unrecognized.
*/
- String component;
+ String part;
switch (length) {
- case 3: component = "urn"; break;
- case 4: component = "http"; break;
+ case 3: part = "urn"; break;
+ case 4: part = "http"; break;
default: return null;
}
- if (!uri.regionMatches(true, lower, component, 0, length)) {
+ if (!uri.regionMatches(true, lower, part, 0, length)) {
return null;
}
if (length == 4) {
return codeForGML(type, authority, uri, upper+1, null);
}
/*
- * At this point we have determined that the protocol is URN. The next
components after "urn"
+ * At this point we have determined that the protocol is URN. The next
parts after "urn"
* shall be "ogc" or "x-ogc", then "def", then the type and authority
given in arguments.
*/
for (int p=0; p!=4; p++) {
lower = upper + 1;
upper = uri.indexOf(SEPARATOR, lower);
if (upper < 0) {
- return null;
// No more components.
+ return null;
// No more parts.
}
switch (p) {
// "ogc" is tested before "x-ogc" because more common.
case 0: if (regionMatches("ogc", uri, lower, upper)) continue;
- component = "x-ogc"; break; // Fallback if the
component is not "ogc".
- case 1: component = "def"; break;
- case 2: component = type; break;
- case 3: component = authority; break;
+ part = "x-ogc"; break; // Fallback if the part
is not "ogc".
+ case 1: part = "def"; break;
+ case 2: part = type; break;
+ case 3: part = authority; break;
default: throw new AssertionError(p);
}
- if (!regionMatches(component, uri, lower, upper)) {
+ if (!regionMatches(part, uri, lower, upper)) {
return null;
}
}
@@ -534,15 +637,15 @@ public final class DefinitionURI {
public static String format(final String type, final String authority,
final String version, final String code) {
final StringBuilder buffer = new StringBuilder(PREFIX);
loop: for (int p=0; ; p++) {
- final String component;
+ final String part;
switch (p) {
- case 0: component = type; break;
- case 1: component = authority; break;
- case 2: component = version; break;
- case 3: component = code; break;
+ case 0: part = type; break;
+ case 1: part = authority; break;
+ case 2: part = version; break;
+ case 3: part = code; break;
default: break loop;
}
- if (!appendUnicodeIdentifier(buffer.append(SEPARATOR), '\u0000',
component, ".-", false)) {
+ if (!appendUnicodeIdentifier(buffer.append(SEPARATOR), '\u0000',
part, ".-", false)) {
/*
* Only the version (p = 2) is optional. All other fields are
mandatory.
* If no character has been added for a mandatory field, we
can not build a URN.
@@ -571,10 +674,23 @@ loop: for (int p=0; ; p++) {
return "http:" + path + authority + ".xml#" + code;
}
}
- final StringBuilder buffer = new StringBuilder(PREFIX);
- char separator = SEPARATOR;
+ final StringBuilder buffer = new StringBuilder(40);
+ if (!isHTTP) {
+ buffer.append(PREFIX);
+ }
+ toString(buffer, SEPARATOR);
+ return buffer.toString();
+ }
+
+ /**
+ * Formats the string representation of this URI into the given buffer.
This method invoke itself recursively
+ * if this URI has {@linkplain #components}. The {@value #PREFIX} must be
appended by the caller, if applicable.
+ *
+ * @param buffer where to format the string representation.
+ * @param separator first separator to append. Ignored if the URI is
actually a URL.
+ */
+ private void toString(final StringBuilder buffer, char separator) {
if (isHTTP) {
- buffer.setLength(0);
buffer.append("http://").append(DOMAIN).append("/def");
separator = '/';
}
@@ -583,21 +699,45 @@ loop: for (int p=0; ; p++) {
n += parameters.length;
}
for (int p=0; p<n; p++) {
- String component;
+ String part;
switch (p) {
- case 0: component = type; break;
- case 1: component = authority; break;
- case 2: component = version; break;
- case 3: component = code; break;
- default: component = parameters[p-4]; break;
+ case 0: part = type; break;
+ case 1: part = authority; break;
+ case 2: part = version; break;
+ case 3: part = code; break;
+ default: part = parameters[p-4]; break;
}
buffer.append(separator);
- if (component == null) {
- if (!isHTTP) continue;
- component = NO_VERSION;
+ if (isHTTP) {
+ if (part == null) {
+ part = NO_VERSION;
+ }
+ } else {
+ separator = SEPARATOR;
+ if (part == null) {
+ continue;
+ }
+ }
+ buffer.append(part);
+ }
+ /*
+ * Before to return the URI, trim trailing separators. For example if
the URN has only a type
+ * (no authority, version, code, etc.), then we want to return only
"urn:ogc:def:crs" instead
+ * 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) {
+ 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".
+ */
+ if (components != null) {
+ for (final DefinitionURI c : components) {
+ c.toString(buffer, COMPONENT_SEPARATOR);
}
- buffer.append(component);
}
- return buffer.toString();
}
}
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=1812053&r1=1812052&r2=1812053&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] Thu Oct 12 21:38:33 2017
@@ -27,7 +27,7 @@ import static org.junit.Assert.*;
* Tests {@link DefinitionURI}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
* @since 0.4
* @module
*/
@@ -143,6 +143,20 @@ public final strictfp class DefinitionUR
}
/**
+ * Tests comma-separated URNs in the {@code "urn:ogc:def"} namespace.
+ * Example: {@code "urn:ogc:def:crs,crs:EPSG:6.3:27700,crs:EPSG:6.3:5701"}.
+ */
+ @Test
+ 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);
+ 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,crs:EPSG:9.1:27700,crs:EPSG:9.1:5701",
parsed.toString());
+ }
+
+ /**
* Tests {@link DefinitionURI#codeOf(String, String, String)} with URI
like {@code "EPSG:4326"}.
*/
@Test