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

svn commit: r1563121 - in /sis/branches/JDK7/core/sis-referencing/src: main/java/org/apache/sis/io/wkt/ test/java/org/apache/sis/io/wkt/

Author: desruisseaux
Date: Fri Jan 31 12:22:26 2014
New Revision: 1563121

URL: http://svn.apache.org/r1563121
Log:
WKT formatter needs to double the quote character if it appears inside the quoted text.

Modified:
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Symbols.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java?rev=1563121&r1=1563120&r2=1563121&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java [UTF-8] Fri Jan 31 12:22:26 2014
@@ -698,11 +698,28 @@ public class Formatter {
     }
 
     /**
-     * Appends the given string as a quoted text.
+     * Appends the given string as a quoted text. If the given string contains the closing quote character,
+     * that character will be doubled. 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) {
-        buffer.appendCodePoint(symbols.getOpeningQuote(0)).append(text)
-              .appendCodePoint(symbols.getClosingQuote(0));
+        final int base = buffer.appendCodePoint(symbols.getOpeningQuote(0)).length();
+        buffer.append(text);
+        closeQuote(base);
+    }
+
+    /**
+     * Double any closing quote character that may appear at or after the given index,
+     * then append the closing quote character.
+     */
+    private void closeQuote(int fromIndex) {
+        final String quote = symbols.getQuote();
+        while ((fromIndex = buffer.indexOf(quote, fromIndex)) >= 0) {
+            final int n = quote.length();
+            buffer.insert(fromIndex += n, quote);
+            fromIndex += n;
+        }
+        buffer.append(quote);
     }
 
     /**
@@ -767,7 +784,7 @@ public class Formatter {
             appendSeparator(requestNewLine);
             buffer.append("UNIT").appendCodePoint(symbols.getOpeningBracket(0));
             setColor(ElementKind.UNIT);
-            buffer.appendCodePoint(symbols.getOpeningQuote(0));
+            final int fromIndex = buffer.appendCodePoint(symbols.getOpeningQuote(0)).length();
             if (NonSI.DEGREE_ANGLE.equals(unit)) {
                 buffer.append("degree");
             } else if (SI.METRE.equals(unit)) {
@@ -775,7 +792,7 @@ public class Formatter {
             } else {
                 unitFormat.format(unit, buffer, dummy);
             }
-            buffer.appendCodePoint(symbols.getClosingQuote(0));
+            closeQuote(fromIndex);
             resetColor();
             append(Units.toStandardUnit(unit));
             buffer.appendCodePoint(symbols.getClosingBracket(0));

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Symbols.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Symbols.java?rev=1563121&r1=1563120&r2=1563121&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Symbols.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Symbols.java [UTF-8] Fri Jan 31 12:22:26 2014
@@ -16,7 +16,10 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Arrays;
 import java.util.Locale;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.text.NumberFormat;
 import org.apache.sis.util.Localized;
@@ -34,8 +37,8 @@ import static org.apache.sis.util.Argume
  *   <li>An English locale for {@linkplain java.text.DecimalFormatSymbols decimal format symbols}.</li>
  *   <li>Square brackets, as in {@code DATUM["WGS84"]}. An alternative allowed by the WKT
  *       specification is curly brackets as in {@code DATUM("WGS84")}.</li>
- *   <li>English quotation mark ({@code '"'}). SIS also accepts {@code “…”} quotation marks
- *       for more readable {@link String} constants in Java code.</li>
+ *   <li>English quotation mark ({@code '"'}). SIS also accepts {@code '“'} opening quote and {@code '”'}
+ *       closing quote for more readable {@link String} constants in Java code, but the later are not legal WKT.</li>
  *   <li>Coma separator followed by a space ({@code ", "}).</li>
  * </ul>
  *
@@ -43,7 +46,7 @@ import static org.apache.sis.util.Argume
  * The {@link WKTFormat#getLocale()} property specifies the language to use when formatting
  * {@link org.opengis.util.InternationalString} instances. This can be set to any value.
  * On the contrary, the {@code Locale} property of this {@code Symbols} class controls
- * the decimal format symbols and is very rarely set to an other locale than an English one.
+ * the decimal format symbols and is very rarely set to an other locale than {@link Locale#ROOT}.
  *
  * @author  Martin Desruisseaux (IRD)
  * @since   0.4 (derived from geotk-2.1)
@@ -114,6 +117,16 @@ public class Symbols implements Localize
     private int[] quotes;
 
     /**
+     * The preferred closing quote character ({@code quotes[1]}) as a string.
+     * We use the closing quote because this is the character that the parser
+     * will look for determining the text end.
+     *
+     * @see #getQuote()
+     * @see #readObject(ObjectInputStream)
+     */
+    private transient String quote;
+
+    /**
      * The character (as Unicode code point) used for opening ({@code openSequence})
      * or closing ({@code closeSequence}) an array or enumeration.
      */
