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 2013/04/26 19:31:35 UTC

svn commit: r1476307 - in /sis/branches/JDK7/sis-metadata/src: main/java/org/apache/sis/metadata/ test/java/org/apache/sis/metadata/

Author: desruisseaux
Date: Fri Apr 26 17:31:35 2013
New Revision: 1476307

URL: http://svn.apache.org/r1476307
Log:
First draft of MetadataTreeTable (not yet finished).

Added:
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java   (with props)
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java   (with props)
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java   (with props)
    sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java   (with props)
Modified:
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
    sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java

Modified: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1476307&r1=1476306&r2=1476307&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -186,6 +186,7 @@ public abstract class AbstractMetadata i
      * the UML identifier.</p>
      *
      * <p>The default implementation is equivalent to the following method call:</p>
+     *
      * {@preformat java
      *   return getStandard().asValueMap(this, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
      * }
@@ -203,13 +204,21 @@ public abstract class AbstractMetadata i
 
     /**
      * Returns the property types and values as a tree table.
-     * In the current implementation, the tree is not live (i.e. changes in metadata are not
-     * reflected in the tree). However it may be improved in a future SIS implementation.
+     * The tree table is backed by the metadata object using Java reflection, so changes in the
+     * underlying metadata object are immediately reflected in the tree table and conversely.
+     *
+     * <p>The default implementation is equivalent to the following method call:</p>
+     *
+     * {@preformat java
+     *   return getStandard().asTreeTable(this, ValueExistencePolicy.NON_EMPTY);
+     * }
+     *
+     * @return A tree table representation of the specified metadata.
      *
-     * @return The property types and values as a tree table.
+     * @see MetadataStandard#asTreeTable(Object, ValueExistencePolicy)
      */
     public TreeTable asTreeTable() {
-        return getStandard().asTreeTable(this);
+        return getStandard().asTreeTable(this, ValueExistencePolicy.NON_EMPTY);
     }
 
     /**

Modified: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java?rev=1476307&r1=1476306&r2=1476307&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -595,18 +595,24 @@ public class MetadataStandard {
 
     /**
      * Returns the specified metadata object as a tree table.
-     * In the current implementation, the tree is not live (i.e. changes in metadata are not
-     * reflected in the tree). However it may be improved in a future SIS implementation.
+     * The tree table is backed by the metadata object using Java reflection, so changes in the
+     * underlying metadata object are immediately reflected in the tree table and conversely.
      *
-     * @param  metadata The metadata object to formats as a tree table.
+     * @param  metadata The metadata object to view as a tree table.
+     * @param  valuePolicy Whether the property having null value or empty collection shall be
+     *         included in the tree.
      * @return A tree table representation of the specified metadata.
      * @throws ClassCastException if the metadata object doesn't implement a metadata
      *         interface of the expected package.
      *
      * @see AbstractMetadata#asTreeTable()
      */
-    public TreeTable asTreeTable(final Object metadata) throws ClassCastException {
-        throw new UnsupportedOperationException("Not yet implemented"); // TODO
+    public TreeTable asTreeTable(final Object metadata, final ValueExistencePolicy valuePolicy)
+            throws ClassCastException
+    {
+        ensureNonNull("metadata",    metadata);
+        ensureNonNull("valuePolicy", valuePolicy);
+        return new MetadataTreeTable(this, metadata, valuePolicy);
     }
 
     /**

Added: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java?rev=1476307&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java (added)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -0,0 +1,596 @@
+/*
+ * 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.metadata;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.AbstractSequentialList;
+import java.util.NoSuchElementException;
+import java.util.ConcurrentModificationException;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Debug;
+
+
+/**
+ * The list of children to be returned by {@link MetadataTreeNode#getChildren()}.
+ * This list holds a reference to the metadata object at creation time; it does
+ * not track changes in {@code parent.getUserObject()}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+final class MetadataTreeChildren extends AbstractSequentialList<TreeTable.Node> {
+    /**
+     * The parent of the children to be returned by the iterator.
+     * Some useful information are available indirectly through this parent:
+     *
+     * <ul>
+     *   <li>{@link ValueExistencePolicy}: {@code parent.table.valuePolicy}</li>
+     * </ul>
+     *
+     * @see #childAt(int)
+     */
+    private final MetadataTreeNode parent;
+
+    /**
+     * The metadata object for which property values will be the elements of this list.
+     * This is typically an {@link AbstractMetadata} instance, but not necessarily.
+     * Any type for which {@link MetadataStandard#isMetadata(Class)} returns {@code true} is okay.
+     *
+     * <p>This field is a snapshot of the {@linkplain #parent} {@link MetadataTreeNode#getUserObject()}
+     * at creation time. This list does not track changes in the reference returned by the above-cited
+     * {@code getUserObject()}. In other words, changes in the {@code metadata} object will be reflected
+     * in this list, but if {@code parent.getUserObject()} returns a reference to another object, this
+     * change will not be reflected in this list.
+     */
+    final Object metadata;
+
+    /**
+     * The accessor to use for accessing the property names, types and values of the
+     * {@link #metadata} object. This is given at construction time and shall be the
+     * same than the following code:
+     *
+     * {@preformat java
+     *     accessor = parent.table.standard.getAccessor(metadata.getClass(), true);
+     * }
+     */
+    private final PropertyAccessor accessor;
+
+    /**
+     * The children to be returned by this list. All elements in this list are initially
+     * {@code null}, then created by {@link #childAt(int)} when first needed.
+     *
+     * <p>Not all elements in this array will be returned by the list iterator.
+     * The value needs to be verified for the {@link ValueExistencePolicy}.</p>
+     */
+    private final MetadataTreeNode[] children;
+
+    /**
+     * Creates a list of children for the specified metadata.
+     *
+     * @param parent   The parent for which this node is an element.
+     * @param metadata The metadata object for which property values will be the elements of this list.
+     * @param accessor The accessor to use for accessing the property names, types and values of the metadata object.
+     */
+    MetadataTreeChildren(final MetadataTreeNode parent, final Object metadata, final PropertyAccessor accessor) {
+        this.parent   = parent;
+        this.metadata = metadata;
+        this.accessor = accessor;
+        children = new MetadataTreeNode[accessor.count()];
+    }
+
+    /**
+     * Clears the value at the given index. The given {@code index} is relative to
+     * the {@link #accessor} indexing, <strong>not</strong> to this list index.
+     *
+     * <p>The cleared elements may or may not be considered as removed, depending on the
+     * value policy. To check if the element shall be considered as removed (for example
+     * in order to update index), invoke {@code isSkipped(value)} after this method.</p>
+     *
+     * {@note We do not provide setter method because this <code>List</code> contract
+     *        requires the values to be instances of {@code TreeTable.Node}, which is
+     *        not very convenient in the case of our list view.}
+     *
+     * @param index The index in the accessor (<em>not</em> the index in this list).
+     */
+    final void clearAt(final int index) {
+        accessor.set(index, metadata, null, false);
+    }
+
+    /**
+     * Returns the value at the given index. The given {@code index} is relative to
+     * the {@link #accessor} indexing, <strong>not</strong> to this list index.
+     *
+     * @param  index The index in the accessor (<em>not</em> the index in this list).
+     * @return The value at the given index. May be {@code null} or a collection.
+     */
+    final Object valueAt(final int index) {
+        return accessor.get(index, metadata);
+    }
+
+    /**
+     * Returns {@code true} if the type at the given index is a collection. The given
+     * {@code index} is relative to the {@link #accessor} indexing, <strong>not</strong>
+     * to this list index.
+     *
+     * {@note We do not test <code>(value instanceof Collection)</code> because the value
+     *        could be any user's implementation. Nothing prevent users from implementing
+     *        the collection interface even for singleton elements if they wish.}
+     *
+     * @param  index The index in the accessor (<em>not</em> the index in this list).
+     * @return {@code true} if the value at the given index is a collection.
+     */
+    final boolean isCollection(final int index) {
+        return accessor.isCollection(index);
+    }
+
+    /**
+     * Returns {@code true} if the give value shall be skipped by the iterators,
+     * according the value policy.
+     *
+     * @param  value The value to test.
+     * @return {@code true} if the given value shall be skipped by the iterators.
+     */
+    final boolean isSkipped(final Object value) {
+        return parent.table.valuePolicy.isSkipped(value);
+    }
+
+    /**
+     * Returns the child at the given index, creating it if needed. The given {@code index}
+     * is relative to the {@link #accessor} indexing, <strong>not</strong> to this list index.
+     *
+     * <p>This method does not check if the child at the given index should be skipped.
+     * It is caller responsibility to do such verification before this method call.</p>
+     *
+     * @param  index The index in the accessor (<em>not</em> the index in this list).
+     * @param  childIndex If the property at {@link #index} is a collection, the index
+     *         in that collection (<em>not</em> the index in this list). Otherwise -1.
+     * @return The node to be returned by pulic API.
+     */
+    final MetadataTreeNode childAt(final int index, final int childIndex) {
+        MetadataTreeNode node = children[index];
+        if (childIndex >= 0) {
+            /*
+             * If the value is an element of a collection, we will cache only the last used value.
+             * We don't cache all elements in order to avoid yet more complex code, and this cover
+             * the majority of cases where the collection has only one element anyway.
+             */
+            if (node == null || ((MetadataTreeNode.CollectionElement) node).indexInList != childIndex) {
+                node = new MetadataTreeNode.CollectionElement(parent, metadata, accessor, index, childIndex);
+            }
+        } else {
+            /*
+             * If the property is a singleton (not an element of a collection), returns a more
+             * dynamic node which will fetch the value from the metadata object. This allows
+             * the node to reflect changes in the metadata object, and conversely.
+             */
+            if (node == null) {
+                node = new MetadataTreeNode.Element(parent, metadata, accessor, index);
+            }
+        }
+        children[index] = node;
+        return node;
+    }
+
+    /**
+     * Returns the maximal number of children. This is the number of all possible elements
+     * according the {@link #accessor}, including {@linkplain #isSkipped(Object) skipped}
+     * ones. This is <strong>not</strong> the list size.
+     */
+    final int childCount() {
+        return children.length;
+    }
+
+    /**
+     * Returns the number of elements in this list, ignoring the {@link #isSkipped(Object)
+     * skipped} ones.
+     */
+    @Override
+    public int size() {
+        return accessor.count(metadata, parent.table.valuePolicy, PropertyAccessor.COUNT_DEEP);
+    }
+
+    /**
+     * Returns {@code true} if this list contains no elements. Invoking this method is more efficient
+     * than testing {@code size() == 0} because this method does not iterate over all properties.
+     */
+    @Override
+    public boolean isEmpty() {
+        return accessor.count(metadata, parent.table.valuePolicy, PropertyAccessor.COUNT_FIRST) == 0;
+    }
+
+    /**
+     * Returns an iterator over the nodes in the list of children.
+     */
+    @Override
+    public Iterator<TreeTable.Node> iterator() {
+        return new Iter();
+    }
+
+    /**
+     * Returns a bidirectional iterator over the nodes in the list of children.
+     */
+    @Override
+    public ListIterator<TreeTable.Node> listIterator() {
+        return new BiIter();
+    }
+
+    /**
+     * Returns an iterator over the nodes in the list of children, starting at the given index.
+     */
+    @Override
+    public ListIterator<TreeTable.Node> listIterator(final int index) {
+        if (index >= 0) {
+            final BiIter it = new BiIter();
+            if (it.skip(index)) {
+                return it;
+            }
+        }
+        throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, index));
+    }
+
+    /**
+     * The iterator returned by {@link #iterator()}. This iterator fetches metadata property
+     * values and creates the nodes only when first needed.
+     */
+    private class Iter implements Iterator<TreeTable.Node> {
+        /**
+         * Index in {@link MetadataTreeChildren#accessor} of the next element to be
+         * returned by {@link #next()}, or {@link PropertyAccessor#count()} if we
+         * have reached the end of the list.
+         */
+        private int nextInAccessor;
+
+        /**
+         * Index in {@link MetadataTreeChildren#accessor} of the element returned by
+         * the last call to {@link #next()}, or -1 if none.
+         */
+        private int previousInAccessor = -1;
+
+        /**
+         * {@code true} if we have verified the value at {@link #nextInAccessor} index
+         * for non-null or non-empty element.
+         */
+        private boolean isNextVerified;
+
+        /**
+         * The value to be returned by {@link #next()} method. This value is computed ahead
+         * of time by {@link #hasNext()} since we need that information in order to determine
+         * if the value needs to be skipped or not.
+         */
+        private Object nextValue;
+
+        /**
+         * If the call to {@link #next()} found a collection, the iterator over the elements
+         * in that collection. Otherwise {@code null}.
+         *
+         * <p>A non-null value (even if that child iterator has no next elements) means that
+         * {@link #nextValue} is an element of that child iteration.</p>
+         */
+        private Iterator<?> childIterator;
+
+        /**
+         * Position of the {@link #nextValue} in the {@link #childIterator}.
+         * This field has no meaning if the child iterator is null.
+         */
+        private int childIndex;
+
+        /**
+         * The value of {@link AbstractSequentialList#modCount} at construction time or
+         * after the last change done by this iterator. Used for concurrent modification
+         * checks.
+         *
+         * {@note Actually this iterator should be robust to most concurrent modifications.
+         *        But we check anyway in order to prevent concurrent modifications in user
+         *        code, in case a future SIS version become more sensitive to such changes.}
+         */
+        private int modCountCheck;
+
+        /**
+         * Creates a new iterator.
+         */
+        Iter() {
+            modCountCheck = modCount;
+        }
+
+        /**
+         * Throws {@link ConcurrentModificationException} if an unexpected change has been detected.
+         */
+        final void checkConcurrentModification() {
+            if (modCountCheck != modCount) {
+                throw new ConcurrentModificationException();
+            }
+        }
+
+        /**
+         * Ensures that {@link #nextInAccessor} is valid. If the index has not been validated, then this method
+         * moves the iterator to the next valid element, starting at the current {@link #nextInAccessor} value.
+         *
+         * @return {@code true} on success, or {@code false} if we reached the end of the list.
+         */
+        @Override
+        public boolean hasNext() {
+            checkConcurrentModification();
+            if (isNextVerified) {
+                return true;
+            }
+            /*
+             * If an iteration was under progress, move to the next element from that iteration.
+             * We do not check for 'isSkipped(value)' here because empty elements in collections
+             * are probably mistakes, and we want to see them.
+             */
+            if (childIterator != null) {
+                if (childIterator.hasNext()) {
+                    childIndex++;
+                    nextValue = childIterator.next();
+                    isNextVerified = true;
+                    return true;
+                }
+                childIterator = null;
+                nextInAccessor++; // See the comment before nextInAccessor++ in the next() method.
+            }
+            /*
+             * Search for the next property, which may be either a singleton or the first element
+             * of a collection. In the later case, we will create a child iterator.
+             */
+            final int count = childCount();
+            while (nextInAccessor < count) {
+                nextValue = valueAt(nextInAccessor);
+                if (!isSkipped(nextValue)) {
+                    if (isCollection(nextInAccessor)) {
+                        /*
+                         * If the property is a collection, unconditionally get the first element
+                         * even if absent (null) in order to comply with the ValueExistencePolicy.
+                         * if we were expected to ignore empty collections, 'isSkipped(nextValue)'
+                         * would have returned 'true'.
+                         */
+                        if (nextValue != null) {
+                            childIterator = ((Collection<?>) nextValue).iterator();
+                        } else {
+                            childIterator = Collections.emptyIterator();
+                            // Null collections are illegal (it shall be empty collections instead),
+                            // but we try to keep the iterator robut to ill-formed metadata, because
+                            // we want AbstractMetadata.toString() to work so we can spot problems.
+                        }
+                        childIndex = 0;
+                        if (childIterator.hasNext()) {
+                            nextValue = childIterator.next();
+                        } else {
+                            nextValue = null;
+                            // Do not set 'childIterator' to null, since the above 'nextValue'
+                            // is considered as part of the child iteration.
+                        }
+                    }
+                    isNextVerified = true;
+                    return true;
+                }
+                nextInAccessor++;
+            }
+            return false;
+        }
+
+        /**
+         * Returns the node for the metadata property at the current {@link #nextInAccessor}.
+         * The value of this property is initially {@link #nextValue}, but this may change at
+         * any time if the user modify the underlying metadata object.
+         */
+        @Override
+        public TreeTable.Node next() {
+            if (hasNext()) {
+                final boolean isElementOfCollection = (childIterator != null);
+                final MetadataTreeNode node = childAt(nextInAccessor, isElementOfCollection ? childIndex : -1);
+                previousInAccessor = nextInAccessor;
+                if (!isElementOfCollection) {
+                    /*
+                     * If we are iterating over the elements in a collection, the PropertyAccessor index
+                     * still the same and will be incremented by 'hasNext()' only when the iteration is
+                     * over. Otherwise (not iterating in a collection), move to the next property. The
+                     * 'hasNext()' method will determine later if this property is non-empty, or if we
+                     * need to move forward again.
+                     */
+                    nextInAccessor++;
+                }
+                isNextVerified = false;
+                return node;
+            }
+            throw new NoSuchElementException();
+        }
+
+        /**
+         * Clears the element returned by the last call to {@link #next()}.
+         * Whether the cleared element is considered removed or not depends
+         * on the value policy and on the element type.
+         */
+        @Override
+        public void remove() {
+            if (previousInAccessor < 0) {
+                throw new IllegalStateException();
+            }
+            checkConcurrentModification();
+            if (childIterator != null) {
+                childIterator.remove();
+            } else {
+                clearAt(previousInAccessor);
+                previousInAccessor = -1;
+            }
+            modCountCheck = ++modCount;
+        }
+    }
+
+    /**
+     * The bidirectional iterator returned by {@link #listIterator(int)}.
+     */
+    private final class BiIter extends Iter implements ListIterator<TreeTable.Node> {
+        /**
+         * The previous elements returned by this iterator.
+         */
+        private TreeTable.Node[] previous;
+
+        /**
+         * Number of valid elements in the {@link #previous} array.
+         * This is the position of the {@link Iter} super-class.
+         */
+        private int position;
+
+        /**
+         * Index to be returned by {@link #nextIndex()}.
+         *
+         * @see #nextIndex()
+         * @see #previousIndex()
+         */
+        private int nextIndex;
+
+        /**
+         * Creates a new iterator.
+         */
+        BiIter() {
+            previous = new TreeTable.Node[childCount()];
+        }
+
+        /**
+         * Skips the given amount of elements. This is a convenience method
+         * for implementation of {@link MetadataTreeChildren#listIterator(int)}.
+         *
+         * @param  n Number of elements to skip.
+         * @return {@code true} on success, or {@code false} if the list doesn't contain enough elements.
+         */
+        boolean skip(int n) {
+            while (--n >= 0) {
+                if (!super.hasNext()) {
+                    return false;
+                }
+                next();
+            }
+            return true;
+        }
+
+        /**
+         * Returns the index of the element to be returned by {@link #next()},
+         * or the list size if the iterator is at the end of the list.
+         */
+        @Override
+        public int nextIndex() {
+            return nextIndex;
+        }
+
+        /**
+         * Returns the index of the element to be returned by {@link #previous()},
+         * or -1 if the iterator is at the beginning of the list.
+         */
+        @Override
+        public int previousIndex() {
+            return nextIndex - 1;
+        }
+
+        /**
+         * Returns {@code true} if {@link #next()} can return an element.
+         */
+        @Override
+        public boolean hasNext() {
+            return (nextIndex < position) || super.hasNext();
+        }
+
+        /**
+         * Returns {@code true} if {@link #previous()} can return an element.
+         */
+        @Override
+        public boolean hasPrevious() {
+            checkConcurrentModification();
+            return nextIndex != 0;
+        }
+
+        /**
+         * Returns the next element.
+         */
+        @Override
+        public TreeTable.Node next() {
+            if (nextIndex < position) {
+                checkConcurrentModification();
+                return previous[nextIndex++];
+            }
+            final TreeTable.Node node = super.next();
+            if (nextIndex == previous.length) {
+                previous = Arrays.copyOf(previous, nextIndex*2);
+            }
+            previous[nextIndex++] = node;
+            position = nextIndex;
+            return node;
+        }
+
+        /**
+         * Returns the previous element.
+         */
+        @Override
+        public TreeTable.Node previous() {
+            if (hasPrevious()) {
+                return previous[--nextIndex];
+            }
+            throw new NoSuchElementException();
+        }
+
+        /**
+         * Current implementation does not support removal after {@link #previous()}.
+         */
+        @Override
+        public void remove() {
+            if (nextIndex != position) {
+                throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedOperation_1, "remove"));
+            }
+            super.remove();
+        }
+
+        /**
+         * Unsupported operation.
+         */
+        @Override
+        public void set(TreeTable.Node e) {
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedOperation_1, "set"));
+        }
+
+        /**
+         * Unsupported operation.
+         */
+        @Override
+        public void add(TreeTable.Node e) {
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedOperation_1, "add"));
+        }
+    }
+
+    /**
+     * Returns a string representation of this list for debugging purpose.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        final String lineSeparator = System.lineSeparator();
+        final StringBuilder buffer = new StringBuilder(512);
+        parent.toString(buffer);
+        buffer.append(lineSeparator);
+        for (final TreeTable.Node node : this) {
+            buffer.append("  ");
+            ((MetadataTreeNode) node).toString(buffer);
+            buffer.append(lineSeparator);
+        }
+        return buffer.toString();
+    }
+}

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java?rev=1476307&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java (added)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -0,0 +1,483 @@
+/*
+ * 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.metadata;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.Collections;
+import java.io.Serializable;
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * A node in a {@link MetadataTreeTable} view. The {@code MetadataTreeTable} class is used directly
+ * only for the root node, or for nodes containing a fixed value instead than a value fetched from
+ * the metadata object. For all other nodes, the actual node class shall be either {@link Element}
+ * or {@link CollectionElement}.
+ *
+ * <p>The value of a node is extracted from the {@linkplain #metadata} object by {@link #getUserObject()}.
+ * For each instance of {@code MetadataTreeTable}, that value is always a singleton, never a collection.
+ * If a metadata property is a collection, then there is an instance of the {@link CollectionElement}
+ * subclass for each element in the collection.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+class MetadataTreeNode implements TreeTable.Node, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -3499128444388320L;
+
+    /**
+     * The table for which this node is an element. Contains information like
+     * the metadata standard and the value existence policy.
+     *
+     * <p>All {@code MetadataTreeNode} instances in the same tree have
+     * a reference to the same {@code MetadataTreeTable} instance.</p>
+     */
+    final MetadataTreeTable table;
+
+    /**
+     * The parent of this node to be returned by {@link #getParent()},
+     * or {@code null} if this node is the root of the tree.
+     *
+     * @see #getParent()
+     */
+    private final MetadataTreeNode parent;
+
+    /**
+     * The metadata object from which the {@link #getUserObject()} method will fetch the value.
+     * The value is fetched in different ways, which depend on the {@code MetadataTreeNode} subclass:
+     *
+     * <ul>
+     *   <li>For {@code MetadataTreeNode} (usually the root of the tree),
+     *       this is the value to return directly.</li>
+     *   <li>For {@link Element} (a metadata property which is not a collection)
+     *       The value is {@code accessor.get(indexInData, metadata)}.</li>
+     *   <li>For {@link CollectionElement} (an element in a collection),
+     *       an other index is used for fetching the element in that collection.</li>
+     * </ul>
+     *
+     * This field shall never be null.
+     *
+     * @see #getUserObject()
+     */
+    final Object metadata;
+
+    /**
+     * The value of {@link TableColumn#NAME}, computed by {@link #getName()} then cached.
+     *
+     * @see #getName()
+     */
+    private transient String name;
+
+    /**
+     * The children of this node, or {@code null} if not yet computed. If and only if the node
+     * can not have children (i.e. {@linkplain #isLeaf() is a leaf}), then this field is set to
+     * {@link Collections#EMPTY_LIST}.
+     *
+     * @see #getChildren()
+     */
+    private transient List<TreeTable.Node> children;
+
+    /**
+     * Creates the root node of a new metadata tree table.
+     *
+     * @param  table    The table which is creating this root node.
+     * @param  metadata The root metadata object (can not be null).
+     */
+    MetadataTreeNode(final MetadataTreeTable table, final Object metadata) {
+        this.table    = table;
+        this.parent   = null;
+        this.metadata = metadata;
+    }
+
+    /**
+     * Creates a new child for an element of the given metadata.
+     *
+     * @param  parent   The parent of this node.
+     * @param  metadata The metadata object for which this node will be a value.
+     */
+    MetadataTreeNode(final MetadataTreeNode parent, final Object metadata) {
+        this.table    = parent.table;
+        this.parent   = parent;
+        this.metadata = metadata;
+    }
+
+    /**
+     * Gets the name of this node. The name shall be stable, since it will be cached by the caller.
+     * The default implementation is suitable only for the root node - subclasses must override.
+     */
+    String getName() {
+        final Class<?> type = metadata.getClass();
+        String name = Types.getStandardName(type);
+        if (name == null) {
+            name = Classes.getShortName(type);
+        }
+        return name;
+    }
+
+    /**
+     * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+     * The default implementation is suitable only for the root node - subclasses must override.
+     */
+    void identifier(final StringBuilder buffer) {
+        buffer.append(Classes.getShortClassName(metadata));
+    }
+
+    /**
+     * Returns the base type of values to be returned by {@link #getUserObject()}.
+     * The default implementation is suitable only for the root node - subclasses must override.
+     */
+    public Class<?> getElementType() {
+        return table.standard.getInterface(metadata.getClass());
+    }
+
+    /**
+     * The metadata value for this node, to be returned by {@code getValue(TableColumn.VALUE)}.
+     * The default implementation is suitable only for the root node - subclasses must override.
+     */
+    @Override
+    public Object getUserObject() {
+        return metadata;
+    }
+
+    /**
+     * Sets the metadata value for this node. Subclasses must override this method.
+     *
+     * @throws UnsupportedOperationException If the metadata value is not writable.
+     */
+    void setUserObject(final Object value) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableCellValue_2,
+                getValue(TableColumn.NAME), TableColumn.VALUE.getHeader()));
+    }
+
+    /**
+     * Returns {@code true} if the metadata value can be set.
+     * Subclasses must override this method.
+     */
+    boolean isWritable() {
+        return false;
+    }
+
+    /**
+     * A node for a metadata property value. This class does not store the property value directly.
+     * Instead, is stores a reference to the metadata object that contains the property values,
+     * together with the index for fetching the value in that object. That way, the real storage
+     * objects still the metadata object, which allow {@link MetadataTreeTable} to be a dynamic view.
+     *
+     * <p>Instances of this class shall be instantiated only for metadata singletons. If a metadata
+     * property is a collection, then the {@link CollectionElement} subclass shall be instantiated
+     * instead.</p>
+     */
+    static class Element extends MetadataTreeNode {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = -1837090036924521907L;
+
+        /**
+         * The accessor to use for fetching the property names, types and values from the
+         * {@link #metadata} object. Note that the value of this field is the same for all
+         * siblings.
+         */
+        private final PropertyAccessor accessor;
+
+        /**
+         * Index of the value in the {@link #metadata} object to be fetched with the
+         * {@link #accessor}.
+         */
+        private final int indexInData;
+
+        /**
+         * Creates a new child for a property of the given metadata at the given index.
+         *
+         * @param  parent      The parent of this node.
+         * @param  metadata    The metadata object for which this node will be a value.
+         * @param  accessor    Accessor to use for fetching the name, type and value.
+         * @param  indexInData Index to be given to the accessor of fetching the value.
+         */
+        Element(final MetadataTreeNode parent, final Object metadata,
+                final PropertyAccessor accessor, final int indexInData)
+        {
+            super(parent, metadata);
+            this.accessor = accessor;
+            this.indexInData = indexInData;
+        }
+
+        /**
+         * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+         */
+        @Override
+        void identifier(final StringBuilder buffer) {
+            super.identifier(buffer);
+            buffer.append('.').append(accessor.name(indexInData, KeyNamePolicy.JAVABEANS_PROPERTY));
+        }
+
+        /**
+         * Gets the name of this node. Current implementation derives the name from the
+         * {@link KeyNamePolicy#UML_IDENTIFIER} instead than {@link KeyNamePolicy#JAVABEANS_PROPERTY}
+         * in order to get the singular form instead of the plural one, because we will create one
+         * node for each element in a collection.
+         */
+        @Override
+        String getName() {
+            return CharSequences.camelCaseToSentence(accessor.name(indexInData, KeyNamePolicy.UML_IDENTIFIER)).toString();
+        }
+
+        /**
+         * Returns the type of property elements.
+         */
+        @Override
+        public final Class<?> getElementType() {
+            return accessor.type(indexInData, TypeValuePolicy.ELEMENT_TYPE);
+        }
+
+        /**
+         * Fetches the node value from the metadata object.
+         */
+        @Override
+        public Object getUserObject() {
+            return accessor.get(indexInData, metadata);
+        }
+
+        /**
+         * Sets the metadata value for this node.
+         */
+        @Override
+        void setUserObject(final Object value) {
+            accessor.set(indexInData, metadata, value, false);
+        }
+
+        /**
+         * Returns {@code true} if the metadata is writable.
+         */
+        @Override
+        final boolean isWritable() {
+            return accessor.isWritable(indexInData);
+        }
+    }
+
+    /**
+     * A node for an element in a collection. This class needs the iteration order to be stable.
+     */
+    static final class CollectionElement extends Element {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = -1156865958960250473L;
+
+        /**
+         * Index of the element in the collection, in iteration order.
+         */
+        final int indexInList;
+
+        /**
+         * Creates a new node for the given collection element.
+         *
+         * @param  parent      The parent of this node.
+         * @param  metadata    The metadata object for which this node will be a value.
+         * @param  accessor    Accessor to use for fetching the name, type and collection.
+         * @param  indexInData Index to be given to the accessor of fetching the collection.
+         * @param  indexInList Index of the element in the collection, in iteration order.
+         */
+        CollectionElement(final MetadataTreeNode parent, final Object metadata,
+                final PropertyAccessor accessor, final int indexInData, final int indexInList)
+        {
+            super(parent, metadata, accessor, indexInData);
+            this.indexInList = indexInList;
+        }
+
+        /**
+         * Appends an identifier for this node in the given buffer, for {@link #toString()} implementation.
+         */
+        @Override
+        void identifier(final StringBuilder buffer) {
+            super.identifier(buffer);
+            buffer.append('[').append(indexInList).append(']');
+        }
+
+        /**
+         * Fetches the node value from the metadata object.
+         */
+        @Override
+        public Object getUserObject() {
+            final Collection<?> values = (Collection<?>) super.getUserObject();
+            if (values instanceof List<?>) {
+                return ((List<?>) values).get(indexInList);
+            }
+            // TODO: following fallback is inefficient.
+            final Iterator<?> it = values.iterator();
+            for (int i=0; i<indexInList; i++) {
+                it.next();
+            }
+            return it.next();
+        }
+    }
+
+    /**
+     * Returns the parent node, or {@code null} if this node is the root of the tree.
+     */
+    @Override
+    public final TreeTable.Node getParent() {
+        return parent;
+    }
+
+    /**
+     * Returns {@code false} if the value is a metadata object (and consequently can have children),
+     * or {@code true} if the value is not a metadata object.
+     */
+    @Override
+    public final boolean isLeaf() {
+        return !table.standard.isMetadata(getElementType());
+    }
+
+    /**
+     * Returns the children of this node, or an empty list if none.
+     * Only metadata object can have children.
+     */
+    @Override
+    public final List<TreeTable.Node> getChildren() {
+        /*
+         * 'children' is set to EMPTY_LIST if an only if the node *can not* have children,
+         * in which case we do not need to check for changes in the underlying metadata.
+         */
+        if (children != Collections.EMPTY_LIST) {
+            final Object value = getUserObject();
+            if (value == null) {
+                /*
+                 * If there is no value, returns an empty list but *do not* set 'children'
+                 * to that list, in order to allow this method to check again the next time
+                 * that this method is invoked.
+                 */
+                children = null; // Let GC do its work.
+                return Collections.emptyList();
+            }
+            /*
+             * If there is a value, check if the cached list is still applicable.
+             */
+            if (children instanceof MetadataTreeChildren) {
+                final MetadataTreeChildren candidate = (MetadataTreeChildren) children;
+                if (candidate.metadata == value) {
+                    return candidate;
+                }
+            }
+            /*
+             * At this point, we need to create a new list. The property accessor will be
+             * null if the value is not a metadata object, in which case we will remember
+             * that fact by setting the children list definitively to an empty list.
+             */
+            final PropertyAccessor accessor = table.standard.getAccessor(value.getClass(), false);
+            if (accessor != null) {
+                children = new MetadataTreeChildren(this, value, accessor);
+            } else {
+                children = Collections.emptyList();
+            }
+        }
+        return children;
+    }
+
+    /**
+     * Unconditionally throws {@link UnsupportedOperationException}, because there is no
+     * way we can safely determine which metadata property a new child would be for.
+     */
+    @Override
+    public final TreeTable.Node newChild() {
+        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedOperation_1, "newChild"));
+    }
+
+    /**
+     * Returns the value of this node in the given column, or {@code null} if none. This method verifies
+     * the {@code column} argument, then delegates to {@link #getName()}, {@link #getElementType()} or
+     * {@link #getUserObject()}.
+     */
+    @Override
+    public final <V> V getValue(final TableColumn<V> column) {
+        ArgumentChecks.ensureNonNull("column", column);
+        Object value = null;
+        if (column == TableColumn.NAME) {
+            value = name;
+            if (value == null) {
+                value = name = getName();
+            }
+        } else if (column == TableColumn.VALUE) {
+            value = getUserObject();
+        } else if (column == TableColumn.TYPE) {
+            value = getElementType();
+        }
+        return column.getElementType().cast(value);
+    }
+
+    /**
+     * Sets the value if the given column is {@link TableColumn#VALUE}. This method verifies
+     * the {@code column} argument, then delegates to {@link #setUserObject(Object)}.
+     */
+    @Override
+    public final <V> void setValue(final TableColumn<V> column, final V value) throws UnsupportedOperationException {
+        ArgumentChecks.ensureNonNull("column", column);
+        if (column == TableColumn.VALUE) {
+            setUserObject(value);
+        } else if (MetadataTreeTable.COLUMNS.contains(column)) {
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableCellValue_2,
+                    getValue(TableColumn.NAME), column.getHeader()));
+        } else {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "column", column));
+        }
+    }
+
+    /**
+     * Returns {@code true} if the given column is {@link TableColumn#VALUE} and the property is writable,
+     * or {@code false} in all other cases. This method verifies the {@code column} argument, then delegates
+     * to {@link #isWritable()}.
+     */
+    @Override
+    public boolean isEditable(final TableColumn<?> column) {
+        ArgumentChecks.ensureNonNull("column", column);
+        return (column == TableColumn.VALUE) && isWritable();
+    }
+
+    /**
+     * Returns a string representation of this node for debugging purpose.
+     */
+    @Debug
+    @Override
+    public final String toString() {
+        final StringBuilder buffer = new StringBuilder(60);
+        toString(buffer);
+        return buffer.toString();
+    }
+
+    /**
+     * Implementation of {@link #toString()} appending the string representation
+     * in the given buffer.
+     */
+    final void toString(final StringBuilder buffer) {
+        identifier(buffer.append("Node["));
+        buffer.append(" : ").append(Classes.getShortName(getElementType())).append(']');
+    }
+}

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java?rev=1476307&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java (added)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -0,0 +1,114 @@
+/*
+ * 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.metadata;
+
+import java.util.List;
+import java.io.Serializable;
+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.internal.util.UnmodifiableArrayList;
+
+
+/**
+ * A tree table view over a metadata object.
+ * The tree table is made of three columns:
+ *
+ * <ul>
+ *   <li>{@link TableColumn#NAME}  - the metadata property name.</li>
+ *   <li>{@link TableColumn#TYPE}  - the metadata element type.</li>
+ *   <li>{@link TableColumn#VALUE} - the metadata property value.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+final class MetadataTreeTable implements TreeTable, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 6320615192545089879L;
+
+    /**
+     * The columns to be returned by {@link #getColumns()}.
+     */
+    static final List<TableColumn<?>> COLUMNS = UnmodifiableArrayList.wrap(new TableColumn<?>[] {
+        TableColumn.NAME,
+        TableColumn.TYPE,
+        TableColumn.VALUE
+    });
+
+    /**
+     * The root of the metadata tree.
+     */
+    private final Node root;
+
+    /**
+     * The metadata standard implemented by the metadata objects.
+     */
+    final MetadataStandard standard;
+
+    /**
+     * The behavior of this tree table toward null or empty values.
+     */
+    final ValueExistencePolicy valuePolicy;
+
+    /**
+     * Creates a tree table for the specified metadata object.
+     *
+     * @param standard    The metadata standard implemented by the given metadata.
+     * @param metadata    The metadata object to wrap.
+     * @param valuePolicy The behavior of this map toward null or empty values.
+     */
+    MetadataTreeTable(final MetadataStandard standard, final Object metadata, final ValueExistencePolicy valuePolicy) {
+        this.standard    = standard;
+        this.valuePolicy = valuePolicy;
+        this.root = new MetadataTreeNode(this, metadata);
+    }
+
+    /**
+     * Returns the columns included in this tree table.
+     */
+    @Override
+    public List<TableColumn<?>> getColumns() {
+        return COLUMNS;
+    }
+
+    /**
+     * Returns the root of this metadata tree.
+     */
+    @Override
+    public Node getRoot() {
+        return root;
+    }
+
+    /**
+     * Returns a string representation of this tree table.
+     * The current implementation uses a shared instance of {@link TreeTableFormat}.
+     * This is okay for debugging or occasional usages. However for more extensive usages,
+     * developers are encouraged to create and configure their own {@link TreeTableFormat}
+     * instance.
+     *
+     * @return A string representation of this tree table.
+     */
+    @Override
+    public String toString() {
+        return ""; // TODO TreeTableFormat.toString(this);
+    }
+}

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java?rev=1476307&r1=1476306&r2=1476307&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] (original)
+++ sis/branches/JDK7/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -953,9 +953,9 @@ final class PropertyAccessor {
                         break;
                     }
                     case COUNT_DEEP: {
-                        if (value != null) {
-                            count += isCollection(i) ? ((Collection<?>) value).size() : 1;
-                        }
+                        // Count always at least one element because if the user wanted to skip null or empty
+                        // collections, then 'valuePolicy.isSkipped(value)' above would have returned 'true'.
+                        count += (value != null && isCollection(i)) ? Math.max(((Collection<?>) value).size(), 1) : 1;
                         break;
                     }
                     default: throw new AssertionError(mode);

