sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1518719 - in /sis/branches/JDK7/core: sis-metadata/src/main/java/org/apache/sis/metadata/iso/ sis-metadata/src/test/java/org/apache/sis/metadata/iso/ sis-referencing/src/main/java/org/apache/sis/geometry/ sis-referencing/src/main/java/org/...
Date Thu, 29 Aug 2013 17:40:24 GMT
Author: desruisseaux
Date: Thu Aug 29 17:40:23 2013
New Revision: 1518719

URL: http://svn.apache.org/r1518719
Log:
Factor out the localization part of ImmutableIdentifier in a separated method - Types.toInternationalString(Map,
String) - so we can share it in AbstractIdentifiedObject.

Modified:
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/ImmutableIdentifierTest.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Locales.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultInternationalString.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Types.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/util/iso/TypesTest.java

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -18,7 +18,6 @@ package org.apache.sis.metadata.iso;
 
 import java.util.Map;
 import java.util.Locale;
-import java.util.logging.Level;
 import java.io.Serializable;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -28,20 +27,18 @@ import org.opengis.metadata.citation.Cit
 import org.opengis.parameter.InvalidParameterValueException;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
-import org.apache.sis.util.Locales;
 import org.apache.sis.util.Immutable;
 import org.apache.sis.util.Deprecable;
-import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.resources.Messages;
-import org.apache.sis.util.iso.SimpleInternationalString;
-import org.apache.sis.util.iso.DefaultInternationalString;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.jaxb.metadata.CI_Citation;
 import org.apache.sis.internal.jaxb.gco.StringAdapter;
 import org.apache.sis.internal.simple.SimpleIdentifiedObject;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+import static org.apache.sis.util.collection.Containers.property;
 import static org.opengis.referencing.IdentifiedObject.REMARKS_KEY;
 
 // Related to JDK7
@@ -60,7 +57,7 @@ import java.util.Objects;
  *
  * @author Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.03)
- * @version 0.3
+ * @version 0.4
  * @module
  *
  * @see DefaultIdentifier
@@ -209,7 +206,7 @@ public class ImmutableIdentifier impleme
      *   <tr>
      *     <th>Property name</th>
      *     <th>Value type</th>