@@ -141,6 +154,7 @@ public class Symbols implements Localize
         locale        = symbols.locale;
         brackets      = symbols.brackets;
         quotes        = symbols.quotes;
+        quote         = symbols.quote;
         openSequence  = symbols.openSequence;
         closeSequence = symbols.closeSequence;
         separator     = symbols.separator;
@@ -151,9 +165,10 @@ public class Symbols implements Localize
      * The given array is stored by reference - it is not cloned.
      */
     private Symbols(final int[] brackets, final int[] quotes) {
-        this.locale        = Locale.US;
+        this.locale        = Locale.ROOT;
         this.brackets      = brackets;
         this.quotes        = quotes;
+        this.quote         = "\"";
         this.openSequence  = '{';
         this.closeSequence = '}';
         this.separator     = ", ";
@@ -198,6 +213,15 @@ public class Symbols implements Localize
         void checkWritePermission() throws UnsupportedOperationException {
             throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "Symbols"));
         }
+
+        /**
+         * Invoked on deserialization for replacing the deserialized instance by the constant instance.
+         */
+        Object readResolve() {
+            if (equals(SQUARE_BRACKETS)) return SQUARE_BRACKETS;
+            if (equals(CURLY_BRACKETS))  return CURLY_BRACKETS;
+            return this;
+        }
     }
 
     /**
@@ -215,9 +239,9 @@ public class Symbols implements Localize
     }
 
     /**
-     * Returns the locale of {@linkplain java.text.DecimalFormatSymbols decimal format symbols}
-     * or other symbols. This is usually an English locale. Note that this is not the same locale
-     * than the {@link WKTFormat} one, which is used for choosing the language of international strings.
+     * Returns the locale of {@linkplain java.text.DecimalFormatSymbols decimal format symbols} or other symbols.
+     * The default value is {@link Locale#ROOT}. Note that this is not the same locale than the {@link WKTFormat}
+     * one, which is used for choosing the language of international strings.
      *
      * @return The symbols locale.
      */
@@ -228,6 +252,8 @@ public class Symbols implements Localize
 
     /**
      * Sets the locale of decimal format symbols or other symbols.
+     * Note that any non-English locale is likely to produce WKT that do not conform to ISO 19162.
+     * Such WKT should be used for human reading only, not for data export.
      *
      * @param locale The new symbols locale.
      */
@@ -348,6 +374,15 @@ public class Symbols implements Localize
     }
 
     /**
+     * Returns the preferred closing quote character as a string. This is the quote to double if it
+     * appears in a Unicode string to format. We check for the closing quote because this is the one
+     * that the parser will look for determining the text end.
+     */
+    final String getQuote() {
+        return quote;
+    }
+
+    /**
      * Sets the opening and closing quotes to the given pairs.
      * Each string shall contain exactly two code points (usually two characters).
      * The first code point is taken as the opening quote, and the second code point as the closing quote.
@@ -365,6 +400,7 @@ public class Symbols implements Localize
     public void setPairedQuotes(final String preferred, final String... alternatives) {
         checkWritePermission();
         quotes = toCodePoints(preferred, alternatives);
+        quote = preferred.substring(Character.charCount(quotes[0])).trim();
     }
 
     /**
@@ -372,7 +408,7 @@ public class Symbols implements Localize
      * This method also verifies arguments validity.
      */
     private static int[] toCodePoints(final String preferred, final String[] alternatives) {
-        ensureNonNull("preferred", preferred);
+        ensureNonEmpty("preferred", preferred);
         final int n = (alternatives != null) ? alternatives.length : 0;
         final int[] array = new int[(n+1) * 2];
         String name = "preferred";
@@ -574,4 +610,43 @@ public class Symbols implements Localize
         }
         return false;
     }
