Author: desruisseaux Date: Fri Jul 3 18:18:51 2015 New Revision: 1689052 URL: http://svn.apache.org/r1689052 Log: Merge WKT 2 consolidation from JDK6 branch.
Modified: sis/trunk/ (props changed) sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java Propchange: sis/trunk/ ------------------------------------------------------------------------------ --- svn:mergeinfo (original) +++ svn:mergeinfo Fri Jul 3 18:18:51 2015 @@ -1,4 +1,4 @@ /sis/branches/Android:1430670-1480699 -/sis/branches/JDK6:1394364-1688859 -/sis/branches/JDK7:1394913-1688858 -/sis/branches/JDK8:1584960-1688856 +/sis/branches/JDK6:1394364-1689051 +/sis/branches/JDK7:1394913-1689050 +/sis/branches/JDK8:1584960-1689049 Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -17,6 +17,7 @@ package org.apache.sis.internal.metadata; import javax.measure.unit.Unit; +import javax.measure.quantity.Angle; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.cs.CoordinateSystemAxis; @@ -360,6 +361,36 @@ public final class AxisDirections extend private static final byte[] DISPLAY_ORDER = {1, 3, 0, 2}; /** + * Returns the angular unit of the specified coordinate system. + * The preference will be given to the longitude axis, if found. + * + * @param cs The coordinate system from which to get the angular unit, or {@code null}. + * @param unit The default unit to return if no angular unit is found. + * @return The angular unit, of {@code null} if no angular unit was found. + * + * @since 0.6 + * + * @see org.apache.sis.internal.referencing.ReferencingUtilities#getUnit(CoordinateSystem) + */ + public static Unit<Angle> getAngularUnit(final CoordinateSystem cs, Unit<Angle> unit) { + if (cs != null) { + for (int i = cs.getDimension(); --i>=0;) { + final CoordinateSystemAxis axis = cs.getAxis(i); + if (axis != null) { // Paranoiac check. + final Unit<?> candidate = axis.getUnit(); + if (Units.isAngular(candidate)) { + unit = candidate.asType(Angle.class); + if (AxisDirection.EAST.equals(absolute(axis.getDirection()))) { + break; // Found the longitude axis. + } + } + } + } + } + return unit; + } + + /** * Finds the dimension of an axis having the given direction or its opposite. * If more than one axis has the given direction, only the first occurrence is returned. * If both the given direction and its opposite exist, then the dimension for the given Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/FormattableObject.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -20,7 +20,6 @@ import java.io.Console; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicReference; import javax.xml.bind.annotation.XmlTransient; -import org.opengis.util.InternationalString; import org.apache.sis.util.Debug; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.internal.util.X364; @@ -181,9 +180,14 @@ public abstract class FormattableObject try { formatter.append(this); if (strict) { - final InternationalString message = formatter.getErrorMessage(); - if (message != null) { - throw new UnformattableObjectException(message.toString(), formatter.getErrorCause()); + /* + * If a warning occurred, consider the object as non-formattable. + * We take the last message since it is more likely to be about the enclosing element. + */ + final Warnings warnings = formatter.getWarnings(); + if (warnings != null) { + final int n = warnings.getNumMessages() - 1; + throw new UnformattableObjectException(warnings.getMessage(n), warnings.getException(n)); } } wkt = formatter.toWKT(); Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -61,6 +61,7 @@ import org.apache.sis.util.Debug; import org.apache.sis.util.Classes; import org.apache.sis.util.Numbers; import org.apache.sis.util.Localized; +import org.apache.sis.util.Characters; import org.apache.sis.util.CharSequences; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; @@ -298,20 +299,12 @@ public class Formatter implements Locali private boolean highlightError; /** - * Non-null if the WKT is invalid. If non-null, then this field contains a keyword that identify the - * problematic part. + * The warnings that occurred during WKT formatting, or {@code null} if none. * * @see #isInvalidWKT() - * @see #getErrorMessage() + * @see #getWarnings() */ - private String invalidElement; - - /** - * Error that occurred during WKT formatting, or {@code null} if none. - * - * @see #getErrorCause() - */ - private Exception errorCause; + private Warnings warnings; /** * Creates a new formatter instance with the default configuration. @@ -627,11 +620,10 @@ public class Formatter implements Locali if (keyword == null) { if (info != null) { setInvalidWKT(info, null); - keyword = getName(info.getClass()); } else { setInvalidWKT(object.getClass(), null); - keyword = invalidElement; } + keyword = getName(object.getClass()); } else if (toUpperCase != 0) { final Locale locale = symbols.getLocale(); keyword = (toUpperCase >= 0) ? keyword.toUpperCase(locale) : keyword.toLowerCase(locale); @@ -977,14 +969,34 @@ public class Formatter implements Locali * that character will be doubled (WKT 2) or deleted (WKT 1). We check for the closing quote only because * it is the character that the parser will look for determining the text end. */ - private void quote(final String text, final ElementKind type) { + private void quote(String text, final ElementKind type) { setColor(type); final int base = buffer.appendCodePoint(symbols.getOpeningQuote(0)).length(); - if (type == ElementKind.REMARKS) { - buffer.append(text); - } else { - buffer.append(transliterator.filter(text)); + if (type != ElementKind.REMARKS) { + text = transliterator.filter(text); + int startAt = 0; // Index of the last space character. + final int length = text.length(); + for (int i = 0; i < length;) { + int c = text.codePointAt(i); + int n = Character.charCount(c); + if (!Characters.isValidWKT(c)) { + final String illegal = text.substring(i, i+n); + while ((i += n) < length) { + c = text.codePointAt(i); + n = Character.charCount(c); + if (c == ' ' || c == '_') break; + } + warnings().add(Errors.formatInternational(Errors.Keys.IllegalCharacterForFormat_3, + "Well-Known Text", text.substring(startAt, i), illegal), null, null); + break; + } + i += n; + if (c == ' ' || c == '_') { + startAt = i; + } + } } + buffer.append(text); closeQuote(base); resetColor(); } @@ -1452,7 +1464,7 @@ public class Formatter implements Locali * @return {@code true} if the WKT is invalid. */ public boolean isInvalidWKT() { - return (invalidElement != null) || (buffer != null && buffer.length() == 0); + return (warnings != null) || (buffer != null && buffer.length() == 0); /* * Note: we really use a "and" condition (not an other "or") for the buffer test because * the buffer is reset to 'null' by WKTFormat after a successfull formatting. @@ -1460,15 +1472,21 @@ public class Formatter implements Locali } /** + * Returns the object where to store warnings. + */ + private Warnings warnings() { + if (warnings == null) { + warnings = new Warnings(locale, (byte) 0, Collections.<String, List<String>>emptyMap()); + } + return warnings; + } + + /** * Marks the current WKT representation of the given object as not strictly compliant with the WKT specification. * This method can be invoked by implementations of {@link FormattableObject#formatTo(Formatter)} when the object * to format is more complex than what the WKT specification allows. * Applications can test {@link #isInvalidWKT()} later for checking WKT validity. * - * <p>If any {@code setInvalidWKT(…)} method is invoked more than once during formatting, - * then only information about the last failure will be retained. The reason is that the - * last failure is typically the enclosing element.</p> - * * @param unformattable The object that can not be formatted, * @param cause The cause for the failure to format, or {@code null} if the cause is not an exception. */ @@ -1479,9 +1497,7 @@ public class Formatter implements Locali if (id == null || (name = id.getCode()) == null) { name = getName(unformattable.getClass()); } - invalidElement = name; - errorCause = cause; - highlightError = true; + setInvalidWKT(name, cause); } /** @@ -1489,18 +1505,22 @@ public class Formatter implements Locali * This method can be used as an alternative to {@link #setInvalidWKT(IdentifiedObject, Exception)} when the * problematic object is not an instance of {@code IdentifiedObject}. * - * <p>If any {@code setInvalidWKT(…)} method is invoked more than once during formatting, - * then only information about the first failure will be retained.</p> - * * @param unformattable The class of the object that can not be formatted, * @param cause The cause for the failure to format, or {@code null} if the cause is not an exception. */ public void setInvalidWKT(final Class<?> unformattable, final Exception cause) { ArgumentChecks.ensureNonNull("unformattable", unformattable); - if (invalidElement == null) { - invalidElement = getName(unformattable); - errorCause = cause; - } + setInvalidWKT(getName(unformattable), cause); + } + + /** + * Implementation of public {@code setInvalidWKT(…)} methods. + * + * <div class="note"><b>Note:</b> the message is stored as an {@link InternationalString} + * in order to defer the actual message formatting until needed.</div> + */ + private void setInvalidWKT(final String invalidElement, final Exception cause) { + warnings().add(Errors.formatInternational(Errors.Keys.CanNotRepresentInFormat_2, "WKT", invalidElement), cause, null); highlightError = true; } @@ -1521,24 +1541,10 @@ public class Formatter implements Locali } /** - * Returns the error message {@link #isInvalidWKT()} is set, or {@code null} otherwise. - * If non-null, a cause may be available in the {@link #getErrorCause()} method. - * - * <div class="note"><b>Note:</b> the message is returned as an {@link InternationalString} - * in order to defer the actual message formatting until needed.</div> - */ - final InternationalString getErrorMessage() { - if (!isInvalidWKT()) { - return null; - } - return Errors.formatInternational(Errors.Keys.CanNotRepresentInFormat_2, "WKT", invalidElement); - } - - /** - * Returns the cause of the error, or {@code null} if the cause is not an exception. + * Returns the warnings, or {@code null} if none. */ - final Exception getErrorCause() { - return errorCause; + final Warnings getWarnings() { + return warnings; } /** @@ -1589,7 +1595,6 @@ public class Formatter implements Locali requestNewLine = false; isComplement = false; highlightError = false; - invalidElement = null; - errorCause = null; + warnings = null; } } Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -357,6 +357,27 @@ final class GeodeticObjectParser extends } /** + * Parses a coordinate reference system wrapped in an element of the given name. + * + * @param parent The parent element containing the CRS to parse. + * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. + * @param keyword "SourceCRS", "TargetCRS" or "InterpolationCRS". + * @return The coordinate reference system, or {@code null} if none. + * @throws ParseException if the CRS can not be parsed. + */ + private CoordinateReferenceSystem parseCoordinateReferenceSystem(final Element parent, final int mode, + final String keyword) throws ParseException + { + final Element element = parent.pullElement(mode, keyword); + if (element == null) { + return null; + } + final CoordinateReferenceSystem crs = parseCoordinateReferenceSystem(element, true); + element.close(ignoredElements); + return crs; + } + + /** * Returns the value associated to {@link IdentifiedObject#IDENTIFIERS_KEY} as an {@code Identifier} object. * This method shall accept all value types that {@link #parseMetadataAndClose(Element, Object)} may store. * @@ -552,20 +573,6 @@ final class GeodeticObjectParser extends } /** - * Returns the given unit as a linear unit, or {@code null} if it is not linear. - */ - private static Unit<Length> toLinear(final Unit<?> unit) { - return Units.isLinear(unit) ? unit.asType(Length.class) : null; - } - - /** - * Returns the given unit as an angular unit, or {@code null} if it is not angular. - */ - private static Unit<Angle> toAngular(final Unit<?> unit) { - return Units.isAngular(unit) ? unit.asType(Angle.class) : null; - } - - /** * Parses an optional {@code "UNIT"} element of a known dimension. * This element has the following pattern: * @@ -642,7 +649,7 @@ final class GeodeticObjectParser extends * @param dimension The minimal number of dimensions. Can be 1 if unknown. * @param isWKT1 {@code true} if the parent element is an element from the WKT 1 standard. * @param defaultUnit The contextual unit (usually {@code SI.METRE} or {@code SI.RADIAN}), or {@code null} if unknown. - * @param datum The datum of the enclosing CRS. + * @param datum The datum of the enclosing CRS, or {@code null} if unknown. * @return The {@code "CS"}, {@code "UNIT"} and/or {@code "AXIS"} elements as a Coordinate System, or {@code null}. * @throws ParseException if an element can not be parsed. * @throws FactoryException if the factory can not create the coordinate system. @@ -719,11 +726,13 @@ final class GeodeticObjectParser extends if (type == null) { throw parent.missingComponent(WKTKeywords.Axis); } - String nx = null, x = null; // Easting or Longitude axis name and abbreviation. - String ny = null, y = null; // Northing or latitude axis name and abbreviation. - String nz = null, z = null; // Depth, height or time axis name and abbreviation. - AxisDirection direction = null; // Depth, height or time axis direction. - Unit<?> unit = defaultUnit; // Depth, height or time axis unit. + String nx = null, x = null; // Easting or Longitude axis name and abbreviation. + String ny = null, y = null; // Northing or latitude axis name and abbreviation. + String nz = null, z = null; // Depth, height or time axis name and abbreviation. + AxisDirection dx = AxisDirection.EAST; + AxisDirection dy = AxisDirection.NORTH; + AxisDirection direction = null; // Depth, height or time axis direction. + Unit<?> unit = defaultUnit; // Depth, height or time axis unit. /*switch (type)*/ { /* * Cartesian — we can create axes only for geodetic datum, in which case the axes are for @@ -749,14 +758,22 @@ final class GeodeticObjectParser extends } /* * Ellipsoidal — can be two- or three- dimensional, in which case the height can - * only be ellipsoidal height. + * only be ellipsoidal height. The default axis order depends on the WKT version: + * + * - WKT 1 said explicitely that the default order is (longitude, latitude). + * - WKT 2 has no default, and allows only (latitude, longitude) order. */ else if (type.equals(WKTKeywords.ellipsoidal)) { if (defaultUnit == null) { throw parent.missingComponent(WKTKeywords.AngleUnit); } - nx = AxisNames.GEODETIC_LONGITUDE; x = "λ"; - ny = AxisNames.GEODETIC_LATITUDE; y = "φ"; + if (isWKT1) { + nx = AxisNames.GEODETIC_LONGITUDE; x = "λ"; + ny = AxisNames.GEODETIC_LATITUDE; y = "φ"; + } else { + nx = AxisNames.GEODETIC_LATITUDE; x = "φ"; dx = AxisDirection.NORTH; + ny = AxisNames.GEODETIC_LONGITUDE; y = "λ"; dy = AxisDirection.EAST; + } if (dimension >= 3) { direction = AxisDirection.UP; z = "h"; @@ -772,21 +789,23 @@ final class GeodeticObjectParser extends if (defaultUnit == null) { throw parent.missingComponent(WKTKeywords.Unit); } + z = "h"; + nz = "Height"; direction = AxisDirection.UP; - final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType(); - if (VerticalDatumType.GEOIDAL.equals(vt)) { - nz = AxisNames.GRAVITY_RELATED_HEIGHT; - z = "H"; - } else if (VerticalDatumType.DEPTH.equals(vt)) { - direction = AxisDirection.DOWN; - nz = AxisNames.DEPTH; - z = "D"; - } else if (VerticalDatumTypes.ELLIPSOIDAL.equals(vt)) { - nz = AxisNames.ELLIPSOIDAL_HEIGHT; // Not allowed by ISO 19111 as a standalone axis, but SIS is - z = "h"; // tolerant to this case since it is sometime hard to avoid. - } else { - nz = "Height"; - z = "h"; + if (datum instanceof VerticalDatum) { + final VerticalDatumType vt = ((VerticalDatum) datum).getVerticalDatumType(); + if (VerticalDatumType.GEOIDAL.equals(vt)) { + nz = AxisNames.GRAVITY_RELATED_HEIGHT; + z = "H"; + } else if (VerticalDatumType.DEPTH.equals(vt)) { + direction = AxisDirection.DOWN; + nz = AxisNames.DEPTH; + z = "D"; + } else if (VerticalDatumTypes.ELLIPSOIDAL.equals(vt)) { + // Not allowed by ISO 19111 as a standalone axis, but SIS is + // tolerant to this case since it is sometime hard to avoid. + nz = AxisNames.ELLIPSOIDAL_HEIGHT; + } } } /* @@ -809,8 +828,8 @@ final class GeodeticObjectParser extends } int i = 0; axes = new CoordinateSystemAxis[dimension]; - if (x != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nx), x, AxisDirection.EAST, defaultUnit); - if (y != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, ny), y, AxisDirection.NORTH, defaultUnit); + if (x != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nx), x, dx, defaultUnit); + if (y != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, ny), y, dy, defaultUnit); if (z != null && i < dimension) axes[i++] = csFactory.createCoordinateSystemAxis(singletonMap(CoordinateSystemAxis.NAME_KEY, nz), z, direction, unit); // Not a problem if the array does not have the expected length for the CS type. This will be verified below in this method. } @@ -1158,6 +1177,44 @@ final class GeodeticObjectParser extends } /** + * Parses a {@code "Method"} (WKT 2) element, without the parameters. + * + * @param parent The parent element. + * @param keywords The element keywords. + * @return The operation method. + * @throws ParseException if the {@code "Method"} element can not be parsed. + */ + private OperationMethod parseMethod(final Element parent, final String... keywords) throws ParseException { + final Element element = parent.pullElement(MANDATORY, keywords); + final String name = element.pullString("method"); + Map<String,?> properties = parseMetadataAndClose(element, name, null); + final Identifier id = toIdentifier(properties.remove(IdentifiedObject.IDENTIFIERS_KEY)); // See NOTE 2 in parseDerivingConversion. + /* + * The map projection method may be specified by an EPSG identifier (or any other authority), + * which is preferred to the method name since the later is potentially ambiguous. However not + * all CoordinateOperationFactory may accept identifier as an argument to 'getOperationMethod'. + * So if an identifier is present, we will try to use it but fallback on the name if we can + * not use the identifier. + */ + FactoryException suppressed = null; + if (id instanceof ReferenceIdentifier) try { + // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values. + return referencing.getOperationMethod(opFactory, mtFactory, + ((ReferenceIdentifier) id).getCodeSpace() + DefaultNameSpace.DEFAULT_SEPARATOR + id.getCode()); + } catch (FactoryException e) { + suppressed = e; + } + try { + return referencing.getOperationMethod(opFactory, mtFactory, name); + } catch (FactoryException e) { + if (suppressed != null) { +// e.addSuppressed(suppressed); // Not available on JDK6. + } + throw element.parseFailed(e); + } + } + + /** * Parses a {@code "Method"} (WKT 2) element, followed by parameter values. The syntax is given by * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#62">WKT 2 specification §9.3</a>. * @@ -1171,16 +1228,16 @@ final class GeodeticObjectParser extends * element which is itself inside the {@code ProjectedCRS} element. This is different than WKT 1, which * puts this element right into the the {@code ProjectedCRS} element without {@code Conversion} wrapper. * - * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. - * @param parent The parent element. - * @param wrapper "Conversion" or "DerivingConversion" wrapper name, or null if parsing a WKT 1. - * @param linearUnit The linear unit of the parent {@code ProjectedCRS} element, or {@code null}. - * @param angularUnit The angular unit of the sibling {@code GeographicCRS} element, or {@code null}. + * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. + * @param parent The parent element. + * @param wrapper "Conversion" or "DerivingConversion" wrapper name, or null if parsing a WKT 1. + * @param defaultUnit The unit (usually linear) of the parent element, or {@code null}. + * @param defaultAngularUnit The angular unit of the sibling {@code GeographicCRS} element, or {@code null}. * @return The {@code "Method"} element and its parameters as a defining conversion. * @throws ParseException if the {@code "Method"} element can not be parsed. */ private Conversion parseDerivingConversion(final int mode, Element parent, final String wrapper, - final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException + final Unit<?> defaultUnit, final Unit<Angle> defaultAngularUnit) throws ParseException { final String name; if (wrapper == null) { @@ -1196,26 +1253,8 @@ final class GeodeticObjectParser extends } name = parent.pullString("name"); } - final Element element = parent.pullElement(MANDATORY, WKTKeywords.Method, WKTKeywords.Projection); - final String methodName = element.pullString("method"); - Map<String,?> properties = parseMetadataAndClose(element, methodName, null); - final Identifier id = toIdentifier(properties.remove(IdentifiedObject.IDENTIFIERS_KEY)); // See NOTE 2. - /* - * The map projection method may be specified by an EPSG identifier (or any other authority), - * which is preferred to the method name since the later is potentially ambiguous. However not - * all CoordinateOperationFactory may accept identifier as an argument to 'getOperationMethod'. - * So if an identifier is present, we will try to use it but fallback on the name if we can - * not use the identifier. - */ - OperationMethod method = null; - FactoryException suppressed = null; - if (id instanceof ReferenceIdentifier) try { - // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values. - method = referencing.getOperationMethod(opFactory, mtFactory, - ((ReferenceIdentifier) id).getCodeSpace() + DefaultNameSpace.DEFAULT_SEPARATOR + id.getCode()); - } catch (FactoryException e) { - suppressed = e; - } + OperationMethod method = parseMethod(parent, WKTKeywords.Method, WKTKeywords.Projection); + Map<String,?> properties = this.properties; // Same properties then OperationMethod, with ID removed. /* * Set the list of parameters. * @@ -1226,32 +1265,26 @@ final class GeodeticObjectParser extends * but we shall not inherit the OperationMethod identifier. This is the reason why we invoked * properties.remove(IdentifiedObject.IDENTIFIERS_KEY)) above. */ + final ParameterValueGroup parameters = method.getParameters().createValue(); + parseParameters(parent, parameters, defaultUnit, defaultAngularUnit); + if (wrapper != null) { + properties = parseMetadataAndClose(parent, name, method); + /* + * DEPARTURE FROM ISO 19162: the specification in §9.3.2 said: + * + * "If an identifier is provided as an attribute within the <map projection conversion> object, + * because it is expected to describe a complete collection of zone name, method, parameters and + * parameter values, it shall override any identifiers given within the map projection method and + * map projection parameter objects." + * + * However this would require this GeodeticObjectParser to hold a CoordinateOperationAuthorityFactory, + * which we do not yet implement. See https://issues.apache.org/jira/browse/SIS-210 + */ + } try { - if (method == null) { - method = referencing.getOperationMethod(opFactory, mtFactory, methodName); - } - final ParameterValueGroup parameters = method.getParameters().createValue(); - parseParameters(parent, parameters, linearUnit, angularUnit); - if (wrapper != null) { - properties = parseMetadataAndClose(parent, name, method); - /* - * DEPARTURE FROM ISO 19162: the specification in §9.3.2 said: - * - * "If an identifier is provided as an attribute within the <map projection conversion> object, - * because it is expected to describe a complete collection of zone name, method, parameters and - * parameter values, it shall override any identifiers given within the map projection method and - * map projection parameter objects." - * - * However this would require this GeodeticObjectParser to hold a CoordinateOperationAuthorityFactory, - * which we do not yet implement. See https://issues.apache.org/jira/browse/SIS-163 - */ - } return opFactory.createDefiningConversion(properties, method, parameters); } catch (FactoryException exception) { - if (suppressed != null) { -// exception.addSuppressed(suppressed); // Not available on JDK6. - } - throw element.parseFailed(exception); + throw parent.parseFailed(exception); } } @@ -1463,7 +1496,17 @@ final class GeodeticObjectParser extends SingleCRS baseCRS = null; Conversion fromBase = null; if (!isWKT1 && !isBaseCRS) { - fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, null, null); + /* + * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said + * what to do if they are missing. In this code, we default to the contextual units in the same way than + * what we do for ProjectedCRS parameters, in the hope to be consistent. + * + * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…) + * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more + * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory + * instead than inferred from the WKT. + */ + fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, unit, null); if (fromBase != null) { /* * The order of base types below is arbitrary. But no matter their type, @@ -1508,9 +1551,9 @@ final class GeodeticObjectParser extends if (element == null) { return null; } - final String name = element.pullString("name"); - final ImageDatum datum = parseImageDatum(MANDATORY, element); - final Unit<?> unit = parseUnit(element); + final String name = element.pullString("name"); + final ImageDatum datum = parseImageDatum(MANDATORY, element); + final Unit<?> unit = parseUnit(element); final CoordinateSystem cs; try { cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, false, unit, datum); @@ -1564,40 +1607,66 @@ final class GeodeticObjectParser extends if (element == null) { return null; } - final boolean isWKT1; - final Unit<Angle> angularUnit; - Unit<?> defaultUnit; + final boolean isWKT1; + Unit<?> csUnit; + Unit<Angle> angularUnit; switch (element.getKeywordIndex()) { - default: { // WKT2 element. - isWKT1 = false; - defaultUnit = parseUnit(element); - angularUnit = Units.isAngular(defaultUnit) ? defaultUnit.asType(Angle.class) : NonSI.DEGREE_ANGLE; - if (defaultUnit == null) { - /* - * A UNIT[…] is mandatory either in the CoordinateSystem as a whole (defaultUnit != null), - * or inside each AXIS[…] component (defaultUnit == null). An exception to this rule is when - * parsing a BaseGeodCRS inside a ProjectedCRS or DerivedCRS, in which case axes are omitted. - * We recognize those cases by a non-null 'csType' given in argument to this method. - */ - if (WKTKeywords.ellipsoidal.equals(csType)) { - defaultUnit = NonSI.DEGREE_ANGLE; // For BaseGeodCRS in ProjectedCRS. + default: { + /* + * WKT 2 "GeodeticCRS" element. + * The specification in §8.2.2 (ii) said: + * + * "If the subtype of the geodetic CRS to which the prime meridian is an attribute + * is geographic, the prime meridian’s <irm longitude> value shall be given in the + * same angular units as those for the horizontal axes of the geographic CRS; + * if the geodetic CRS subtype is geocentric the prime meridian’s <irm longitude> + * value shall be given in degrees." + * + * An apparent ambiguity exists for Geocentric CRS using a Spherical CS instead than the more + * usual Cartesian CS: despite using angular units, we should not use the result of parseUnit + * for those CRS. However this ambiguity should not happen in practice because such Spherical + * CS have a third axis in metre. Since the unit is not the same for all axes, csUnit should + * be null if the WKT is well-formed. + */ + isWKT1 = false; + csUnit = parseUnit(element); + if (Units.isAngular(csUnit)) { + angularUnit = csUnit.asType(Angle.class); + } else { + angularUnit = NonSI.DEGREE_ANGLE; + if (csUnit == null) { + /* + * A UNIT[…] is mandatory either in the CoordinateSystem as a whole (csUnit != null), + * or inside each AXIS[…] component (csUnit == null). An exception to this rule is when + * parsing a BaseGeodCRS inside a ProjectedCRS or DerivedCRS, in which case axes are omitted. + * We recognize those cases by a non-null 'csType' given in argument to this method. + */ + if (WKTKeywords.ellipsoidal.equals(csType)) { + csUnit = NonSI.DEGREE_ANGLE; // For BaseGeodCRS in ProjectedCRS. + } } } break; } - case 1: { // WKT1 "GeogCS" element. + case 1: { + /* + * WKT 1 "GeogCS" (Geographic) element. + */ isWKT1 = true; csType = WKTKeywords.ellipsoidal; angularUnit = parseScaledUnit(element, WKTKeywords.AngleUnit, SI.RADIAN); - defaultUnit = angularUnit; + csUnit = angularUnit; dimension = 2; break; } - case 3: { // WKT1 "GeocCS" element. + case 3: { + /* + * WKT 1 "GeocCS" (Geocentric) element. + */ isWKT1 = true; csType = WKTKeywords.Cartesian; angularUnit = NonSI.DEGREE_ANGLE; - defaultUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE); + csUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE); dimension = 3; break; } @@ -1607,31 +1676,50 @@ final class GeodeticObjectParser extends * A GeodeticCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind GeodeticCRS. * In the later case, the datum is null and we have instead DerivingConversion element from a BaseGeodCRS. */ - GeodeticDatum datum = null; - SingleCRS baseCRS = null; - Conversion fromBase = null; + SingleCRS baseCRS = null; + Conversion fromBase = null; if (!isWKT1 && csType == null) { - fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, toLinear(defaultUnit), angularUnit); + /* + * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said + * what to do if they are missing. In this code, we default to the contextual units in the same way than + * what we do for ProjectedCRS parameters, in the hope to be consistent. + * + * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…) + * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more + * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory + * instead than inferred from the WKT. + */ + fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, csUnit, angularUnit); if (fromBase != null) { baseCRS = parseGeodeticCRS(MANDATORY, element, getSourceDimensions(fromBase.getMethod()), WKTKeywords.ellipsoidal); } } - if (baseCRS == null) { - // The most usual case. - final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, angularUnit); - datum = parseDatum(MANDATORY, element, meridian); - } /* * At this point, we have either a non-null 'datum' or non-null 'baseCRS' + 'fromBase'. * The coordinate system is parsed in the same way for both cases, but the CRS is created differently. */ final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, csType, dimension, isWKT1, defaultUnit, datum); - final Map<String,?> properties = parseMetadataAndClose(element, name, datum); + cs = parseCoordinateSystem(element, csType, dimension, isWKT1, csUnit, null); if (baseCRS != null) { + final Map<String,?> properties = parseMetadataAndClose(element, name, null); return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs); } + /* + * The specifiation in §8.2.2 (ii) said: + * + * "(snip) the prime meridian’s <irm longitude> value shall be given in the + * same angular units as those for the horizontal axes of the geographic CRS." + * + * This is a little bit different than using the 'angularUnit' variable directly, + * since the WKT could have overwritten the unit directly in the AXIS[…] element. + * So we re-fetch the angular unit. Normally, we will get the same value (unless + * the previous value was null). + */ + angularUnit = AxisDirections.getAngularUnit(cs, angularUnit); + final PrimeMeridian meridian = parsePrimeMeridian(OPTIONAL, element, isWKT1, angularUnit); + final GeodeticDatum datum = parseDatum(MANDATORY, element, meridian); + final Map<String,?> properties = parseMetadataAndClose(element, name, datum); if (cs instanceof EllipsoidalCS) { // By far the most frequent case. return crsFactory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs); } @@ -1686,7 +1774,17 @@ final class GeodeticObjectParser extends SingleCRS baseCRS = null; Conversion fromBase = null; if (!isWKT1 && !isBaseCRS) { - fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, toLinear(unit), null); + /* + * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said + * what to do if they are missing. In this code, we default to the contextual units in the same way than + * what we do for ProjectedCRS parameters, in the hope to be consistent. + * + * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…) + * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more + * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory + * instead than inferred from the WKT. + */ + fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, unit, null); if (fromBase != null) { baseCRS = parseVerticalCRS(MANDATORY, element, true); } @@ -1755,7 +1853,17 @@ final class GeodeticObjectParser extends SingleCRS baseCRS = null; Conversion fromBase = null; if (!isBaseCRS) { - fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, null, null); + /* + * UNIT[…] in DerivedCRS parameters are mandatory according ISO 19162 and the specification does not said + * what to do if they are missing. In this code, we default to the contextual units in the same way than + * what we do for ProjectedCRS parameters, in the hope to be consistent. + * + * An alternative would be to specify null units, in which case MathTransformParser.parseParameters(…) + * defaults to the units specified in the parameter descriptor. But this would make the CRS parser more + * implementation-dependent, because the parameter descriptors are provided by the MathTransformFactory + * instead than inferred from the WKT. + */ + fromBase = parseDerivingConversion(OPTIONAL, element, WKTKeywords.DerivingConversion, unit, null); if (fromBase != null) { baseCRS = parseTimeCRS(MANDATORY, element, true); } @@ -1818,27 +1926,39 @@ final class GeodeticObjectParser extends /* * Parse the projection parameters. If a default linear unit is specified, it will apply to * all parameters that do not specify explicitely a LengthUnit. If no such crs-wide unit was - * specified, then the default will be the unit specified in the parameter descriptor. + * specified, then the default will be degrees. + * + * More specifically §9.3.4 in the specification said about the default units: + * + * - lengths shall be given in the unit for the projected CRS axes. + * - angles shall be given in the unit for the base geographic CRS of the projected CRS. */ - Unit<Length> linearUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE); - final boolean ignoreUnits = isWKT1 && usesCommonUnits; + Unit<Length> csUnit = parseScaledUnit(element, WKTKeywords.LengthUnit, SI.METRE); + final Unit<Length> linearUnit; + final Unit<Angle> angularUnit; + if (isWKT1 && usesCommonUnits) { + linearUnit = SI.METRE; + angularUnit = NonSI.DEGREE_ANGLE; + } else { + linearUnit = csUnit; + angularUnit = AxisDirections.getAngularUnit(geoCRS.getCoordinateSystem(), NonSI.DEGREE_ANGLE); + } final Conversion conversion = parseDerivingConversion(MANDATORY, element, - isWKT1 ? null : WKTKeywords.Conversion, ignoreUnits ? SI.METRE : linearUnit, - ignoreUnits ? NonSI.DEGREE_ANGLE : geoCRS.getCoordinateSystem().getAxis(0).getUnit().asType(Angle.class)); + isWKT1 ? null : WKTKeywords.Conversion, linearUnit, angularUnit); /* * Parse the coordinate system. The linear unit must be specified somewhere, either explicitely in each axis - * or for the whole CRS with the above 'linearUnit' value. If 'linearUnit' is null, then an exception will be - * thrown with a message like "A LengthUnit component is missing in ProjectedCRS". + * or for the whole CRS with the above 'csUnit' value. If 'csUnit' is null, then an exception will be thrown + * with a message like "A LengthUnit component is missing in ProjectedCRS". * * However we make an exception if we are parsing a BaseProjCRS, since the coordinate system is unspecified * in the WKT of base CRS. In this case only, we will default to metre. */ - if (linearUnit == null && isBaseCRS) { - linearUnit = SI.METRE; + if (csUnit == null && isBaseCRS) { + csUnit = SI.METRE; } final CoordinateSystem cs; try { - cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, linearUnit, geoCRS.getDatum()); + cs = parseCoordinateSystem(element, WKTKeywords.Cartesian, 2, isWKT1, csUnit, geoCRS.getDatum()); final Map<String,?> properties = parseMetadataAndClose(element, name, conversion); if (cs instanceof CartesianCS) { return crsFactory.createProjectedCRS(properties, (GeographicCRS) geoCRS, conversion, (CartesianCS) cs); @@ -1850,8 +1970,10 @@ final class GeodeticObjectParser extends } /** - * Parses a {@code "COMPD_CS"} element. - * This element has the following pattern: + * Parses a {@code "CompoundCRS"} element. The syntax is given by + * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#110">WKT 2 specification §16</a>. + * + * The legacy WKT 1 specification was: * * {@preformat text * COMPD_CS["<name>", <head cs>, <tail cs> {,<authority>}] @@ -1859,8 +1981,8 @@ final class GeodeticObjectParser extends * * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. * @param parent The parent element. - * @return The {@code "COMPD_CS"} element as a {@link CompoundCRS} object. - * @throws ParseException if the {@code "COMPD_CS"} element can not be parsed. + * @return The {@code "CompoundCRS"} element as a {@link CompoundCRS} object. + * @throws ParseException if the {@code "CompoundCRS"} element can not be parsed. */ private CompoundCRS parseCompoundCRS(final int mode, final Element parent) throws ParseException { final Element element = parent.pullElement(mode, WKTKeywords.CompoundCRS, WKTKeywords.Compd_CS); @@ -1940,7 +2062,33 @@ final class GeodeticObjectParser extends } } + /** + * Parses a {@code "CoordinateOperation"} element. The syntax is given by + * <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#113">WKT 2 specification §17</a>. + * + * @param mode {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}. + * @param parent The parent element. + * @return The {@code "CoordinateOperation"} element as a {@link CoordinateOperation} object. + * @throws ParseException if the {@code "CoordinateOperation"} element can not be parsed. + */ private CoordinateOperation parseOperation(final int mode, final Element parent) throws ParseException { + final Element element = parent.pullElement(mode, WKTKeywords.CoordinateOperation); + if (element == null) { + return null; + } + final String name = element.pullString("name"); + final CoordinateReferenceSystem sourceCRS = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.SourceCRS); + final CoordinateReferenceSystem targetCRS = parseCoordinateReferenceSystem(element, MANDATORY, WKTKeywords.TargetCRS); + final CoordinateReferenceSystem interpolationCRS = parseCoordinateReferenceSystem(element, OPTIONAL, WKTKeywords.InterpolationCRS); + final OperationMethod method = parseMethod(parent, WKTKeywords.Method); + final Element accuracy = parent.pullElement(OPTIONAL, WKTKeywords.OperationAccuracy); + final Map<String,Object> properties = parseMetadataAndClose(parent, name, method); + final ParameterValueGroup parameters = method.getParameters().createValue(); + parseParameters(parent, parameters, null, null); + if (accuracy != null) { + accuracy.pullDouble("accuracy"); // TODO: share the code from EPSG factory. + accuracy.close(ignoredElements); + } return null; // Not yet implemented. } } Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -24,7 +24,6 @@ import javax.measure.unit.SI; import javax.measure.unit.Unit; import javax.measure.unit.UnitFormat; import javax.measure.quantity.Angle; -import javax.measure.quantity.Length; import org.opengis.util.FactoryException; import org.opengis.util.NoSuchIdentifierException; import org.opengis.parameter.ParameterValue; @@ -247,15 +246,17 @@ class MathTransformParser extends Abstra /** * Parses a sequence of {@code "PARAMETER"} elements. * - * @param element The parent element containing the parameters to parse. - * @param parameters The group where to store the parameter values. - * @param linearUnit The default linear unit, or {@code null}. - * @param angularUnit The default angular unit, or {@code null}. + * @param element The parent element containing the parameters to parse. + * @param parameters The group where to store the parameter values. + * @param defaultUnit The default unit (for arbitrary quantity, including angular), or {@code null}. + * @param defaultAngularUnit The default angular unit, or {@code null} if none. This is determined by the + * context, especially when {@link GeodeticObjectParser} parses a {@code ProjectedCRS} element. * @throws ParseException if the {@code "PARAMETER"} element can not be parsed. */ final void parseParameters(final Element element, final ParameterValueGroup parameters, - final Unit<Length> linearUnit, final Unit<Angle> angularUnit) throws ParseException + final Unit<?> defaultUnit, final Unit<Angle> defaultAngularUnit) throws ParseException { + final Unit<?> defaultSI = (defaultUnit != null) ? defaultUnit.toSI() : null; Element param = element; try { while ((param = element.pullElement(OPTIONAL, WKTKeywords.Parameter)) != null) { @@ -267,7 +268,7 @@ class MathTransformParser extends Abstra * than the parameter name. However we do not yet have a "get parameter by ID" in Apache SIS * or in GeoAPI interfaces. This was not considered necessary since SIS is lenient (hopefully * without introducing ambiguity) regarding parameter names, but we may revisit in a future - * version if it become no longer the case. See https://issues.apache.org/jira/browse/SIS-163 + * version if it become no longer the case. See https://issues.apache.org/jira/browse/SIS-210 */ final ParameterValue<?> parameter = parameters.parameter(name); final ParameterDescriptor<?> descriptor = parameter.getDescriptor(); @@ -275,10 +276,13 @@ class MathTransformParser extends Abstra final boolean isNumeric = Number.class.isAssignableFrom(valueClass); if (isNumeric && unit == null) { unit = descriptor.getUnit(); - if (Units.isLinear(unit)) { - unit = linearUnit; - } else if (Units.isAngular(unit)) { - unit = angularUnit; + if (unit != null) { + final Unit<?> si = unit.toSI(); + if (si.equals(defaultSI)) { + unit = defaultUnit; + } else if (si.equals(SI.RADIAN)) { + unit = defaultAngularUnit; + } } } if (unit != null) { Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Transliterator.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -92,6 +92,7 @@ import org.apache.sis.util.Characters; * @version 0.6 * @module * + * @see org.apache.sis.util.Characters#isValidWKT(int) * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#39">WKT 2 specification §7.5.3</a> */ public abstract class Transliterator implements Serializable { @@ -101,6 +102,11 @@ public abstract class Transliterator imp private static final long serialVersionUID = 7115456393795045932L; /** + * A bitmask of control characters that are considered as spaces according {@link Character#isWhitespace(char)}. + */ + static final int SPACES = 0xF0003E00; + + /** * Default names to associate to axis directions in a Cartesian coordinate system. * Those names do not apply to other kind of coordinate systems. * @@ -146,13 +152,38 @@ public abstract class Transliterator imp * {@linkplain Symbols#getClosingQuote(int) closing quotes}. The quotes will be doubled by the * caller if needed after this method has been invoked.</p> * - * <p>The default implementation invokes {@link CharSequences#toASCII(CharSequence)}.</p> + * <p>The default implementation invokes {@link CharSequences#toASCII(CharSequence)}, + * replaces line feed and tabulations by single spaces, then remove control characters.</p> * * @param text The text to format without non-ASCII characters. * @return The text to write in <cite>Well Known Text</cite>. + * + * @see org.apache.sis.util.Characters#isValidWKT(int) */ public String filter(final String text) { - return CharSequences.toASCII(text).toString(); + CharSequence s = CharSequences.toASCII(text); + StringBuilder buffer = null; + for (int i=s.length(); --i >= 0;) { + final char c = s.charAt(i); + if (c < 32) { + if (buffer == null) { + if (s == text) { + s = buffer = new StringBuilder(text); + } else { + buffer = (StringBuilder) s; + } + } + if ((SPACES & (1 << c)) != 0) { + buffer.setCharAt(i, ' '); + if (i != 0 && c == '\n' && s.charAt(i-1) == '\r') { + buffer.deleteCharAt(--i); + } + } else { + buffer.deleteCharAt(i); + } + } + } + return s.toString(); } /** Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/WKTFormat.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -21,8 +21,6 @@ import java.util.Locale; import java.util.TimeZone; import java.util.Map; import java.util.HashMap; -import java.util.Collections; -import java.util.List; import java.io.IOException; import java.text.Format; import java.text.NumberFormat; @@ -81,6 +79,11 @@ import org.apache.sis.internal.util.Stan * * <div class="section">Limitations</div> * <ul> + * <li><strong>The WKT format is not lossless!</strong> + * Objects formatted by {@code WKTFormat} are not guaranteed to be identical after parsing. + * Some metadata may be lost or altered, but the coordinate operations between two CRS should produce + * the same numerical results provided that the two CRS were formatted independently (do not rely on + * {@link org.opengis.referencing.crs.GeneralDerivedCRS#getConversionFromBase()} for instance).</li> * <li>Instances of this class are not synchronized for multi-threading. * It is recommended to create separated format instances for each thread. * If multiple threads access a {@code WKTFormat} concurrently, it must be synchronized externally.</li> @@ -554,18 +557,15 @@ public class WKTFormat extends CompoundF this.formatter = formatter; } final boolean valid; - final InternationalString warning; try { formatter.setBuffer(buffer); valid = formatter.appendElement(object) || formatter.appendValue(object); } finally { - warning = formatter.getErrorMessage(); // Must be saved before formatter.clear() is invoked. + warnings = formatter.getWarnings(); // Must be saved before formatter.clear() is invoked. formatter.setBuffer(null); formatter.clear(); } - if (warning != null) { - warnings = new Warnings(getLocale(), (byte) 0, Collections.<String, List<String>>emptyMap()); - warnings.add(warning, formatter.getErrorCause(), null); + if (warnings != null) { warnings.setRoot(object); } if (!valid) { Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -32,6 +32,7 @@ import org.opengis.util.InternationalStr import org.apache.sis.util.Classes; import org.apache.sis.util.Exceptions; import org.apache.sis.util.Localized; +import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.resources.Messages; import org.apache.sis.util.resources.Vocabulary; @@ -237,6 +238,49 @@ public final class Warnings implements L } /** + * Returns the number of warning messages. + * + * @return The number of warning messages. + */ + public final int getNumMessages() { + return (messages != null) ? messages.size() / 2 : 0; + } + + /** + * Returns a warning message. + * + * @param index 0 for the first warning, 1 for the second warning, <i>etc.</i> until {@link #getNumMessages()} - 1. + * @return The <var>i</var>-th warning message. + */ + public String getMessage(int index) { + ArgumentChecks.ensureValidIndex(getNumMessages(), index); + index *= 2; + final InternationalString i18n = (InternationalString) messages.get(index); + if (i18n != null) { + return i18n.toString(errorLocale); + } else { + final Exception cause = (Exception) messages.get(index + 1); + final String[] sources = exceptionSources.get(cause); // See comment in 'toString(Locale)'. + if (sources != null) { + return Errors.getResources(errorLocale).getString(Errors.Keys.UnparsableStringInElement_2, sources); + } else { + return cause.toString(); + } + } + } + + /** + * Returns the exception which was the cause of the message at the given index, or {@code null} if none. + * + * @param index The value given to {@link #getMessage(int)}. + * @return The exception which was the cause of the warning message, or {@code null} if none. + */ + public Exception getException(final int index) { + ArgumentChecks.ensureValidIndex(getNumMessages(), index); + return (Exception) messages.get(index*2 + 1); + } + + /** * Returns the non-fatal exceptions that occurred during the parsing or formatting. * If no exception occurred, returns an empty set. * @@ -313,7 +357,7 @@ public final class Warnings implements L .append(lineSeparator); if (messages != null) { for (final Iterator<?> it = messages.iterator(); it.hasNext();) { - InternationalString i18n = (InternationalString) it.next(); + final InternationalString i18n = (InternationalString) it.next(); Exception cause = (Exception) it.next(); final String message; if (i18n != null) { Modified: sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java [UTF-8] (original) +++ sis/trunk/core/sis-metadata/src/test/java/org/apache/sis/io/wkt/TransliteratorTest.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -22,6 +22,7 @@ import org.opengis.referencing.cs.Ellips import org.opengis.referencing.cs.CoordinateSystem; import org.apache.sis.internal.metadata.AxisNames; import org.apache.sis.test.mock.CoordinateSystemAxisMock; +import org.apache.sis.test.DependsOnMethod; import org.apache.sis.test.TestCase; import org.junit.Test; @@ -38,6 +39,31 @@ import static org.junit.Assert.*; */ public final strictfp class TransliteratorTest extends TestCase { /** + * Verify the value of the {@link Transliterator#SPACES} constant. + */ + @Test + public void testSpacesConstant() { + int code = 0; + for (char c=0; c<32; c++) { + if (Character.isWhitespace(c)) { + code |= (1 << c); + } + } + assertEquals(Transliterator.SPACES, code); + } + + /** + * Tests {@link Transliterator#filter(String)}. + */ + @Test + @DependsOnMethod("testSpacesConstant") + public void testFilter() { + final Transliterator t = Transliterator.DEFAULT; + assertEquals("Nouvelle triangulation francaise", t.filter("Nouvelle\r\ntriangulation\nfrançaise")); + assertEquals("ABC D E", t.filter("AB\bC\rD\tE")); + } + + /** * Tests {@link Transliterator#toLongAxisName(String, AxisDirection, String)}. */ @Test Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] (original) +++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -32,12 +32,10 @@ import org.apache.sis.util.logging.Loggi import org.apache.sis.util.resources.Errors; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.internal.jaxb.Context; -import org.apache.sis.internal.metadata.AxisDirections; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.datum.DefaultPrimeMeridian; import org.apache.sis.referencing.crs.DefaultGeographicCRS; import org.apache.sis.referencing.cs.AxesConvention; -import org.apache.sis.measure.Units; import static java.util.Collections.singletonMap; import static org.apache.sis.internal.util.Numerics.epsilonEqual; @@ -108,34 +106,6 @@ public final class ReferencingUtilities } /** - * Returns the angular unit of the specified coordinate system. - * The preference will be given to the longitude axis, if found. - * - * @param cs The coordinate system from which to get the angular unit, or {@code null}. - * @return The angular unit, of {@code null} if no angular unit was found. - * - * @since 0.6 - */ - public static Unit<Angle> getAngularUnit(final CoordinateSystem cs) { - Unit<Angle> unit = null; - if (cs != null) { - for (int i = cs.getDimension(); --i>=0;) { - final CoordinateSystemAxis axis = cs.getAxis(i); - if (axis != null) { // Paranoiac check. - final Unit<?> candidate = axis.getUnit(); - if (Units.isAngular(candidate)) { - unit = candidate.asType(Angle.class); - if (AxisDirection.EAST.equals(AxisDirections.absolute(axis.getDirection()))) { - break; // Found the longitude axis. - } - } - } - } - } - return unit; - } - - /** * Returns the unit used for all axes in the given coordinate system. * If not all axes use the same unit, then this method returns {@code null}. * @@ -145,6 +115,8 @@ public final class ReferencingUtilities * * @param cs The coordinate system for which to get the unit, or {@code null}. * @return The unit for all axis in the given coordinate system, or {@code null}. + * + * @see org.apache.sis.internal.metadata.AxisDirections#getAngularUnit(CoordinateSystem) */ public static Unit<?> getUnit(final CoordinateSystem cs) { Unit<?> unit = null; Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java [UTF-8] (original) +++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -31,6 +31,7 @@ import org.opengis.referencing.crs.Geode import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.datum.PrimeMeridian; import org.apache.sis.internal.referencing.Legacy; +import org.apache.sis.internal.metadata.AxisDirections; import org.apache.sis.internal.metadata.WKTKeywords; import org.apache.sis.internal.referencing.WKTUtilities; import org.apache.sis.internal.referencing.ReferencingUtilities; @@ -39,7 +40,6 @@ import org.apache.sis.io.wkt.Convention; import org.apache.sis.io.wkt.Formatter; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; -import static org.apache.sis.internal.referencing.WKTUtilities.toFormattable; /** @@ -188,16 +188,16 @@ class DefaultGeodeticCRS extends Abstrac */ final GeodeticDatum datum = getDatum(); // Gives subclasses a chance to override. formatter.newLine(); - formatter.append(toFormattable(datum)); + formatter.append(WKTUtilities.toFormattable(datum)); formatter.newLine(); final PrimeMeridian pm = datum.getPrimeMeridian(); - final Unit<Angle> angularUnit = ReferencingUtilities.getAngularUnit(cs); + final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, null); if (convention != Convention.WKT2_SIMPLIFIED || ReferencingUtilities.getGreenwichLongitude(pm, NonSI.DEGREE_ANGLE) != 0) { final Unit<Angle> oldUnit = formatter.addContextualUnit(angularUnit); formatter.indent(1); - formatter.append(toFormattable(pm)); + formatter.append(WKTUtilities.toFormattable(pm)); formatter.indent(-1); formatter.newLine(); formatter.restoreContextualUnit(angularUnit, oldUnit); Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java [UTF-8] (original) +++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultProjectedCRS.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -39,6 +39,7 @@ import org.apache.sis.referencing.Identi import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.operation.DefaultOperationMethod; import org.apache.sis.internal.referencing.ReferencingUtilities; +import org.apache.sis.internal.metadata.AxisDirections; import org.apache.sis.internal.metadata.WKTKeywords; import org.apache.sis.internal.referencing.WKTUtilities; import org.apache.sis.internal.util.Constants; @@ -386,7 +387,7 @@ public class DefaultProjectedCRS extends final CartesianCS cs = getCoordinateSystem(); final GeographicCRS baseCRS = getBaseCRS(); final Unit<?> lengthUnit = ReferencingUtilities.getUnit(cs); - final Unit<Angle> angularUnit = ReferencingUtilities.getAngularUnit(baseCRS.getCoordinateSystem()); + final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(baseCRS.getCoordinateSystem(), null); final Unit<Angle> oldAngle = formatter.addContextualUnit(angularUnit); final Unit<?> oldLength = formatter.addContextualUnit(lengthUnit); /* Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] (original) +++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTFormatTest.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -16,8 +16,11 @@ */ package org.apache.sis.io.wkt; +import java.util.Collections; import java.text.ParseException; +import javax.measure.unit.NonSI; import org.opengis.referencing.crs.VerticalCRS; +import org.apache.sis.referencing.datum.DefaultPrimeMeridian; import org.apache.sis.test.DependsOnMethod; import org.apache.sis.test.DependsOn; import org.apache.sis.test.TestCase; @@ -164,7 +167,9 @@ public final strictfp class WKTFormatTes + "GEOGCS[“NAD27”," + "DATUM[“North American Datum 1927”," + "SPHEROID[“Clarke 1866”, 6378206.4, 294.97869821]]," - + "UNIT[“degree”,0.0174532925199433]]," + + "UNIT[“degree”,0.0174532925199433]," + + "AXIS[“Lat”,NORTH]," + + "AXIS[“Long”,EAST]]," + "PROJECTION[“Lambert_Conformal_Conic_2SP”]," + "PARAMETER[“latitude_of_origin”,27.83333333333333]," + "PARAMETER[“central_meridian”,-99.0]," @@ -227,4 +232,38 @@ public final strictfp class WKTFormatTes final Object reparsed = format.parseObject(reformat); assertEqualsIgnoreMetadata(expected, reparsed); } + + /** + * Tests the production of a warning messages when the WKT contains unformattable elements. + * + * @throws ParseException if the parsing (tested after formatting) failed. + */ + @Test + public void testWarnings() throws ParseException { + DefaultPrimeMeridian pm = new DefaultPrimeMeridian(Collections.singletonMap( + DefaultPrimeMeridian.NAME_KEY, "Invalid “$name” here"), -10, NonSI.DEGREE_ANGLE); + format = new WKTFormat(null, null); + final String wkt = format.format(pm); + final Warnings warnings = format.getWarnings(); + assertNotNull("warnings", warnings); + assertEquals ("warnings.numMessages", 1, warnings.getNumMessages()); + assertEquals ("PrimeMeridian[\"Invalid \"\"$name\"\" here\", -10.0, AngleUnit[\"degree\", 0.017453292519943295]]", wkt); + assertEquals ("The “$” character in “\"$name\"” is not permitted by the “Well-Known Text” format.", warnings.getMessage(0)); + assertNull (warnings.getException(0)); + /* + * Verify that FormattableObject.toWKT() reports that the WKT is invalid. + */ + try { + pm.toWKT(); + fail("Expected UnformattableObjectException."); + } catch (UnformattableObjectException e) { + final String message = e.getMessage(); + assertTrue(message, message.contains("$name")); + } + /* + * Verify that the WKT is still parseable despite the warning. + */ + pm = (DefaultPrimeMeridian) format.parseObject(wkt); + assertEquals("Invalid \"$name\" here", pm.getName().getCode()); + } } Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java [UTF-8] (original) +++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Characters.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -28,7 +28,7 @@ import org.apache.sis.util.resources.Err * * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.5 + * @version 0.6 * @module */ public final class Characters extends Static { @@ -78,6 +78,34 @@ public final class Characters extends St } /** + * Returns {@code true} if the given code point is a valid character for <cite>Well Known Text</cite> (WKT). + * This method returns {@code true} for the following characters: + * + * <blockquote><pre>{@literal A-Z a-z 0-9 _ [ ] ( ) { } < = > . , : ; + - (space) % & ' " * ^ / \ ? | °}</pre></blockquote> + * + * They are ASCII codes 32 to 125 inclusive except ! (33), # (35), $ (36), @ (64) and ` (96), + * plus the addition of ° (176) despite being formally outside the ASCII character set. + * + * @param c The code point to test. + * @return {@code true} if the given code point is a valid WKT character. + * + * @see org.apache.sis.io.wkt.Transliterator + * + * @since 0.6 + */ + public static boolean isValidWKT(final int c) { + switch (c) { + case '!': + case '#': + case '$': + case '@': + case '`': return false; + case '°': return true; + default : return (c >= ' ') && (c <= '}'); + } + } + + /** * Returns {@code true} if the given code point is a {@linkplain Character#LINE_SEPARATOR * line separator}, a {@linkplain Character#PARAGRAPH_SEPARATOR paragraph separator} or one * of the {@code '\r'} or {@code '\n'} control characters. Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java [UTF-8] (original) +++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -227,6 +227,8 @@ public final class StringBuilders extend cr = ASCII.charAt(r); } else { switch (getType(c)) { + case FORMAT: + case CONTROL: buffer.delete(i, i + n); continue; // Character.isIdentifierIgnorable case PARAGRAPH_SEPARATOR: // Fall through case LINE_SEPARATOR: cr = '\n'; break; case SPACE_SEPARATOR: cr = ' '; break; Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original) +++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -328,6 +328,11 @@ public final class Errors extends Indexe public static final short IllegalCRSType_1 = 194; /** + * The “{2}” character in “{1}” is not permitted by the “{0}” format. + */ + public static final short IllegalCharacterForFormat_3 = 195; + + /** * Class ‘{1}’ is illegal. It must be ‘{0}’ or a derived class. */ public static final short IllegalClass_2 = 34; Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original) +++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Fri Jul 3 18:18:51 2015 @@ -74,6 +74,7 @@ IllegalArgumentField_4 = Argu IllegalArgumentValue_2 = Argument \u2018{0}\u2019 can not take the \u201c{1}\u201d value. IllegalAxisDirection_2 = Coordinate system of class \u2018{0}\u2019 can not have axis in the {1} direction. IllegalBitsPattern_1 = Illegal bits pattern: {0}. +IllegalCharacterForFormat_3 = The \u201c{2}\u201d character in \u201c{1}\u201d is not permitted by the \u201c{0}\u201d format. IllegalClass_2 = Class \u2018{1}\u2019 is illegal. It must be \u2018{0}\u2019 or a derived class. IllegalCoordinateSystem_1 = Coordinate system can not be \u201c{0}\u201d. IllegalCRSType_1 = Coordinate reference system can not be of type \u2018{0}\u2019. Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original) +++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Fri Jul 3 18:18:51 2015 @@ -72,6 +72,7 @@ IllegalArgumentValue_2 = L\u2 IllegalAxisDirection_2 = Les syst\u00e8mes de coordonn\u00e9es de classe \u2018{0}\u2019 ne peuvent pas avoir d\u2019axe dans la direction \u00ab\u202f{1}\u202f\u00bb. IllegalBitsPattern_1 = Pattern de bits invalide: {0}. IllegalClass_2 = La classe \u2018{1}\u2019 est ill\u00e9gale. Il doit s\u2019agir d\u2019une classe \u2018{0}\u2019 ou d\u00e9riv\u00e9e. +IllegalCharacterForFormat_3 = Le caract\u00e8re \u00ab\u202f{2}\u202f\u00bb dans \u00ab\u202f{1}\u202f\u00bb n\u2019est pas permis par le format \u00ab\u202f{0}\u202f\u00bb. IllegalCoordinateSystem_1 = Le syst\u00e8me de coordonn\u00e9es ne peut pas \u00eatre \u00ab\u202f{0}\u202f\u00bb. IllegalCRSType_1 = Le syst\u00e8me de r\u00e9f\u00e9rence des coordonn\u00e9es ne peut pas \u00eatre de type \u2018{0}\u2019. IllegalFormatPatternForClass_2 = Le mod\u00e8le \u00ab\u202f{1}\u202f\u00bb ne peut pas \u00eatre appliqu\u00e9 au formatage d\u2019objets de type \u2018{0}\u2019. Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java?rev=1689052&r1=1689051&r2=1689052&view=diff ============================================================================== --- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java [UTF-8] (original) +++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/CharactersTest.java [UTF-8] Fri Jul 3 18:18:51 2015 @@ -29,7 +29,7 @@ import static org.apache.sis.util.Charac * * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.5 + * @version 0.6 * @module */ public final strictfp class CharactersTest extends TestCase { @@ -44,6 +44,21 @@ public final strictfp class CharactersTe } /** + * Tests the {@link Characters#isValidWKT(int)} method. + */ + @Test + public void testIsValidWKT() { + final String valids = "_[](){}<=>.,:;+- %&'\"*^/\\?|°"; + for (char c=0; c<256; c++) { + final boolean valid = (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || valids.indexOf(c) >= 0; + assertEquals(valid, isValidWKT(c)); + } + } + + /** * Tests the {@link Characters#isLineOrParagraphSeparator(int)} method. */ @Test