-     *     <th>Value given to</th>
+     *     <th>Returned by</th>
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.metadata.Identifier#CODE_KEY}</td>
@@ -249,102 +246,32 @@ public class ImmutableIdentifier impleme
      */
     public ImmutableIdentifier(final Map<String,?> properties) throws IllegalArgumentException
{
         ensureNonNull("properties", properties);
-        Object code      = null;
-        Object codeSpace = null;
-        Object version   = null;
-        Object authority = null;
-        Object remarks   = null;
-        DefaultInternationalString localized = null;
+        code      = property(properties, CODE_KEY,      String.class);
+        version   = property(properties, VERSION_KEY,   String.class);
+        remarks   = Types.toInternationalString(properties, REMARKS_KEY);
         /*
-         * Iterate through each map entry. This have two purposes:
-         *
-         *   1) Ignore case (a call to properties.get("foo") can't do that)
-         *   2) Find localized remarks.
-         *
-         * This algorithm is sub-optimal if the map contains a lot of entries of no interest
to
-         * this identifier. Hopefully, most users will fill a map with only useful entries.
+         * Map String authority to one of the pre-defined constants (typically EPSG or OGC).
          */
-        for (final Map.Entry<String,?> entry : properties.entrySet()) {
-            String key   = entry.getKey().trim().toLowerCase();
-            Object value = entry.getValue();
-            switch (key) {
-                case CODE_KEY: {
-                    code = value;
-                    continue;
-                }
-                case CODESPACE_KEY: {
-                    codeSpace = value;
-                    continue;
-                }
-                case VERSION_KEY: {
-                    version = value;
-                    continue;
-                }
-                case AUTHORITY_KEY: {
-                    if (value instanceof String) {
-                        value = Citations.fromName((String) value);
-                    }
-                    authority = value;
-                    continue;
-                }
-                case REMARKS_KEY: {
-                    if (value instanceof String) {
-                        value = new SimpleInternationalString((String) value);
-                    }
-                    remarks = value;
-                    continue;
-                }
-            }
-            /*
-             * Search for additional locales (e.g. "remarks_fr").
-             */
-            final Locale locale = Locales.parseSuffix(REMARKS_KEY, key);
-            if (locale != null) {
-                if (localized == null) {
-                    localized = new DefaultInternationalString();
-                }
-                localized.add(locale, (String) value);
-            }
-        }
-        /*
-         * Get the localized remarks, if it was not yet set. If a user specified remarks
-         * both as InternationalString and as String for some locales (which is a weird
-         * usage...), then current implementation discards the later with a warning.
-         */
-        if (localized != null) {
-            if (remarks == null) {
-                remarks = localized;
-            } else if (remarks instanceof SimpleInternationalString) {
-                localized.add(Locale.ROOT, remarks.toString());
-                remarks = localized;
-            } else {
-                Logging.log(ImmutableIdentifier.class, "<init>",
-                    Messages.getResources(null).getLogRecord(Level.WARNING, Messages.Keys.LocalesDiscarded));
-            }
+        Object value = properties.get(AUTHORITY_KEY);
+        if (value instanceof String) {
+            authority = Citations.fromName((String) value);
+        } else if (value == null || value instanceof Citation) {
+            authority = (Citation) value;
+        } else {
+            throw illegalPropertyType(AUTHORITY_KEY, value);
         }
         /*
          * Complete the code space if it was not explicitly set. We take the first
          * identifier if there is any, otherwise we take the shortest title.
          */
-        if (codeSpace == null && authority instanceof Citation) {
-            codeSpace = Citations.getIdentifier((Citation) authority);
-        }
-        /*
-         * Store the definitive reference to the attributes. Note that casts are performed
only
-         * there (not before). This is a wanted feature, since we want to catch ClassCastExceptions
-         * and rethrown them as more informative exceptions.
-         */
-        String key   = null;
-        Object value = null;
-        try {
-            key=      CODE_KEY; this.code      = (String)              (value = code);
-            key=   VERSION_KEY; this.version   = (String)              (value = version);
-            key= CODESPACE_KEY; this.codeSpace = (String)              (value = codeSpace);
-            key= AUTHORITY_KEY; this.authority = (Citation)            (value = authority);
-            key=   REMARKS_KEY; this.remarks   = (InternationalString) (value = remarks);
-        } catch (ClassCastException exception) {
-            throw new InvalidParameterValueException(
-                    Errors.format(Errors.Keys.IllegalArgumentValue_2, key, value), exception,
key, value);
+        value = properties.get(CODESPACE_KEY);
+        if (value == null) {
+            final String id = Citations.getIdentifier(authority);
+            codeSpace = (id != null && CharSequences.isUnicodeIdentifier(id)) ? id
: null;
+        } else if (value instanceof String) {
+            codeSpace = (String) value;
+        } else {
+            throw illegalPropertyType(CODESPACE_KEY, value);
         }
         if (code == null) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.MissingValueForProperty_1,
CODE_KEY));
@@ -352,6 +279,13 @@ public class ImmutableIdentifier impleme
     }
 
     /**
+     * Returns the exception to be thrown when a property if of illegal type.
+     */
+    private static IllegalArgumentException illegalPropertyType(final String key, final Object
value) {
+        return new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
key, value.getClass()));
+    }
+
+    /**
      * Returns a SIS identifier implementation with the values of the given arbitrary implementation.
      * This method performs the first applicable actions in the following choices:
      *

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/ImmutableIdentifierTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/ImmutableIdentifierTest.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/ImmutableIdentifierTest.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/ImmutableIdentifierTest.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -19,7 +19,7 @@ package org.apache.sis.metadata.iso;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Locale;
-import org.opengis.parameter.InvalidParameterValueException;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.test.DependsOnMethod;
@@ -65,6 +65,7 @@ public final strictfp class ImmutableIde
         Validators.validate(identifier);
 
         assertEquals(CODE_KEY,        "This is a code",       identifier.getCode());
+        assertNull  (CODESPACE_KEY,                           identifier.getCodeSpace());
         assertEquals(AUTHORITY_KEY,   "This is an authority", identifier.getAuthority().getTitle().toString());
         assertEquals(VERSION_KEY,     "This is a version",    identifier.getVersion());
         assertEquals("remarks",       "There is remarks",     identifier.getRemarks().toString(Locale.ENGLISH));
@@ -85,6 +86,7 @@ public final strictfp class ImmutableIde
         Validators.validate(identifier);
 
         assertEquals(CODE_KEY,        "This is a code",       identifier.getCode());
+        assertNull  (CODESPACE_KEY,                           identifier.getCodeSpace());
         assertEquals(AUTHORITY_KEY,   "This is an authority", identifier.getAuthority().getTitle().toString());
         assertEquals(VERSION_KEY,     "This is a version",    identifier.getVersion());
         assertEquals("remarks",       "Overwritten remarks",  identifier.getRemarks().toString(Locale.ENGLISH));
@@ -94,18 +96,17 @@ public final strictfp class ImmutableIde
 
     /**
      * Tests the constructor with the {@code "authority"} attribute as a {@link DefaultCitation}.
-     * This test also opportunistically test mixed-case key.
      */
     @Test
     @DependsOnMethod("testConstructorWithStringValues")
     public void testConstructorWithCitation() {
         final Map<String,Object> properties = properties();
-        assertNotNull(properties.remove(AUTHORITY_KEY));
-        assertNull(properties.put("AutHOrITY", new DefaultCitation("An other authority")));
+        assertNotNull(properties.put(AUTHORITY_KEY, new DefaultCitation("An other authority")));
         final ImmutableIdentifier identifier = new ImmutableIdentifier(properties);
         Validators.validate(identifier);
 
         assertEquals(CODE_KEY,        "This is a code",       identifier.getCode());
+        assertNull  (CODESPACE_KEY,                           identifier.getCodeSpace());
         assertEquals(AUTHORITY_KEY,   "An other authority",   identifier.getAuthority().getTitle().toString());
         assertEquals(VERSION_KEY,     "This is a version",    identifier.getVersion());
         assertEquals("remarks",       "There is remarks",     identifier.getRemarks().toString(Locale.ENGLISH));
@@ -114,6 +115,28 @@ public final strictfp class ImmutableIde
     }
 
     /**
+     * Tests the constructor with the {@code "authority"} attribute as one of the pre-defined
constants.
+     *
+     * @see Citations#fromName(String)
+     */
+    @Test
+    @DependsOnMethod("testConstructorWithStringValues")
+    public void testPredefinedCitation() {
+        final Map<String,Object> properties = properties();
+        assertNotNull(properties.put(AUTHORITY_KEY, "EPSG"));
+        final ImmutableIdentifier identifier = new ImmutableIdentifier(properties);
+        Validators.validate(identifier);
+
+        assertEquals(CODE_KEY,        "This is a code",       identifier.getCode());
+        assertSame  (AUTHORITY_KEY,   Citations.EPSG,         identifier.getAuthority());
+        assertEquals(CODESPACE_KEY,   "EPSG",                 identifier.getCodeSpace());
// Inferred from authority.
+        assertEquals(VERSION_KEY,     "This is a version",    identifier.getVersion());
+        assertEquals("remarks",       "There is remarks",     identifier.getRemarks().toString(Locale.ENGLISH));
+        assertEquals("remarks_fr",    "Voici des remarques",  identifier.getRemarks().toString(Locale.FRENCH));
+        assertEquals("remarks_fr_CA", "Pareil",               identifier.getRemarks().toString(Locale.CANADA_FRENCH));
+    }
+
+    /**
      * Tests the constructor with an argument of the wrong type.
      */
     @Test
