sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1456692 - in /sis/branches/JDK7/sis-utility/src: main/java/org/apache/sis/internal/converter/ main/java/org/apache/sis/util/ test/java/org/apache/sis/internal/converter/
Date Thu, 14 Mar 2013 21:53:41 GMT
Author: desruisseaux
Date: Thu Mar 14 21:53:41 2013
New Revision: 1456692

URL: http://svn.apache.org/r1456692
Log:
Almost done with the internal.converter package:
- Implemented ConverterRegistry.toString()
- More internal classes extends SystemConverter
  (when we will be done, all internal classes should extend it).
- Ported HeuristicRegistry, which encapsulate the knownledge about
  special cases: CodeLists, InternationalString, Numbers.

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java   (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/CharSequenceConverter.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/Column.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/ConverterRegistry.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/FallbackConverter.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/IdentityConverter.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemConverter.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ObjectConverters.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/converter/NumberConverterTest.java

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/CharSequenceConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/CharSequenceConverter.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/CharSequenceConverter.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/CharSequenceConverter.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -16,9 +16,11 @@
  */
 package org.apache.sis.internal.converter;
 
-import java.io.Serializable;
+import java.util.EnumSet;
+import java.util.Set;
 import net.jcip.annotations.Immutable;
 import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.UnconvertibleObjectException;
 
 
@@ -37,26 +39,13 @@ import org.apache.sis.util.Unconvertible
  * @module
  */
 @Immutable
-final class CharSequenceConverter<T> extends SurjectiveConverter<CharSequence,T> implements Serializable {
+final class CharSequenceConverter<T> extends SystemConverter<CharSequence,T> {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 2591675151163578878L;
 
     /**
-     * A converter from {@link CharSequence} to {@link String}.
-     */
-    static final CharSequenceConverter<String> STRING =
-            new CharSequenceConverter<>(String.class, IdentityConverter.create(String.class));
-
-    /**
-     * The target type requested by the user. We retain this type explicitly instead
-     * than querying {@code next.getTargetType()} because it may be a super-class of
-     * the later.
-     */
-    private final Class<T> targetType;
-
-    /**
      * The converter to apply after this one.
      */
     private final ObjectConverter<? super String, ? extends T> next;
@@ -64,54 +53,34 @@ final class CharSequenceConverter<T> ext
     /**
      * Creates a new converter from {@link CharSequence} to the given target type.
      *
-     * @param targetType The target type requested by the user.
+     * @param targetClass The target class requested by the user.
      * @param next The converter to apply after this one.
      */
-    private CharSequenceConverter(final Class<T> targetType, final ObjectConverter<? super String, ? extends T> next) {
-        this.targetType = targetType;
+    CharSequenceConverter(final Class<T> targetClass, final ObjectConverter<? super String, ? extends T> next) {
+        super(CharSequence.class, targetClass);
         this.next = next;
     }
 
     /**
-     * Creates a new converter from {@link CharSequence} to the given target type.
-     *
-     * @param targetType The target type requested by the user.
-     * @param next The converter to apply after this one.
-     */
-    @SuppressWarnings("unchecked")
-    public static <T> ObjectConverter<? super CharSequence, ? extends T> create(
-            final Class<T> targetType, final ObjectConverter<? super String, ? extends T> next)
-    {
-        if (next.getSourceClass().isAssignableFrom(CharSequence.class)) {
-            return (ObjectConverter<? super CharSequence, ? extends T>) next;
-        }
-        return new CharSequenceConverter<>(targetType, next);
-    }
-
-    /**
-     * Returns the source class, which is always {@link CharSequence}.
-     */
-    @Override
-    public final Class<CharSequence> getSourceClass() {
-        return CharSequence.class;
-    }
-
-    /**
-     * Returns the target class.
+     * Converts an object to an object of the target type.
      */
     @Override
-    public final Class<T> getTargetClass() {
-        return targetType;
+    public T convert(final CharSequence source) throws UnconvertibleObjectException {
+        if (targetClass.isInstance(source)) {
+            return targetClass.cast(source);
+        }
+        return next.convert(source != null ? source.toString() : null);
     }
 
     /**
-     * Converts an object to an object of the target type.
+     * Returns the properties of the converter given at construction time minus
+     * {@link FunctionProperty#INJECTIVE}, because we don't know how many source
+     * {@code CharSequence}s can produce the same {@code String}.
      */
     @Override
-    public T convert(final CharSequence source) throws UnconvertibleObjectException {
-        if (targetType.isInstance(source)) {
-            return targetType.cast(source);
-        }
-        return next.convert(source != null ? source.toString() : null);
+    public Set<FunctionProperty> properties() {
+        final EnumSet<FunctionProperty> properties = EnumSet.copyOf(next.properties());
+        properties.remove(FunctionProperty.INJECTIVE);
+        return properties;
     }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/Column.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/Column.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/Column.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/Column.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -25,6 +25,7 @@ import org.apache.sis.util.resources.Voc
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTableFormat;
+import org.apache.sis.util.collection.DefaultTreeTable;
 
 
 /**
@@ -82,19 +83,24 @@ final class Column extends TableColumn<C
     }
 
     /**
+     * Creates a table.
+     */
+    static TreeTable createTable() {
+        return new DefaultTreeTable(Column.SOURCE, Column.TARGET);
+    }
+
+    /**
      * Creates a node for the given converter and adds it to the given tree.
      * Used by {@link FallbackConverter} and {@link ConverterRegistry} for
      * implementing their {@code toString()} method.
      *
      * @param  converter The converter for which to create a tree.
      * @param  addTo     The node in which to add the converter.
-     * @return The child node created by this method.
      */
-    static TreeTable.Node toTree(final ObjectConverter<?,?> converter, final TreeTable.Node addTo) {
+    static void toTree(final ObjectConverter<?,?> converter, final TreeTable.Node addTo) {
         final TreeTable.Node node = addTo.newChild();
         node.setValue(SOURCE, converter.getSourceClass());
         node.setValue(TARGET, converter.getTargetClass());
-        return node;
     }
 
     /**
@@ -106,7 +112,7 @@ final class Column extends TableColumn<C
     @Debug
     static String format(final TreeTable table) {
         final TreeTableFormat format = new TreeTableFormat(null, null);
-        format.setColumnSeparatorPattern("[ ] ⇨ ");
+        format.setColumnSeparatorPattern("?[ ] ⇨ ");
         return format.format(table);
     }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/ConverterRegistry.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/ConverterRegistry.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/ConverterRegistry.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/ConverterRegistry.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -19,11 +19,12 @@ package org.apache.sis.internal.converte
 import java.util.Map;
 import java.util.LinkedHashMap;
 import net.jcip.annotations.ThreadSafe;
-import org.apache.sis.internal.util.SystemListener;
+import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.resources.Errors;
 
 
@@ -36,7 +37,7 @@ import org.apache.sis.util.resources.Err
  *
  * <p>New instances of {@code ConverterRegistry} are initially empty. Custom converters must be
  * explicitly {@linkplain #register(ObjectConverter) registered}. However a system-wide registry
- * initialized with default converters is provided by the {@link #SYSTEM} constant.</p>
+ * initialized with default converters is provided by the {@link HeuristicRegistry#SYSTEM} constant.</p>
  *
  * {@section Note about conversions from interfaces}
  * {@code ConverterRegistry} is primarily designed for handling converters from classes to
@@ -45,40 +46,13 @@ import org.apache.sis.util.resources.Err
  * multi-inheritance in interface hierarchy.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @since   0.3 (derived from geotk-3.20)
+ * @since   0.3 (derived from geotk-3.00)
  * @version 0.3
  * @module
  */
 @ThreadSafe
 public class ConverterRegistry {
     /**
-     * The default system-wide instance. This register is initialized with conversions between
-     * some basic Java and SIS objects, like conversions between {@link java.util.Date} and
-     * {@link java.lang.Long}. Those conversions are defined for the lifetime of the JVM.
-     *
-     * <p>If a temporary set of converters is desired, a new instance of {@code ConverterRegistry}
-     * should be created explicitly instead.</p>
-     *
-     * {@section Adding system-wide converters}
-     * Applications can add system-wide custom providers either by explicit call to the
-     * {@link #register(ObjectConverter)} method on the system converter, or by listing
-     * the fully qualified classnames of their {@link ObjectConverter} instances in the
-     * following file (see {@link ServiceLoader} for more info about services loading):
-     *
-     * {@preformat text
-     *     META-INF/services/org.apache.sis.util.converter.ObjectConverter
-     * }
-     */
-    public static final ConverterRegistry SYSTEM = new ConverterRegistry();
-    static {
-        SystemListener.add(new SystemListener() {
-            @Override protected void classpathChanged() {
-                SYSTEM.clear();
-            }
-        });
-    }
-
-    /**
      * The map of converters of any kind. For any key of type {@code ClassPair<S,T>},
      * the value shall be of type {@code ObjectConverter<? super S, ? extends T>}.
      * To ensure this constraint, values should be read and written using only the
@@ -193,27 +167,6 @@ public class ConverterRegistry {
     }
 
     /**
-     * Returns an unique instance of the given converter. If a converter already exists for the
-     * same source an target classes, then that converter is returned. Otherwise that converter
-     * is cached and returned.
-     *
-     * @param  converter The converter to look for a unique instance.
-     * @return A previously existing instance if one exists, or the given converter otherwise.
-     */
-    @SuppressWarnings("unchecked")
-    final <S,T> ObjectConverter<S,T> unique(final SystemConverter<S,T> converter) {
-        ObjectConverter<S,T> existing = findEquals(converter);
-        if (existing == null) {
-            register(converter);
-            existing = findEquals(converter);
-            if (existing == null) {
-                return converter;
-            }
-        }
-        return existing;
-    }
-
-    /**
      * Registers a new converter. This method should be invoked only once for a given converter,
      * for example in class static initializer. For example if a {@code Angle} class is defined,
      * the static initializer of that class could register a converter from {@code Angle} to
@@ -379,19 +332,47 @@ public class ConverterRegistry {
     }
 
     /**
-     * Returns a converter for the specified source and target classes.
+     * Returns a converter for exactly the given source and target classes.
+     * The default implementation invokes {@link #find(Class, Class)}, then
+     * ensures that the converter source and target classes are the same ones
+     * than the classes given in argument to this method.
      *
      * @param  <S> The source class.
      * @param  <T> The target class.
-     * @param  source The source class.
-     * @param  target The target class, or {@code Object.class} for any.
+     * @param  sourceClass The source class.
+     * @param  targetClass The target class, or {@code Object.class} for any.
      * @return The converter from the specified source class to the target class.
-     * @throws UnconvertibleObjectException if no converter is found.
+     * @throws UnconvertibleObjectException if no converter is found for the given classes.
      */
-    public <S,T> ObjectConverter<? super S, ? extends T> find(final Class<S> source, final Class<T> target)
+    @SuppressWarnings("unchecked")
+    public <S,T> ObjectConverter<S,T> findExact(final Class<S> sourceClass, final Class<T> targetClass)
             throws UnconvertibleObjectException
     {
-        final ClassPair<S,T> key = new ClassPair<>(source, target);
+        final ObjectConverter<? super S, ? extends T> candidate = find(sourceClass, targetClass);
+        if (candidate.getSourceClass() == sourceClass &&
+            candidate.getTargetClass() == targetClass)
+        {
+            return (ObjectConverter<S,T>) candidate;
+        }
+        throw new UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertFromType_2, sourceClass, targetClass));
+    }
+
+    /**
+     * Returns a converter suitable for the given source and target classes.
+     * This method may return a converter accepting more generic sources or
+     * converting to more specific targets.
+     *
+     * @param  <S> The source class.
+     * @param  <T> The target class.
+     * @param  sourceClass The source class.
+     * @param  targetClass The target class, or {@code Object.class} for any.
+     * @return The converter from the specified source class to the target class.
+     * @throws UnconvertibleObjectException if no converter is found for the given classes.
+     */
+    public <S,T> ObjectConverter<? super S, ? extends T> find(final Class<S> sourceClass, final Class<T> targetClass)
+            throws UnconvertibleObjectException
+    {
+        final ClassPair<S,T> key = new ClassPair<>(sourceClass, targetClass);
         synchronized (converters) {
             ObjectConverter<? super S, ? extends T> converter = get(key);
             if (converter != null) {
@@ -414,23 +395,15 @@ public class ConverterRegistry {
             }
             /*
              * No converter found. Gives a chance to subclasses to provide dynamically-generated
-             * converter. The default implementation does not provide any.
+             * converter.
              */
-            converter = createConverter(source, target);
+            converter = createConverter(sourceClass, targetClass);
             if (converter != null) {
                 put(key, converter);
                 return converter;
             }
         }
-        /*
-         * No explicit converter were found. Checks for the trivial case where an identity
-         * converter would fit. We perform this operation last in order to give a chance to
-         * register an explicit converter if we need to.
-         */
-        if (target.isAssignableFrom(source)) {
-            return key.cast(IdentityConverter.create(source));
-        }
-        throw new UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertFromType_2, source, target));
+        throw new UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertFromType_2, sourceClass, targetClass));
     }
 
     /**
@@ -438,14 +411,72 @@ public class ConverterRegistry {
      * This method is invoked by <code>{@linkplain #find find}(source, target)</code> when no
      * registered converter were found for the given types.
      *
+     * <p>The default implementation checks for the trivial case where an identity converter
+     * would fit, and returns {@code null} in all other cases.
+     * Subclasses can override this method in order to generate some converters dynamically.</p>
+     *
      * @param  <S> The source class.
      * @param  <T> The target class.
-     * @param  source The source class.
-     * @param  target The target class, or {@code Object.class} for any.
+     * @param  sourceClass The source class.
+     * @param  targetClass The target class, or {@code Object.class} for any.
      * @return A newly generated converter from the specified source class to the target class,
      *         or {@code null} if none.
      */
-    protected <S,T> ObjectConverter<S,T> createConverter(final Class<S> source, final Class<T> target) {
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    protected <S,T> ObjectConverter<S,T> createConverter(final Class<S> sourceClass, final Class<T> targetClass) {
+        if (targetClass.isAssignableFrom(sourceClass)) {
+            return new IdentityConverter(sourceClass, targetClass);
+        }
         return null;
     }
+
+    /**
+     * Returns a string representation of registered converters for debugging purpose.
+     * The converters are show in a tree where all real converters are leafs. Parents
+     * of those leafs are {@link FallbackConverter}s which delegate their work to the
+     * leafs.
+     *
+     * @return A string representation of registered converters.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        final TreeTable table = Column.createTable();
+        final TreeTable.Node root = table.getRoot();
+        root.setValue(Column.SOURCE, getClass());
+        synchronized (converters) {
+            for (final Map.Entry<ClassPair<?,?>, ObjectConverter<?,?>> entry : converters.entrySet()) {
+                TreeTable.Node addTo = root;
+                final ClassPair<?,?> key = entry.getKey();
+                final ObjectConverter<?,?> converter = entry.getValue();
+                if (converter.getSourceClass() != key.sourceClass ||
+                    converter.getTargetClass() != key.targetClass)
+                {
+                    /*
+                     * If we enter this block, then the converter is not really for this
+                     * (source, target) classes pair. Instead, we are leveraging a converter
+                     * which was defined for an other ClassPair.  We show this fact be first
+                     * showing this ClassPair, then the actual converter (source, target) as
+                     * below:
+                     *
+                     *     String     ⇨ Number              (the ClassPair key)
+                     *       └─String ⇨ Integer             (the ObjectConverter value)
+                     *
+                     * This is the same idea than the formatting done by FallbackConverter,
+                     * except that there is only one child. Actually this can be though as
+                     * a lightweight fallback converter.
+                     */
+                    addTo = addTo.newChild();
+                    addTo.setValue(Column.SOURCE, key.sourceClass);
+                    addTo.setValue(Column.TARGET, key.targetClass);
+                }
+                if (converter instanceof FallbackConverter<?,?>) {
+                    ((FallbackConverter<?,?>) converter).toTree(addTo.newChild(), true);
+                } else {
+                    Column.toTree(converter, addTo);
+                }
+            }
+        }
+        return Column.format(table);
+    }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/FallbackConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/FallbackConverter.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/FallbackConverter.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/FallbackConverter.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -25,7 +25,6 @@ import org.apache.sis.util.Classes;
 import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.Debug;
 
