sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1415758 [3/3] - in /sis/trunk: ./ ide-project/NetBeans/ ide-project/NetBeans/nbproject/ sis-utility/src/main/java/org/apache/sis/internal/util/ sis-utility/src/main/java/org/apache/sis/io/ sis-utility/src/main/java/org/apache/sis/math/ sis...
Date Fri, 30 Nov 2012 17:38:20 GMT
Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java Fri Nov 30 17:38:17 2012
@@ -41,7 +41,15 @@ public final class Messages extends Inde
      * @version 0.3
      * @module
      */
-    public static final class Keys {
+    public static final class Keys extends KeyConstants {
+        /**
+         * The unique instance of key constants handler.
+         */
+        static final Keys INSTANCE = new Keys();
+
+        /**
+         * For {@link #INSTANCE} creation only.
+         */
         private Keys() {
         }
 
@@ -66,12 +74,11 @@ public final class Messages extends Inde
     }
 
     /**
-     * Returns the {@code Keys} class.
+     * Returns the handle for the {@code Keys} constants.
      */
     @Override
-    final Class<?> getKeysClass() throws ClassNotFoundException {
-        assert super.getKeysClass() == Keys.class;
-        return Keys.class;
+    final KeyConstants getKeyConstants() {
+        return Keys.INSTANCE;
     }
 
     /**
@@ -174,9 +181,10 @@ public final class Messages extends Inde
     private static final class International extends ResourceInternationalString {
         private static final long serialVersionUID = -229348959712294903L;
 
-        International(int key)              {super(key);}
-        International(int key, Object args) {super(key, args);}
-        @Override IndexedResourceBundle getBundle(Locale locale) {
+        International(int key)                   {super(key);}
+        International(int key, Object args)      {super(key, args);}
+        @Override KeyConstants getKeyConstants() {return Keys.INSTANCE;}
+        @Override IndexedResourceBundle getBundle(final Locale locale) {
             return getResources(locale);
         }
     }

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/ResourceInternationalString.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/ResourceInternationalString.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/ResourceInternationalString.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/ResourceInternationalString.java Fri Nov 30 17:38:17 2012
@@ -17,10 +17,15 @@
 package org.apache.sis.util.resources;
 
 import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.InvalidObjectException;
+import java.io.IOException;
 import java.util.Locale;
 import java.util.MissingResourceException;
 import net.jcip.annotations.Immutable;
 import org.apache.sis.util.Utilities;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.type.AbstractInternationalString;
 
 // Related to JDK7
@@ -45,16 +50,16 @@ abstract class ResourceInternationalStri
     private static final long serialVersionUID = 4744571031462678126L;
 
     /**
-     * The key for the resource to fetch.
+     * The key for the resource to fetch. A negative value means that the resource takes no
+     * argument, in which case the {@link #arguments} field shall be ignored. Negative key
+     * values are converted to positive values using the {@code ~} operator.
      */
-    private final int key;
+    private transient int key;
 
     /**
-     * The argument(s), or {@code Loader.class} if none. The choice of {@code Loader.class} for
-     * meaning "no argument" is arbitrary - we just need a reference that the user is unlikely
-     * to known, and we need the referenced object to be serializable. We can not use the
-     * {@code null} value for "no argument" because the user may really wants to specify
-     * {@code null} as an argument value.
+     * The argument(s), or {@code null} if none. Note that the user may also really want to
+     * specify {@code null} as an argument value. We distinguish the two cases with the sign
+     * of the {@link #key} value.
      */
     private final Object arguments;
 
@@ -64,8 +69,9 @@ abstract class ResourceInternationalStri
      * @param key The key for the resource to fetch.
      */
     ResourceInternationalString(final int key) {
-        this.key  = key;
-        arguments = Loader.class;
+        ArgumentChecks.ensurePositive("key", key);
+        this.key  = ~key;
+        arguments = null;
     }
 
     /**
@@ -75,11 +81,21 @@ abstract class ResourceInternationalStri
      * @param The argument(s).
      */
     ResourceInternationalString(final int key, final Object arguments) {
+        ArgumentChecks.ensurePositive("key", key);
         this.key = key;
         this.arguments = arguments;
     }
 
     /**
+     * Returns a handler for the constants declared in the inner {@code Keys} class.
+     * This is used at serialization time in order to serialize the constant name
+     * rather than its numeric value.
+     *
+     * @return A handler for the constants declared in the inner {@code Keys} class.
+     */
+    abstract KeyConstants getKeyConstants();
+
+    /**
      * Returns the resource bundle for the given locale.
      *
      * @param  locale The locale for which to get the resource bundle.
@@ -104,8 +120,8 @@ abstract class ResourceInternationalStri
             locale = Locale.ENGLISH;
         }
         final IndexedResourceBundle resources = getBundle(locale);
-        return (arguments == Loader.class)
-                ? resources.getString(key)
+        return (key < 0)
+                ? resources.getString(~key)
                 : resources.getString(key, arguments);
     }
 
@@ -117,7 +133,7 @@ abstract class ResourceInternationalStri
      */
     @Override
     public boolean equals(final Object object) {
-        if (object instanceof ResourceInternationalString) {
+        if (object != null && object.getClass() == getClass()) {
             final ResourceInternationalString that = (ResourceInternationalString) object;
             return this.key == that.key && Objects.equals(this.arguments, that.arguments);
         }
@@ -131,6 +147,32 @@ abstract class ResourceInternationalStri
      */
     @Override
     public int hashCode() {
-        return key ^ Utilities.deepHashCode(arguments) ^ (int) serialVersionUID;
+        return getClass().hashCode() ^ (key + 31*Utilities.deepHashCode(arguments)) ^ (int) serialVersionUID;
+    }
+
+    /**
+     * Serializes this international string using the key name rather than numerical value.
+     */
+    private void writeObject(final ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        out.writeUTF(getKeyConstants().getKeyName(key >= 0 ? key : ~key));
+        out.writeBoolean(key < 0);
+    }
+
+    /**
+     * Deserializes an object serialized by {@link #writeObject(ObjectOutputStream)}.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        try {
+            key = getKeyConstants().getKeyValue(in.readUTF());
+        } catch (Exception cause) { // (ReflectiveOperationException) on JDK7
+            InvalidObjectException e = new InvalidObjectException(cause.toString());
+            e.initCause(cause);
+            throw e;
+        }
+        if (in.readBoolean()) {
+            key = ~key;
+        }
     }
 }

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java Fri Nov 30 17:38:17 2012
@@ -41,16 +41,59 @@ public final class Vocabulary extends In
      * @version 0.3
      * @module
      */
-    public static final class Keys {
+    public static final class Keys extends KeyConstants {
+        /**
+         * The unique instance of key constants handler.
+         */
+        static final Keys INSTANCE = new Keys();
+
+        /**
+         * For {@link #INSTANCE} creation only.
+         */
         private Keys() {
         }
 
         /**
+         * Maximum value
+         */
+        public static final int MaximumValue = 5;
+
+        /**
+         * Mean value
+         */
+        public static final int MeanValue = 6;
+
+        /**
+         * Minimum value
+         */
+        public static final int MinimumValue = 4;
+
+        /**
          * Name
          */
         public static final int Name = 0;
 
         /**
+         * Number of ‘NaN’
+         */
+        public static final int NumberOfNaN = 3;
+
+        /**
+         * Number of values
+         */
+        public static final int NumberOfValues = 2;
+
+        /**
+         * Root Mean Square
+         */
+        public static final int RootMeanSquare = 7;
+
+        /**
+         * Standard deviation
+         */
+        public static final int StandardDeviation = 8;
+
+        /**
          * Type
          */
         public static final int Type = 1;
@@ -66,12 +109,11 @@ public final class Vocabulary extends In
     }
 
     /**
-     * Returns the {@code Keys} class.
+     * Returns the handle for the {@code Keys} constants.
      */
     @Override
-    final Class<?> getKeysClass() throws ClassNotFoundException {
-        assert super.getKeysClass() == Keys.class;
-        return Keys.class;
+    final KeyConstants getKeyConstants() {
+        return Keys.INSTANCE;
     }
 
     /**
@@ -81,8 +123,8 @@ public final class Vocabulary extends In
      * @return Resources in the given locale.
      * @throws MissingResourceException if resources can't be found.
      */
-    public static Messages getResources(final Locale locale) throws MissingResourceException {
-        return getBundle(Messages.class, locale);
+    public static Vocabulary getResources(final Locale locale) throws MissingResourceException {
+        return getBundle(Vocabulary.class, locale);
     }
 
     /**
@@ -102,9 +144,10 @@ public final class Vocabulary extends In
     private static final class International extends ResourceInternationalString {
         private static final long serialVersionUID = 8360132666298806838L;
 
-        International(int key)              {super(key);}
-        International(int key, Object args) {super(key, args);}
-        @Override IndexedResourceBundle getBundle(Locale locale) {
+        International(int key)                   {super(key);}
+        International(int key, Object args)      {super(key, args);}
+        @Override KeyConstants getKeyConstants() {return Keys.INSTANCE;}
+        @Override IndexedResourceBundle getBundle(final Locale locale) {
             return getResources(locale);
         }
     }

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties Fri Nov 30 17:38:17 2012
@@ -14,5 +14,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-Name        = Name
-Type        = Type
+MaximumValue      = Maximum value
+MeanValue         = Mean value
+MinimumValue      = Minimum value
+Name              = Name
+NumberOfValues    = Number of values
+NumberOfNaN       = Number of \u2018NaN\u2019
+RootMeanSquare    = Root Mean Square
+StandardDeviation = Standard deviation
+Type              = Type

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties Fri Nov 30 17:38:17 2012
@@ -14,5 +14,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-Name        = Nom
-Type        = Type
+MaximumValue      = Valeur maximale
+MeanValue         = Valeur moyenne
+MinimumValue      = Valeur minimale
+Name              = Nom
+NumberOfValues    = Nombre de valeurs
+NumberOfNaN       = Nombre de \u2018NaN\u2019
+RootMeanSquare    = Moyenne quadratique
+StandardDeviation = \u00c9cart type
+Type              = Type

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/util/X364Test.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/util/X364Test.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/util/X364Test.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/util/X364Test.java Fri Nov 30 17:38:17 2012
@@ -18,7 +18,9 @@ package org.apache.sis.internal.util;
 
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOn;
 
+import static java.lang.String.valueOf;
 import static org.junit.Assert.*;
 import static org.apache.sis.internal.util.X364.*;
 
@@ -31,6 +33,7 @@ import static org.apache.sis.internal.ut
  * @version 0.3
  * @module
  */
+@DependsOn(org.apache.sis.util.CharSequencesTest.class)
 public final strictfp class X364Test extends TestCase {
     /**
      * Tests the {@link X364#plain(String)} method.
@@ -40,13 +43,13 @@ public final strictfp class X364Test ext
         String colored, plain;
         colored = "Some plain text";
         plain   = "Some plain text";
-        assertEquals(plain, plain(colored));
-        assertEquals(plain.length(), lengthOfPlain(colored));
+        assertEquals(plain,          valueOf(plain(colored, 0, colored.length())));
+        assertEquals(plain.length(), lengthOfPlain(colored, 0, colored.length()));
 
         plain   = "With blue in the middle";
         colored = "With " + FOREGROUND_BLUE.sequence() +
                   "blue"  + FOREGROUND_DEFAULT.sequence() + " in the middle";
-        assertEquals(plain, plain(colored));
-        assertEquals(plain.length(), lengthOfPlain(colored));
+        assertEquals(plain,          valueOf(plain(colored, 0, colored.length())));
+        assertEquals(plain.length(), lengthOfPlain(colored, 0, colored.length()));
     }
 }

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java Fri Nov 30 17:38:17 2012
@@ -21,6 +21,7 @@ import java.util.Locale;
 import java.util.TimeZone;
 import java.util.Iterator;
 import java.util.concurrent.Callable;
+import java.io.PrintWriter;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.text.Format;
 import java.text.DateFormat;
@@ -28,6 +29,7 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.internal.util.X364;
 
 import static org.junit.Assert.*;
 
@@ -42,6 +44,11 @@ import static org.junit.Assert.*;
  */
 public final strictfp class TestUtilities extends Static {
     /**
+     * Width of the separator to print to {@link TestCase#out}, in number of characters.
+     */
+    private static final int SEPARATOR_WIDTH = 80;
+
+    /**
      * Maximal time that {@code waitFoo()} methods can wait, in milliseconds.
      *
      * @see #waitForBlockedState(Thread)
@@ -67,6 +74,41 @@ public final strictfp class TestUtilitie
     }
 
     /**
+     * Prints the given title to {@link TestCase#out} in a box. This method is invoked for
+     * writing a clear visual separator between the verbose output of different test cases.
+     *
+     * @param title The title to write.
+     */
+    public static void printSeparator(final String title) {
+        final PrintWriter out = TestCase.out;
+        if (out != null) {
+            final boolean isAnsiSupported = X364.isAnsiSupported();
+            if (isAnsiSupported) {
+                out.print(X364.FOREGROUND_CYAN.sequence());
+            }
+            out.print('╒');
+            for (int i=0; i<SEPARATOR_WIDTH-2; i++) {
+                out.print('═');
+            }
+            out.println('╕');
+            out.print("│ ");
+            out.print(title);
+            for (int i=title.codePointCount(0, title.length()); i<SEPARATOR_WIDTH-3; i++) {
+                out.print(' ');
+            }
+            out.println('│');
+            out.print('└');
+            for (int i=0; i<SEPARATOR_WIDTH-2; i++) {
+                out.print('─');
+            }
+            out.println('┘');
+            if (isAnsiSupported) {
+                out.print(X364.FOREGROUND_DEFAULT.sequence());
+            }
+        }
+    }
+
+    /**
      * Parses the date for the given string using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
      * in UTC timezone.
      *

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java Fri Nov 30 17:38:17 2012
@@ -47,6 +47,7 @@ import org.apache.sis.util.ArgumentCheck
 import static java.lang.StrictMath.*;
 import static org.opengis.test.Assert.*;
 import static org.apache.sis.util.Characters.NO_BREAK_SPACE;
+import static org.apache.sis.util.CharSequences.trimWhitespaces;
 
 // Related to JDK7
 import org.apache.sis.internal.util.JDK7;
@@ -460,8 +461,8 @@ public strictfp class XMLComparator {
 
                     // For text node, continue the search if the node is empty.
                     case Node.TEXT_NODE: {
-                        final String text = node.getTextContent();
-                        if (text == null || text.trim().isEmpty()) {
+                        final String text = trimWhitespaces(node.getTextContent());
+                        if (text == null || text.isEmpty()) {
                             continue;
                         }
                         break;
@@ -528,7 +529,7 @@ public strictfp class XMLComparator {
      * if it is actually a {@link String} object.
      */
     private static Comparable<?> trim(final Comparable<?> property) {
-        return (property instanceof String) ? ((String) property).trim() : property;
+        return (property instanceof String) ? trimWhitespaces(((String) property)) : property;
     }
 
     /**

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java Fri Nov 30 17:38:17 2012
@@ -43,6 +43,7 @@ import org.junit.runners.Suite;
   org.apache.sis.util.resources.IndexedResourceBundleTest.class,
   org.apache.sis.util.logging.PerformanceLevelTest.class,
   org.apache.sis.math.MathFunctionsTest.class,
+  org.apache.sis.math.StatisticsTest.class,
 
   // Collections.
   org.apache.sis.internal.util.ReferenceQueueConsumerTest.class,
@@ -51,6 +52,7 @@ import org.junit.runners.Suite;
   org.apache.sis.util.collection.CacheTest.class,
   org.apache.sis.util.collection.DerivedSetTest.class,
   org.apache.sis.util.collection.DerivedMapTest.class,
+  org.apache.sis.util.collection.TableColumnTest.class,
   org.apache.sis.util.collection.DefaultTreeTableTest.class,
 
   // GeoAPI most basic types.
@@ -70,6 +72,7 @@ import org.junit.runners.Suite;
 
   // XML most basic types.
   org.apache.sis.xml.XLinkTest.class,
+  org.apache.sis.xml.NilReasonTest.class,
   org.apache.sis.internal.jaxb.IdentifierMapAdapterTest.class,
   org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCasesTest.class
 })

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/util/CharSequencesTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/util/CharSequencesTest.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/util/CharSequencesTest.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/util/CharSequencesTest.java Fri Nov 30 17:38:17 2012
@@ -209,7 +209,10 @@ public final strictfp class CharSequence
      */
     @Test
     public void testTrimWhitespaces() {
-        assertEquals("A text.", trimWhitespaces("  A text. "));
+        assertEquals("A text.", trimWhitespaces(               "  A text. "));
+        assertEquals("A text.", trimWhitespaces((CharSequence) "  A text. "));
+        assertEquals("",        trimWhitespaces(               "          "));
+        assertEquals("",        trimWhitespaces((CharSequence) "          "));
     }
 
     /**
@@ -324,7 +327,7 @@ public final strictfp class CharSequence
     public void testIsUpperCase() {
         assertTrue ("ABC", isUpperCase("ABC"));
         assertFalse("AbC", isUpperCase("AbC"));
-        assertFalse("A2C", isUpperCase("A2C")); // TODO: actually an unspecified behavior; we can change that.
+        assertFalse("A2C", isUpperCase("A2C"));
     }
 
     /**

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/CacheTest.java Fri Nov 30 17:38:17 2012
@@ -21,7 +21,11 @@ import java.util.HashMap;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicReference;
+import java.io.IOException;
+import java.io.PrintWriter;
 
+import org.apache.sis.math.Statistics;
+import org.apache.sis.io.TableFormatter;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
@@ -169,13 +173,19 @@ public final strictfp class CacheTest ex
     /**
      * Validates the entries created by the {@link #stress()} test. The check performed in
      * this method shall obviously be consistent with the values created by {@code stress()}.
+     *
+     * @param  cache The cache to validate.
+     * @return Statistics on the key values of the given map.
      */
-    private static void validateStressEntries(final Map<Integer,Integer> cache) {
+    private static Statistics validateStressEntries(final Map<Integer,Integer> cache) {
+        final Statistics statistics = new Statistics();
         for (final Map.Entry<Integer,Integer> entry : cache.entrySet()) {
             final int key = entry.getKey();
             final int value = entry.getValue();
             assertEquals(key*key, value);
+            statistics.add(key);
         }
+        return statistics;
     }
 
     /**
@@ -234,15 +244,20 @@ public final strictfp class CacheTest ex
         /*
          * Verifies the values.
          */
-        validateStressEntries(cache);
+        final Statistics beforeGC = validateStressEntries(cache);
         assertTrue("Should not have more entries than what we put in.", cache.size() <= count);
         assertFalse("Some entries should be retained by strong references.", cache.isEmpty());
+        /*
+         * If verbose test output is enabled, report the number of cache hits.
+         * The numbers below are for tuning the test only. The output is somewhat
+         * random so we can not check it in a test suite.  However if the test is
+         * properly tuned, most values should be non-zero.
+         */
+        final PrintWriter out = CacheTest.out;
         if (out != null) {
-            out.println();
-            out.println("The numbers below are for tuning the test only. The output is somewhat");
-            out.println("random so we can not check it in a test suite.  However if the test is");
-            out.println("properly tuned, most values should be non-zero.");
-            out.println();
+            TestUtilities.printSeparator("CacheTest.stress() - testing concurrent accesses");
+            out.print("There is "); out.print(threads.length); out.print(" threads, each of them"
+                    + " fetching or creating "); out.print(count); out.println(" values.");
             out.println("Number of times a cached value has been reused, for each thread:");
             for (int i=0; i<threads.length;) {
                 final String n = String.valueOf(threads[i++].addCount);
@@ -267,7 +282,32 @@ public final strictfp class CacheTest ex
             out.println();
             out.flush();
         }
+        /*
+         * Gets the statistics of key values after garbage collection. The mean value should
+         * be higher, because oldest values (which should have been garbage collected first)
+         * have lower values. If verbose output is enabled, then we will print the statistics
+         * before to perform the actual check in order to allow the developer to have more
+         * information in case of failure.
+         */
         System.gc();
-        validateStressEntries(cache);
+        final Statistics afterGC = validateStressEntries(cache);
+        if (out != null) {
+            final TableFormatter table = new TableFormatter(out, " │ ");
+            table.setMultiLinesCells(true);
+            table.append("Statistics on the keys before garbage collection:");
+            table.nextColumn();
+            table.append("Statistics on the keys after garbage collection.\n" +
+                         "The minimum and the mean values should be greater.");
+            table.nextLine();
+            table.append(beforeGC.toString());
+            table.nextColumn();
+            table.append(afterGC.toString());
+            try {
+                table.flush();
+            } catch (IOException e) {
+                throw new AssertionError(e);
+            }
+        }
+        assertTrue("Mean key value should be greater after garbage collection.", afterGC.mean() >= beforeGC.mean());
     }
 }

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/DefaultTreeTableTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/DefaultTreeTableTest.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/DefaultTreeTableTest.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/DefaultTreeTableTest.java Fri Nov 30 17:38:17 2012
@@ -20,10 +20,11 @@ import java.util.List;
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.TestStep;
+import org.apache.sis.test.DependsOn;
 
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.test.TestUtilities.getSingleton;
-import static org.apache.sis.util.collection.TreeTables.*;
+import static org.apache.sis.util.collection.TableColumn.*;
 
 
 /**
@@ -35,6 +36,7 @@ import static org.apache.sis.util.collec
  * @version 0.3
  * @module
  */
+@DependsOn(TableColumnTest.class)
 public final strictfp class DefaultTreeTableTest extends TestCase {
     /**
      * Tests the creation of an {@link DefaultTreeTable} with initially no root node.
@@ -112,7 +114,8 @@ public final strictfp class DefaultTreeT
      * Tests the displacement of nodes, in particular ensures that the parent is updated.
      *
      * <p>This method is part of a chain.
-     * The previous method is {@link #testNodeCreation(DefaultTreeTable)}.</p>
+     * The previous method is {@link #testNodeCreation(DefaultTreeTable)} and
+     * the next method is {@link #testSerialization(TreeTable)}.</p>
      *
      * @param root The root node where to move children.
      */
@@ -139,12 +142,53 @@ public final strictfp class DefaultTreeT
     }
 
     /**
+     * Tests {@link DefaultTreeTable#clone()}.
+     * This will also indirectly tests {@link DefaultTreeTable#equals(Object)}.
+     *
+     * <p>This method is part of a chain.
+     * The previous method is {@link #testNodeDisplacement(TreeTable.Node)}.</p>
+     *
+     * @throws CloneNotSupportedException Should never happen.
+     */
+    @TestStep
+    private void testClone(final DefaultTreeTable table) throws CloneNotSupportedException {
+        final TreeTable newTable = table.clone();
+        assertNotSame("clone", table, newTable);
+        assertEquals("newTable.equals(table)", table, newTable);
+        assertEquals("hashCode", table.hashCode(), newTable.hashCode());
+        newTable.getRoot().getChildren().get(1).setValue(NAME, "New name");
+        assertFalse("newTable.equals(table)", newTable.equals(table));
+    }
+
+    /**
+     * Tests {@link DefaultTreeTable} serialization.
+     *
+     * <p>This method is part of a chain.
+     * The previous method is {@link #testNodeDisplacement(TreeTable.Node)}.</p>
+     */
+    @TestStep
+    private void testSerialization(final TreeTable table) {
+        final TreeTable newTable = assertSerializedEquals(table);
+        newTable.getRoot().getChildren().get(1).setValue(NAME, "New name");
+        assertFalse("newTable.equals(table)", newTable.equals(table));
+    }
+
+    /**
      * Tests the creation of a tree table with a few nodes, and tests the displacement of a node
-     * from one branch to another. This test is actually a chain of {@link TestStep} methods.
+     * from one branch to another. Finally tests the serialization of that table and the comparison
+     * with the original object.
+     *
+     * <p>This test is actually a chain of {@link TestStep} methods.</p>
+     *
+     * @throws CloneNotSupportedException If the {@link DefaultTreeTable#clone()} method failed.
      */
     @Test
-    public void testTreeTableCreation() {
-        testNodeDisplacement(testNodeCreation(testTableCreation()));
+    public void testTreeTableCreation() throws CloneNotSupportedException {
+        final DefaultTreeTable table = testTableCreation();
+        final TreeTable.Node   root  = testNodeCreation(table);
+        testNodeDisplacement(root);
+        testClone(table);
+        testSerialization(table);
     }
 
     /**

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java Fri Nov 30 17:38:17 2012
@@ -20,9 +20,10 @@ import java.text.ParseException;
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.DependsOnMethod;
 
 import static org.apache.sis.test.Assert.*;
-import static org.apache.sis.util.collection.ColumnConstant.*;
+import static org.apache.sis.util.collection.TableColumn.*;
 
 
 /**
@@ -39,22 +40,15 @@ import static org.apache.sis.util.collec
 })
 public final strictfp class TreeTableFormatTest extends TestCase {
     /**
-     * Creates a node with a single column for object names.
-     */
-    private static DefaultTreeTable.Node createNode(final CharSequence name) {
-        return new DefaultTreeTable.Node(NAME_MAP, new CharSequence[] {name});
-    }
-
-    /**
      * Tests the formatting as a tree, with control on the indentation.
      */
     @Test
     public void testTreeFormat() {
-        final DefaultTreeTable.Node root   = createNode("Node #1");
-        final DefaultTreeTable.Node branch = createNode("Node #2");
+        final DefaultTreeTable.Node root   = new DefaultTreeTable.Node("Node #1");
+        final DefaultTreeTable.Node branch = new DefaultTreeTable.Node("Node #2");
         root.getChildren().add(branch);
-        root.getChildren().add(createNode("Node #3"));
-        branch.getChildren().add(createNode("Node #4"));
+        root.getChildren().add(new DefaultTreeTable.Node("Node #3"));
+        branch.getChildren().add(new DefaultTreeTable.Node("Node #4"));
 
         final TreeTableFormat tf = new TreeTableFormat(null, null);
         tf.setVerticalLinePosition(2);
@@ -73,6 +67,7 @@ public final strictfp class TreeTableFor
      * @throws ParseException Should never happen.
      */
     @Test
+    @DependsOnMethod("testTreeFormat")
     public void testTreeParse() throws ParseException {
         final TreeTableFormat tf = new TreeTableFormat(null, null);
         tf.setVerticalLinePosition(0);
@@ -89,36 +84,116 @@ public final strictfp class TreeTableFor
      * Tests the formatting of a tree table.
      */
     @Test
+    @DependsOnMethod("testTreeFormat")
     public void testTreeTableFormat() {
-        final StringColumn     valueA = new StringColumn("value #1");
-        final StringColumn     valueB = new StringColumn("value #2");
+        final TableColumn<Integer> valueA = new TableColumn<Integer>(Integer.class, "value #1");
+        final TableColumn<String>  valueB = new TableColumn<String> (String .class, "value #2");
         final DefaultTreeTable table  = new DefaultTreeTable(NAME, valueA, valueB);
         final TreeTable.Node   root   = new DefaultTreeTable.Node(table);
-        root.setValue(NAME,   "Node #1");
-        root.setValue(valueA, "Value #1A");
+        root.setValue(NAME, "Node #1");
+        root.setValue(valueA, 10);
         root.setValue(valueB, "Value #1B");
         final TreeTable.Node branch1 = new DefaultTreeTable.Node(table);
-        branch1.setValue(NAME,   "Node #2");
-        branch1.setValue(valueA, "Value #2A");
+        branch1.setValue(NAME, "Node #2");
+        branch1.setValue(valueA, 20);
         root.getChildren().add(branch1);
         final TreeTable.Node branch2 = new DefaultTreeTable.Node(table);
-        branch2.setValue(NAME,   "Node #3");
+        branch2.setValue(NAME, "Node #3");
         branch2.setValue(valueB, "Value #3B");
         root.getChildren().add(branch2);
         final TreeTable.Node leaf = new DefaultTreeTable.Node(table);
-        leaf.setValue(NAME,   "Node #4");
-        leaf.setValue(valueA, "Value #4A");
-        leaf.setValue(valueB, "ext #4\tafter tab\nand a new line");
+        leaf.setValue(NAME, "Node #4");
+        leaf.setValue(valueA, 40);
+        leaf.setValue(valueB, "val #4\twith tab\nand a new line");
         branch1.getChildren().add(leaf);
         table.setRoot(root);
 
         final TreeTableFormat tf = new TreeTableFormat(null, null);
         tf.setVerticalLinePosition(1);
-        final String text = tf.format(table);
         assertMultilinesEquals(
-                "Node #1…………………………Value #1A……Value #1B\n" +
-                " ├──Node #2………………Value #2A……\n" +
-                " │   └──Node #4……Value #4A……ext #4  after tab ¶ and a new line\n" +
-                " └──Node #3……………………………………………Value #3B\n", text);
+                "Node #1………………………… 10…… Value #1B\n" +
+                " ├──Node #2……………… 20\n" +
+                " │   └──Node #4…… 40…… val #4  with tab ¶ and a new line\n" +
+                " └──Node #3……………… ………… Value #3B\n", tf.format(table));
+
+        tf.setColumns(NAME, valueA);
+        assertMultilinesEquals(
+                "Node #1………………………… 10\n" +
+                " ├──Node #2……………… 20\n" +
+                " │   └──Node #4…… 40\n" +
+                " └──Node #3\n", tf.format(table));
+
+        tf.setColumns(NAME, valueB);
+        assertMultilinesEquals(
+                "Node #1………………………… Value #1B\n" +
+                " ├──Node #2\n" +
+                " │   └──Node #4…… val #4  with tab ¶ and a new line\n" +
+                " └──Node #3……………… Value #3B\n", tf.format(table));
+    }
+
+    /**
+     * Tests the parsing of a tree table. This method parses and reformats a tree table,
+     * and performs its check on the assumption that the tree table formatting is accurate.
+     *
+     * @throws ParseException Should never happen.
+     */
+    @Test
+    @DependsOnMethod("testTreeTableFormat")
+    public void testTreeTableParse() throws ParseException {
+        final TableColumn<Integer> valueA = new TableColumn<Integer>(Integer.class, "value #1");
+        final TableColumn<String>  valueB = new TableColumn<String> (String .class, "value #2");
+        final TreeTableFormat tf = new TreeTableFormat(null, null);
+        tf.setColumns(NAME, valueA, valueB);
+        tf.setVerticalLinePosition(1);
+        final String text =
+                "Node #1………………………… 10…… Value #1B\n" +
+                " ├──Node #2……………… 20\n" +
+                " │   └──Node #4…… 40…… Value #4B\n" +
+                " └──Node #3……………… ………… Value #3B\n";
+        final TreeTable table = tf.parseObject(text);
+        assertMultilinesEquals(text, tf.format(table));
+    }
+
+    /**
+     * Tests parsing and formatting using a different column separator.
+     *
+     * @throws ParseException Should never happen.
+     */
+    @Test
+    @DependsOnMethod("testTreeTableParse")
+    public void testAlternativeColumnSeparatorPattern() throws ParseException {
+        final TableColumn<Integer> valueA = new TableColumn<Integer>(Integer.class, "value #1");
+        final TableColumn<String>  valueB = new TableColumn<String> (String .class, "value #2");
+        final TreeTableFormat tf = new TreeTableFormat(null, null);
+        assertEquals("?……[…] ", tf.getColumnSeparatorPattern());
+        tf.setColumns(NAME, valueA, valueB);
+        tf.setVerticalLinePosition(1);
+        /*
+         * Test with all column separators.
+         */
+        tf.setColumnSeparatorPattern(" [ ]│ ");
+        assertEquals(" [ ]│ ", tf.getColumnSeparatorPattern());
+        final String text =
+                "Node #1         │ 10 │ Value #1B\n" +
+                " ├──Node #2     │ 20 │ \n" +
+                " │   └──Node #4 │ 40 │ Value #4B\n" +
+                " └──Node #3     │    │ Value #3B\n";
+        final TreeTable table = tf.parseObject(text);
+        assertMultilinesEquals(text, tf.format(table));
+        /*
+         * Test with omission of column separator for trailing null values.
+         */
+        tf.setColumnSeparatorPattern("? [ ]; ");
+        assertMultilinesEquals(
+                "Node #1         ; 10 ; Value #1B\n" +
+                " ├──Node #2     ; 20\n" + // Column separator omitted here.
+                " │   └──Node #4 ; 40 ; Value #4B\n" +
+                " └──Node #3     ;    ; Value #3B\n", tf.format(table));
+        /*
+         * Test with regular expression at parsing time.
+         */
+        tf.setColumnSeparatorPattern("?……[…] /\\w*│+\\w*");
+        assertEquals("?……[…] /\\w*│+\\w*", tf.getColumnSeparatorPattern());
+        assertEquals(table, tf.parseObject(text));
     }
 }

Modified: sis/trunk/sis-utility/src/test/java/org/apache/sis/util/resources/IndexedResourceBundleTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/util/resources/IndexedResourceBundleTest.java?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/util/resources/IndexedResourceBundleTest.java (original)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/util/resources/IndexedResourceBundleTest.java Fri Nov 30 17:38:17 2012
@@ -30,7 +30,7 @@ import org.apache.sis.test.TestCase;
 import org.junit.Test;
 import org.junit.After;
 
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -155,10 +155,12 @@ public final strictfp class IndexedResou
         InternationalString i18n = Errors.formatInternational(Errors.Keys.NullArgument_1);
         assertEquals("Argument ‘{0}’ shall not be null.",      i18n.toString(Locale.ENGLISH));
         assertEquals("L’argument ‘{0}’ ne doit pas être nul.", i18n.toString(Locale.FRENCH));
+        assertNotSame(i18n, assertSerializedEquals(i18n));
 
         i18n = Errors.formatInternational(Errors.Keys.NullArgument_1, "CRS");
         assertEquals("Argument ‘CRS’ shall not be null.",      i18n.toString(Locale.ENGLISH));
         assertEquals("L’argument ‘CRS’ ne doit pas être nul.", i18n.toString(Locale.FRENCH));
+        assertNotSame(i18n, assertSerializedEquals(i18n));
     }
 
     /**

Modified: sis/trunk/src/main/docbook/book.xsl
URL: http://svn.apache.org/viewvc/sis/trunk/src/main/docbook/book.xsl?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/src/main/docbook/book.xsl (original)
+++ sis/trunk/src/main/docbook/book.xsl Fri Nov 30 17:38:17 2012
@@ -95,6 +95,9 @@
   <xsl:template match= "d:function[@role = 'OGC']"    mode="class.value"> <xsl:value-of select="'OGC'"   /> </xsl:template>
   <xsl:template match= "d:function[@role = 'GeoAPI']" mode="class.value"> <xsl:value-of select="'GeoAPI'"/> </xsl:template>
   <xsl:template match= "d:function[@role = 'SIS']"    mode="class.value"> <xsl:value-of select="'SIS'"   /> </xsl:template>
+  <xsl:template match= "d:constant[@role = 'OGC']"    mode="class.value"> <xsl:value-of select="'OGC'"   /> </xsl:template>
+  <xsl:template match= "d:constant[@role = 'GeoAPI']" mode="class.value"> <xsl:value-of select="'GeoAPI'"/> </xsl:template>
+  <xsl:template match= "d:constant[@role = 'SIS']"    mode="class.value"> <xsl:value-of select="'SIS'"   /> </xsl:template>
   <xsl:template match=  "d:literal[@role = 'OGC']"    mode="class.value"> <xsl:value-of select="'OGC'"   /> </xsl:template>
   <xsl:template match=  "d:literal[@role = 'GeoAPI']" mode="class.value"> <xsl:value-of select="'GeoAPI'"/> </xsl:template>
   <xsl:template match=  "d:literal[@role = 'SIS']"    mode="class.value"> <xsl:value-of select="'SIS'"   /> </xsl:template>

Modified: sis/trunk/src/main/docbook/fr/XML.xml
URL: http://svn.apache.org/viewvc/sis/trunk/src/main/docbook/fr/XML.xml?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/src/main/docbook/fr/XML.xml (original)
+++ sis/trunk/src/main/docbook/fr/XML.xml Fri Nov 30 17:38:17 2012
@@ -226,5 +226,63 @@ public class MyClass {
         où peuvent être manipulés tous types d’identifiants (pas seulement <acronym>XML</acronym>) associés à un objet.
       </para>
     </section>
+
+    <section>
+      <title>Représentation de valeurs manquantes</title>
+      <para>
+        Lorsqu’un attribut n’est pas défini, la méthode correspondante de GeoAPI retourne généralement <literal>null</literal>.
+        Toutefois les choses se compliquent lorsque l’attribut manquant est une valeur considérée comme obligatoire par le standard <acronym>ISO</acronym> 19115.
+        Le standard <acronym>ISO</acronym> 19139 autorise l’omission d’attributs obligatoires à la condition d’indiquer pourquoi la valeur est manquante.
+        Les raisons peuvent être que l’attribut ne s’applique pas (<constant role="OGC">inapplicable</constant>),
+        que la valeur existe probablement mais n’est pas connue (<constant role="OGC">unknown</constant>),
+        que la valeur pourrait ne pas exister (<constant role="OGC">missing</constant>),
+        qu’elle ne peut pas être divulguée (<constant role="OGC">withheld</constant>), <foreignphrase>etc.</foreignphrase>
+        La transmission de cette information nécessite l’utilisation d’un objet non-nul même lorsque la valeur est manquante.
+        <acronym>SIS</acronym> procède en retournant un objet qui, en plus d’implémenter l’interface GeoAPI attendue,
+        implémente aussi l’interface <classname role="SIS">org.apache.xml.NilObject</classname>.
+        Cette interface marque les instances dont toutes les méthodes retournent une collection vide,
+        un tableau vide, <literal>null</literal>, <literal>NaN</literal>, <literal>0</literal> ou <literal>false</literal>,
+        dans cet ordre de préférence selon ce que les types de retours des méthodes permettent.
+        Chaque instance implémentant <classname role="SIS">NilObject</classname> fournit une méthode
+        <classname role="SIS">getNilReason()</classname> indiquant pourquoi l’objet est nul.
+      </para>
+      <para>
+        Dans l’exemple suivant, la partie gauche montre un élément <classname role="OGC">CI_Citation</classname>
+        contenant un élément <classname role="OGC">CI_Series</classname>, alors que dans la partie droite la série est inconnue.
+        Si l’élément <classname role="OGC">CI_Series</classname> avait été complètement omis,
+        alors la méthode <function role="GeoAPI">Citation.getSeries()</function> retournerait <literal>null</literal> en Java.
+        Mais en présence d’un attribut <literal role="OGC">nilReason</literal>, l’implémentation <acronym>SIS</acronym>
+        de <function role="SIS">getSeries()</function> retournera plutôt un objet implémentant à la fois les interfaces
+        <classname role="GeoAPI">Series</classname> et <classname role="SIS">NilReason</classname>,
+        et dont la méthode <function role="SIS">getNilReason()</function> retournera la constante <constant role="SIS">UNKNOWN</constant>.
+      </para>
+      <example>
+        <title>Élément obligatoire marqué comme inconnu dans une méta-donnée</title>
+        <informaltable frame="none">
+          <tgroup cols="2">
+            <colspec colwidth="50%"/>
+            <colspec colwidth="50%"/>
+            <tbody>
+              <row>
+                <entry>
+                  <programlisting language="xml">&lt;gmd:CI_Citation&gt;
+  &lt;gmd:series&gt;
+    &lt;gmd:CI_Series&gt;
+      &lt;!-- Some content here --&gt;
+    &lt;/gmd:CI_Series&gt;
+  &lt;/gmd:series&gt;
+&lt;/gmd:CI_Citation&gt;</programlisting>
+                </entry>
+                <entry>
+                  <programlisting language="xml">&lt;gmd:CI_Citation&gt;
+  &lt;gmd:series nilReason="unknown"/&gt;
+&lt;/gmd:CI_Citation&gt;</programlisting>
+                </entry>
+              </row>
+            </tbody>
+          </tgroup>
+        </informaltable>
+      </example>
+    </section>
   </section>
 </chapter>

Modified: sis/trunk/src/main/docbook/fr/utility.xml
URL: http://svn.apache.org/viewvc/sis/trunk/src/main/docbook/fr/utility.xml?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/src/main/docbook/fr/utility.xml (original)
+++ sis/trunk/src/main/docbook/fr/utility.xml Fri Nov 30 17:38:17 2012
@@ -13,6 +13,72 @@
   </para>
 
   <section>
+    <title>Modes de comparaisons des objets</title>
+    <para>
+      Il existe différentes opinions sur la façon d’implémenter la méthode <function>Object.equals(Object)</function> du Java standard.
+      Selon certains, il doit être possible de comparer différentes implémentations d’une même interface ou classe de base.
+      Mais cette politique nécessite que chaque interface ou classe de base définisse entièrement dans sa Javadoc les critères ou calculs
+      que doivent employer les méthodes <function>equals(Object)</function> et <function>hashCode()</function> dans toutes les implémentations.
+      Cette approche est choisie notamment par <classname>java.util.Collection</classname> et ses interfaces filles.
+      Elle se fait toutefois au détriment de la possibilité de prendre en compte des attributs supplémentaires dans les interfaces filles,
+      si cette possibilité n’a pas été spécifiée dans l’interface parente.
+      Cette contrainte découle des points suivants du contrat des méthodes <function>equals(Object)</function> et <function>hashCode()</function>:
+    </para>
+    <itemizedlist>
+      <listitem><literal>A.equals(B)</literal> implique <literal>B.equals(A)</literal> (symétrie);</listitem>
+      <listitem><literal>A.equals(B)</literal> et <literal>B.equals(C)</literal> implique <literal>A.equals(C)</literal> (transitivité);</listitem>
+      <listitem><literal>A.equals(B)</literal> implique <literal>A.hashCode() == B.hashCode()</literal>.</listitem>
+    </itemizedlist>
+    <para>
+      Par exemple ces trois contraintes sont violées si <varname>A</varname> (et éventuellement <varname>C</varname>)
+      peuvent contenir des attributs que <varname>B</varname> ignore.
+      Pour contourner cette difficulté, une approche alternative consiste à exiger que les objets comparés par la méthode
+      <function>Object.equals(Object)</function> soient exactement de la même classe, c’est-à-dire que <literal>A.getClass() == B.getClass()</literal>.
+      Cette approche est parfois considérée contraire aux principes de la programmation orientée objets.
+      Dans la pratique, pour des applications relativement complexes, ça dépend du contexte dans lequel les objets sont comparés:
+      si les objets sont ajoutés à un <classname>HashSet</classname> ou utilisés comme clés dans un <classname>HashMap</classname>,
+      alors nous avons besoin d’un strict respect du contrat de <function>equals(Object)</function> et <function>hashCode()</function>.
+      Mais si le développeur compare les objets lui-même, par exemple pour vérifier si des informations qui l’intéresse ont changées,
+      alors les contraintes de symétrie, transitivité ou de cohérence avec les valeurs de hachages peuvent ne pas être pertinentes pour lui.
+      Des comparaisons plus permissives peuvent être souhaitables, allant parfois jusqu’à tolérer de légers écarts dans les valeurs numériques.
+    </para>
+    <para>
+      Afin de donner une certaine flexibilité aux développeurs, un grand nombre de classes de la bibliothèque <acronym>SIS</acronym>
+      implémentent l’interface <classname role="SIS">org.apache.sis.util.LenientComparable</classname>, qui défini une méthode <function role="SIS">equals(Object, ComparisonMode)</function>.
+      Les principaux modes de comparaisons sont:
+    </para>
+    <itemizedlist>
+      <listitem><para>
+        <emphasis role="bold"><constant role="SIS">STRICT</constant></emphasis> — Les objets comparés doivent être de la même classe
+        et tous leurs attributs strictement égaux, y compris d’éventuels attributs publics propres à l’implémentation.
+      </para></listitem>
+      <listitem><para>
+        <emphasis role="bold"><constant role="SIS">BY_CONTRACT</constant></emphasis> — Les objets comparés doivent implémenter la même interface de GeoAPI (ou tout autre standard),
+        mais n’ont pas besoin d’être de la même classe d’implémentation. Seuls les attributs définis dans l’interface sont comparés;
+        tout autres attributs propres à l’implémentation — même s’ils sont publics — sont ignorés.
+      </para></listitem>
+      <listitem><para>
+        <emphasis role="bold"><constant role="SIS">IGNORE_METADATA</constant></emphasis> — Comme <constant role="SIS">BY_CONTRACT</constant>,
+        mais ne compare que les attributs qui influencent les opérations (calculs numériques ou autre) effectuées par l’objet.
+        Par exemple dans un référentiel géodésique, la longitude (par rapport à Greenwich) du méridien d’origine sera pris en compte
+        alors que le nom de ce méridien sera ignoré.
+      </para></listitem>
+      <listitem><para>
+        <emphasis role="bold"><constant role="SIS">APPROXIMATIVE</constant></emphasis> — Comme <constant role="SIS">IGNORE_METADATA</constant>,
+        mais tolère de légères différences dans les valeurs numériques.
+      </para></listitem>
+    </itemizedlist>
+    <para>
+      Le mode par défaut, utilisé par les toutes les méthodes <function>equals(Object)</function> de <acronym>SIS</acronym>,
+      est <constant role="SIS">STRICT</constant>. Ce mode est choisi à la fois pour une utilisation plus sécuritaire avec <classname>HashMap</classname>,
+      et aussi parce que définir rigoureusement le contrat des méthodes <function>equals(Object)</function> et <function>hashCode()</function>
+      dans les centaines d’interfaces de GeoAPI semble une entreprise peu réaliste, qui risque d’être assez peu suivit par les diverses implémentations.
+    </para>
+  </section>
+
+
+
+  <section>
     <title>Internationalisation</title>
     <para>
       Dans une architecture où un programme exécuté sur un serveur fournit ses données à plusieurs clients,
@@ -26,7 +92,7 @@
     </para>
 
     <section>
-      <title>Instances distinctes pour chaque conventions locales</title>
+      <title>Chaînes de caractères distinctes pour chaque conventions locales</title>
       <para>
         Certaines classes ne sont conçues que pour fonctionner selon une convention locale à la fois.
         C’est évidemment le cas des implémentations standards de <classname>java.text.Format</classname>,
@@ -34,7 +100,7 @@
         Mais c’est aussi le cas de d’autres classes moins évidentes comme
         <classname>javax.imageio.ImageReader</classname>/<classname>ImageWriter</classname> ainsi que les exceptions.
         Lorsque une de ces classes est implémentée par <acronym>SIS</acronym>,
-        nous l’identifions en implémentant l’interface <classname role="SIS">Localized</classname>.
+        nous l’identifions en implémentant l’interface <classname role="SIS">org.apache.sis.util.Localized</classname>.
         La méthode <function role="SIS">getLocale()</function> de cette interface permet alors de déterminer
         selon quelles conventions locales l’instance produira ses messages.
       </para>
@@ -99,5 +165,95 @@
         pour des raisons d’économie de mémoire.
       </para>
     </section>
+
+    <section>
+      <title>Traitement des caractères</title>
+      <para>
+        Les chaînes de caractères en Java utilisent l’encodage UTF-16. Il existe une correspondance directe
+        entre les valeurs de type <classname>char</classname> et la très grande majorité des caractères, ce
+        qui facilite l’utilisation des chaînes lorsque ces caractères suffisent.
+        Mais certains caractères Unicode ne sont pas représentables par un seul <classname>char</classname>.
+        Ces <firstterm>caractères supplémentaires</firstterm> comprennent certains idéogrammes,
+        mais aussi des symboles routiers et géographiques dans la plage 1F680 à 1F700.
+        Le support de ces caractères supplémentaires nécessite des itérations un peu plus complexes
+        que le cas classique où l’on supposait une correspondance directe.
+        Ainsi, au lieu de la boucle de gauche ci-dessous, les applications internationales devraient
+        généralement utiliser la boucle de droite:
+      </para>
+      <example>
+        <title>Boucle sur une chaîne pouvant contenir des caractères supplémentaires</title>
+        <informaltable frame="none">
+          <tgroup cols="2">
+            <colspec colwidth="50%"/>
+            <colspec colwidth="50%"/>
+            <tbody>
+              <row>
+                <entry>
+                  <programlisting language="java">for (int i=0; i&lt;string.length(); i++) {
+    char c = string.charAt(i);
+    if (Character.isWhitespace(c)) {
+        // Un espace blanc a été trouvé.
+    }
+}</programlisting>
+                </entry>
+                <entry>
+                  <programlisting language="java">for (int i=0; i&lt;string.length();) {
+    int c = string.codePointAt(i);
+    if (Character.isWhitespace(c)) {
+        // Un espace blanc a été trouvé.
+    }
+    i += Character.charCount(c);
+}</programlisting>
+                </entry>
+              </row>
+            </tbody>
+          </tgroup>
+        </informaltable>
+      </example>
+      <para>
+        <acronym>SIS</acronym> supporte les caractères supplémentaires en utilisant la boucle de droite lorsque nécessaire.
+        Mais la boucle de gauche reste occasionnellement utilisée lorsqu’il est connu que les caractères recherchés ne sont
+        pas des caractères supplémentaires, même si la chaîne dans laquelle on fait la recherche peut en contenir.
+      </para>
+
+      <section>
+      <title>Interprétation des espaces blancs</title>
+        <para>
+          Le Java standard fournit deux méthodes pour déterminer si un caractères est un espace blanc:
+          <function>Character.isWhitespace(…)</function> et <function>Character.isSpaceChar(…)</function>.
+          Ces deux méthodes diffèrent dans leurs interprétations des espaces insécables, des tabulations et des retours à la ligne.
+          La première méthode est conforme à l’interprétation couramment utilisée dans des langages telles que le Java, C/C++ et XML,
+          qui considère les tabulations et retours à la ligne comme des espaces blancs,
+          alors que les espaces insécables sont interprétés comme des caractères non-blanc.
+          La seconde méthode — strictement conforme à la définition Unicode — fait l’interprétation inverse.
+        </para>
+        <para>
+          <acronym>SIS</acronym> emploie ces deux méthodes dans des contextes différents.
+          <function>isWhitespace(…)</function> est utilisée pour <emphasis>séparer</emphasis>
+          les éléments d’une liste (nombres, dates, mots, <foreignphrase>etc.</foreignphrase>),
+          tandis que <function>isSpaceChar(…)</function> est utilisée pour ignorer les espaces
+          blancs <emphasis>à l’intérieur</emphasis> d’un seul élément.
+        </para>
+        <informalexample><para>
+          <emphasis role="bold">Exemple:</emphasis>
+          Supposons une liste de nombres représentés selon les conventions françaises.
+          Chaque nombre peut contenir des <emphasis>espace insécables</emphasis> comme séparateurs des milliers,
+          tandis que les différents nombres de la liste peuvent être séparés par des espaces ordinaires, des tabulations ou des retours à la ligne.
+          Pendant l’analyse d’un nombre, on veut considérer les espaces insécables comme faisant partie du nombre,
+          alors qu’une tabulation ou un retour à la ligne indique très probablement une séparation entre ce nombre et le nombre suivant.
+          On utilisera donc <function>isSpaceChar(…)</function>.
+          Inversement, lors de la séparation des nombres de la liste, on veut considérer les tabulations et
+          les retours à la ligne comme des séparateurs mais pas les espaces insécables.
+          On utilisera donc <function>isWhitespace(…)</function>.
+          Le rôle des espaces ordinaires, qui pourraient s’appliquer aux deux cas, doit être décidé en amont.
+        </para></informalexample>
+        <para>
+          Dans la pratique, cette distinction se traduit pas une utilisation de <function>isSpaceChar(…)</function>
+          dans les implémentations de <classname>java.text.Format</classname>,
+          et une utilisation de <function>isWhitespace(…)</function> dans pratiquement tout le reste
+          de la bibliothèque <acronym>SIS</acronym>.
+        </para>
+      </section>
+    </section>
   </section>
 </chapter>

Modified: sis/trunk/src/site/resources/book/book.css
URL: http://svn.apache.org/viewvc/sis/trunk/src/site/resources/book/book.css?rev=1415758&r1=1415757&r2=1415758&view=diff
==============================================================================
--- sis/trunk/src/site/resources/book/book.css (original)
+++ sis/trunk/src/site/resources/book/book.css Fri Nov 30 17:38:17 2012
@@ -54,10 +54,18 @@ p {
  */
 pre.programlisting {
   border-style: solid;
+  border-color: gray;
   border-width: 1pt;
   padding:      9pt;
 }
 
+/* If inside a table, rely on the table border instead. */
+table pre.programlisting {
+  border-style: none;
+  margin:       0pt;
+  padding:      0pt;
+}
+
 div.sidebar {
   margin-left:      1.25cm;
   margin-right:     1.5cm;
@@ -79,11 +87,22 @@ div.informalexample {
 /*
  * Tables.
  */
+div.informaltable table tr {
+  vertical-align: top;
+}
+
+div.informaltable table tr td {
+  border-style: solid;
+  border-color: gray;
+  border-width: 1pt;
+  padding:      9pt;
+}
+
 div.table p.title {
   text-align: center;
 }
 
-table {
+div.table-contents table {
   margin-left:    auto;
   margin-right:   auto;
   border-style:   solid;
@@ -91,25 +110,25 @@ table {
   border-spacing: 0pt;
 }
 
-table tr th {
+div.table-contents table tr th {
   background-color:    #B9DCFF;
   border-bottom-style: solid;
   border-bottom-width: 1pt;
   padding:             3pt;
 }
 
-table tr td {
+div.table-contents table tr td {
   padding-left:  9pt;
   padding-right: 9pt;
 }
 
-table tr td.leftBorder {
+div.table-contents table tr td.leftBorder {
   border-left-style: solid;
   border-left-width: 1pt;
   border-left-color: lightgray;
 }
 
-table tr td.separator {
+div.table-contents table tr td.separator {
   text-align:          center;
   font-weight:         bold;
   background-color:    #EEEEFF;



Mime
View raw message