Added: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java?rev=1476307&view=auto
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java (added)
+++ sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java [UTF-8] Fri Apr 26 17:31:35 2013
@@ -0,0 +1,279 @@
+/*
+ * 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.metadata;
+
+import java.util.Random;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.ListIterator;
+import org.opengis.metadata.citation.PresentationForm;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.util.iso.SimpleInternationalString;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.apache.sis.test.TestUtilities.createRandomNumberGenerator;
+
+
+/**
+ * Tests the {@link MetadataTreeChildren} class.
+ * Unless otherwise specified, all tests use the {@link MetadataStandard#ISO_19115} constant.
+ *
+ * {@section Test dependency}
+ * This class uses the {@link MetadataTreeNode#getUserObject()} method for comparing the values.
+ * We can hardly avoid to use some {@code MetadataTreeNode} methods because of the cross-dependencies.
+ * However we try to use nothing else than {@code getUserObject()} because the purpose of this class
+ * is not to test {@link MetadataTreeNode}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+@DependsOn(PropertyAccessorTest.class)
+public final strictfp class MetadataTreeChildrenTest extends TestCase {
+    /**
+     * Creates a shallow metadata object without collections.
+     */
+    static DefaultCitation metadataWithoutCollections() {
+        final DefaultCitation citation = new DefaultCitation("Some title");
+        citation.setEdition(new SimpleInternationalString("Some edition"));
+        citation.setOtherCitationDetails(new SimpleInternationalString("Some other details"));
+        return citation;
+    }
+
+    /**
+     * Creates a shallow metadata object with singleton value in collections.
+     */
+    static DefaultCitation metadataWithSingletonInCollections() {
+        final DefaultCitation citation = metadataWithoutCollections();
+        assertTrue(citation.getAlternateTitles().add(new SimpleInternationalString("First alternate title")));
+        assertTrue(citation.getPresentationForms().add(PresentationForm.MAP_DIGITAL));
+        return citation;
+    }
+
+    /**
+     * Creates a shallow metadata object with multi-occurrences
+     * (i.e. more than one value in collections).
+     */
+    static DefaultCitation metadataWithMultiOccurrences() {
+        final DefaultCitation citation = metadataWithSingletonInCollections();
+        assertTrue(citation.getAlternateTitles().add(new SimpleInternationalString("Second alternate title")));
+        assertTrue(citation.getPresentationForms().add(PresentationForm.MAP_HARDCOPY));
+        return citation;
+    }
+
+    /**
+     * Creates a list to be tested for the given metadata object and value policy.
+     */
+    private static MetadataTreeChildren create(final AbstractMetadata metadata, final ValueExistencePolicy valuePolicy) {
+        final MetadataStandard  standard = MetadataStandard.ISO_19115;
+        final MetadataTreeTable table    = new MetadataTreeTable(standard, metadata, valuePolicy);
+        final MetadataTreeNode  node     = (MetadataTreeNode) table.getRoot();
+        final PropertyAccessor  accessor = standard.getAccessor(metadata.getClass(), true);
+        return new MetadataTreeChildren(node, metadata, accessor);
+    }
+
+    /**
+     * Tests read-only operations on a list of properties for a shallow metadata object without collections.
+     */
+    @Test
+    public void testReadOnlyWithoutCollections() {
+        random = createRandomNumberGenerator("testReadOnlyWithoutCollections");
+        final DefaultCitation      citation = metadataWithoutCollections();
+        final MetadataTreeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY);
+        final String[] expected = {
+            "Some title",
+            "Some edition",
+            "Some other details"
+        };
+        assertFalse ("isEmpty()", children.isEmpty());
+        assertEquals("size()", expected.length, children.size());
+
+        assertAllNextEqual(expected, children.iterator());
+        assertAllEqual(true, expected, children.listIterator());
+        testGet(expected, children);
+    }
+
+    /**
+     * Tests read-only operations on a list of properties for a shallow metadata object with singleton
+     * values in collections.
+     */
+    @Test
+    @DependsOnMethod("testReadOnlyWithoutCollections")
+    public void testReadOnlyWithSingletonInCollections() {
+        random = createRandomNumberGenerator("testReadOnlyWithSingletonInCollections");
+        final DefaultCitation      citation = metadataWithSingletonInCollections();
+        final MetadataTreeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY);
+        final String[] expected = {
+            "Some title",
+            "First alternate title",
+            "Some edition",
+            "PresentationForm[MAP_DIGITAL]",
+            "Some other details"
+        };
+        assertFalse ("isEmpty()", children.isEmpty());
+        assertEquals("size()", expected.length, children.size());
+
+        assertAllNextEqual(expected, children.iterator());
+        assertAllEqual(true, expected, children.listIterator());
+        testGet(expected, children);
+    }
+
+    /**
+     * Tests read-only operations on a list of properties for a shallow metadata object with more
+     * than one values in collections.
+     */
+    @Test
+    @DependsOnMethod("testReadOnlyWithSingletonInCollections")
+    public void testReadOnlyWithMultiOccurrences() {
+        random = createRandomNumberGenerator("testReadOnlyWithMultiOccurrences");
+        final DefaultCitation      citation = metadataWithMultiOccurrences();
+        final MetadataTreeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY);
+        final String[] expected = {
+            "Some title",
+            "First alternate title",
+            "Second alternate title",
+            "Some edition",
+            "PresentationForm[MAP_DIGITAL]",
+            "PresentationForm[MAP_HARDCOPY]",
+            "Some other details"
+        };
+        assertFalse ("isEmpty()", children.isEmpty());
+        assertEquals("size()", expected.length, children.size());
+
+        assertAllNextEqual(expected, children.iterator());
+        assertAllEqual(false, expected, children.listIterator());
+        testGet(expected, children);
+    }
+
+
+    // ------------------------ Support methods for the above tests ------------------------
+
+
+    /**
+     * Random number generator used by the {@code assert*} methods.
+     * Must be initialized by the public test methods.
+     */
+    private Random random;
+
+    /**
+     * Returns the string representation of the user object in the given node.
+     * This is the value that we are going compare in the assertion methods below.
+     *
+     * <p>We use only {@link MetadataTreeNode#getUserObject()}, nothing else,
+     * because the purpose of this class is not to test {@link MetadataTreeNode}.</p>
+     */
+    private static String valueOf(final TreeTable.Node node) {
+        return String.valueOf(node.getUserObject());
+    }
+
+    /**
+     * Asserts that the string representation of user objects of all next element are equal
+     * to the expected strings.
+     */
+    private static void assertAllNextEqual(final String[] expected, final Iterator<TreeTable.Node> it) {
+        for (final String e : expected) {
+            assertTrue(e, it.hasNext());
+            assertEquals("Iterator.next()", e, valueOf(it.next()));
+        }
+        assertFalse("Iterator.hasNext()", it.hasNext());
+    }
+
+    /**
+     * Same assertion than {@link #assertAllNextEqual(String[], Iterator)},
+     * but move randomly forward and backward.
+     *
+     * @param cached {@code true} if all nodes returned by the iterator are expected to be cached,
+     *               i.e. if asking for the element at the same index shall return the same instance.
+     */
+    private void assertAllEqual(final boolean cached, final String[] expected, final ListIterator<TreeTable.Node> it) {
+        final TreeTable.Node[] cache = cached ? new TreeTable.Node[expected.length] : null;
+        int index = 0; // For verification purpose only.
+        boolean forward = true;
+        for (int i=0; i<50; i++) {
+            /*
+             * Select randomly a traversal direction for this step. We reverse the
+             * direction only 1/3 of time in order to give the iterator more chances
+             * to span the full range of expected values.
+             */
+            if (index == 0) {
+                assertFalse(it.hasPrevious());
+                forward = true;
+            } else if (index == expected.length) {
+                assertFalse(it.hasNext());
+                forward = false;
+            } else if (random.nextInt(3) == 0) {
+                forward = !forward;
+            }
+            /*
+             * Get the next or previous node, depe,ding on the current traversal direction.
+             */
+            final TreeTable.Node node;
+            final String message;
+            final int at;
+            if (forward) {
+                message = "next index=" + index + " iter="+i;
+                assertEquals(message, index, it.nextIndex());
+                assertTrue(message, it.hasNext());
+                node = it.next();
+                assertEquals(message, index, it.previousIndex());
+                at = index++;
+            } else {
+                at = --index;
+                message = "previous index=" + index + " iter="+i;
+                assertEquals(message, index, it.previousIndex());
+                assertTrue(message, it.hasPrevious());
+                node = it.previous();
+                assertEquals(message, index, it.nextIndex());
+            }
+            assertEquals(message, expected[at], valueOf(node));
+            /*
+             * If the nodes are expected to be cached, verify that.
+             */
+            if (cached) {
+                if (cache[at] == null) {
+                    cache[at] = node;
+                } else {
+                    assertSame(message, cache[at], node);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests {@link MetadataTreeChildren#get(int)}, which will indirectly tests
+     * {@link MetadataTreeChildren#listIterator(int)}. The indices will be tested
+     * in random order.
+     */
+    private void testGet(final String[] expected, final MetadataTreeChildren children) {
+        final Integer[] index = new Integer[expected.length];
+        for (int i=0; i<index.length; i++) {
+            index[i] = i;
+        }
+        Collections.shuffle(Arrays.asList(index), random);
+        for (int j=0; j<index.length; j++) {
+            final int i = index[j];
+            assertEquals(expected[i], valueOf(children.get(i)));
+        }
+    }
+}

Propchange: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8