@@ -331,10 +330,11 @@ final class FallbackConverter<S,T> exten
      */
     private void toTree(final ObjectConverter<?,?> converter, TreeTable.Node addTo) {
         if (converter instanceof FallbackConverter<?,?>) {
-            if (converter.getTargetClass() != targetClass) {
-                addTo = Column.toTree(converter, addTo);
+            final boolean isNew = converter.getTargetClass() != targetClass;
+            if (isNew) {
+                addTo = addTo.newChild();
             }
-            ((FallbackConverter<?,?>) converter).toTree(addTo);
+            ((FallbackConverter<?,?>) converter).toTree(addTo, isNew);
         } else {
             Column.toTree(converter, addTo);
         }
@@ -345,8 +345,13 @@ final class FallbackConverter<S,T> exten
      * to the given node.
      *
      * @param addTo The node in which to add the converter.
+     * @param isNew {@code true} if {@code addTo} is a newly created node.
      */
-    final void toTree(final TreeTable.Node addTo) {
+    final void toTree(final TreeTable.Node addTo, final boolean isNew) {
+        if (isNew) {
+            addTo.setValue(Column.SOURCE, sourceClass);
+            addTo.setValue(Column.TARGET, targetClass);
+        }
         toTree(primary,  addTo);
         toTree(fallback, addTo);
     }
@@ -358,12 +363,8 @@ final class FallbackConverter<S,T> exten
     @Debug
     @Override
     public String toString() {
-        final DefaultTreeTable table = new DefaultTreeTable(Column.SOURCE, Column.TARGET);
-        final DefaultTreeTable.Node root = new DefaultTreeTable.Node(table);
-        root.setValue(Column.SOURCE, sourceClass);
-        root.setValue(Column.TARGET, targetClass);
-        table.setRoot(root);
-        toTree(root);
+        final TreeTable table = Column.createTable();
+        toTree(table.getRoot(), true);
         return Column.format(table);
     }
 }

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java?rev=1456692&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.converter;
+
+import org.opengis.util.CodeList;
+import net.jcip.annotations.ThreadSafe;
+import org.apache.sis.util.Numbers;
+import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.internal.util.SystemListener;
+
+
+/**
+ * A {@link ConverterRegistry} which applies heuristic rules in addition of the explicitly
+ * registered converters. Those heuristic rules are provided in a separated class in order
+ * to keep the {@link ConverterRegistry} class "pure", and concentrate all arbitrary
+ * decisions in this single class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.02)
+ * @version 0.3
+ * @module
+ */
+@ThreadSafe
+public final class HeuristicRegistry extends ConverterRegistry {
+    /**
+     * The default system-wide instance. This register is initialized with conversions between
+     * some basic Java and SIS objects, like conversions between {@link java.util.Date} and
+     * {@link java.lang.Long}. Those conversions are defined for the lifetime of the JVM.
+     *
+     * <p>If a temporary set of converters is desired, a new instance of {@code ConverterRegistry}
+     * should be created explicitly instead.</p>
+     *
+     * {@section Adding system-wide converters}
+     * Applications can add system-wide custom providers either by explicit call to the
+     * {@link #register(ObjectConverter)} method on the system converter, or by listing
+     * the fully qualified classnames of their {@link ObjectConverter} instances in the
+     * following file (see {@link ServiceLoader} for more info about services loading):
+     *
+     * {@preformat text
+     *     META-INF/services/org.apache.sis.util.converter.ObjectConverter
+     * }
+     */
+    public static final ConverterRegistry SYSTEM = new HeuristicRegistry();
+    static {
+        SystemListener.add(new SystemListener() {
+            @Override protected void classpathChanged() {
+                SYSTEM.clear();
+            }
+        });
+    }
+
+    /**
+     * Creates an initially empty set of object converters. The heuristic
+     * rules apply right away, even if no converter have been registered yet.
+     */
+    private HeuristicRegistry() {
+    }
+
+    /**
+     * Create dynamically the converters for a few special cases.
+     *
+     * <ul>
+     *   <li>If the source class is {@link CharSequence}, tries to delegate to an other
+     *       converter accepting {@link String} sources.</li>
+     *   <li>If the source and target types are numbers, generates a {@link NumberConverter}
+     *       on the fly.</li>
+     *   <li>If the target type is a code list, generate the converter on-the-fly.
+     *       We do not register every code lists in advance because there is too
+     *       many of them, and a generic code is available for all of them.</li>
+     * </ul>
+     */
+    @Override
+    @SuppressWarnings({"unchecked","rawtypes"})
+    protected <S,T> ObjectConverter<S,T> createConverter(final Class<S> sourceClass, final Class<T> targetClass) {
+        /*
+         * Before to try any heuristic rule, check for the identity converter.
+         */
+        final ObjectConverter<S,T> identity = super.createConverter(sourceClass, targetClass);
+        if (identity != null) {
+            return identity;
+        }
+        /*
+         * From CharSequence to anything.
+         */
+        if (sourceClass == CharSequence.class) {
+            return (ObjectConverter<S,T>) new CharSequenceConverter<>(
+                    targetClass, find(String.class, targetClass));
+        }
+        /*
+         * From String to various kind of CodeList.
+         */
+        if (sourceClass == String.class && CodeList.class.isAssignableFrom(targetClass)) {
+            return (ObjectConverter<S,T>) new StringConverter.CodeList<>(
+                    targetClass.asSubclass(CodeList.class));
+        }
+        /*
+         * From Number to other kinds of Number.
+         */
+        if (sourceClass == Number.class || isSupportedNumber(sourceClass)) {
+            if (isSupportedNumber(targetClass)) {
+                return (ObjectConverter<S,T>) new NumberConverter<>(
+                        sourceClass.asSubclass(Number.class),
+                        targetClass.asSubclass(Number.class));
+            }
+            if (targetClass == Comparable.class) {
+                return (ObjectConverter<S,T>) new NumberConverter.Comparable<>(
+                        sourceClass.asSubclass(Number.class));
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if the given type is one of the types supported
+     * by {@link NumberConverter}.
+     */
+    private static boolean isSupportedNumber(final Class<?> type) {
+        final int code = Numbers.getEnumConstant(type);
+        return (code >= Numbers.BYTE && code <= Numbers.BIG_DECIMAL);
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/HeuristicRegistry.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/IdentityConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/IdentityConverter.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/IdentityConverter.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/IdentityConverter.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -18,87 +18,38 @@ package org.apache.sis.internal.converte
 
 import java.util.Set;
 import java.util.EnumSet;
-import java.util.Map;
-import java.util.IdentityHashMap;
-import java.io.ObjectStreamException;
-import java.io.Serializable;
 import net.jcip.annotations.Immutable;
-import org.apache.sis.internal.util.SystemListener;
 import org.apache.sis.math.FunctionProperty;
-import org.apache.sis.util.ObjectConverter;
 
 
 /**
  * An object converter which returns the source unchanged.
  *
- * @param <T> The base type of source and converted objects.
+ * @param <S> The base type of source objects.
+ * @param <T> The base type of converted objects.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.3 (derived from geotk-3.01)
  * @version 0.3
  * @module
+ *
+ * @see org.apache.sis.util.ObjectConverters#identity(Class)
  */
 @Immutable
-public final class IdentityConverter<T> implements ObjectConverter<T,T>, Serializable {
+public final class IdentityConverter<S extends T, T> extends SystemConverter<S,T> {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = -7203549932226245206L;
 
     /**
-     * Identity converters created in the JVM, for sharing unique instances.
-     */
-    private static final Map<Class<?>, IdentityConverter<?>> CACHE = new IdentityHashMap<>();
-    static {
-        SystemListener.add(new SystemListener() {
-            @Override protected void classpathChanged() {
-                clearCache();
-            }
-        });
-    }
-
-    /**
-     * Returns an identity converter for the given type.
-     *
-     * @param  <T>  The compile-time type.
-     * @param  type The type of the desired converter.
-     * @return The identity converter for the given type.
-     */
-    public static <T> IdentityConverter<T> create(final Class<T> type) {
-        synchronized (CACHE) {
-            @SuppressWarnings("unchecked")
-            IdentityConverter<T> converter = (IdentityConverter<T>) CACHE.get(type);
-            if (converter == null) {
-                converter = new IdentityConverter<>(type);
-                CACHE.put(type, converter);
-            }
-            return converter;
-        }
-    }
-
-    /**
-     * Invoked when the cache needs to be cleared because the classpath changed.
-     * Some cached type may no longer be on the classpath, so we need to release
-     * references in order to allow the garbage collector to unload them.
-     */
-    static void clearCache() {
-        synchronized (CACHE) {
-            CACHE.clear();
-        }
-    }
-
-    /**
-     * The type of source and converted objects.
-     */
-    private final Class<T> type;
-
-    /**
      * Creates a new identity converter.
      *
-     * @param type The type of source and converted objects.
+     * @param sourceClass The {@linkplain #getSourceClass() source class}.
+     * @param targetClass The {@linkplain #getTargetClass() target class}.
      */
-    private IdentityConverter(final Class<T> type) {
-        this.type = type;
+    public IdentityConverter(final Class<S> sourceClass, final Class<T> targetClass) {
+        super(sourceClass, targetClass);
     }
 
     /**
@@ -111,23 +62,12 @@ public final class IdentityConverter<T> 
      */
     @Override
     public Set<FunctionProperty> properties() {
-        return EnumSet.allOf(FunctionProperty.class);
-    }
-
-    /**
-     * Returns the type for source objects.
-     */
-    @Override
-    public Class<T> getSourceClass() {
-        return type;
-    }
-
-    /**
-     * Returns the type of converted objects.
-     */
-    @Override
-    public Class<T> getTargetClass() {
-        return type;
+        final EnumSet<FunctionProperty> properties = EnumSet.allOf(FunctionProperty.class);
+        if (sourceClass != targetClass) {
+            // Conservative choice (actually we don't really know).
+            properties.remove(FunctionProperty.INVERTIBLE);
+        }
+        return properties;
     }
 
     /**
@@ -136,30 +76,7 @@ public final class IdentityConverter<T> 
      * @param source The value to convert.
      */
     @Override
-    public T convert(final T source) {
+    public T convert(final S source) {
         return source;
     }
-
-    /**
-     * Returns {@code this}, since this converter is its own inverse.
-     */
-    @Override
-    public ObjectConverter<T,T> inverse() {
-        return this;
-    }
-
-    /**
-     * Invoked on deserialization for resolving to a unique instance.
-     */
-    private Object readResolve() throws ObjectStreamException {
-        synchronized (CACHE) {
-            @SuppressWarnings("unchecked")
-            final IdentityConverter<T> converter = (IdentityConverter<T>) CACHE.get(type);
-            if (converter != null) {
-                return converter;
-            }
-            CACHE.put(type, this);
-        }
-        return this;
-    }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -20,7 +20,6 @@ import java.util.Set;
 import java.util.EnumSet;
 import net.jcip.annotations.Immutable;
 import org.apache.sis.util.Numbers;
-import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.math.FunctionProperty;
 import org.apache.sis.util.UnconvertibleObjectException;
 
@@ -94,14 +93,6 @@ final class NumberConverter<S extends Nu
     }
 
     /**
-     * Returns the inverse of this converter.
-     */
-    @Override
-    public ObjectConverter<T,S> inverse() {
-        return new NumberConverter<>(targetClass, sourceClass).unique();
-    }
-
-    /**
      * Converter from numbers to comparables. This special case exists because {@link Number}
      * does not implement {@link java.lang.Comparable} directly, but all known subclasses do.
      */

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemConverter.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemConverter.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/converter/SystemConverter.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -18,6 +18,7 @@ package org.apache.sis.internal.converte
 
 import java.io.ObjectStreamException;
 import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.util.resources.Errors;
 
 
@@ -41,10 +42,15 @@ abstract class SystemConverter<S,T> exte
     private static final long serialVersionUID = 885663610056067478L;
 
     /**
+     * The inverse converter, created when first needed.
+     */
+    private transient volatile ObjectConverter<T,S> inverse;
+
+    /**
      * Creates a new converter for the given source and target classes.
      *
-     * @param sourceClass The {@linkplain ObjectConverter#getSourceClass() source class}.
-     * @param targetClass The {@linkplain ObjectConverter#getTargetClass() target class}.
+     * @param sourceClass The {@linkplain #getSourceClass() source class}.
+     * @param targetClass The {@linkplain #getTargetClass() target class}.
      */
     SystemConverter(final Class<S> sourceClass, final Class<T> targetClass) {
         super(sourceClass, targetClass);
@@ -67,11 +73,19 @@ abstract class SystemConverter<S,T> exte
     }
 
     /**
-     * Unsupported by default. To be overridden by subclasses that support this operation.
+     * Returns the inverse converter, creating it when first needed.
      */
     @Override
-    public ObjectConverter<T, S> inverse() throws UnsupportedOperationException {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.NonInvertibleConversion));
+    public final ObjectConverter<T,S> inverse() throws UnsupportedOperationException {
+        // No need to synchronize. This is not a big deal if the same object is fetched twice.
+        // The ConverterRegistry clas provides the required synchronization.
+        ObjectConverter<T,S> candidate = inverse;
+        if (candidate == null) try {
+            inverse = candidate = HeuristicRegistry.SYSTEM.findExact(targetClass, sourceClass);
+        } catch (UnconvertibleObjectException e) {
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.NonInvertibleConversion), e);
+        }
+        return candidate;
     }
 
     /**
@@ -110,12 +124,13 @@ abstract class SystemConverter<S,T> exte
     }
 
     /**
-     * Returns an unique instance of this converter. If a converter already exists for the same
-     * source an target classes, then this converter is returned. Otherwise this converter is
-     * cached and returned.
+     * Returns an unique instance of this converter if one exists. If a converter already
+     * exists for the same source an target classes, then this converter is returned.
+     * Otherwise this converter is returned <strong>without</strong> being cached.
      */
-    final ObjectConverter<S,T> unique() {
-        return ConverterRegistry.SYSTEM.unique(this);
+    public final ObjectConverter<S,T> unique() {
+        final ObjectConverter<S,T> existing = HeuristicRegistry.SYSTEM.findEquals(this);
+        return (existing != null) ? existing : this;
     }
 
     /**
@@ -123,8 +138,7 @@ abstract class SystemConverter<S,T> exte
      * in the virtual machine, we do not cache the instance (for now) for security reasons.
      */
     protected final Object readResolve() throws ObjectStreamException {
-        final ObjectConverter<S,T> existing = ConverterRegistry.SYSTEM.findEquals(this);
-        return (existing != null) ? existing : this;
+        return unique();
     }
 
     /**

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ObjectConverters.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ObjectConverters.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ObjectConverters.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ObjectConverters.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -18,9 +18,9 @@ package org.apache.sis.util;
 
 import java.util.Map;
 import java.util.Set;
-import org.apache.sis.internal.converter.IdentityConverter;
 import org.apache.sis.util.collection.CollectionsExt;
-import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.converter.IdentityConverter;
+import org.apache.sis.internal.converter.HeuristicRegistry;
 
 
 /**
@@ -78,7 +78,7 @@ public final class ObjectConverters exte
      */
     public static <T> ObjectConverter<T,T> identity(final Class<T> type) {
         ArgumentChecks.ensureNonNull("type", type);
-        return IdentityConverter.create(type);
+        return new IdentityConverter<>(type, type).unique();
     }
 
     /**
@@ -94,8 +94,7 @@ public final class ObjectConverters exte
     public static <S,T> ObjectConverter<? super S, ? extends T> find(final Class<S> source, final Class<T> target)
             throws UnconvertibleObjectException
     {
-        // TODO: port the implementation from Geotk
-        throw new UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertFromType_2, source, target));
+        return HeuristicRegistry.SYSTEM.find(source, target);
     }
 
     /**
@@ -174,7 +173,7 @@ public final class ObjectConverters exte
                                                 final Class<V> valueType)
     {
         ArgumentChecks.ensureNonNull("valueType", valueType);
-        return CollectionsExt.derivedMap(storage, keyConverter, IdentityConverter.create(valueType));
+        return CollectionsExt.derivedMap(storage, keyConverter, identity(valueType));
     }
 
     /**
@@ -202,6 +201,6 @@ public final class ObjectConverters exte
                                                   final ObjectConverter<SV,V> valueConverter)
     {
         ArgumentChecks.ensureNonNull("keyType", keyType);
-        return CollectionsExt.derivedMap(storage, IdentityConverter.create(keyType), valueConverter);
+        return CollectionsExt.derivedMap(storage, identity(keyType), valueConverter);
     }
 }

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/converter/NumberConverterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/converter/NumberConverterTest.java?rev=1456692&r1=1456691&r2=1456692&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/converter/NumberConverterTest.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/internal/converter/NumberConverterTest.java [UTF-8] Thu Mar 14 21:53:41 2013
@@ -37,6 +37,21 @@ import static org.apache.sis.test.Assert
  */
 public final strictfp class NumberConverterTest extends TestCase {
     /**
+     * Creates a {@link NumberConverter} for the given source and target classes.
+     * We have to use the {@link ConverterRegistry} instead than instantiating the
+     * converters directly because some tests are going to verify that the converter
+     * has been properly cached.
+     */
+    private static <S extends Number, T> ObjectConverter<S,T> create(
+            final Class<S> sourceClass, final Class<T> targetClass)
+    {
+        final ObjectConverter<S,T> converter = HeuristicRegistry.SYSTEM.findExact(sourceClass, targetClass);
+        assertInstanceOf("ConverterRegistry.find(" + sourceClass.getSimpleName() + ", " + targetClass.getSimpleName() + ')',
+                (targetClass == Comparable.class) ? NumberConverter.Comparable.class : NumberConverter.class, converter);
+        return converter;
+    }
+
+    /**
      * Asserts that conversion of the given {@code source} value produces
      * the given {@code target} value, and tests the inverse conversion.
      */
@@ -69,8 +84,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testByte() {
-        final ObjectConverter<Integer, Byte> c =
-                new NumberConverter<>(Integer.class, Byte.class).unique();
+        final ObjectConverter<Integer, Byte> c = create(Integer.class, Byte.class);
         runInvertibleConversion(c, Integer.valueOf(-8), Byte.valueOf((byte) -8));
         runInvertibleConversion(c, Integer.valueOf(Byte.MIN_VALUE), Byte.valueOf(Byte.MIN_VALUE));
         runInvertibleConversion(c, Integer.valueOf(Byte.MAX_VALUE), Byte.valueOf(Byte.MAX_VALUE));
@@ -84,8 +98,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testShort() {
-        final ObjectConverter<Integer, Short> c =
-                new NumberConverter<>(Integer.class, Short.class).unique();
+        final ObjectConverter<Integer, Short> c = create(Integer.class, Short.class);
         runInvertibleConversion(c, Integer.valueOf(-8), Short.valueOf((short) -8));
         runInvertibleConversion(c, Integer.valueOf(Short.MIN_VALUE), Short.valueOf(Short.MIN_VALUE));
         runInvertibleConversion(c, Integer.valueOf(Short.MAX_VALUE), Short.valueOf(Short.MAX_VALUE));
@@ -99,8 +112,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testInteger() {
-        final ObjectConverter<Float, Integer> c =
-                new NumberConverter<>(Float.class, Integer.class).unique();
+        final ObjectConverter<Float, Integer> c = create(Float.class, Integer.class);
         runInvertibleConversion(c, Float.valueOf(-8), Integer.valueOf(-8));
         // Can not easily tests the values around Integer.MIN/MAX_VALUE because of rounding errors in float.
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));
@@ -111,8 +123,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testLong() {
-        final ObjectConverter<Float, Long> c =
-                new NumberConverter<>(Float.class, Long.class).unique();
+        final ObjectConverter<Float, Long> c = create(Float.class, Long.class);
         runInvertibleConversion(c, Float.valueOf(-8), Long.valueOf(-8));
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));
     }
@@ -122,8 +133,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testFloat() {
-        final ObjectConverter<Double, Float> c =
-                new NumberConverter<>(Double.class, Float.class).unique();
+        final ObjectConverter<Double, Float> c = create(Double.class, Float.class);
         runInvertibleConversion(c, Double.valueOf(2.5), Float.valueOf(2.5f));
         tryUnconvertibleValue  (c, Double.valueOf(1E+40));
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));
@@ -134,8 +144,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testDouble() {
-        final ObjectConverter<BigDecimal, Double> c =
-                new NumberConverter<>(BigDecimal.class, Double.class).unique();
+        final ObjectConverter<BigDecimal, Double> c = create(BigDecimal.class, Double.class);
         runInvertibleConversion(c, BigDecimal.valueOf(2.5), Double.valueOf(2.5));
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));
     }
@@ -145,8 +154,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testBigInteger() {
-        final ObjectConverter<Double, BigInteger> c =
-                new NumberConverter<>(Double.class, BigInteger.class).unique();
+        final ObjectConverter<Double, BigInteger> c = create(Double.class, BigInteger.class);
         runInvertibleConversion(c, Double.valueOf(1000), BigInteger.valueOf(1000));
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));
     }
@@ -156,8 +164,7 @@ public final strictfp class NumberConver
      */
     @Test
     public void testBigDecimal() {
-        final ObjectConverter<Double, BigDecimal> c =
-                new NumberConverter<>(Double.class, BigDecimal.class).unique();
+        final ObjectConverter<Double, BigDecimal> c = create(Double.class, BigDecimal.class);
         runInvertibleConversion(c, Double.valueOf(2.5), BigDecimal.valueOf(2.5));
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));
     }
@@ -168,8 +175,8 @@ public final strictfp class NumberConver
      */
     @Test
     public void testComparable() {
-        final ObjectConverter<Number,Comparable<?>> c =
-                new NumberConverter.Comparable<>(Number.class).unique();
+        @SuppressWarnings("unchecked")
+        final ObjectConverter<Number,Comparable<?>> c = create(Number.class, (Class) Comparable.class);
         final Integer value = 8;
         assertSame(value, c.convert(value));
         assertSame("Deserialization shall resolves to the singleton instance.", c, assertSerializedEquals(c));



Mime
View raw message