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