@@ -124,7 +147,7 @@ public final strictfp class ImmutableIde
         try {
             final ImmutableIdentifier identifier = new ImmutableIdentifier(properties);
             fail(identifier.toString());
-        } catch (InvalidParameterValueException e) {
+        } catch (IllegalArgumentException e) {
             // This is the expected exception
             final String message = e.getMessage();
             assertTrue(message, message.contains(AUTHORITY_KEY));

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -608,10 +608,16 @@ public abstract class AbstractEnvelope i
     }
 
     /**
-     * Determines whether or not this envelope is empty. An envelope is non-empty only if
it has
-     * at least one {@linkplain #getDimension() dimension}, and the {@linkplain #getSpan(int)
span}
-     * is greater than 0 along all dimensions. Note that {@link #isAllNaN()} always returns
-     * {@code false} for a non-empty envelope, but the converse is not always true.
+     * Determines whether or not this envelope is empty. An envelope is empty if it has zero
+     * {@linkplain #getDimension() dimension}, or if the {@linkplain #getSpan(int) span}
of
+     * at least one axis is negative, 0 or {@link Double#NaN NaN}.
+     *
+     * {@note Strictly speaking, there is an ambiguity if the span of at least one axis is
infinite
+     *        while all other axes meet the criterion for an empty envelope. In such cases,
this method
+     *        arbitrarily ignores the infinite values and returns <code>true</code>.}
+     *
+     * If {@code isEmpty()} returns {@code false}, then {@link #isAllNaN()} is guaranteed
to
+     * also return {@code false}. However the converse is not always true.
      *
      * @return {@code true} if this envelope is empty.
      *
@@ -634,10 +640,10 @@ public abstract class AbstractEnvelope i
 
     /**
      * Returns {@code false} if at least one ordinate value is not {@linkplain Double#NaN
NaN}.
-     * This {@code isAllNaN()} check is a little bit different than the {@link #isEmpty()}
check
-     * since it returns {@code false} for a partially initialized envelope, while {@code
isEmpty()}
-     * returns {@code false} only after all dimensions have been initialized. More specifically,
-     * the following rules apply:
+     * This {@code isAllNaN()} check is different than the {@link #isEmpty()} check since
it
+     * returns {@code false} for a partially initialized envelope, while {@code isEmpty()}
+     * returns {@code false} only after all dimensions have been initialized.
+     * More specifically, the following rules apply:
      *
      * <ul>
      *   <li>If {@code isAllNaN() == true}, then {@code isEmpty() == true}</li>
@@ -645,7 +651,7 @@ public abstract class AbstractEnvelope i
      *   <li>The converse of the above-cited rules are not always true.</li>
      * </ul>
      *
-     * Note that a all-NaN envelope can still have a non-null
+     * Note that an all-NaN envelope can still have a non-null
      * {@linkplain #getCoordinateReferenceSystem() coordinate reference system}.
      *
      * @return {@code true} if this envelope has NaN values.

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -145,7 +145,7 @@ public class AbstractIdentifiedObject ex
      *   <tr>
      *     <th>Property name</th>
      *     <th>Value type</th>
-     *     <th>Value given to</th>
+     *     <th>Returned by</th>
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.util;
 
+import java.util.Map; // For javadoc
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.geometry.Envelope;
@@ -155,8 +156,9 @@ public final class ArgumentChecks extend
      *         Can be {@code null} if the name is unknown.
      * @param  expectedType the expected type (class or interface).
      * @param  value The value to check, or {@code null}.
-     * @throws IllegalArgumentException if {@code value} is non-null and is not assignable
-     *         to the given type.
+     * @throws IllegalArgumentException if {@code value} is non-null and is not assignable
to the given type.
+     *
+     * @see org.apache.sis.util.collection.Containers#property(Map, Object, Class)
      */
     public static void ensureCanCast(final String name, final Class<?> expectedType,
final Object value)
             throws IllegalArgumentException

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Locales.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Locales.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Locales.java [UTF-8]
(original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/Locales.java [UTF-8]
Thu Aug 29 17:40:23 2013
@@ -208,20 +208,33 @@ public final class Locales extends Stati
      * anyway.</p>
      *
      * @param  code The language code, which may be followed by country code.
-     * @return The language for the given code.
+     * @return The language for the given code (never {@code null}).
      * @throws IllegalArgumentException If the given code doesn't seem to be a valid locale.
      *
      * @see Locale#forLanguageTag(String)
      */
     public static Locale parse(final String code) throws IllegalArgumentException {
+        ArgumentChecks.ensureNonNull("code", code);
+        return parse(code, 0);
+    }
+
+    /**
+     * Implementation of {@link #parse(String)} which start the parsing process from the
given {@code fromIndex}.
+     *
+     * @param  code The language code, which may be followed by country code.
+     * @param  fromIndex Index of the first character to parse.
+     * @return The language for the given code (never {@code null}).
+     * @throws IllegalArgumentException If the given code doesn't seem to be a valid locale.
+     */
+    private static Locale parse(final String code, final int fromIndex) throws IllegalArgumentException
{
         final String language, country, variant;
-        int ci = code.indexOf('_');
+        int ci = code.indexOf('_', fromIndex);
         if (ci < 0) {
-            language = trimWhitespaces(code);
+            language = (String) trimWhitespaces(code, fromIndex, code.length());
             country  = "";
             variant  = "";
         } else {
-            language = (String) trimWhitespaces(code, 0, ci);
+            language = (String) trimWhitespaces(code, fromIndex, ci);
             int vi = code.indexOf('_', ++ci);
             if (vi < 0) {
                 country = (String) trimWhitespaces(code, ci, code.length());
@@ -230,7 +243,8 @@ public final class Locales extends Stati
                 country = (String) trimWhitespaces(code, ci, vi);
                 variant = (String) trimWhitespaces(code, ++vi, code.length());
                 if (code.indexOf('_', vi) >= 0) {
-                    throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalLanguageCode_1,
code));
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalLanguageCode_1,
+                            code.substring(fromIndex)));
                 }
             }
         }
