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

svn commit: r1595219 - in /sis/branches/JDK8/core: sis-feature/src/main/java/org/apache/sis/feature/ sis-feature/src/test/java/org/apache/sis/feature/ sis-feature/src/test/java/org/apache/sis/test/suite/ sis-referencing/src/main/java/org/apache/sis/par...

Author: desruisseaux
Date: Fri May 16 14:50:30 2014
New Revision: 1595219

URL: http://svn.apache.org/r1595219
Log:
Implemented the String representation of Feature in a tabular format.

Added:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
      - copied, changed from r1595029, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java   (with props)
Modified:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java [UTF-8] Fri May 16 14:50:30 2014
@@ -223,7 +223,7 @@ public class DefaultAssociation extends 
     @Debug
     @Override
     public String toString() {
-        final StringBuilder buffer = role.toString("FeatureAssociation", role.getValueType().getName().toString());
+        final StringBuilder buffer = role.toString("FeatureAssociation", role.getValueType().getName());
         if (value != null) {
             final String pt = role.getTitleProperty();
             if (pt != null) {

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] Fri May 16 14:50:30 2014
@@ -212,6 +212,6 @@ public class DefaultAssociationRole exte
     @Debug
     @Override
     public String toString() {
-        return toString("FeatureAssociationRole", valueType.getName().toString()).toString();
+        return toString("FeatureAssociationRole", valueType.getName()).toString();
     }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java [UTF-8] Fri May 16 14:50:30 2014
@@ -22,7 +22,6 @@ import java.util.ConcurrentModificationE
 import java.io.Serializable;
 import org.opengis.metadata.quality.DataQuality;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.Containers;
@@ -206,8 +205,12 @@ public class DefaultFeature implements S
         if (element != null) {
             if (!propertiesAreInstantiated) {
                 return element;
-            } else {
+            } else if (element instanceof DefaultAttribute<?>) {
                 return ((DefaultAttribute<?>) element).getValue();
+            } else if (element instanceof DefaultAssociation) {
+                return ((DefaultAssociation) element).getValue();
+            } else {
+                throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnknownType_1, element.getClass()));
             }
         } else if (properties.containsKey(name)) {
             return null; // Null has been explicitely set.
@@ -215,8 +218,10 @@ public class DefaultFeature implements S
             final PropertyType pt = getPropertyType(name);
             if (pt instanceof DefaultAttributeType<?>) {
                 return ((DefaultAttributeType<?>) pt).getDefaultValue();
+            } else if (pt instanceof DefaultAssociationRole) {
+                return null;
             } else {
-                throw new UnsupportedOperationException(); // TODO
+                throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnknownType_1, pt.getClass()));
             }
         }
     }
@@ -283,17 +288,29 @@ public class DefaultFeature implements S
     }
 
     /**
-     * Returns {@code true} if the given type is an attribute type, and if the given value
-     * is valid for that attribute type.
+     * Returns {@code true} if the given type is an attribute type or association role,
+     * and if the given value is valid for that type or role.
      */
     private static boolean isValidAttributeValue(final PropertyType type, final Object value) {
-        if (!(type instanceof DefaultAttributeType<?>)) {
-            return false;
+        if (type instanceof DefaultAttributeType<?>) {
+            if (value == null) {
+                return true;
+            }
+            if (((DefaultAttributeType<?>) type).getValueClass().isInstance(value)) {
+                return true;
+            }
         }
-        if (value == null) {
-            return true;
+        if (type instanceof DefaultAssociationRole) {
+            if (value == null) {
+                return true;
+            }
+            if (value instanceof DefaultFeature) {
+                if (((DefaultAssociationRole) type).getValueType().maybeAssignableFrom(((DefaultFeature) value).getType())) {
+                    return true;
+                }
+            }
         }
-        return ((DefaultAttributeType<?>) type).getValueClass().isInstance(value);
+        return false;
     }
 
     /**
@@ -386,27 +403,14 @@ public class DefaultFeature implements S
     }
 
     /**
-     * Returns a string representation of this feature.
-     * The returned string is for debugging purpose and may change in any future SIS version.
+     * Formats this feature in a tabular format.
+     *
+     * @return A string representation of this feature in a tabular format.
      *
-     * @return A string representation of this feature for debugging purpose.
+     * @see FeatureFormat
      */
-    @Debug
     @Override
     public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        final String lineSeparator = System.lineSeparator();
-        for (final Map.Entry<String,Object> entry : properties.entrySet()) {
-            final PropertyType pt;
-            Object element = entry.getValue();
-            if (propertiesAreInstantiated) {
-                pt = ((DefaultAttribute<?>) element).getType();
-                element = ((DefaultAttribute<?>) element).getValue();
-            } else {
-                pt = type.getProperty(entry.getKey());
-            }
-            sb.append(pt.getName()).append(": ").append(element).append(lineSeparator);
-        }
-        return sb.toString();
+        return FeatureFormat.sharedFormat(this);
     }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] Fri May 16 14:50:30 2014
@@ -538,4 +538,16 @@ public class DefaultFeatureType extends 
         }
         return false;
     }
+
+    /**
+     * Formats this feature in a tabular format.
+     *
+     * @return A string representation of this feature in a tabular format.
+     *
+     * @see FeatureFormat
+     */
+    @Override
+    public String toString() {
+        return FeatureFormat.sharedFormat(this);
+    }
 }

