sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1781503 [2/3] - in /sis/trunk: ./ application/sis-console/ application/sis-console/src/main/java/org/apache/sis/console/ application/sis-console/src/test/java/org/apache/sis/console/ core/sis-metadata/src/main/java/org/apache/sis/internal/...
Date Fri, 03 Feb 2017 08:01:03 GMT
Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/TypeRegistration.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.internal.jaxb;
 
+import java.util.Map;
+import java.util.HashMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import javax.xml.bind.JAXBContext;
@@ -47,14 +50,37 @@ import org.apache.sis.internal.system.De
  */
 public abstract class TypeRegistration {
     /**
+     * Undocumented (for now) marshaller property for specifying conversions to apply on root objects
+     * before marshalling. Conversions are applied by the {@link #toImplementation(Object)} method.
+     *
+     * @see #addDefaultRootAdapters(Map)
+     */
+    public static final String ROOT_ADAPTERS = "org.apache.sis.xml.rootAdapters";
+
+    /**
      * The JAXB context, or {@code null} if not yet created or if the classpath changed.
+     *
+     * @see #getSharedContext()
      */
     private static Reference<JAXBContext> context;
+
+    /**
+     * The {@link TypeRegistration} instances found on the classpath for which the
+     * {@link #toImplementation(Object)} method has been overridden.
+     *
+     * @see #addDefaultRootAdapters(Map)
+     */
+    private static TypeRegistration[] converters;
+
+    /**
+     * Forces reloading of JAXB context and converters if the classpath changes.
+     */
     static {
         SystemListener.add(new SystemListener(Modules.UTILITIES) {
             @Override protected void classpathChanged() {
                 synchronized (TypeRegistration.class) {
-                    context = null;
+                    context    = null;
+                    converters = null;
                 }
             }
         });
@@ -67,39 +93,83 @@ public abstract class TypeRegistration {
     }
 
     /**
-     * Adds to the given collection every types that should be given to
-     * the initial JAXB context.
+     * Adds to the given collection every types that should be given to the initial JAXB context.
+     * The types added by this method include only implementation classes having JAXB annotations.
+     * If the module can also marshal arbitrary implementations of some interfaces (e.g. GeoAPI),
+     * then the {@link #canMarshalInterfaces()} method should be overridden.
      *
      * @param  addTo  the collection in which to add new types.
      */
-    public abstract void getTypes(final Collection<Class<?>> addTo);
+    protected abstract void getTypes(final Collection<Class<?>> addTo);
+
+    /**
+     * Returns {@code true} if the module can also marshal arbitrary implementation of some interfaces.
+     * If this method returns {@code true}, then the {@link #toImplementation(Object)} method shall be
+     * overridden.
+     *
+     * @return whether the module can also marshal arbitrary implementation of some interfaces.
+     *
+     * @since 0.8
+     */
+    protected boolean canMarshalInterfaces() {
+        return false;
+    }
 
     /**
-     * Returns the root classes of SIS objects to be marshalled by default.
-     * Those classes can be given as the last argument to the {@code MarshallerPool}
-     * constructors, in order to bound a default set of classes with {@code JAXBContext}.
+     * If the given value needs to be converted before marshalling, apply the conversion now.
+     * Otherwise returns {@code null} if the value class is not recognized, or {@code value}
+     * if the class is recognized but the value does not need to be changed.
+     *
+     * <p>Subclasses that override this method will typically perform an {@code instanceof} check, then
+     * invoke one of the {@code castOrCopy(…)} static methods defined in various Apache SIS classes.</p>
+     *
+     * <p>This method is invoked only if {@link #canMarshalInterfaces()} returns {@code true}.</p>
      *
-     * <p>The list of classes is determined dynamically from the SIS modules found on
-     * the classpath.</p>
+     * @param  value  the value to convert before marshalling.
+     * @return the value to marshall; or {@code null} if this method does not recognize the value class.
+     * @throws JAXBException if an error occurred while converting the given object.
      *
-     * @return the default set of classes to be bound to the {@code JAXBContext}.
+     * @since 0.8
      */
-    private static Class<?>[] defaultClassesToBeBound() {
+    public Object toImplementation(final Object value) throws JAXBException {
+        return null;
+    }
+
+    /**
+     * Scans the classpath for root classes to put in JAXB context and for converters to those classes.
+     * Those lists are determined dynamically from the SIS modules found on the classpath.
+     * The list of root classes is created only if the {@code getTypes} argument is {@code true}.
+     *
+     * @param  getTypes  whether to get the root classes to put in JAXB context (may cause class loading).
+     * @return if {@code getTypes} was {@code true}, the root classes to be bound in {@code JAXBContext}.
+     */
+    private static Class<?>[] load(final boolean getTypes) {
         /*
          * Implementation note: do not keep the ServiceLoader in static field because:
          *
-         * 1) It would cache the TypeRegistration instances, which are not needed after this method call.
+         * 1) It would cache more TypeRegistration instances than needed for this method call.
          * 2) The ClassLoader between different invocations may be different in an OSGi context.
          */
         final ArrayList<Class<?>> types = new ArrayList<>();
-        for (final TypeRegistration t : DefaultFactories.createServiceLoader(TypeRegistration.class)) {
-            t.getTypes(types);
+        final ArrayList<TypeRegistration> toImpl = (converters == null) ? new ArrayList<TypeRegistration>() : null;
+        if (toImpl != null || getTypes) {
+            for (final TypeRegistration t : DefaultFactories.createServiceLoader(TypeRegistration.class)) {
+                if (getTypes) {
+                    t.getTypes(types);
+                }
+                if (toImpl != null && t.canMarshalInterfaces()) {
+                    toImpl.add(t);
+                }
+            }
+            if (toImpl != null) {
+                converters = toImpl.toArray(new TypeRegistration[toImpl.size()]);
+            }
         }
         return types.toArray(new Class<?>[types.size()]);
     }
 
     /**
-     * Returns the shared {@code JAXBContext} for the set of {@link #defaultClassesToBeBound()}.
+     * Returns the shared {@code JAXBContext} for the set of {@link #load()}.
      * Note that the {@code JAXBContext} class is thread safe, but the {@code Marshaller},
      * {@code Unmarshaller}, and {@code Validator} classes are not thread safe.
      *
@@ -114,8 +184,43 @@ public abstract class TypeRegistration {
                 return instance;
             }
         }
-        final JAXBContext instance = JAXBContext.newInstance(defaultClassesToBeBound());
+        final JAXBContext instance = JAXBContext.newInstance(load(true));
         context = new WeakReference<>(instance);
         return instance;
     }
+
+    /**
+     * Completes the given properties with an entry for {@link #ROOT_ADAPTERS} if not already present.
+     * If a {@code ROOT_ADAPTERS} entry is already present, then the map is returned unchanged.
+     *
+     * <p>This method store a direct reference to the internal {@code TypeRegistration[]} array in the given map.
+     * <strong>That array shall not be modified.</strong> This method is currently for Apache SIS internal usage only,
+     * because the {@code TypeRegistration} class is not part of public API. However if we add this functionality in a
+     * future SIS release (probably as an interface rather than exposing {@code TypeRegistration} itself), then we may
+     * consider removing this method.</p>
+     *
+     * @param  properties  the properties to complete.
+     * @return the given properties with the {@link #ROOT_ADAPTERS} entry added.
+     *
+     * @since 0.8
+     */
+    public static Map<String,?> addDefaultRootAdapters(final Map<String,?> properties) {
+        if (properties != null && properties.containsKey(ROOT_ADAPTERS)) {
+            return properties;
+        }
+        TypeRegistration[] c;
+        synchronized (TypeRegistration.class) {
+            c = converters;
+            if (c == null) {
+                load(false);
+                c = converters;
+            }
+        }
+        if (properties == null) {
+            return Collections.singletonMap(ROOT_ADAPTERS, c);
+        }
+        final Map<String,Object> copy = new HashMap<String,Object>(properties);
+        copy.put(ROOT_ADAPTERS, c);
+        return copy;
+    }
 }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Constants.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -37,6 +37,14 @@ import org.apache.sis.util.Static;
  */
 public final class Constants extends Static {
     /**
+     * The default indentation value to use in various text formats (both WKT and XML).
+     * We use a small value (2 instead of 4) because OGC's XML are very verbose.
+     *
+     * @see org.apache.sis.setup.OptionKey#INDENTATION
+     */
+    public static final byte DEFAULT_INDENTATION = 2;
+
+    /**
      * The {@value} code space.
      */
     public static final String EPSG = "EPSG";

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/StreamWriterDelegate.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/StreamWriterDelegate.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/StreamWriterDelegate.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/StreamWriterDelegate.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -72,20 +72,20 @@ public class StreamWriterDelegate implem
 
     /** Forwards the call verbatim. */
     @Override
-    public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
-        out.writeEmptyElement(namespaceURI, localName);
+    public void writeEmptyElement(String localName) throws XMLStreamException {
+        out.writeEmptyElement(localName);
     }
 
     /** Forwards the call verbatim. */
     @Override
-    public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
-        out.writeEmptyElement(prefix, localName, namespaceURI);
+    public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
+        out.writeEmptyElement(namespaceURI, localName);
     }
 
     /** Forwards the call verbatim. */
     @Override
-    public void writeEmptyElement(String localName) throws XMLStreamException {
-        out.writeEmptyElement(localName);
+    public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
+        out.writeEmptyElement(prefix, localName, namespaceURI);
     }
 
     /** Forwards the call verbatim. */

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -191,8 +191,10 @@ public final class Utilities extends Sta
         if (precision >= 0) {
             for (int i=0,n=0; i<length; i += n) {
                 if (--precision < 0) {
-                    // Found the amount of characters to keep. The 'n' variable can be
-                    // zero only if precision == 0, in which case the string is empty.
+                    /*
+                     * Found the amount of characters to keep. The 'n' variable can be
+                     * zero only if precision == 0, in which case the string is empty.
+                     */
                     if (n == 0) {
                         value = "";
                     } else {

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/XPaths.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -71,7 +71,7 @@ scan:   while (offset < length) {
                     case '(': parenthesis++; break;
                     case ')': parenthesis--; break;
                     default: {
-                        if (Character.isWhitespace(c)) break;           // Not supposed to be valid, but be lenient.
+                        if (Character.isSpaceChar(c)) break;            // Not supposed to be valid, but be lenient.
                         if (parenthesis != 0) break;
                         break scan;                                     // Non-valid character outside parenthesis.
                     }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -24,9 +24,9 @@ import java.util.Date;
 import java.io.IOException;
 import java.text.Format;
 import java.text.DateFormat;
+import java.text.NumberFormat;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
-import java.text.NumberFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import javax.measure.Unit;
@@ -40,11 +40,13 @@ import org.apache.sis.measure.UnitFormat
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.internal.util.LocalizedParseException;
 
 import static org.apache.sis.internal.util.StandardDateFormat.UTC;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.UncheckedIOException;
+
 
 /**
  * Base class of {@link Format} implementations which delegate part of their work to other
@@ -73,7 +75,7 @@ import static org.apache.sis.internal.ut
  * throws a {@code ParseException} on error. This allows both substring parsing and more accurate exception message
  * in case of error.</div>
  *
- * @param <T> The base type of objects parsed and formatted by this class.
+ * @param  <T>  the base type of objects parsed and formatted by this class.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
@@ -171,7 +173,7 @@ public abstract class CompoundFormat<T>
      * @return the timezone used for this format, or UTC for unlocalized format.
      */
     public TimeZone getTimeZone() {
-        return timezone != null ? (TimeZone) timezone.clone() : TimeZone.getTimeZone(UTC);
+        return (timezone != null) ? (TimeZone) timezone.clone() : TimeZone.getTimeZone(UTC);
     }
 
     /**
@@ -214,13 +216,14 @@ public abstract class CompoundFormat<T>
      * </ul>
      *
      * <div class="note"><b>Example:</b>
-     * If parsing of the {@code "30.0 40,0"} coordinate fails on the coma in the last number, then the {@code pos}
+     * if parsing of the {@code "30.0 40,0"} coordinate fails on the coma in the last number, then the {@code pos}
      * error index will be set to 5 (the beginning of the {@code "40.0"} character sequence) while the
      * {@link ParseException} error offset will be set to 2 (the coma position relative the beginning
      * of the {@code "40.0"} character sequence).</div>
      *
      * This error offset policy is a consequence of the compound nature of {@code CompoundFormat},
-     * since the exception may have been produced by a call to {@link Format#parseObject(String)}.
+     * since the exception may have been produced by a call to {@link Format#parseObject(String)}
+     * on one of the {@linkplain #getFormat(Class) sub-formats} used by this {@code CompoundFormat}.
      *
      * @param  text  the character sequence for the object to parse.
      * @param  pos   the position where to start the parsing.
@@ -331,16 +334,16 @@ public abstract class CompoundFormat<T>
     @Override
     public StringBuffer format(final Object object, final StringBuffer toAppendTo, final FieldPosition pos) {
         final Class<? extends T> valueType = getValueType();
-        ArgumentChecks.ensureCanCast("tree", valueType, object);
+        ArgumentChecks.ensureCanCast("object", valueType, object);
         try {
             format(valueType.cast(object), toAppendTo);
         } catch (IOException e) {
             /*
              * Should never happen when writing into a StringBuffer, unless the user
-             * override the format(Object, Appendable) method. We do not rethrown an
+             * override the format(Object, Appendable) method.  We do not rethrow an
              * AssertionError because of this possibility.
              */
-            throw new BackingStoreException(e);
+            throw new UncheckedIOException(e);
         }
         return toAppendTo;
     }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/io/DefaultFormat.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -39,7 +39,7 @@ import org.apache.sis.internal.util.Loca
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 @SuppressWarnings("CloneableClassWithoutClone")   // Because this class does not contain field that need to be cloned.
@@ -125,15 +125,39 @@ final class DefaultFormat extends Format
      */
     @Override
     public Object parseObject(String source, final ParsePosition pos) {
-        final int length = source.length();
-        final int index = CharSequences.skipLeadingWhitespaces(source, pos.getIndex(), length);
-        source = source.substring(index, CharSequences.skipTrailingWhitespaces(source, index, length));
+        boolean exponent = false;
+        final int index = CharSequences.skipLeadingWhitespaces(source, pos.getIndex(), source.length());
+        int end;
+        for (end = index; end < source.length(); end++) {
+            final char c = source.charAt(end);
+            switch (c) {
+                default: {
+                    if (c >= '+' && c <= '9') continue;
+                    break;
+                    /*
+                     * ASCII characters in above range are +,-./0123456789
+                     * But the , and / characters are excluded by the case below.
+                     */
+                }
+                case ',': case '/': break;
+                case 'E': case 'e': {
+                    if (exponent) break;
+                    exponent = true;
+                    continue;
+                }
+            }
+            break;
+        }
+        source = source.substring(index, end);
+        final Object value;
         try {
-            return valueOf(source);
+            value = valueOf(source);
         } catch (NumberFormatException cause) {
             pos.setErrorIndex(index);
             return null;
         }
+        pos.setIndex(end);
+        return value;
     }
 
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -308,8 +308,11 @@ abstract class AbstractUnit<Q extends Qu
     /**
      * Returns {@code true} if the given Unicode code point is a valid character for a unit symbol.
      * Current implementation accepts letters, subscripts and the degree sign, but the set of legal
-     * characters may be expanded in any future SIS version. The most important goal is to avoid
-     * confusion with exponents and to detect where a unit symbol ends.
+     * characters may be expanded in any future SIS version (however it should never allow spaces).
+     * The goal is to avoid confusion with exponents and to detect where a unit symbol ends.
+     *
+     * <p>Space characters must be excluded from the set of legal characters because allowing them
+     * would make harder for {@link UnitFormat} to detect correctly where a unit symbol ends.</p>
      *
      * <p>Note that some units defined in the {@link Units} class break this rule. In particular,
      * some of those units contains superscripts or division sign. But the hard-coded symbols in

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -127,6 +127,7 @@ import org.apache.sis.internal.jdk8.JDK8
  * @see Angle
  * @see Latitude
  * @see Longitude
+ * @see org.apache.sis.geometry.CoordinateFormat
  */
 public class AngleFormat extends Format implements Localized {
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -52,6 +52,7 @@ package org.apache.sis.measure;
  *
  * @see Longitude
  * @see AngleFormat
+ * @see org.apache.sis.geometry.CoordinateFormat
  */
 public final class Latitude extends Angle {
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -105,6 +105,11 @@ final class LinearConverter extends Abst
     private transient volatile BigDecimal scale10, offset10;
 
     /**
+     * The inverse of this unit converter. Computed when first needed.
+     */
+    private transient volatile LinearConverter inverse;
+
+    /**
      * Creates a new linear converter for the given scale and offset.
      * The complete formula applied is {@code y = (x*scale + offset) / divisor}.
      */
@@ -253,7 +258,11 @@ final class LinearConverter extends Abst
      */
     @Override
     public synchronized UnitConverter inverse() {
-        return isIdentity() ? this : new LinearConverter(divisor, -offset, scale);
+        if (inverse == null) {
+            inverse = isIdentity() ? this : new LinearConverter(divisor, -offset, scale);
+            inverse.inverse = this;
+        }
+        return inverse;
     }
 
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -37,6 +37,7 @@ package org.apache.sis.measure;
  *
  * @see Latitude
  * @see AngleFormat
+ * @see org.apache.sis.geometry.CoordinateFormat
  */
 public final class Longitude extends Angle {
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -182,7 +182,7 @@ public class Range<E extends Comparable<
      * by the {@link #create(Comparable, boolean, Comparable, boolean)} method - otherwise we may
      * get an {@link ArrayStoreException}.
      */
-    @SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation.
+    @SuppressWarnings({"unchecked","rawtypes"})                     // Generic array creation.
     Range<E>[] newArray(final int length) {
         return new Range[length];
     }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -282,7 +282,7 @@ public class UnitFormat extends Format i
      * Mapping from long localized and unlocalized names to unit instances.
      * This map is used only for parsing and created when first needed.
      *
-     * @see #nameToUnit()
+     * @see #fromName(String)
      */
     private transient volatile Map<String,Unit<?>> nameToUnit;
 
@@ -291,7 +291,7 @@ public class UnitFormat extends Format i
      * if the user create many {@code UnitFormat} instances. Note that we do not cache {@link #symbolToName} because
      * {@link ResourceBundle} already provides its own caching mechanism.
      *
-     * @see #nameToUnit()
+     * @see #fromName(String)
      */
     private static final WeakValueHashMap<Locale, Map<String,Unit<?>>> SHARED = new WeakValueHashMap<>(Locale.class);
 
@@ -383,8 +383,9 @@ public class UnitFormat extends Format i
      *
      * <div class="section">Restriction on character set</div>
      * Current implementation accepts only {@linkplain Character#isLetter(int) letters},
-     * {@linkplain Characters#isSubScript(int) subscripts}, {@linkplain Character#isWhitespace(int) whitespaces}
-     * and the degree sign (°),
+     * {@linkplain Characters#isSubScript(int) subscripts}, {@linkplain Character#isSpaceChar(int) spaces}
+     * (including non-breaking spaces but <strong>not</strong> CR/LF characters), the degree sign (°) and
+     * a few other characters like underscore,
      * but the set of legal characters may be expanded in future Apache SIS versions.
      * However the following restrictions are likely to remain:
      *
@@ -405,7 +406,7 @@ public class UnitFormat extends Format i
         ArgumentChecks.ensureNonEmpty("label", label);
         for (int i=0; i < label.length();) {
             final int c = label.codePointAt(i);
-            if (!AbstractUnit.isSymbolChar(c) && !Character.isWhitespace(c)) {
+            if (!AbstractUnit.isSymbolChar(c) && !Character.isSpaceChar(c)) {       // NOT Character.isWhitespace(int)
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "label", label));
             }
             i += Character.charCount(c);
@@ -444,16 +445,36 @@ public class UnitFormat extends Format i
     }
 
     /**
-     * Returns the mapping from long localized and unlocalized names to unit instances.
-     * This mapping is somewhat the converse of {@link #symbolToName()}, but includes
+     * Returns the unit instance for the given long (un)localized or name.
+     * This method is somewhat the converse of {@link #symbolToName()}, but recognizes also
      * international and American spelling of unit names in addition of localized names.
      * The intend is to recognize "meter" as well as "metre".
      *
      * <p>While we said that {@code UnitFormat} is not thread safe, we make an exception for this method
      * for allowing the singleton {@link #INSTANCE} to parse symbols in a multi-threads environment.</p>
      */
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    private Map<String,Unit<?>> nameToUnit() {
+    @SuppressWarnings("fallthrough")
+    private Unit<?> fromName(String uom) {
+        /*
+         * Before to search in resource bundles, check for degrees units. The "deg" unit can be both angular
+         * and Celsius degrees. We try to resolve this ambiguity by looking for the "C" suffix. We perform a
+         * special case for the degrees units because SI symbols are case-sentive and unit names in resource
+         * bundles are case-insensitive, but the "deg" case is a mix of both.
+         */
+        if (uom.regionMatches(true, 0, "deg", 0, 3)) {
+            final int length = uom.length();
+            switch (length) {
+                case 3: return Units.DEGREE;                    // Exactly "deg"  (ignoring case)
+                case 5: final char c = uom.charAt(3);
+                        if (c != '_' && !Character.isSpaceChar(c)) break;
+                        // else fallthrough
+                case 4: switch (uom.charAt(length - 1)) {
+                            case 'K':                           // Unicode U+212A
+                            case 'K': return Units.KELVIN;      // Exactly "degK" (ignoring case except for 'K')
+                            case 'C': return Units.CELSIUS;
+                        }
+            }
+        }
         Map<String,Unit<?>> map = nameToUnit;
         if (map == null) {
             map = SHARED.get(locale);
@@ -489,7 +510,17 @@ public class UnitFormat extends Format i
             }
             nameToUnit = map;
         }
-        return map;
+        /*
+         * The 'nameToUnit' map contains plural forms (declared in UnitAliases.properties),
+         * but we make a special case for "degrees", "metres" and "meters" because they
+         * appear in numerous places.
+         */
+        uom = uom.replace('_', ' ').toLowerCase(locale);
+        uom = CharSequences.replace(CharSequences.replace(CharSequences.replace(CharSequences.toASCII(uom),
+                "meters",  "meter"),
+                "metres",  "metre"),
+                "degrees", "degree").toString();
+        return map.get(uom);
     }
 
     /**
@@ -775,18 +806,33 @@ public class UnitFormat extends Format i
      * Returns {@code true} if the given character is a digit in the sense of the {@code UnitFormat} parser.
      * Note that "digit" is taken here in a much more restrictive way than {@link Character#isDigit(int)}.
      */
-    private static boolean isDigit(final int c) {
+    private static boolean isDigit(final char c) {
         return c >= '0' && c <= '9';
     }
 
     /**
      * Returns {@code true} if the given character is the sign of a number according the {@code UnitFormat} parser.
      */
-    private static boolean isSign(final int c) {
+    private static boolean isSign(final char c) {
         return c == '+' || c == '-';
     }
 
     /**
+     * Returns {@code true} if the given character sequence contains at least one digit.
+     * This is a hack for allowing to recognize units like "100 feet" (in principle not
+     * legal, but seen in practice). This verification has some value if digits are not
+     * allowed as unit label or symbol.
+     */
+    private static boolean hasDigit(final CharSequence symbol, int lower, final int upper) {
+        while (lower < upper) {
+            if (isDigit(symbol.charAt(lower++))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Parses the given text as an instance of {@code Unit}.
      * If the parse completes without reading the entire length of the text, an exception is thrown.
      *
@@ -810,7 +856,7 @@ public class UnitFormat extends Format i
         final ParsePosition position = new ParsePosition(0);
         final Unit<?> unit = parse(symbols, position);
         final int length = symbols.length();
-        final int unrecognized = CharSequences.skipTrailingWhitespaces(symbols, position.getIndex(), length);
+        final int unrecognized = CharSequences.skipLeadingWhitespaces(symbols, position.getIndex(), length);
         if (unrecognized < length) {
             throw new ParserException(Errors.format(Errors.Keys.UnexpectedCharactersAfter_2,
                     CharSequences.trimWhitespaces(symbols, 0, unrecognized),
@@ -845,25 +891,27 @@ public class UnitFormat extends Format i
         ArgumentChecks.ensureNonNull("position", position);
         /*
          * Check for authority codes (currently only EPSG, but more could be added later).
-         * If the unit is not an authority code (which is the most common case), then we
-         * will check for hard-coded unit symbols.
-         *
-         * DefinitionURI.codeOf(…) returns 'uom' directly (provided that whitespaces were already trimmed)
-         * if no ':' character were found, in which case the string is assumed to be the code directly.
-         * This is the intended behavior for AuthorityFactory, but in the particular case of this method
-         * we want to try to parse as a xpointer before to give up.
+         * Example: "urn:ogc:def:uom:EPSG::9001". If the unit is not an authority code
+         * (which is the most common case), only then we will parse the unit symbols.
          */
-        int start = CharSequences.skipLeadingWhitespaces(symbols, position.getIndex(), symbols.length());
-        int end = XPaths.endOfURI(symbols, start);
-        if (end >= 0) {
-            final String uom = symbols.subSequence(start, end).toString();
+        int end   = symbols.length();
+        int start = CharSequences.skipLeadingWhitespaces(symbols, position.getIndex(), end);
+        int endOfURI = XPaths.endOfURI(symbols, start);
+        if (endOfURI >= 0) {
+            final String uom = symbols.subSequence(start, endOfURI).toString();
             String code = DefinitionURI.codeOf("uom", Constants.EPSG, uom);
-            if (code != null && code != uom) {                  // Really identity check, see above comment.
+            /*
+             * DefinitionURI.codeOf(…) returns 'uom' directly (provided that whitespaces were already trimmed)
+             * if no ':' character were found, in which case the string is assumed to be the code directly.
+             * This is the intended behavior for AuthorityFactory, but in the particular case of this method
+             * we want to try to parse as a xpointer before to give up.
+             */
+            if (code != null && code != uom) {
                 NumberFormatException failure = null;
                 try {
                     final Unit<?> unit = Units.valueOfEPSG(Integer.parseInt(code));
                     if (unit != null) {
-                        position.setIndex(end);
+                        position.setIndex(endOfURI);
                         return unit;
                     }
                 } catch (NumberFormatException e) {
@@ -871,12 +919,28 @@ public class UnitFormat extends Format i
                 }
                 throw (ParserException) new ParserException(Errors.format(Errors.Keys.UnknownUnit_1,
                         Constants.EPSG + DefaultNameSpace.DEFAULT_SEPARATOR + code),
-                        symbols, start + Math.max(0, uom.indexOf(code))).initCause(failure);
+                        symbols, start + Math.max(0, uom.lastIndexOf(code))).initCause(failure);
             }
+            /*
+             * Not an EPSG code. Maybe it is a URI like this example:
+             * http://schemas.opengis.net/iso/19139/20070417/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])
+             *
+             * If we find such 'uom' value, we could replace 'symbols' by that 'uom'. But it would cause a wrong
+             * error index to be reported in case of parsing failure. We will rather try to adjust the indices
+             * (and replace 'symbols' only in last resort).
+             */
             code = XPaths.xpointer("uom", uom);
             if (code != null) {
-                symbols = code;
-                start = 0;
+                final int base = start;
+                start = endOfURI - code.length();
+                do if (--start < base) {          // Should never happen (see above comment), but we are paranoiac.
+                    symbols = code;
+                    start = 0;
+                    break;
+                } while (!CharSequences.regionMatches(symbols, start, code));
+                end = start + code.length();
+            } else {
+                endOfURI = -1;
             }
         }
         /*
@@ -887,7 +951,7 @@ public class UnitFormat extends Format i
          */
         int operation = NOOP;            // Enumeration value: IMPLICIT, MULTIPLY, DIVIDE.
         Unit<?> unit = null;
-        end = symbols.length();
+        boolean hasSpaces = false;
         int i = start;
 scan:   for (int n; i < end; i += n) {
             final int c = Character.codePointAt(symbols, i);
@@ -896,7 +960,7 @@ scan:   for (int n; i < end; i += n) {
             switch (c) {
                 /*
                  * For any character that are is not an operator or parenthesis, either continue the scanning of
-                 * character or stop it, depending on whether the character is valid for a unit symbol or not.
+                 * characters or stop it, depending on whether the character is valid for a unit symbol or not.
                  * In the later case, we consider that we reached the end of a unit symbol.
                  */
                 default:  {
@@ -906,7 +970,11 @@ scan:   for (int n; i < end; i += n) {
                         }
                         continue;
                     }
-                    if (Character.isWhitespace(c) || Character.isDigit(c) || Characters.isSuperScript(c)) {
+                    if (Character.isDigit(c) || Characters.isSuperScript(c)) {
+                        continue;
+                    }
+                    if (Character.isSpaceChar(c)) {                         // NOT Character.isWhitespace(int)
+                        hasSpaces = true;
                         continue;
                     }
                     break scan;
@@ -972,15 +1040,54 @@ scan:   for (int n; i < end; i += n) {
             if (operation != IMPLICIT) {
                 unit = apply(operation, unit, parseSymbol(symbols, start, i));
             }
+            hasSpaces = false;
             operation = next;
             start = i + n;
         }
         /*
-         * At this point we either found an unrecognized character or reached the end of string. Parse the
-         * remaining characters as a unit and apply the pending unit operation (multiplication or division).
+         * At this point we either found an unrecognized character or reached the end of string. We will
+         * parse the remaining characters as a unit and apply the pending unit operation (multiplication
+         * or division). But before, we need to check if the parsing should stop at the first whitespace.
+         * This verification assumes that spaces are allowed only in labels specified by the label(…)
+         * method and in resource bundles, not in labels specified by AbstractUnit.alternate(String).
          */
-        unit = apply(operation, unit, parseSymbol(symbols, start, i));
-        position.setIndex(i);
+        Unit<?> component = null;
+        if (hasSpaces) {
+            end = i;
+            start = CharSequences.skipLeadingWhitespaces(symbols, start, i);
+search:     while ((i = CharSequences.skipTrailingWhitespaces(symbols, start, i)) > start) {
+                final String uom = symbols.subSequence(start, i).toString();
+                if ((component = labelToUnit.get(uom)) != null) break;
+                if ((component =        fromName(uom)) != null) break;
+                int j=i, c;
+                do {
+                    c = Character.codePointBefore(symbols, j);
+                    j -= Character.charCount(c);
+                    if (j <= start) break search;
+                } while (!Character.isWhitespace(c));
+                /*
+                 * Really use Character.isWhitespace(c) above, not Character.isSpaceChar(c), because we want
+                 * to exclude non-breaking spaces.   This block should be the only place in UnitFormat class
+                 * where we use isWhitespace(c) instead of isSpaceChar(c).
+                 */
+                i = j;                  // Will become the index of first space after search loop completion.
+            }
+            /*
+             * At this point we did not found any user-specified label or localized name matching the substring.
+             * Assume that the parsing should stop at the first space, on the basis that spaces are not allowed
+             * in unit symbols. We make an exception if we detect that the part before the first space contains
+             * digits (not allowed in unit symbols neither), in which case the substring may be something like
+             * "100 feet".
+             */
+            if (hasDigit(symbols, start, i)) {
+                i = end;                        // Restore the full length (until the first illegal character).
+            }
+        }
+        if (component == null) {
+            component = parseSymbol(symbols, start, i);
+        }
+        unit = apply(operation, unit, component);
+        position.setIndex(endOfURI >= 0 ? endOfURI : i);
         return unit;
     }
 
@@ -1017,7 +1124,6 @@ scan:   for (int n; i < end; i += n) {
      * @return the parsed unit symbol (never {@code null}).
      * @throws ParserException if a problem occurred while parsing the given symbols.
      */
-    @SuppressWarnings("fallthrough")
     private Unit<?> parseSymbol(final CharSequence symbols, final int lower, final int upper) throws ParserException {
         final String uom = CharSequences.trimWhitespaces(symbols, lower, upper).toString();
         /*
@@ -1109,38 +1215,10 @@ scan:   for (int n; i < end; i += n) {
                     }
                 }
                 /*
-                 * Check for degrees units. Note that "deg" could be both angular and Celsius degrees.
-                 * We try to resolve this ambiguity in the code below by looking for the "C" suffix.
-                 * We perform a special case for those checks because the above check for unit symbol
-                 * is case-sentive, the check for unit name (later) is case-insensitive, while this
-                 * check for "deg" is a mix of both.
-                 */
-                if (uom.regionMatches(true, 0, "deg", 0, 3)) {
-                    switch (length) {
-                        case 3: return Units.DEGREE;                    // Exactly "deg"  (ignoring case)
-                        case 5: final char c = uom.charAt(3);
-                                if (c != '_' && !Character.isSpaceChar(c)) break;
-                                // else fallthrough
-                        case 4: switch (uom.charAt(length - 1)) {
-                                    case 'K':                           // Unicode U+212A
-                                    case 'K': return Units.KELVIN;      // Exactly "degK" (ignoring case except for 'K')
-                                    case 'C': return Units.CELSIUS;
-                                }
-                    }
-                }
-                /*
                  * At this point, we have determined that the label is not a known unit symbol.
                  * It may be a unit name, in which case the label is not case-sensitive anymore.
-                 * The 'nameToUnit' map contains plural forms (declared in UnitAliases.properties),
-                 * but we make a special case for "degrees", "metres" and "meters" because they
-                 * appear in numerous places.
                  */
-                String lc = uom.replace('_', ' ').toLowerCase(locale);
-                lc = CharSequences.replace(CharSequences.replace(CharSequences.replace(CharSequences.toASCII(lc),
-                        "meters",  "meter"),
-                        "metres",  "metre"),
-                        "degrees", "degree").toString();
-                unit = nameToUnit().get(lc);
+                unit = fromName(uom);
                 if (unit == null) {
                     if (CharSequences.regionMatches(symbols, lower, UNITY, true)) {
                         return Units.UNITY;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/package-info.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -56,7 +56,6 @@
  *      ({@link org.apache.sis.measure.ValueRange})</li>
  *   <li>Formatters
  *      ({@link org.apache.sis.measure.AngleFormat},
- *       {@link org.apache.sis.measure.CoordinateFormat},
  *       {@link org.apache.sis.measure.RangeFormat})</li>
  * </ul>
  *

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -113,6 +113,8 @@ public class OptionKey<T> implements Ser
      * <p>If this option is not provided, then the default value is format specific.
      * That default is often, but not necessarily, the {@linkplain Charset#defaultCharset() platform default}.</p>
      *
+     * @see javax.xml.bind.Marshaller#JAXB_ENCODING
+     *
      * @since 0.4
      */
     public static final OptionKey<Charset> ENCODING = new OptionKey<>("ENCODING", Charset.class);
@@ -179,6 +181,27 @@ public class OptionKey<T> implements Ser
     public static final OptionKey<ByteBuffer> BYTE_BUFFER = new OptionKey<>("BYTE_BUFFER", ByteBuffer.class);
 
     /**
+     * The number of spaces to use for indentation when formatting text files in WKT or XML formats.
+     * A value of {@value org.apache.sis.io.wkt.WKTFormat#SINGLE_LINE} means to format the whole WKT
+     * or XML document on a single line without line feeds or indentation.
+     *
+     * <p>If this option is not provided, then the most typical default value used in Apache SIS is 2.
+     * Such small indentation value is used because XML documents defined by OGC standards tend to be
+     * verbose.</p>
+     *
+     * @see org.apache.sis.io.wkt.WKTFormat#SINGLE_LINE
+     * @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT
+     *
+     * @since 0.8
+     */
+    public static final OptionKey<Integer> INDENTATION = new OptionKey<>("INDENTATION", Integer.class);
+
+    /*
+     * Note: we do not provide a LINE_SEPARATOR option for now because we can not control the line separator
+     * in JDK's JAXB implementation, and Apache SIS provides an org.apache.sis.io.LineAppender alternative.
+     */
+
+    /**
      * The name of this key. For {@code OptionKey} instances, it shall be the name of the static constants.
      * For subclasses of {@code OptionKey}, there is no restriction.
      */

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Version.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Version.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Version.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/Version.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -70,7 +70,9 @@ public class Version implements CharSequ
      */
     private static final Version[] CONSTANTS = {
         new Version("1"),
-        new Version("2")
+        new Version("2"),
+        new Version("1.0"),
+        new Version("1.1")
     };
 
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -414,8 +414,10 @@ public class TreeTableFormat extends Tab
                         parseValue(node, columns[ci], formats[ci], text.subSequence(indexOfValue, endOfValue).toString());
                     }
                     if (!found) break;
-                    // The end of this column will be the beginning of the next column,
-                    // after skipping the last character of the column separator.
+                    /*
+                     * The end of this column will be the beginning of the next column,
+                     * after skipping the last character of the column separator.
+                     */
                     indexOfValue = matcher.end();
                 }
             } catch (ParseException e) {

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -378,7 +378,7 @@ public final class Errors extends Indexe
         public static final short IllegalUnicodeCodePoint_2 = 61;
 
         /**
-         * Can not use the “{1}” format with “{0}”.
+         * Can not use the {1} format with “{0}”.
          */
         public static final short IncompatibleFormat_2 = 62;
 

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Fri Feb  3 08:01:02 2017
@@ -86,7 +86,7 @@ IllegalPropertyValueClass_2       = Prop
 IllegalPropertyValueClass_3       = Expected an instance of \u2018{1}\u2019 for the \u201c{0}\u201d property, but got an instance of \u2018{2}\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.
-IncompatibleFormat_2              = Can not use the \u201c{1}\u201d format with \u201c{0}\u201d.
+IncompatibleFormat_2              = Can not use the {1} format with \u201c{0}\u201d.
 IncompatiblePropertyValue_1       = Property \u201c{0}\u201d has an incompatible value.
 IncompatibleUnit_1                = Unit \u201c{0}\u201d is incompatible with current value.
 IncompatibleUnits_2               = Units \u201c{0}\u201d and \u201c{1}\u201d are incompatible.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Fri Feb  3 08:01:02 2017
@@ -83,7 +83,7 @@ IllegalPropertyValueClass_2       = La p
 IllegalPropertyValueClass_3       = Une instance \u2018{1}\u2019 \u00e9tait attendue pour la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb, mais la valeur donn\u00e9e est une instance de \u2018{2}\u2019.
 IllegalRange_2                    = La plage [{0} \u2026 {1}] n\u2019est pas valide.
 IllegalUnicodeCodePoint_2         = La valeur {1} de \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un code Unicode valide.
-IncompatibleFormat_2              = Le format \u00ab\u202f{1}\u202f\u00bb ne s\u2019applique pas \u00e0 \u00ab\u202f{0}\u202f\u00bb.
+IncompatibleFormat_2              = Le format {1} ne s\u2019applique pas \u00e0 \u00ab\u202f{0}\u202f\u00bb.
 IncompatiblePropertyValue_1       = La valeur de la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas compatible.
 IncompatibleUnit_1                = L\u2019unit\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas compatible avec la valeur actuelle.
 IncompatibleUnits_2               = Les unit\u00e9s \u00ab\u202f{0}\u202f\u00bb et \u00ab\u202f{1}\u202f\u00bb ne sont pas compatibles.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/MarshallerPool.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/MarshallerPool.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/MarshallerPool.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/MarshallerPool.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -32,7 +32,9 @@ import org.apache.sis.internal.system.De
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.jaxb.AdapterReplacement;
 import org.apache.sis.internal.jaxb.TypeRegistration;
+import org.apache.sis.internal.util.Constants;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.CharSequences;
 
 
 /**
@@ -66,11 +68,6 @@ import org.apache.sis.util.ArgumentCheck
  */
 public class MarshallerPool {
     /**
-     * The indentation string, fixed to 2 spaces instead of 4 because ISO/OGC XML are very verbose.
-     */
-    private static final String INDENTATION = "  ";
-
-    /**
      * Amount of nanoseconds to wait before to remove unused (un)marshallers.
      * This is a very approximative value: actual timeout will not be shorter,
      * but may be twice longer.
@@ -78,21 +75,19 @@ public class MarshallerPool {
     private static final long TIMEOUT = 15000000000L;           // 15 seconds.
 
     /**
-     * Kind of JAXB implementations.
-     */
-    private static final byte INTERNAL = 0, ENDORSED = 1, OTHER = 2;
-
-    /**
      * The JAXB context to use for creating marshaller and unmarshaller.
+     *
+     * @see #createMarshaller()
+     * @see #createUnmarshaller()
      */
-    private final JAXBContext context;
+    protected final JAXBContext context;
 
     /**
-     * {@link #INTERNAL} if the JAXB implementation is the one bundled in the JDK,
-     * {@link #ENDORSED} if the TAXB implementation is the endorsed JAXB (Glassfish), or
-     * {@link #OTHER} if unknown.
+     * {@code INTERNAL} if the JAXB implementation is the one bundled in the JDK,
+     * {@code ENDORSED} if the TAXB implementation is the endorsed JAXB (Glassfish), or
+     * {@code null} if unknown.
      */
-    private final byte implementation;
+    private final Implementation implementation;
 
     /**
      * The mapper between namespaces and prefix.
@@ -164,7 +159,12 @@ public class MarshallerPool {
      * @throws JAXBException if the JAXB context can not be created.
      */
     public MarshallerPool(final Map<String,?> properties) throws JAXBException {
-        this(TypeRegistration.getSharedContext(), properties);
+        /*
+         * We currently add the default root adapters only when using the JAXB context provided by Apache SIS.
+         * We presume that if the user specified his own JAXBContext, then he does not expect us to change the
+         * classes that he wants to marshal.
+         */
+        this(TypeRegistration.getSharedContext(), TypeRegistration.addDefaultRootAdapters(properties));
     }
 
     /**
@@ -186,30 +186,12 @@ public class MarshallerPool {
         ArgumentChecks.ensureNonNull("context", context);
         this.context = context;
         replacements = DefaultFactories.createServiceLoader(AdapterReplacement.class);
-        /*
-         * Detects if we are using the endorsed JAXB implementation (i.e. the one provided in
-         * separated JAR files) or the one bundled in JDK 6. We use the JAXB context package
-         * name as a criterion:
-         *
-         *   JAXB endorsed JAR uses    "com.sun.xml.bind"
-         *   JAXB bundled in JDK uses  "com.sun.xml.internal.bind"
-         */
-        String classname = context.getClass().getName();
-        if (classname.startsWith("com.sun.xml.internal.bind.")) {
-            classname = "org.apache.sis.xml.OGCNamespacePrefixMapper";
-            implementation = INTERNAL;
-        } else if (classname.startsWith(Pooled.ENDORSED_PREFIX)) {
-            classname = "org.apache.sis.xml.OGCNamespacePrefixMapper_Endorsed";
-            implementation = ENDORSED;
-        } else {
-            classname = null;
-            implementation = OTHER;
-        }
+        implementation = Implementation.detect(context);
         /*
          * Prepares a copy of the property map (if any), then removes the
          * properties which are handled especially by this constructor.
          */
-        template = new PooledTemplate(properties, implementation == INTERNAL);
+        template = new PooledTemplate(properties, implementation);
         final Object rootNamespace = template.remove(XML.DEFAULT_NAMESPACE, "");
         /*
          * Instantiates the OGCNamespacePrefixMapper appropriate for the implementation
@@ -217,6 +199,7 @@ public class MarshallerPool {
          * usual ClassNotFoundException if the class was found but its parent class has
          * not been found.
          */
+        final String classname = implementation.mapper;
         if (classname == null) {
             mapper = null;
         } else try {
@@ -243,8 +226,10 @@ public class MarshallerPool {
         try {
             ((Pooled) marshaller).reset(template);
         } catch (JAXBException exception) {
-            // Not expected to happen because we are supposed
-            // to reset the properties to their initial values.
+            /*
+             * Not expected to happen because we are supposed
+             * to reset the properties to their initial values.
+             */
             Logging.unexpectedException(Logging.getLogger(Loggers.XML), MarshallerPool.class, "recycle", exception);
             return;
         }
@@ -438,23 +423,23 @@ public class MarshallerPool {
      *
      * @return a new marshaller configured for formatting OGC/ISO XML.
      * @throws JAXBException if an error occurred while creating and configuring the marshaller.
+     *
+     * @see #context
+     * @see #acquireMarshaller()
      */
     protected Marshaller createMarshaller() throws JAXBException {
         final Marshaller marshaller = context.createMarshaller();
         marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
-        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
-        switch (implementation) {
-            case INTERNAL: {
-                marshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", mapper);
-                marshaller.setProperty("com.sun.xml.internal.bind.indentString", INDENTATION);
-                break;
-            }
-            case ENDORSED: {
-                marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", mapper);
-                marshaller.setProperty("com.sun.xml.bind.indentString", INDENTATION);
-                break;
-            }
-            // Do nothing for the OTHER case.
+        /*
+         * Note: we do not set the Marshaller.JAXB_ENCODING property because specification
+         * said that the default value is "UTF-8", which is what we want.
+         */
+        String key;
+        if ((key = implementation.mapperKey) != null) {
+            marshaller.setProperty(key, mapper);
+        }
+        if ((key = implementation.indentKey) != null) {
+            marshaller.setProperty(key, CharSequences.spaces(Constants.DEFAULT_INDENTATION));
         }
         synchronized (replacements) {
             for (final AdapterReplacement adapter : replacements) {
@@ -471,6 +456,9 @@ public class MarshallerPool {
      *
      * @return a new unmarshaller configured for parsing OGC/ISO XML.
      * @throws JAXBException if an error occurred while creating and configuring the unmarshaller.
+     *
+     * @see #context
+     * @see #acquireUnmarshaller()
      */
     protected Unmarshaller createUnmarshaller() throws JAXBException {
         final Unmarshaller unmarshaller = context.createUnmarshaller();

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -38,6 +38,7 @@ import org.apache.sis.util.logging.Warni
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.LegacyNamespaces;
+import org.apache.sis.internal.jaxb.TypeRegistration;
 
 
 /**
@@ -49,7 +50,7 @@ import org.apache.sis.internal.jaxb.Lega
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 abstract class Pooled {
@@ -60,21 +61,12 @@ abstract class Pooled {
     private static final String[] SCHEMA_KEYS = {"gmd"};
 
     /**
-     * The prefix of property names which are provided in external (endorsed) implementation of JAXB.
-     * This is slightly different than the prefix used by the implementation bundled with the JDK 6,
-     * which is {@code "com.sun.xml.internal.bind"}.
-     *
-     * @see #convertPropertyKey(String)
-     */
-    static final String ENDORSED_PREFIX = "com.sun.xml.bind.";
-
-    /**
      * {@code true} if the JAXB implementation is the one bundled in JDK 6, or {@code false}
      * if this is the external implementation provided as a JAR file in the endorsed directory.
      * If {@code true}, then an additional {@code "internal"} package name needs to be inserted
      * in the property keys.
      *
-     * @see #convertPropertyKey(String)
+     * @see Implementation#toInternal(String)
      */
     private final boolean internal;
 
@@ -156,6 +148,14 @@ abstract class Pooled {
     private ValueConverter converter;
 
     /**
+     * Converters from arbitrary classes implementing GeoAPI interfaces to Apache SIS implementations
+     * providing JAXB annotations, or null or an empty array if none. This is used at marshalling time.
+     *
+     * @see #getRootAdapters()
+     */
+    private TypeRegistration[] rootAdapters;
+
+    /**
      * The object to inform about warnings, or {@code null} if none.
      */
     private WarningListener<?> warningListener;
@@ -170,7 +170,7 @@ abstract class Pooled {
     /**
      * Creates a {@link PooledTemplate}.
      *
-     * @param internal {@code true} if the JAXB implementation is the one bundled in JDK 6,
+     * @param internal  {@code true} if the JAXB implementation is the one bundled in JDK 6,
      *        or {@code false} if this is the external implementation provided as a JAR file
      *        in the endorsed directory.
      */
@@ -199,7 +199,7 @@ abstract class Pooled {
      * @throws JAXBException if an error occurred while setting a property.
      */
     final void initialize(final Pooled template) throws JAXBException {
-        reset(template); // Set the SIS properties first. JAXB properties are set below.
+        reset(template);     // Set the SIS properties first. JAXB properties are set below.
         for (final Map.Entry<Object,Object> entry : template.initialProperties.entrySet()) {
             setStandardProperty((String) entry.getKey(), entry.getValue());
         }
@@ -226,6 +226,7 @@ abstract class Pooled {
         versionGML       = template.versionGML;
         resolver         = template.resolver;
         converter        = template.converter;
+        rootAdapters     = template.rootAdapters;
         warningListener  = template.warningListener;
         resetTime        = System.nanoTime();
         if (this instanceof Marshaller) {
@@ -303,22 +304,6 @@ abstract class Pooled {
     }
 
     /**
-     * Converts a property key from the JAXB name to the underlying implementation name.
-     * This applies only to property keys in the {@code "com.sun.xml.bind"} namespace.
-     *
-     * @param  key  the JAXB property key.
-     * @return the property key to use.
-     */
-    private String convertPropertyKey(String key) {
-        if (internal && key.startsWith(ENDORSED_PREFIX)) {
-            final StringBuilder buffer = new StringBuilder(key.length() + 10);
-            key = buffer.append("com.sun.xml.internal.bind.")
-                    .append(key, ENDORSED_PREFIX.length(), key.length()).toString();
-        }
-        return key;
-    }
-
-    /**
      * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
      * It saves the initial state if it was not already done, but subclasses will
      * need to complete the work.
@@ -397,6 +382,11 @@ abstract class Pooled {
                     }
                     return;
                 }
+                case TypeRegistration.ROOT_ADAPTERS: {
+                    rootAdapters = (TypeRegistration[]) value;
+                    // No clone for now because ROOT_ADAPTERS is not yet a public API.
+                    return;
+                }
             }
         } catch (ClassCastException | IllformedLocaleException e) {
             throw new PropertyException(Errors.format(
@@ -406,7 +396,9 @@ abstract class Pooled {
          * If we reach this point, the given name is not a SIS property. Try to handle
          * it as a (un)marshaller-specific property, after saving the previous value.
          */
-        name = convertPropertyKey(name);
+        if (internal) {
+            name = Implementation.toInternal(name);
+        }
         if (!initialProperties.containsKey(name)) {
             if (initialProperties.put(name, getStandardProperty(name)) != null) {
                 // Should never happen, unless on concurrent changes in a backgroung thread.
@@ -420,7 +412,7 @@ abstract class Pooled {
      * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
      */
     @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Because unmodifiable.
-    public final Object getProperty(final String name) throws PropertyException {
+    public final Object getProperty(String name) throws PropertyException {
         switch (name) {
             case XML.LOCALE:           return locale;
             case XML.TIMEZONE:         return timezone;
@@ -445,8 +437,12 @@ abstract class Pooled {
                     default: return null;
                 }
             }
+            case TypeRegistration.ROOT_ADAPTERS: return (rootAdapters != null) ? rootAdapters.clone() : null;
             default: {
-                return getStandardProperty(convertPropertyKey(name));
+                if (internal) {
+                    name = Implementation.toInternal(name);
+                }
+                return getStandardProperty(name);
             }
         }
     }
@@ -493,6 +489,18 @@ abstract class Pooled {
     public abstract <A extends XmlAdapter> A getAdapter(final Class<A> type);
 
     /**
+     * Returns the adapters to apply on the root object to marshal, or {@code null} or an empty array if none.
+     * This is used for converting from arbitrary implementations of GeoAPI interfaces to Apache SIS implementations
+     * providing JAXB annotations.
+     *
+     * @return a direct reference to the internal array of converters - do not modify.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    final TypeRegistration[] getRootAdapters() {
+        return rootAdapters;
+    }
+
+    /**
      * A method which is common to both {@code Marshaller} and {@code Unmarshaller}.
      * It saves the initial state if it was not already done, but subclasses will
      * need to complete the work.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledMarshaller.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -36,6 +36,7 @@ import javax.xml.validation.Schema;
 import org.xml.sax.ContentHandler;
 import org.w3c.dom.Node;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.jaxb.TypeRegistration;
 
 
 /**
@@ -53,7 +54,7 @@ import org.apache.sis.internal.jaxb.Cont
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.8
  * @module
  */
 final class PooledMarshaller extends Pooled implements Marshaller {
@@ -90,7 +91,7 @@ final class PooledMarshaller extends Poo
         if (key instanceof String) {
             final String k = (String) key;
             if (value == null && (k.endsWith(".xmlHeaders") || k.equals(JAXB_SCHEMA_LOCATION))) {
-                value = ""; // Null value doesn't seem to be accepted for those properties.
+                value = "";      // Null value does not seem to be accepted for those properties.
             }
             marshaller.setProperty(k, value);
         } else if (key == AttachmentMarshaller.class) {
@@ -114,14 +115,30 @@ final class PooledMarshaller extends Poo
     }
 
     /**
+     * Converts the given arbitrary object to an implementation having JAXB annotations.
+     * If the given object is not recognized or is already an instance of the expected class,
+     * then it is returned unchanged.
+     */
+    private Object toImplementation(final Object value) throws JAXBException {
+        final TypeRegistration[] converters = getRootAdapters();
+        if (converters != null) {
+            for (final TypeRegistration t : converters) {
+                final Object c = t.toImplementation(value);
+                if (c != null) return c;
+            }
+        }
+        return value;
+    }
+
+    /**
      * Marshals to the given output with on-the-fly substitution of namespaces.
      * This method is invoked only when the user asked to marshal to a different GML version
      * than the one supported natively by SIS, i.e. when {@link #getFilterVersion()} returns
      * a non-null value.
      *
-     * @param object  the object to marshall.
-     * @param output  the writer created by SIS (<b>not</b> the writer given by the user).
-     * @param version Identify the namespace substitutions to perform.
+     * @param object   the object to marshall.
+     * @param output   the writer created by SIS (<b>not</b> the writer given by the user).
+     * @param version  identifies the namespace substitutions to perform.
      */
     private void marshal(final Object object, XMLStreamWriter output, final FilterVersion version)
             throws XMLStreamException, JAXBException
@@ -129,7 +146,7 @@ final class PooledMarshaller extends Poo
         output = new FilteredStreamWriter(output, version);
         final Context context = begin();
         try {
-            marshaller.marshal(object, output);
+            marshaller.marshal(toImplementation(object), output);
         } finally {
             context.finish();
         }
@@ -150,7 +167,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -171,7 +188,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -194,7 +211,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -215,7 +232,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -236,7 +253,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -257,7 +274,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -275,7 +292,7 @@ final class PooledMarshaller extends Poo
         }
         final Context context = begin();
         try {
-            marshaller.marshal(object, output);
+            marshaller.marshal(toImplementation(object), output);
         } finally {
             context.finish();
         }
@@ -295,7 +312,7 @@ final class PooledMarshaller extends Poo
             // Marshalling to the default GML version.
             final Context context = begin();
             try {
-                marshaller.marshal(object, output);
+                marshaller.marshal(toImplementation(object), output);
             } finally {
                 context.finish();
             }
@@ -314,7 +331,7 @@ final class PooledMarshaller extends Poo
         } else {
             final Context context = begin();
             try {
-                return marshaller.getNode(object);
+                return marshaller.getNode(toImplementation(object));
             } finally {
                 context.finish();
             }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledTemplate.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledTemplate.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledTemplate.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/PooledTemplate.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -40,15 +40,18 @@ final class PooledTemplate extends Poole
      * Creates a new template.
      *
      * @param properties  the properties to be given to JAXB (un)marshallers, or {@code null} if none.
-     * @param internal {@code true} if the JAXB implementation is the one bundled in JDK 6,
-     *        or {@code false} if this is the external implementation provided as a JAR file
-     *        in the endorsed directory.
+     * @param internal    {@code true} if the JAXB implementation is the one bundled in JDK 6, or
+     *                    {@code false} if this is the external implementation provided as a JAR file
+     *                    in the endorsed directory.
      */
-    PooledTemplate(final Map<String,?> properties, final boolean internal) throws PropertyException {
-        super(internal);
+    PooledTemplate(final Map<String,?> properties, final Implementation implementation) throws PropertyException {
+        super(implementation == Implementation.INTERNAL);
         if (properties != null) {
             for (final Map.Entry<String,?> entry : properties.entrySet()) {
-                setProperty(entry.getKey(), entry.getValue());
+                final String key = entry.getKey();
+                if (implementation.filterProperty(key)) {
+                    setProperty(key, entry.getValue());
+                }
             }
         }
     }
@@ -83,7 +86,7 @@ final class PooledTemplate extends Poole
      * <p>Current implementation expects values of type {@code String}, but this may be generalized
      * in a future SIS version if there is a need for that.</p>
      *
-     * @param  name  the name of the property to remove.
+     * @param  name          the name of the property to remove.
      * @param  defaultValue  the default value to return if the given property is not defined in the map.
      * @return the old value of that property, or {@code defaultValue} if the given property was not defined.
      * @throws PropertyException if the given property is not of the expected type.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -102,6 +102,7 @@ public final class XML extends Static {
      * {@link org.apache.sis.metadata.iso.DefaultMetadata#setLanguage(Locale) setLanguage(Locale)}
      * method will have precedence over this property. This behavior is compliant with INSPIRE rules.
      *
+     * @see org.apache.sis.setup.OptionKey#LOCALE
      * @see Marshaller#setProperty(String, Object)
      * @see org.apache.sis.metadata.iso.DefaultMetadata#setLanguage(Locale)
      */
@@ -115,6 +116,8 @@ public final class XML extends Static {
      * <div class="section">Default behavior</div>
      * If this property is never set, then (un)marshalling will use the
      * {@linkplain TimeZone#getDefault() default timezone}.
+     *
+     * @see org.apache.sis.setup.OptionKey#TIMEZONE
      */
     public static final String TIMEZONE = "org.apache.sis.xml.timezone";
 
@@ -308,8 +311,7 @@ public final class XML extends Static {
      * this field is initially null, then created by {@link #getPool()} when first needed.
      * Once created the field value usually doesn't change. However the field may be reset
      * to {@code null} in an OSGi context when modules are loaded or unloaded, because the
-     * set of classes returned by {@link TypeRegistration#defaultClassesToBeBound()} may
-     * have changed.
+     * set of classes returned by {@link TypeRegistration#load(boolean)} may have changed.
      *
      * @see #getPool()
      */

Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/internal/util/XPathsTest.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.util;
 
+import org.apache.sis.util.Characters;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -42,6 +43,7 @@ public final strictfp class XPathsTest e
         assertEquals(97, XPaths.endOfURI("http://schemas.opengis.net/iso/19139/20070417/resources/uom/gmxUom.xml#xpointer(//*[@gml:id='m'])", 0));
         assertEquals(-1, XPaths.endOfURI("m/s", 0));
         assertEquals(-1, XPaths.endOfURI("m.s", 0));
+        assertEquals(11, XPaths.endOfURI("EPSG" + Characters.NO_BREAK_SPACE + ": 9001", 0));
     }
 
     /**

Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java?rev=1781503&r1=1781502&r2=1781503&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java [UTF-8] Fri Feb  3 08:01:02 2017
@@ -19,6 +19,7 @@ package org.apache.sis.measure;
 import java.util.Set;
 import java.util.HashSet;
 import java.util.Locale;
+import java.text.ParsePosition;
 import java.lang.reflect.Field;
 import javax.measure.Unit;
 import javax.measure.format.ParserException;
@@ -281,6 +282,7 @@ public final strictfp class UnitFormatTe
             fail("Should not accept unknown unit.");
         } catch (ParserException e) {
             final String message = e.getMessage();
+            assertTrue(message, message.contains("degree"));
             assertTrue(message, message.contains("foo"));
         }
         // Tests with localisation.
@@ -289,7 +291,8 @@ public final strictfp class UnitFormatTe
             fail("Should not accept localized unit unless requested.");
         } catch (ParserException e) {
             final String message = e.getMessage();
-            assertTrue(message, message.contains("mètre cube"));
+            assertTrue(message, message.contains("mètre"));
+            assertTrue(message, message.contains("cube"));
         }
         f.setLocale(Locale.FRANCE);
         assertSame(Units.CUBIC_METRE, f.parse("mètre cube"));
@@ -395,6 +398,29 @@ public final strictfp class UnitFormatTe
     }
 
     /**
+     * Tests parsing a unit from another position than zero and verifies that {@code UnitFormat} detects
+     * correctly where the unit symbol ends.
+     */
+    @Test
+    @DependsOnMethod("testParseSymbol")
+    public void testParsePosition() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        final ParsePosition pos = new ParsePosition(4);
+        assertSame(Units.CENTIMETRE, f.parse("ABC cm DEF", pos));
+        assertEquals("ParsePosition.getIndex()", 6, pos.getIndex());
+        assertEquals("ParsePosition.getErrorIndex()", -1, pos.getErrorIndex());
+        /*
+         * Adding "cm DEF" as a unit label should allow UnitFormat to recognize those characters.
+         * We associate a random unit to that label, just for testing purpose.
+         */
+        pos.setIndex(4);
+        f.label(Units.HECTARE, "cm DEF");
+        assertSame(Units.HECTARE, f.parse("ABC cm DEF", pos));
+        assertEquals("ParsePosition.getIndex()", 10, pos.getIndex());
+        assertEquals("ParsePosition.getErrorIndex()", -1, pos.getErrorIndex());
+    }
+
+    /**
      * Tests {@link UnitFormat#clone()}.
      */
     @Test



Mime
View raw message