@@ -289,18 +303,23 @@ public final class Locales extends Stati
      * </ul>
      *
      * @param  prefix The prefix to skip at the beginning of the {@code key}.
-     * @param  key    The property key from which to extract the locale.
-     * @return {@code true} if the key has been recognized, or {@code false} otherwise.
+     * @param  key    The property key from which to extract the locale, or {@code null}.
+     * @return The locale encoded in the given key name, or {@code null} if the key has not
been recognized.
      * @throws IllegalArgumentException if the locale after the prefix is an illegal code.
+     *
+     * @see org.apache.sis.util.iso.Types#toInternationalString(Map, String)
      */
     public static Locale parseSuffix(final String prefix, final String key) throws IllegalArgumentException
{
-        if (key.startsWith(prefix)) {
-            final int offset = prefix.length();
-            if (key.length() == offset) {
-                return Locale.ROOT;
-            }
-            if (key.charAt(offset) == '_') {
-                return parse(key.substring(offset + 1));
+        ArgumentChecks.ensureNonNull("prefix", prefix);
+        if (key != null) { // Tolerance for Map that accept null keys.
+            if (key.startsWith(prefix)) {
+                final int offset = prefix.length();
+                if (key.length() == offset) {
+                    return Locale.ROOT;
+                }
+                if (key.charAt(offset) == '_') {
+                    return parse(key, offset + 1);
+                }
             }
         }
         return null;

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -23,17 +23,18 @@ import java.util.Collection;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
 
 /**
  * Static methods working on {@link Collection} or {@link CheckedContainer} objects.
- * Unless otherwise noted in the javadoc, every collections except {@link Map} returned
- * by the methods in this class implement the {@code CheckedContainer} interface.
+ * Unless otherwise noted in the javadoc, every collections returned by the methods
+ * in this class implement the {@code CheckedContainer} interface.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.3 (derived from geotk-3.00)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final class Containers extends Static {
@@ -185,6 +186,10 @@ public final class Containers extends St
      * <p>The returned map can be serialized if the given map and converters are serializable.
      * The returned map is <strong>not</strong> thread-safe.</p>
      *
+     * <p>The returned map does not implement the {@link CheckedContainer} interface
since {@code Map}
+     * is not {@code Collection} sub-type, but the derived map {@linkplain Map#keySet() key
set} and
+     * {@linkplain Map#entrySet() entry set} do.</p>
+     *
      * @param <SK>         The type of keys   in the storage map.
      * @param <SV>         The type of values in the storage map.
      * @param <K>          The type of keys   in the derived map.
@@ -212,6 +217,41 @@ public final class Containers extends St
     }
 
     /**
+     * Returns the value mapped to the given key casted to the given type,
+     * or {@code null} if the map is null or does not contain a value for the key.
+     * If the mapped value is non-null but can not be casted to the given type, then this
+     * method throws an {@link IllegalArgumentException} with a message of the form
+     * "<cite>Property ‘{@code key}’ does not accept instances of ‘{@code value.class}’.</cite>".
+     *
+     * <p>This is a helper method for processing a {@code Map} argument containing
property values of various
+     * kinds, as in the {@link org.apache.sis.referencing.AbstractIdentifiedObject#AbstractIdentifiedObject(Map)
+     * AbstractIdentifiedObject} constructor.</p>
+     *
+     * @param  <T>        The compile-time value of the {@code type} argument.
+     * @param  properties The map of properties from which to get a value, or {@code null}
if none.
+     * @param  key        The key of the property value to return. Can be {@code null} if
the map supports null key.
+     * @param  type       The expected type of the property value. Can not be null.
+     * @return The property value for the given key casted to the given type, or {@code null}
if none.
+     * @throws IllegalArgumentException If a non-null property value exists for the given
key but can
+     *         not be casted to the given type.
+     *
+     * @see ArgumentChecks#ensureCanCast(String, Class, Object)
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T property(final Map<?,?> properties, final Object key,
final Class<T> type)
+            throws IllegalArgumentException
+    {
+        if (properties == null) {
+            return null;
+        }
+        final Object value = properties.get(key);
+        if (value != null && !type.isInstance(value)) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
key, value.getClass()));
+        }
+        return (T) value;
+    }
+
+    /**
      * Returns the capacity to be given to the {@link java.util.HashMap#HashMap(int) HashMap}
      * constructor for holding the given number of elements. This method computes the capacity
      * for the default <cite>load factor</cite>, which is 0.75.

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultInternationalString.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultInternationalString.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultInternationalString.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/DefaultInternationalString.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -26,11 +26,14 @@ import java.util.LinkedHashMap;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Locale;
+import java.util.logging.Level;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.Locales;
 import org.apache.sis.util.ThreadSafe;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Messages;
 
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 
@@ -49,6 +52,8 @@ import java.util.Objects;
  * @since   0.3 (derived from geotk-2.1)
  * @version 0.3
  * @module
+ *
+ * @see Types#toInternationalString(Map, String)
  */
 @ThreadSafe
 public class DefaultInternationalString extends AbstractInternationalString implements Serializable
{
@@ -98,6 +103,8 @@ public class DefaultInternationalString 
      * will not be reflected into this international string.
      *
      * @param strings The strings in various locales, or {@code null} if none.
+     *
+     * @see Types#toInternationalString(Map, String)
      */
     public DefaultInternationalString(final Map<Locale,String> strings) {
         if (isNullOrEmpty(strings)) {
@@ -124,8 +131,7 @@ public class DefaultInternationalString 
      *
      * @param  locale The locale for the {@code string} value.
      * @param  string The localized string.
-     * @throws IllegalArgumentException if a different string value was already set for
-     *         the given locale.
+     * @throws IllegalArgumentException if a different string value was already set for the
given locale.
      */
     public synchronized void add(final Locale locale, final String string) throws IllegalArgumentException
{
         ArgumentChecks.ensureNonNull("locale", locale);
@@ -157,6 +163,23 @@ public class DefaultInternationalString 
     }
 
     /**
+     * Adds the given character sequence. If the given sequence is an other {@link InternationalString}
instance,
+     * then only the string for the given locale is added. This method is for {@link Types}
internal usage only.
+     *
+     * @param  locale The locale for the {@code string} value.
+     * @param  string The character sequence to add.
+     * @throws IllegalArgumentException if a different string value was already set for the
given locale.
+     */
+    final void add(final Locale locale, final CharSequence string) throws IllegalArgumentException
{
+        final boolean i18n = (string instanceof InternationalString);
+        add(locale, i18n ? ((InternationalString) string).toString(locale) : string.toString());
+        if (i18n && !(string instanceof SimpleInternationalString)) {
+            Logging.log(Types.class, "toInternationalString", // This is the public facade
invoking this method.
+                    Messages.getResources(null).getLogRecord(Level.WARNING, Messages.Keys.LocalesDiscarded));
+        }
+    }
+
+    /**
      * Returns the set of locales defined in this international string.
      *
      * @return The set of locales.

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Types.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Types.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Types.java [UTF-8]
(original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/iso/Types.java [UTF-8]
Thu Aug 29 17:40:23 2013
@@ -18,6 +18,7 @@ package org.apache.sis.util.iso;
 
 import java.util.Map;
 import java.util.HashMap;
+import java.util.SortedMap;
 import java.util.Collection;
 import java.util.Locale;
 import java.util.Properties;
@@ -35,7 +36,9 @@ import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.apache.sis.util.Static;
+import org.apache.sis.util.Locales;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.BackingStoreException;
@@ -59,7 +62,7 @@ import org.apache.sis.internal.system.De
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.3 (derived from geotk-3.19)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final class Types extends Static {
@@ -548,6 +551,102 @@ public final class Types extends Static 
     }
 
     /**
+     * Returns an international strings for the values in the given properties map, or {@code
null} if none.
+     * If the given map is {@code null}, then this method returns {@code null}.
+     * Otherwise this method iterates over the entries having a key that starts with the
specified prefix.
+     * For each such key:
+     *
+     * <ul>
+     *   <li>The part after the prefix is parsed as specified by the {@link Locales#parseSuffix(String,
String)}
+     *       method.</li>
+     *   <li>If {@code parseSuffix(…)} returned a non-null locale, then the value
for that locale is added in the
+     *       international string to be returned.</li>
+     * </ul>
+     *
+     * For example the given map may contains a {@code "remarks"} property defined by values
associated to the
+     * {@code "remarks_en"} and {@code "remarks_fr"} keys, for English and French locales
respectively.
+     *
+     * @param  properties The map from which to get the string values for an international
string, or {@code null}.
+     * @param  prefix     The prefix of keys to use for creating the international string.
+     * @return The international string, or {@code null} if the given map is null or does
not contain values
+     *         associated to keys starting with the given prefix.
+     * @throws IllegalArgumentException If a key starts by the given prefix and:
+     *         <ul>
+     *           <li>The key suffix is an illegal {@link Locale} code,</li>
+     *           <li>or the value associated to that key is a not a {@link CharSequence}.</li>
+     *         </ul>
+     *
+     * @see Locales#parseSuffix(String, String)
+     * @see DefaultInternationalString#DefaultInternationalString(Map)
+     *
+     * @since 0.4
+     */
+    public static InternationalString toInternationalString(Map<String,?> properties,
final String prefix)
+            throws IllegalArgumentException
+    {
+        ArgumentChecks.ensureNonEmpty("prefix", prefix);
+        if (properties == null) {
+            return null;
+        }
+        /*
+         * If the given map is an instance of SortedMap using the natural ordering of keys,
+         * we can skip all keys that lexicographically precedes the given prefix.
+         */
+        boolean isSorted = false;
+        if (properties instanceof SortedMap<?,?>) {
+            final SortedMap<String,?> sorted = (SortedMap<String,?>) properties;
+            if (sorted.comparator() == null) { // We want natural ordering.
+                properties = sorted.tailMap(prefix);
+                isSorted = true;
+            }
+        }
+        /*
+         * Now iterates over the map entry and lazily create the InternationalString
+         * only when first needed. In most cases, we have 0 or 1 matching entry.
+         */
+        CharSequence i18n = null;
+        Locale firstLocale = null;
+        DefaultInternationalString dis = null;
+        final int length = prefix.length();
+        for (final Map.Entry<String,?> entry : properties.entrySet()) {
+            final String key = entry.getKey();
+            final Locale locale = Locales.parseSuffix(prefix, key);
+            if (locale == null) {
+                if (isSorted) {
+                    /*
+                     * If the map is sorted using natural ordering, we can stop as soon as
we find a key which
+                     * is lexicographically greater than prefix + '_'. We check 'startsWith'
last since the other
+                     * tests are cheaper and usually sufficient.
+                     */
+                    if (key.length() <= length || key.charAt(length) > '_' || !key.startsWith(prefix))
{
+                        break;
+                    }
+                }
+            } else {
+                final Object value = entry.getValue();
+                if (value != null) {
+                    if (!(value instanceof CharSequence)) {
+                        throw new IllegalArgumentException(Errors.format(
+                                Errors.Keys.IllegalPropertyClass_2, key, value.getClass()));
+                    }
+                    if (i18n == null) {
+                        i18n = (CharSequence) value;
+                        firstLocale = locale;
+                    } else {
+                        if (dis == null) {
+                            dis = new DefaultInternationalString();
+                            dis.add(firstLocale, i18n);
+                            i18n = dis;
+                        }
+                        dis.add(locale, (CharSequence) value);
+                    }
+                }
+            }
+        }
+        return toInternationalString(i18n);
+    }
+
+    /**
      * Returns the given array of {@code CharSequence}s as an array of {@code InternationalString}s.
      * If the given array is null or an instance of {@code InternationalString[]}, then this
method
      * returns it unchanged. Otherwise a new array of type {@code InternationalString[]}
is created

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -246,7 +246,7 @@ public final class Errors extends Indexe
         public static final int IllegalOrdinateRange_3 = 5;
 
         /**
-         * Property ‘{0}’ can be associated to an instance of ‘{1}’.
+         * Property ‘{0}’ does not accept instances of ‘{1}’.
          */
         public static final int IllegalPropertyClass_2 = 62;
 

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
[ISO-8859-1] Thu Aug 29 17:40:23 2013
@@ -59,7 +59,7 @@ IllegalFormatPatternForClass_2  = The \u
 IllegalLanguageCode_1           = The \u201c{0}\u201d language is not recognized.
 IllegalMemberType_2             = Member \u201c{0}\u201d can not be associated to type \u201c{1}\u201d.
 IllegalOrdinateRange_3          = The [{0} \u2026 {1}] range of ordinate values is not valid
for the \u201c{2}\u201d axis.
-IllegalPropertyClass_2          = Property \u2018{0}\u2019 can be associated to an instance
of \u2018{1}\u2019.
+IllegalPropertyClass_2          = Property \u2018{0}\u2019 does not accept instances of \u2018{1}\u2019.
 IllegalRange_2                  = Range [{0} \u2026 {1}] is not valid.
 IllegalUnicodeCodePoint_2       = Value {1} for \u201c{0}\u201d is not a valid Unicode code
point.
 IncompatiblePropertyValue_1     = Property \u201c{0}\u201d has an incompatible value.

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
[ISO-8859-1] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
[ISO-8859-1] Thu Aug 29 17:40:23 2013
@@ -49,7 +49,7 @@ IllegalFormatPatternForClass_2  = Le mod
 IllegalLanguageCode_1           = Le code de langue \u201c{0}\u201d n\u2019est pas reconnu.
 IllegalMemberType_2             = Le membre \u201c{0}\u201d ne peut pas \u00eatre associ\u00e9
au type \u201c{1}\u201d.
 IllegalOrdinateRange_3          = La plage de valeurs de coordonn\u00e9es [{1} \u2026 {2}]
n\u2019est pas valide pour l\u2019axe \u201c{0}\u201d.
-IllegalPropertyClass_2          = La propri\u00e9t\u00e9 \u2018{0}\u2019 ne peut pas \u00eatre
associ\u00e9e \u00e0 une valeur de type \u2018{1}\u2019.
+IllegalPropertyClass_2          = La propri\u00e9t\u00e9 \u2018{0}\u2019 n\u2019accepte pas
les valeurs de type \u2018{1}\u2019.
 IllegalRange_2                  = La plage [{0} \u2026 {1}] n\u2019est pas valide.
 IllegalUnicodeCodePoint_2       = La valeur {1} de \u201c{0}\u201d n\u2019est pas un code
Unicode valide.
 IncompatiblePropertyValue_1     = La valeur de la propri\u00e9t\u00e9 \u201c{0}\u201d n\u2019est
pas compatible.

Modified: sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/util/iso/TypesTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/util/iso/TypesTest.java?rev=1518719&r1=1518718&r2=1518719&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/util/iso/TypesTest.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/util/iso/TypesTest.java
[UTF-8] Thu Aug 29 17:40:23 2013
@@ -17,8 +17,11 @@
 package org.apache.sis.util.iso;
 
 import java.util.Set;
+import java.util.Map;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.HashMap;
+import java.util.TreeMap;
 import java.util.Locale;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Address;
@@ -31,7 +34,7 @@ import org.opengis.referencing.cs.AxisDi
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.opengis.test.Assert.*;
 
 
 /**
@@ -39,11 +42,49 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.3 (derived from geotk-2.1)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final strictfp class TypesTest extends TestCase {
     /**
+     * Tests the {@link Types#toInternationalString(Map, String)} method.
+     */
+    @Test
+    public void testToInternationalString() {
+        testToInternationalString(new HashMap<String,Object>());
+        testToInternationalString(new TreeMap<String,Object>());
+    }
+
+    /**
+     * Implementation of {@link #testToInternationalString()} using the given map implementation.
+     */
+    private static void testToInternationalString(final Map<String,Object> properties)
{
+        assertNull(properties.put("name",       "Some name"));
+        assertNull(properties.put("identifier", "Some identifier"));
+        assertNull(properties.put("code",       "Some code"));
+        assertNull(properties.put("codeSpace",  "Some code space"));
+        assertNull(properties.put("authority",  "Some authority"));
+        assertNull(properties.put("version",    "Some version"));
+        assertNull(properties.put("remarks",    "Some remarks"));
+        assertNull(Types.toInternationalString(properties, "dummy"));
+
+        InternationalString i18n = Types.toInternationalString(properties, "remarks");
+        assertInstanceOf("Single locale", SimpleInternationalString.class, i18n);
+        assertEquals("Some remarks", i18n.toString());
+
+        assertNull(properties.put("remarks_fr", "Une remarque"));
+        i18n = Types.toInternationalString(properties, "remarks");
+        assertInstanceOf("Two locales", DefaultInternationalString.class, i18n);
+        assertEquals("Some remarks", i18n.toString(Locale.ROOT));
+        assertEquals("Une remarque", i18n.toString(Locale.FRENCH));
+
+        assertNotNull(properties.remove("remarks"));
+        i18n = Types.toInternationalString(properties, "remarks");
+        assertInstanceOf("Single locale", SimpleInternationalString.class, i18n);
+        assertEquals("Une remarque", i18n.toString());
+    }
+
+    /**
      * Tests the {@link Types#getStandardName(Class)} method.
      */
     @Test



Mime
View raw message