Copied: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java (from r1595029, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?p2=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java&p1=sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java&r1=1595029&r2=1595219&rev=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Fri May 16 14:50:30 2014
@@ -14,15 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.parameter;
+package org.apache.sis.feature;
 
-import java.util.Map;
-import java.util.Set;
-import java.util.List;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.TimeZone;
 import java.io.IOException;
@@ -30,202 +23,40 @@ import java.text.Format;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
 import java.text.ParseException;
-import java.io.Console;
 import java.util.concurrent.atomic.AtomicReference;
-import javax.measure.unit.Unit;
-
-import org.opengis.parameter.*;
-import org.opengis.util.ScopedName;
+import org.opengis.util.InternationalString;
 import org.opengis.util.GenericName;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.ReferenceIdentifier;
-import org.opengis.referencing.operation.OperationMethod;
-
-import org.apache.sis.measure.Range;
-import org.apache.sis.io.wkt.Colors;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.io.TabularFormat;
-import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.internal.referencing.NameToIdentifier;
-import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.X364;
 
-import static org.apache.sis.util.collection.Containers.hashMapCapacity;
-
 
 /**
- * Formats {@linkplain DefaultParameterDescriptorGroup parameter descriptors} or
- * {@linkplain DefaultParameterValueGroup parameter values} in a tabular format.
- * This format assumes a monospaced font and an encoding supporting drawing box
- * characters (e.g. UTF-8).
- *
- * <p>This class can format parameters with different levels of verbosity, specified by the {@link ContentLevel}
- * property. The content level controls whether the formatter should write all names and aliases (at the cost of
- * multi-line rows), or to pickup one name per parameter for a more compact table. See {@link ContentLevel}
- * javadoc for output examples.</p>
- *
- * <div class="note"><b>Example:</b>
- * The <cite>Mercator (variant A)</cite> example given in {@link DefaultParameterDescriptorGroup} javadoc
- * will be formatted by default as below:
- *
- * {@preformat text
- *   EPSG: Mercator (variant A)
- *   ┌────────────────────────────────┬────────┬────────────┬───────────────┬───────────────┐
- *   │ Name (EPSG)                    │ Type   │ Obligation │ Value domain  │ Default value │
- *   ├────────────────────────────────┼────────┼────────────┼───────────────┼───────────────┤
- *   │ Latitude of natural origin     │ Double │ Mandatory  │  [-80 … 84]°  │         0.0°  │
- *   │ Longitude of natural origin    │ Double │ Mandatory  │ [-180 … 180]° │         0.0°  │
- *   │ Scale factor at natural origin │ Double │ Mandatory  │    (0 … ∞)    │         1.0   │
- *   │ False easting                  │ Double │ Mandatory  │   (-∞ … ∞) m  │         0.0 m │
- *   │ False northing                 │ Double │ Mandatory  │   (-∞ … ∞) m  │         0.0 m │
- *   └────────────────────────────────┴────────┴────────────┴───────────────┴───────────────┘
- * }
- * </div>
- *
- * The kind of objects accepted by this formatter are:
- * <table class="sis">
- *   <caption>Formattable object types</caption>
- *   <tr><th>Class</th> <th>Remarks</th></tr>
- *   <tr><td>{@link ParameterValueGroup}</td><td><cite>Default values</cite> column is replaced by a column of the actual values.</td></tr>
- *   <tr><td>{@link ParameterDescriptorGroup}</td><td>Table title is the parameter group name.</td></tr>
- *   <tr><td>{@link OperationMethod}</td><td>Table title is the method name (not necessarily the same than parameter group name).</td></tr>
- *   <tr><td><code>{@linkplain IdentifiedObject}[]</code></td><td>Accepted only for {@link ContentLevel#NAME_SUMMARY}.</td></tr>
- * </table>
+ * Formats {@linkplain DefaultFeature features} or {@linkplain DefaultFeatureType feature types} in a tabular format.
+ * This format assumes a monospaced font and an encoding supporting drawing box characters (e.g. UTF-8).
  *
  * <div class="warning"><b>Limitation:</b>
  * Current implementation supports only formatting, not parsing.
  * </div>
  *
- * @author  Martin Desruisseaux (IRD, Geomatys)
- * @since   0.4 (derived from geotk-2.1)
- * @version 0.4
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.5
  * @module
  */
-public class ParameterFormat extends TabularFormat<Object> {
+public class FeatureFormat extends TabularFormat<Object> {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -1345231739800152411L;
+    private static final long serialVersionUID = 8866440357566645070L;
 
     /**
      * An instance created when first needed and potentially shared.
      */
-    private static final AtomicReference<ParameterFormat> INSTANCE = new AtomicReference<>();
-
-    /**
-     * The default column separator. User can change the separator
-     * by a call to {@link #setColumnSeparatorPattern(String)}.
-     */
-    private static final String SEPARATOR = " │ ";
-
-    /**
-     * The amount of information to include in the table formatted by {@link ParameterFormat}.
-     * The content level controls whether the formatter should write all names and aliases
-     * (at the cost of multi-line rows), or to pickup one name per parameter for a more compact table.
-     *
-     * <p>The enumeration value javadoc provide examples of formatting output.</p>
-     *
-     * @since   0.4
-     * @version 0.4
-     * @module
-     */
-    public static enum ContentLevel {
-        /**
-         * The most detailed content, which includes
-         * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getName() name} and
-         * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getAlias() aliases}.
-         * Each parameter may be formatted on many lines if they have aliases.
-         *
-         * <div class="note"><b>Example:</b>
-         * The <cite>Mercator (variant A)</cite> example given in {@link DefaultParameterDescriptorGroup} javadoc,
-         * (augmented with parameter aliases) formatted at this level produces a text like below:
-         *
-         * {@preformat text
-         *   EPSG: Mercator (variant A) (9804)
-         *   EPSG: Mercator (1SP)
-         *   OGC:  Mercator_1SP
-         *   ╔══════════════════════════════════════╤════════╤════════════╤═══════════════╤═══════════════╗
-         *   ║ Name                                 │ Type   │ Obligation │ Value domain  │ Default value ║
-         *   ╟──────────────────────────────────────┼────────┼────────────┼───────────────┼───────────────╢
-         *   ║ EPSG: Latitude of natural origin     │ Double │ Mandatory  │  [-80 … 84]°  │         0.0°  ║
-         *   ║ OGC:  latitude_of_origin             │        │            │               │               ║
-         *   ╟──────────────────────────────────────┼────────┼────────────┼───────────────┼───────────────╢
-         *   ║ EPSG: Longitude of natural origin    │ Double │ Mandatory  │ [-180 … 180]° │         0.0°  ║
-         *   ║ OGC:  central_meridian               │        │            │               │               ║
-         *   ╟──────────────────────────────────────┼────────┼────────────┼───────────────┼───────────────╢
-         *   ║ EPSG: Scale factor at natural origin │ Double │ Mandatory  │    (0 … ∞)    │         1.0   ║
-         *   ║ OGC:  scale_factor                   │        │            │               │               ║
-         *   ╟──────────────────────────────────────┼────────┼────────────┼───────────────┼───────────────╢
-         *   ║ EPSG: False easting                  │ Double │ Mandatory  │   (-∞ … ∞) m  │         0.0 m ║
-         *   ║ OGC:  false_easting                  │        │            │               │               ║
-         *   ╟──────────────────────────────────────┼────────┼────────────┼───────────────┼───────────────╢
-         *   ║ EPSG: False northing                 │ Double │ Mandatory  │   (-∞ … ∞) m  │         0.0 m ║
-         *   ║ OGC:  false_northing                 │        │            │               │               ║
-         *   ╚══════════════════════════════════════╧════════╧════════════╧═══════════════╧═══════════════╝
-         * }
-         * </div>
-         */
-        DETAILED,
-
-        /**
-         * A medium level of content which formats each parameter on a single line. For each parameter only the
-         * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getName() name} is formatted —
-         * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getAlias() aliases} and
-         * {@linkplain org.apache.sis.referencing.AbstractIdentifiedObject#getIdentifiers() identifiers} are omitted.
-         *
-         * <div class="note"><b>Example:</b>
-         * The <cite>Mercator (variant A)</cite> example given in {@link DefaultParameterDescriptorGroup} javadoc
-         * formatted at this level produces a text like below:
-         *
-         * {@preformat text
-         *   EPSG: Mercator (variant A)
-         *   ┌────────────────────────────────┬────────┬────────────┬───────────────┬───────────────┐
-         *   │ Name (EPSG)                    │ Type   │ Obligation │ Value domain  │ Default value │
-         *   ├────────────────────────────────┼────────┼────────────┼───────────────┼───────────────┤
-         *   │ Latitude of natural origin     │ Double │ Mandatory  │  [-80 … 84]°  │         0.0°  │
-         *   │ Longitude of natural origin    │ Double │ Mandatory  │ [-180 … 180]° │         0.0°  │
-         *   │ Scale factor at natural origin │ Double │ Mandatory  │    (0 … ∞)    │         1.0   │
-         *   │ False easting                  │ Double │ Mandatory  │   (-∞ … ∞) m  │         0.0 m │
-         *   │ False northing                 │ Double │ Mandatory  │   (-∞ … ∞) m  │         0.0 m │
-         *   └────────────────────────────────┴────────┴────────────┴───────────────┴───────────────┘
-         * }
-         * </div>
-         */
-        BRIEF,
-
-        /**
-         * Limits the content to names and aliases in a tabular format. In addition to parameters,
-         * this level can also format array of operation method, coordinate reference system, <i>etc.</i>
-         * The summary contains the identifier names and aliases aligned in a table.
-         *
-         * <div class="note"><b>Example:</b>
-         * The <cite>Mercator (variant A)</cite> example given in {@link ParameterBuilder} javadoc
-         * formatted at this level produces a text like below:
-         *
-         * {@preformat text
-         *   EPSG: Mercator (variant A)
-         *   ┌────────────────────────────────┬────────────────────┐
-         *   │ EPSG                           │ OGC                │
-         *   ├────────────────────────────────┼────────────────────┤
-         *   │ Latitude of natural origin     │ latitude_of_origin │
-         *   │ Longitude of natural origin    │ central_meridian   │
-         *   │ Scale factor at natural origin │ scale_factor       │
-         *   │ False easting                  │ false_easting      │
-         *   │ False northing                 │ false_northing     │
-         *   └────────────────────────────────┴────────────────────┘
-         * }
-         * </div>
-         *
-         * <p><b>Tip:</b> The table formatted by default may be quite large. It is recommended to invoke
-         * {@link ParameterFormat#setPreferredCodespaces(String[])} before to format in order to reduce the
-         * amount of columns to display.</p>
-         */
-        NAME_SUMMARY
-    }
+    private static final AtomicReference<FeatureFormat> INSTANCE = new AtomicReference<>();
 
     /**
      * The locale for international strings.
@@ -233,34 +64,12 @@ public class ParameterFormat extends Tab
     private final Locale displayLocale;
 
     /**
-     * The amount of information to put in the table.
-     *
-     * @see #getContentLevel()
-     */
-    private ContentLevel contentLevel = ContentLevel.BRIEF;
-
-    /**
-     * If the identifier should be written only for some code spaces, those code spaces.
-     * Otherwise {@code null}.
-     *
-     * @see #getPreferredCodespaces()
-     */
-    private Set<String> preferredCodespaces;
-
-    /**
-     * The colors for an output on X3.64 compatible terminal, or {@code null} if none.
-     *
-     * @see #getColors()
-     */
-    private Colors colors;
-
-    /**
      * Creates a new formatter for the default locale and timezone.
      */
-    public ParameterFormat() {
+    public FeatureFormat() {
         super(Locale.getDefault(Locale.Category.FORMAT), TimeZone.getDefault());
         displayLocale = Locale.getDefault(Locale.Category.DISPLAY);
-        columnSeparator = SEPARATOR;
+        columnSeparator = " │ ";
     }
 
     /**
@@ -269,15 +78,15 @@ public class ParameterFormat extends Tab
      * @param locale   The locale, or {@code null} for {@code Locale.ROOT}.
      * @param timezone The timezone, or {@code null} for UTC.
      */
-    public ParameterFormat(final Locale locale, final TimeZone timezone) {
+    public FeatureFormat(final Locale locale, final TimeZone timezone) {
         super(locale, timezone);
         displayLocale = (locale != null) ? locale : Locale.ROOT;
-        columnSeparator = SEPARATOR;
+        columnSeparator = " │ ";
     }
 
     /**
      * Returns the type of objects formatted by this class. This method has to return {@code Object.class}
-     * since it is the only common parent to all object types accepted by this formatter.
+     * since it is the only common parent to {@link Feature} and {@link FeatureType}.
      *
      * @return {@code Object.class}
      */
@@ -303,82 +112,6 @@ public class ParameterFormat extends Tab
     }
 
     /**
-     * Returns the amount of information to put in the table.
-     * The default value is {@link ContentLevel#BRIEF}.
-     *
-     * @return The table content.
-     */
-    public ContentLevel getContentLevel() {
-        return contentLevel;
-    }
-
-    /**
-     * Sets the amount of information to put in the table.
-     *
-     * @param level The amount of information to put in the table.
-     */
-    public void setContentLevel(final ContentLevel level) {
-        ArgumentChecks.ensureNonNull("level", level);
-        this.contentLevel = level;
-    }
-
-    /**
-     * Returns the code spaces of names, aliases and identifiers to show, or {@code null} if there is no restriction.
-     * This method returns the sequence specified by the last call to {@link #setPreferredCodespaces(String[])},
-     * without duplicated values.
-     *
-     * <p>The default value is {@code null}.</p>
-     *
-     * @return The code spaces of names and identifiers to show, or {@code null} if no restriction.
-     */
-    public String[] getPreferredCodespaces() {
-        return (preferredCodespaces != null) ? preferredCodespaces.toArray(new String[preferredCodespaces.size()]) : null;
-    }
-
-    /**
-     * Filters names, aliases and identifiers by their code spaces. If the given array is non-null, then the only names,
-     * aliases and identifiers to be formatted are those having a {@link ReferenceIdentifier#getCodeSpace()},
-     * {@link ScopedName#head()} or {@link GenericName#scope()} value in the given list, unless no name or alias
-     * matches this criterion.
-     *
-     * @param codespaces The preferred code spaces of names, aliases and identifiers to format, or {@code null}
-     *        for accepting all of them. Some typical values are {@code "EPSG"}, {@code "OGC"} or {@code "GeoTIFF"}.
-     */
-    public void setPreferredCodespaces(final String... codespaces) {
-        Set<String> copy = null;
-        if (codespaces != null) {
-            copy = CollectionsExt.immutableSet(true, codespaces);
-        }
-        this.preferredCodespaces = copy;
-    }
-
-    /**
-     * Returns {@code true} if a name, alias or identifier in the given codespace should be formatted.
-     */
-    private boolean isPreferredCodespace(final String codespace) {
-        return (preferredCodespaces == null) || preferredCodespaces.contains(codespace);
-    }
-
-    /**
-     * Returns the colors for an output on X3.64 compatible terminal, or {@code null} if none.
-     * The default value is {@code null}.
-     *
-     * @return The colors for an output on X3.64 compatible terminal, or {@code null} if none.
-     */
-    public Colors getColors() {
-        return colors;
-    }
-
-    /**
-     * Sets the colors for an output on X3.64 compatible terminal.
-     *
-     * @param colors The colors for an output on X3.64 compatible terminal, or {@code null} if none.
-     */
-    public void setColors(final Colors colors) {
-        this.colors = colors;
-    }
-
-    /**
      * Invoked when the formatter needs to move to the next column.
      */
     private void nextColumn(final TableAppender table) {
@@ -391,10 +124,8 @@ public class ParameterFormat extends Tab
      * The object may be an instance of any of the following types:
      *
      * <ul>
-     *   <li>{@link ParameterValueGroup}</li>
-     *   <li>{@link ParameterDescriptorGroup}</li>
-     *   <li>{@link OperationMethod}</li>
-     *   <li><code>{@linkplain IdentifiedObject}[]</code> — accepted only for {@link ContentLevel#NAME_SUMMARY}.</li>
+     *   <li>{@link Feature}</li>
+     *   <li>{@link FeatureType}</li>
      * </ul>
      *
      * @throws IOException If an error occurred while writing to the given appendable.
@@ -403,506 +134,144 @@ public class ParameterFormat extends Tab
     public void format(final Object object, final Appendable toAppendTo) throws IOException {
         ArgumentChecks.ensureNonNull("object",     object);
         ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
-        final boolean isSummary = contentLevel == ContentLevel.NAME_SUMMARY;
-        final ParameterDescriptorGroup descriptor;
-        final ParameterValueGroup      values;
-        final ReferenceIdentifier      name;
-        if (object instanceof ParameterValueGroup) {
-            values     = (ParameterValueGroup) object;
-            descriptor = values.getDescriptor();
-            name       = descriptor.getName();
-        } else if (object instanceof ParameterDescriptorGroup) {
-            descriptor = (ParameterDescriptorGroup) object;
-            values     = null;
-            name       = descriptor.getName();
-        } else if (object instanceof OperationMethod) {
-            final OperationMethod operation = (OperationMethod) object;
-            descriptor = operation.getParameters();
-            values     = null;
-            name       = operation.getName();
-        } else if (isSummary && object instanceof IdentifiedObject[]) {
-            formatSummary((IdentifiedObject[]) object, toAppendTo);
-            return;
+        final DefaultFeatureType featureType;
+        final DefaultFeature     feature;
+        if (object instanceof DefaultFeature) {
+            feature     = (DefaultFeature) object;
+            featureType = feature.getType();
+        } else if (object instanceof DefaultFeatureType) {
+            featureType = (DefaultFeatureType) object;
+            feature     = null;
         } else {
             throw new IllegalArgumentException(Errors.getResources(displayLocale)
                     .getString(Errors.Keys.UnsupportedType_1, object.getClass()));
         }
-        if (isSummary) {
-            final List<GeneralParameterDescriptor> parameters = descriptor.descriptors();
-            formatSummary(parameters.toArray(new IdentifiedObject[parameters.size()]), toAppendTo);
-        } else {
-            format(name.getCode(), descriptor, values, toAppendTo);
-        }
-    }
-
-    /**
-     * Implementation of public {@code format(…)} methods for all content levels except {@code NAME_SUMMARY}.
-     *
-     * @param  name       The group name, usually {@code descriptor.getName().getCode()}.
-     * @param  descriptor The parameter descriptor, usually {@code values.getDescriptor()}.
-     * @param  values     The parameter values, or {@code null} if none.
-     * @throws IOException If an error occurred while writing to the given appendable.
-     */
-    private void format(final String name, final ParameterDescriptorGroup group,
-            final ParameterValueGroup values, final Appendable out) throws IOException
-    {
-        final boolean    isBrief        = (contentLevel == ContentLevel.BRIEF);
-        final boolean    showObligation = !isBrief || (values == null);
-        final boolean    hasColors      = (colors != null);
-        final String     lineSeparator  = this.lineSeparator;
-        final ParameterTableRow header  = new ParameterTableRow(group, displayLocale, preferredCodespaces, isBrief);
-        final String    groupCodespace  = header.getCodeSpace();
-        /*
-         * Prepares the informations to be printed later as table rows. We scan all rows before to print them
-         * in order to compute the width of codespaces. During this process, we split the objects to be printed
-         * later in two collections: simple parameters are stored as (descriptor,value) pairs, while groups are
-         * stored in an other collection for deferred formatting after the simple parameters.
-         */
-        int codespaceWidth = 0;
-        final Collection<?> elements = (values != null) ? values.values() : group.descriptors();
-        final Map<GeneralParameterDescriptor, ParameterTableRow> descriptorValues =
-                new LinkedHashMap<>(hashMapCapacity(elements.size()));
-        List<Object> deferredGroups = null; // To be created only if needed (it is usually not).
-        for (final Object element : elements) {
-            final GeneralParameterValue parameter;
-            final GeneralParameterDescriptor descriptor;
-            if (values != null) {
-                parameter  = (GeneralParameterValue) element;
-                descriptor = parameter.getDescriptor();
-            } else {
-                parameter  = null;
-                descriptor = (GeneralParameterDescriptor) element;
-            }
-            if (descriptor instanceof ParameterDescriptorGroup) {
-                if (deferredGroups == null) {
-                    deferredGroups = new ArrayList<>(4);
-                }
-                deferredGroups.add(element);
-                continue;
-            }
-            /*
-             * In the vast majority of cases, there is only one value for each parameter. However
-             * if we find more than one value, we will append all extra occurrences in a "multiple
-             * values" list to be formatted in the same row.
-             */
-            Object value = null;
-            Unit<?> unit = null;
-            if (parameter instanceof ParameterValue<?>) {
-                final ParameterValue<?> p = (ParameterValue<?>) parameter;
-                value = p.getValue();
-                unit  = p.getUnit();
-            } else if (descriptor instanceof ParameterDescriptor<?>) {
-                final ParameterDescriptor<?> p = (ParameterDescriptor<?>) descriptor;
-                value = p.getDefaultValue();
-                unit  = p.getUnit();
-            }
-            ParameterTableRow row = descriptorValues.get(descriptor);
-            if (row == null) {
-                row = new ParameterTableRow(descriptor, displayLocale, preferredCodespaces, isBrief);
-                descriptorValues.put(descriptor, row);
-                if (row.codespaceWidth > codespaceWidth) {
-                    codespaceWidth = row.codespaceWidth;
-                }
-            }
-            row.addValue(value, unit);
-        }
-        /*
-         * Finished to collect the values. Now transform the values:
-         *
-         *   - Singleton value of array types (either primitive or not) are expanded to a list.
-         *   - Values are formatted.
-         *   - Value domains are formatted.
-         *   - Position of the character on which to do the alignment are remembered.
-         */
-        int     unitWidth             = 0;
-        int     valueDomainAlignment  = 0;
-        boolean writeCodespaces       = (groupCodespace == null);
-        final   StringBuffer  buffer  = new StringBuffer();
-        final   FieldPosition dummyFP = new FieldPosition(-1);
-        for (final Map.Entry<GeneralParameterDescriptor,ParameterTableRow> entry : descriptorValues.entrySet()) {
-            final GeneralParameterDescriptor descriptor = entry.getKey();
-            if (descriptor instanceof ParameterDescriptor<?>) {
-                final ParameterTableRow row = entry.getValue();
-                /*
-                 * Verify if all rows use the same codespace than the header, in which case we can omit
-                 * row codespace formatting.
-                 */
-                if (!writeCodespaces && !groupCodespace.equals(entry.getValue().getCodeSpace())) {
-                    writeCodespaces = true;
-                }
-                /*
-                 * Format the value domain, so we can compute the character position on which to perform alignment.
-                 */
-                final Range<?> valueDomain = Parameters.getValueDomain((ParameterDescriptor<?>) descriptor);
-                if (valueDomain != null) {
-                    final int p = row.setValueDomain(valueDomain, getFormat(Range.class), buffer);
-                    if (p > valueDomainAlignment) {
-                        valueDomainAlignment = p;
-                    }
-                }
-                /*
-                 * Singleton array conversion. Because it may be an array of primitive types, we can not just
-                 * cast to Object[]. Then formats the units, with a space before the unit if the symbol is a
-                 * letter or digit (i.e. we do not put a space in front of ° symbol for instance).
-                 */
-                row.expandSingleton();
-                final int length = row.units.size();
-                for (int i=0; i<length; i++) {
-                    final Object unit = row.units.get(i);
-                    if (unit != null) {
-                        if (getFormat(Unit.class).format(unit, buffer, dummyFP).length() != 0) {
-                            if (Character.isLetterOrDigit(buffer.codePointAt(0))) {
-                                buffer.insert(0, ' ');
-                            }
-                        }
-                        final String symbol = buffer.toString();
-                        row.units.set(i, symbol);
-                        buffer.setLength(0);
-                        final int p = symbol.length();
-                        if (p > unitWidth) {
-                            unitWidth = p;
-                        }
-                    }
-                }
-            }
-        }
-        /*
-         * Finished to prepare information. Now begin the actual writing.
-         * First, formats the table header (i.e. the column names).
-         */
-        final Vocabulary resources = Vocabulary.getResources(displayLocale);
-        header.writeIdentifiers(out, true, colors, false, lineSeparator);
-        out.append(lineSeparator);
-        final char horizontalBorder = isBrief ? '─' : '═';
-        final TableAppender table = (isBrief || !columnSeparator.equals(SEPARATOR)) ?
-                new TableAppender(out, columnSeparator) : new TableAppender(out);
+        toAppendTo.append(toString(featureType.getName())).append(getLineSeparator());
+        final StringBuffer  buffer    = new StringBuffer();
+        final FieldPosition dummyFP   = new FieldPosition(-1);
+        final Vocabulary    resources = Vocabulary.getResources(displayLocale);
+        final TableAppender table     = new TableAppender(toAppendTo, columnSeparator);
         table.setMultiLinesCells(true);
-        table.nextLine(horizontalBorder);
-        for (int i=0; ; i++) {
-            boolean end = false;
+        table.nextLine('─');
+header: for (int i=0; ; i++) {
             final short key;
             switch (i) {
-                case 0: {
-                    key = Vocabulary.Keys.Name;
-                    break;
-                }
-                case 1: {
-                    key = Vocabulary.Keys.Type;
-                    break;
-                }
-                case 2: {
-                    if (!showObligation) {
-                       continue;
-                    }
-                    key = Vocabulary.Keys.Obligation;
-                    break;
-                }
-                case 3: {
-                    key = Vocabulary.Keys.ValueDomain;
-                    break;
-                }
-                case 4: {
-                    key = (values == null) ? Vocabulary.Keys.DefaultValue : Vocabulary.Keys.Value;
-                    end = true;
-                    break;
-                }
-                default: throw new AssertionError(i);
+                case 0:                     key = Vocabulary.Keys.Name; break;
+                case 1:  nextColumn(table); key = Vocabulary.Keys.Type; break;
+                case 2:  nextColumn(table); key = Vocabulary.Keys.Cardinality; break;
+                case 3:  nextColumn(table); key = (feature != null) ? Vocabulary.Keys.Value : Vocabulary.Keys.DefaultValue; break;
+                default: break header;
             }
-            if (hasColors) table.append(X364.BOLD.sequence());
             table.append(resources.getString(key));
-            if (hasColors) table.append(X364.NORMAL.sequence());
-            if (!writeCodespaces && i == 0) {
-                table.append(" (").append(groupCodespace).append(')');
-            }
-            if (end) break;
-            nextColumn(table);
         }
         table.nextLine();
+        table.nextLine('─');
         /*
-         * Now process to the formatting of (descriptor,value) pairs. Each descriptor's alias
-         * will be formatted on its own line in a table row. If there is more than one value,
-         * then each value will be formatted on its own line as well. Note that the values may
-         * be null if there is none.
+         * Done writing the header. Now write all property rows.
+         * Rows without value will be skipped only if optional.
          */
-        char horizontalLine = horizontalBorder;
-        for (final Map.Entry<GeneralParameterDescriptor,ParameterTableRow> entry : descriptorValues.entrySet()) {
-            if (horizontalLine != 0) {
-                table.nextLine('─');
-            }
-            horizontalLine = isBrief ? 0 : '─';
-            final ParameterTableRow row = entry.getValue();
-            row.codespaceWidth = codespaceWidth;
-            row.writeIdentifiers(table, writeCodespaces, null, hasColors, lineSeparator);
-            nextColumn(table);
-            final GeneralParameterDescriptor generalDescriptor = entry.getKey();
-            if (generalDescriptor instanceof ParameterDescriptor<?>) {
-                /*
-                 * Writes value type.
-                 */
-                final ParameterDescriptor<?> descriptor = (ParameterDescriptor<?>) generalDescriptor;
-                final Class<?> valueClass = descriptor.getValueClass();
-                table.append(getFormat(Class.class).format(valueClass, buffer, dummyFP).toString());
-                nextColumn(table);
-                buffer.setLength(0);
-                /*
-                 * Writes the obligation (mandatory or optional).
-                 */
-                if (showObligation) {
-                    final int minimumOccurs = descriptor.getMinimumOccurs();
-                    final int maximumOccurs = descriptor.getMaximumOccurs();
-                    if (maximumOccurs == 1) {
-                        table.append(resources.getString(minimumOccurs == 0 ?
-                                Vocabulary.Keys.Optional : Vocabulary.Keys.Mandatory));
-                    } else {
-                        final Format f = getFormat(Integer.class);
-                        table.append(f.format(minimumOccurs, buffer, dummyFP).toString()).append(" … ");
-                        buffer.setLength(0);
-                        if (maximumOccurs == Integer.MAX_VALUE) {
-                            table.append('∞');
-                        } else {
-                            table.append(f.format(maximumOccurs, buffer, dummyFP).toString());
-                            buffer.setLength(0);
-                        }
-                    }
-                    nextColumn(table);
-                }
-                /*
-                 * Writes minimum and maximum values, together with the unit of measurement (if any).
-                 */
-                final String valueDomain = row.valueDomain;
-                if (valueDomain != null) {
-                    table.append(CharSequences.spaces(valueDomainAlignment - row.valueDomainAlignment)).append(valueDomain);
-                }
-                nextColumn(table);
-                /*
-                 * Writes the values, each on its own line, together with their unit of measurement.
-                 */
-                table.setCellAlignment(TableAppender.ALIGN_RIGHT);
-                final int length = row.values.size();
-                for (int i=0; i<length; i++) {
-                    Object value = row.values.get(i);
-                    if (value != null) {
-                        if (i != 0) {
-                            table.append(lineSeparator);
-                        }
-                        final Format format = getFormat(value.getClass());
-                        if (format != null) {
-                            value = format.format(value, buffer, dummyFP);
-                        }
-                        table.append(value.toString());
-                        buffer.setLength(0);
-                        int pad = unitWidth;
-                        final String unit = (String) row.units.get(i);
-                        if (unit != null) {
-                            table.append(unit);
-                            pad -= unit.length();
-                        }
-                        table.append(CharSequences.spaces(pad));
+        for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
+            Object value;
+            if (feature != null) {
+                value = feature.getPropertyValue(propertyType.getName().toString());
+                if (value == null) {
+                    if (propertyType instanceof FieldType && ((FieldType) propertyType).getMinimumOccurs() == 0) {
+                        continue; // If no value, skip the full row.
                     }
                 }
+            } else if (propertyType instanceof DefaultAttributeType<?>) {
+                value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
+            } else {
+                value = null;
             }
-            table.nextLine();
-            table.setCellAlignment(TableAppender.ALIGN_LEFT);
-        }
-        table.nextLine(horizontalBorder);
-        table.flush();
-        /*
-         * Now formats all groups deferred to the end of this table, with recursive calls to
-         * this method (recursive calls use their own TableWriter instance, so they may result
-         * in a different cell layout). Most of the time, there is no such additional group.
-         */
-        if (deferredGroups != null) {
-            for (final Object element : deferredGroups) {
-                final ParameterValueGroup value;
-                final ParameterDescriptorGroup descriptor;
-                if (element instanceof ParameterValueGroup) {
-                    value = (ParameterValueGroup) element;
-                    descriptor = value.getDescriptor();
-                } else {
-                    value = null;
-                    descriptor = (ParameterDescriptorGroup) element;
-                }
-                out.append(lineSeparator);
-                format(name + '/' + descriptor.getName().getCode(), descriptor, value, out);
-            }
-        }
-    }
-
-    /**
-     * Implementation of public {@code format(…)} methods for {@code NAME_SUMMARY} content level.
-     *
-     * @param  objects The collection of objects to format.
-     * @param  out The stream or buffer where to write the summary.
-     * @throws IOException if an error occurred will writing to the given appendable.
-     */
-    private void formatSummary(final IdentifiedObject[] objects, final Appendable out) throws IOException {
-        final Vocabulary resources = Vocabulary.getResources(displayLocale);
-        /*
-         * Prepares all rows before we write them to the output stream, because not all
-         * identified objects may have names with the same scopes in the same order. We
-         * also need to iterate over all rows in order to know the number of columns.
-         *
-         * The first column is reserved for the identifier. We put null as a sentinal key for
-         * that column name, to be replaced later by "Identifier" in user locale. We can not
-         * put the localized strings in the map right now because they could conflict with
-         * the scope of some alias to be processed below.
-         */
-        boolean hasIdentifiers = false;
-        final List<String[]> rows = new ArrayList<>();
-        final Map<String,Integer> columnIndices = new LinkedHashMap<>();
-        columnIndices.put(null, 0); // See above comment for the meaning of "null" here.
-        if (preferredCodespaces != null) {
-            for (final String codespace : preferredCodespaces) {
-                columnIndices.put(codespace, columnIndices.size());
-            }
-        }
-        for (final IdentifiedObject object : objects) {
-            String[] row = new String[columnIndices.size()]; // Will growth later if needed.
             /*
-             * Put the first identifier in the first column. If no identifier has a codespace in the list
-             * supplied by the user, then we will use the first identifier (any codespace) as a fallback.
+             * Column 0 - Name.
              */
-            final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
-            if (identifiers != null) { // Paranoiac check.
-                ReferenceIdentifier identifier = null;
-                for (final ReferenceIdentifier candidate : identifiers) {
-                    if (candidate != null) { // Paranoiac check.
-                        if (isPreferredCodespace(candidate.getCodeSpace())) {
-                            identifier = candidate;
-                            break; // Format now.
-                        }
-                        if (identifier == null) {
-                            identifier = candidate; // To be used as a fallback if we find nothing better.
-                        }
-                    }
-                }
-                if (identifier != null) {
-                    row[0] = IdentifiedObjects.toString(identifier);
-                    hasIdentifiers = true;
-                }
-            }
+            table.append(toString(propertyType.getName()));
+            nextColumn(table);
             /*
-             * If the name's codespace is in the list of codespaces asked by the user, add that name
-             * in the current row and clear the 'name' locale variable. Otherwise, keep the 'name'
-             * locale variable in case we found no alias to format.
+             * Column 1 and 2 - Type and cardinality.
              */
-            ReferenceIdentifier name = object.getName();
-            if (name != null) { // Paranoiac check.
-                final String codespace = name.getCodeSpace();
-                if (isPreferredCodespace(codespace)) {
-                    row = putIfAbsent(resources, row, columnIndices, codespace, name.getCode());
-                    name = null;
-                }
+            final String   valueType;
+            final Class<?> valueClass;
+            final int minimumOccurs, maximumOccurs;
+            if (propertyType instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) propertyType;
+                minimumOccurs = pt.getMinimumOccurs();
+                maximumOccurs = pt.getMaximumOccurs();
+                valueClass    = pt.getValueClass();
+                valueType     = getFormat(Class.class).format(valueClass, buffer, dummyFP).toString();
+                buffer.setLength(0);
+            } else if (propertyType instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole pt = (DefaultAssociationRole) propertyType;
+                minimumOccurs = pt.getMinimumOccurs();
+                maximumOccurs = pt.getMaximumOccurs();
+                valueType     = toString(pt.getValueType().getName());
+                valueClass    = DefaultFeature.class;
+            } else if (propertyType instanceof DefaultOperation) {
+                final DefaultAttributeType<?> resultType = ((DefaultOperation) propertyType).getResult();
+                valueType   = toString(resultType.getName());
+                valueClass  = null;
+                minimumOccurs = -1;
+                maximumOccurs = -1;
+            } else {
+                valueType   = "";
+                valueClass  = null;
+                minimumOccurs = -1;
+                maximumOccurs = -1;
             }
-            /*
-             * Put all aliases having a codespace in the list asked by the user.
-             */
-            final Collection<GenericName> aliases = object.getAlias();
-            if (aliases != null) { // Paranoiac check.
-                for (final GenericName alias : aliases) {
-                    if (alias != null) { // Paranoiac check.
-                        final String codespace = NameToIdentifier.getCodespaceOrAuthority(alias, displayLocale);
-                        if (isPreferredCodespace(codespace)) {
-                            row = putIfAbsent(resources, row, columnIndices, codespace,
-                                    alias.tip().toInternationalString().toString(displayLocale));
-                            name = null;
-                        }
-                    }
+            table.append(valueType);
+            nextColumn(table);
+            if (maximumOccurs >= 0) {
+                final Format format = getFormat(Integer.class);
+                table.append('[').append(format.format(minimumOccurs, buffer, dummyFP)).append(" … ");
+                buffer.setLength(0);
+                if (maximumOccurs != Integer.MAX_VALUE) {
+                    table.append(format.format(maximumOccurs, buffer, dummyFP));
+                } else {
+                    table.append('∞');
                 }
+                buffer.setLength(0);
+                table.append(']');
             }
+            nextColumn(table);
             /*
-             * If no name and no alias have a codespace in the list of codespaces asked by the user,
-             * force the addition of primary name regardless its codespace.
+             * Column 3 - Value or default value.
              */
-            if (name != null) {
-                row = putIfAbsent(resources, row, columnIndices, name.getCodeSpace(), name.getCode());
-            }
-            rows.add(row);
-        }
-        /*
-         * Writes the table. The header will contain one column for each codespace in the order declared
-         * by the user. If the user did not specified any codespace, or if we had to write codespace not
-         * on the user list, then those codespaces will be written in the order we found them.
-         */
-        final boolean hasColors = (colors != null);
-        final TableAppender table = new TableAppender(out, columnSeparator);
-        table.setMultiLinesCells(true);
-        table.appendHorizontalSeparator();
-        for (String codespace : columnIndices.keySet()) {
-            if (codespace == null) {
-                if (!hasIdentifiers) continue; // Skip empty column.
-                codespace = resources.getString(Vocabulary.Keys.Identifier);
-            }
-            if (hasColors) {
-                codespace = X364.BOLD.sequence() + codespace + X364.NORMAL.sequence();
-            }
-            table.append(codespace);
-            nextColumn(table);
-        }
-        table.appendHorizontalSeparator();
-        /*
-         * Writes row content.
-         */
-        final int numColumns = columnIndices.size();
-        for (final String[] row : rows) {
-            for (int i=hasIdentifiers ? 0 : 1; i<numColumns; i++) {
-                if (i < row.length) {
-                    final String name = row[i];
-                    if (name != null) {
-                        table.append(name);
-                    }
+            if (value != null) {
+                final Format format = getFormat(valueClass);
+                if (format != null) {
+                    value = format.format(value, buffer, dummyFP);
+                } else if (value instanceof InternationalString) {
+                    value = ((InternationalString) value).toString(displayLocale);
+                } else if (value instanceof DefaultFeature && propertyType instanceof DefaultAssociationRole) {
+                    value = ((DefaultFeature) value).getPropertyValue(
+                            ((DefaultAssociationRole) propertyType).getTitleProperty());
                 }
-                nextColumn(table);
+                table.append(value.toString());
+                buffer.setLength(0);
             }
             table.nextLine();
         }
-        table.appendHorizontalSeparator();
+        table.nextLine('─');
         table.flush();
     }
 
     /**
-     * Stores a value in the given position of the given row, expanding the array if needed.
-     * This operation is performed only if no value already exists in the cell.
-     *
-     * @param  row           All columns in a single row.
-     * @param  columnIndices Indices of columns for each codespace.
-     * @param  codespace     The codespace of the name or alias to add.
-     * @param  name          The code of the name or alias to add.
-     * @return {@code row}, or a new array if it was necessary to expand the row.
-     */
-    private static String[] putIfAbsent(final Vocabulary resources, String[] row,
-            final Map<String,Integer> columnIndices, String codespace, final String name)
-    {
-        if (codespace == null) {
-            codespace = resources.getString(Vocabulary.Keys.Unnamed);
-        }
-        final Integer columnIndex = columnIndices.get(codespace);
-        final int i;
-        if (columnIndex != null) {
-            i = columnIndex;
-        } else {
-            i = columnIndices.size();
-            columnIndices.put(codespace, i);
-        }
-        if (i >= row.length) {
-            row = Arrays.copyOf(row, i + 1);
-        }
-        if (row[i] == null) {
-            row[i] = name;
-        }
-        return row;
-    }
-
-    /**
-     * Returns a shared instance of {@code ParameterFormat} if possible, or a new one otherwise.
+     * Returns the display name for the given {@code GenericName}.
      */
-    private static ParameterFormat getSharedInstance(final Colors colors) {
-        ParameterFormat f = INSTANCE.getAndSet(null);
-        if (f == null) {
-            f = new ParameterFormat();
+    private String toString(final GenericName name) {
+        if (name == null) { // Should not be null, but let be safe.
+            return "";
+        }
+        final InternationalString i18n = name.toInternationalString();
+        if (i18n != null) { // Should not be null, but let be safe.
+            final String s = i18n.toString(displayLocale);
+            if (s != null) {
+                return s;
+            }
         }
-        f.setColors(colors);
-        return f;
+        return name.toString();
     }
 
     /**
@@ -910,28 +279,16 @@ public class ParameterFormat extends Tab
      * This is used for {@link DefaultParameterDescriptorGroup#toString()} implementation.
      */
     static String sharedFormat(final Object object) {
-        final ParameterFormat f = getSharedInstance(null);
+        FeatureFormat f = INSTANCE.getAndSet(null);
+        if (f == null) {
+            f = new FeatureFormat();
+        }
         final String s = f.format(object);
         INSTANCE.set(f);
         return s;
     }
 
     /**
-     * Writes the given object to the console using a shared instance of {@code ParameterFormat}.
-     */
-    static void print(final Object object) {
-        final Console console = System.console();
-        final Appendable out = (console != null) ? console.writer() : System.out;
-        final ParameterFormat f = getSharedInstance(Colors.NAMING);
-        try {
-            f.format(object, out);
-        } catch (IOException e) {
-            throw new AssertionError(e); // Should never happen, since we are writing to stdout.
-        }
-        INSTANCE.set(f);
-    }
-
-    /**
      * Not yet supported.
      *
      * @return Currently never return.

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java [UTF-8] Fri May 16 14:50:30 2014
@@ -116,7 +116,7 @@ abstract class FieldType extends Propert
     /**
      * Implementation of {@link #toString()} to be shared by subclasses and {@link DefaultAttribute#toString()}.
      */
-    final StringBuilder toString(final String typeName, final String valueName) {
+    final StringBuilder toString(final String typeName, final Object valueName) {
         final StringBuilder buffer = new StringBuilder(40).append(typeName).append('[');
         final GenericName name = getName();
         if (name != null) {

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java [UTF-8] Fri May 16 14:50:30 2014
@@ -46,7 +46,7 @@ public final strictfp class DefaultAssoc
     static DefaultAssociation twinTown() {
         final DefaultFeature twinTown = new DefaultFeature(DefaultFeatureTypeTest.city());
         twinTown.setPropertyValue("city", "Le Mans");
-        twinTown.setPropertyValue("population", 148169);
+        twinTown.setPropertyValue("population", 143240); // In 2011.
         final DefaultAssociation association = new DefaultAssociation(DefaultAssociationRoleTest.twinTown());
         association.setValue(twinTown);
         return association;

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java [UTF-8] Fri May 16 14:50:30 2014
@@ -21,6 +21,7 @@ import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import static java.util.Collections.singletonMap;
 
 
 /**
@@ -38,6 +39,27 @@ import static org.junit.Assert.*;
 })
 public final strictfp class DefaultFeatureTest extends TestCase {
     /**
+     * Creates a feature for twin towns.
+     */
+    static DefaultFeature twinTown() {
+        final DefaultAssociationRole twinTown = DefaultAssociationRoleTest.twinTown();
+        final DefaultFeatureType     city     = twinTown.getValueType();
+        final DefaultFeatureType     type     = new DefaultFeatureType(
+                singletonMap(DefaultFeatureType.NAME_KEY, "Twin town"), false,
+                new DefaultFeatureType[] {city}, twinTown);
+
+        final DefaultFeature leMans = new DefaultFeature(type);
+        leMans.setPropertyValue("city", "Le Mans");
+        leMans.setPropertyValue("population", 143240); // In 2011.
+
+        final DefaultFeature paderborn = new DefaultFeature(type);
+        paderborn.setPropertyValue("city", "Paderborn");
+        paderborn.setPropertyValue("population", 143174); // December 31th, 2011
+        paderborn.setPropertyValue("twin town", leMans);
+        return paderborn;
+    }
+
+    /**
      * Tests the construction of a simple feature without super-types.
      */
     @Test

Added: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java?rev=1595219&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java (added)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java [UTF-8] Fri May 16 14:50:30 2014
@@ -0,0 +1,73 @@
+/*
+ * 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.feature;
+
+import java.util.Locale;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests {@link FeatureFormat}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.5
+ * @module
+ */
+@DependsOn(DefaultFeatureTest.class)
+public final strictfp class FeatureFormatTest extends TestCase {
+    /**
+     * Tests the formatting of a {@link DefaultFeatureType}.
+     */
+    @Test
+    public void testFeatureType() {
+        final DefaultFeatureType feature = DefaultFeatureTypeTest.metropolis();
+        final FeatureFormat format = new FeatureFormat(Locale.US, null);
+        final String text = format.format(feature);
+        assertMultilinesEquals("Metropolis\n" +
+                "┌────────────┬──────────────┬─────────────┬───────────────┐\n" +
+                "│ Name       │ Type         │ Cardinality │ Default value │\n" +
+                "├────────────┼──────────────┼─────────────┼───────────────┤\n" +
+                "│ city       │ String       │ [1 … 1]     │ Utopia        │\n" +
+                "│ population │ Integer      │ [1 … 1]     │               │\n" +
+                "│ region     │ CharSequence │ [1 … 1]     │               │\n" +
+                "│ isGlobal   │ Boolean      │ [1 … 1]     │               │\n" +
+                "└────────────┴──────────────┴─────────────┴───────────────┘\n", text);
+    }
+
+    /**
+     * Tests the formatting of a {@link DefaultFeature}.
+     */
+    @Test
+    public void testFeature() {
+        final DefaultFeature feature = DefaultFeatureTest.twinTown();
+        final FeatureFormat format = new FeatureFormat(Locale.US, null);
+        final String text = format.format(feature);
+        assertMultilinesEquals("Twin town\n" +
+                "┌────────────┬─────────┬─────────────┬───────────┐\n" +
+                "│ Name       │ Type    │ Cardinality │ Value     │\n" +
+                "├────────────┼─────────┼─────────────┼───────────┤\n" +
+                "│ city       │ String  │ [1 … 1]     │ Paderborn │\n" +
+                "│ population │ Integer │ [1 … 1]     │ 143,174   │\n" +
+                "│ twin town  │ City    │ [0 … ∞]     │ Le Mans   │\n" +
+                "└────────────┴─────────┴─────────────┴───────────┘\n", text);
+    }
+}

Propchange: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java [UTF-8] Fri May 16 14:50:30 2014
@@ -37,7 +37,8 @@ import org.junit.BeforeClass;
     org.apache.sis.feature.DefaultFeatureTest.class,
     org.apache.sis.feature.DefaultAssociationRoleTest.class,
     org.apache.sis.feature.DefaultAssociationTest.class,
-    org.apache.sis.feature.DefaultOperationTest.class
+    org.apache.sis.feature.DefaultOperationTest.class,
+    org.apache.sis.feature.FeatureFormatTest.class
 })
 public final strictfp class FeatureTestSuite extends TestSuite {
     /**

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] Fri May 16 14:50:30 2014
@@ -91,8 +91,8 @@ import static org.apache.sis.util.collec
  *   <caption>Formattable object types</caption>
  *   <tr><th>Class</th> <th>Remarks</th></tr>
  *   <tr><td>{@link ParameterValueGroup}</td><td><cite>Default values</cite> column is replaced by a column of the actual values.</td></tr>
- *   <tr><td>{@link ParameterDescriptorGroup}</td><td>Table title is the parameter group name.</td></tr>
- *   <tr><td>{@link OperationMethod}</td><td>Table title is the method name (not necessarily the same than parameter group name).</td></tr>
+ *   <tr><td>{@link ParameterDescriptorGroup}</td><td>Table caption is the parameter group name.</td></tr>
+ *   <tr><td>{@link OperationMethod}</td><td>Table caption is the method name (not necessarily the same than parameter group name).</td></tr>
  *   <tr><td><code>{@linkplain IdentifiedObject}[]</code></td><td>Accepted only for {@link ContentLevel#NAME_SUMMARY}.</td></tr>
  * </table>
  *

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java?rev=1595219&r1=1595218&r2=1595219&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/io/CompoundFormat.java [UTF-8] Fri May 16 14:50:30 2014
@@ -358,7 +358,7 @@ public abstract class CompoundFormat<T> 
      * See {@link #createFormat(Class)} for the list of value types recognized by the default
      * {@code CompoundFormat} implementation.
      *
-     * @param  valueType The base type of values to parse or format.
+     * @param  valueType The base type of values to parse or format, or {@code null} if unknown.
      * @return The format to use for parsing and formatting values of the given type or any
      *         parent type, or {@code null} if none.
      */