+
+    /**
+     * Compares this {@code Symbols} with the given object for equality.
+     *
+     * @param  other The object to compare with this {@code Symbols}.
+     * @return {@code true} if both objects are equal.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof Symbols) {
+            final Symbols that = (Symbols) other;
+            return Arrays.equals(brackets, that.brackets) &&
+                   Arrays.equals(quotes, that.quotes) &&
+                   // no need to compare 'quote' because it is computed from 'quotes'.
+                   openSequence  == that.openSequence &&
+                   closeSequence == that.closeSequence &&
+                   separator.equals(that.separator) &&
+                   locale.equals(that.locale);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this object.
+     *
+     * @return A hash code value.
+     */
+    @Override
+    public int hashCode() {
+        return Arrays.deepHashCode(new Object[] {brackets, quotes, openSequence, closeSequence, separator, locale});
+    }
+
+    /**
+     * Invoked on deserialization for recomputing the {@link #quote} field.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        quote = String.valueOf(Character.toChars(quotes[1]));
+    }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java?rev=1563121&r1=1563120&r2=1563121&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java [UTF-8] Fri Jan 31 12:22:26 2014
@@ -16,13 +16,16 @@
  */
 package org.apache.sis.io.wkt;
 
+import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.internal.util.X364;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
+import static java.util.Collections.singletonMap;
 import static org.apache.sis.referencing.Assert.*;
 
 
@@ -46,6 +49,16 @@ public final strictfp class FormatterTes
     }
 
     /**
+     * Tests {@link Formatter#append(IdentifiedObject)} with a name that contains the quote character.
+     * We test that the closing quote character is doubled.
+     */
+    @Test
+    public void testAppendIdentifiedObject() {
+        assertWktEquals("IdentifiedObject[“My “object””.”]",
+                new AbstractIdentifiedObject(singletonMap(IdentifiedObject.NAME_KEY, "My “object”.")));
+    }
+
+    /**
      * Tests {@link Formatter#append(Matrix)}.
      */
     @Test

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java?rev=1563121&r1=1563120&r2=1563121&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/SymbolsTest.java [UTF-8] Fri Jan 31 12:22:26 2014
@@ -16,11 +16,12 @@
  */
 package org.apache.sis.io.wkt;
 
+import java.util.Locale;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.util.StringBuilders;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -59,4 +60,47 @@ public final strictfp class SymbolsTest 
         assertFalse(wkt.contentEquals(buffer));
         assertEquals(message, expected, Symbols.DEFAULT.containsAxis(buffer));
     }
+
+    /**
+     * Ensures that the static constants are immutable.
+     */
+    @Test
+    public void testImmutability() {
+        try {
+            Symbols.SQUARE_BRACKETS.setPairedBrackets("()", "[]");
+            fail("Constant shall be immutable.");
+        } catch (UnsupportedOperationException e) {
+            // This is the expected exception.
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("Symbols"));
+        }
+        try {
+            Symbols.CURLY_BRACKETS.setLocale(Locale.FRENCH);
+            fail("Constant shall be immutable.");
+        } catch (UnsupportedOperationException e) {
+            // This is the expected exception.
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("Symbols"));
+        }
+    }
+
+    /**
+     * Tests {@link Symbols} serialization.
+     */
+    @Test
+    public void testSerialization() {
+        assertSame(Symbols.SQUARE_BRACKETS, assertSerializedEquals(Symbols.SQUARE_BRACKETS));
+        assertSame(Symbols.CURLY_BRACKETS,  assertSerializedEquals(Symbols.CURLY_BRACKETS));
+        /*
+         * Test with a new instance using a closing quote symbol different than the opening one.
+         * This is necessary in order to ensure that the symbol is recomputed correctly.
+         */
+        final Symbols symbols = new Symbols(Symbols.CURLY_BRACKETS);
+        assertEquals("quote", "\"", symbols.getQuote());
+        symbols.setPairedQuotes("“”", "\"\"");
+        assertEquals("quote", "”", symbols.getQuote());
+        final Symbols c = assertSerializedEquals(symbols);
+        assertNotSame(symbols, c); // Expect a new instance.
+        assertEquals("quote", "”", c.getQuote()); // Verify the recomputed value.
+    }
 }