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/05/03 18:31:03 UTC
svn commit: r1478867 [1/2] - in /sis/branches/JDK6: ./
sis-metadata/src/main/java/org/apache/sis/metadata/
sis-metadata/src/test/java/org/apache/sis/metadata/
sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/
sis-metadata/src/test/java/o...
Author: desruisseaux
Date: Fri May 3 16:31:02 2013
New Revision: 1478867
URL: http://svn.apache.org/r1478867
Log:
Merge from the JDK7 branch.
Added:
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java
- copied, changed from r1478864, sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java
Modified:
sis/branches/JDK6/ (props changed)
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java
sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
sis/branches/JDK6/sis-utility/src/main/java/org/apache/sis/util/ObjectConverters.java
sis/branches/JDK6/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
sis/branches/JDK6/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
sis/branches/JDK6/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
sis/branches/JDK6/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
Propchange: sis/branches/JDK6/
------------------------------------------------------------------------------
Merged /sis/branches/JDK7:r1478203-1478864
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Fri May 3 16:31:02 2013
@@ -23,8 +23,6 @@ import org.apache.sis.util.LenientCompar
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.logging.Logging;
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
-
/**
* Provides basic operations using Java reflection for metadata implementations.
@@ -45,7 +43,6 @@ import static org.apache.sis.util.Argume
*
* <ul>
* <li>{@link #prune()}</li>
- * <li>{@link #shallowCopy(Object)}</li>
* <li>{@link #asMap()} with {@code put} operations</li>
* </ul>
*
@@ -154,36 +151,27 @@ public abstract class AbstractMetadata i
}
/**
- * Copies the values from the specified metadata. The {@code source} metadata must implements
- * the same metadata interface (defined by the {@linkplain #getStandard() standard}) than this
- * class, but doesn't need to be the same implementation class.
- * The default implementation performs the copy using Java reflections.
- *
- * {@note This method is intended to provide the functionality of a <cite>copy constructor</cite>.
- * We do not provide copy constructor directly because usage of Java reflection in this context
- * is unsafe (we could invoke subclass methods before the subclasses construction is completed).}
- *
- * @param source The metadata to copy values from.
- * @throws ClassCastException if the specified metadata doesn't implements the expected
- * metadata interface.
- * @throws UnmodifiableMetadataException if this class doesn't define {@code set*(…)} methods
- * corresponding to the {@code get*()} methods found in the implemented interface, or
- * if this instance is not modifiable for some other reason.
- */
- public void shallowCopy(final Object source) throws ClassCastException, UnmodifiableMetadataException {
- ensureNonNull("source", source);
- getStandard().shallowCopy(source, this);
- }
-
- /**
* Returns a view of the property values in a {@link Map}. The map is backed by this metadata
* object, so changes in the underlying metadata object are immediately reflected in the map
* and conversely.
*
* <p>The map supports the {@link Map#put(Object, Object) put(…)} and {@link Map#remove(Object)
* remove(…)} operations if the underlying metadata object contains setter methods.
- * The keys are case-insensitive and can be either the JavaBeans property name or
- * the UML identifier.</p>
+ * The {@code remove(…)} method is implemented by a call to {@code put(…, null)}.</p>
+ *
+ * <p>The keys are case-insensitive and can be either the JavaBeans property name, the getter method name
+ * or the {@linkplain org.opengis.annotation.UML#identifier() UML identifier}. The value given to a call
+ * to the {@code put(…)} method shall be an instance of the type expected by the corresponding setter method,
+ * or an instance of a type {@linkplain org.apache.sis.util.ObjectConverters#find(Class, Class) convertible}
+ * to the expected type.</p>
+ *
+ * <p>Calls to {@code put(…)} replace the previous value, with one noticeable exception: if the metadata
+ * property associated to the given key is a {@link java.util.Collection} but the given value is a single
+ * element (not a collection), then the given value is {@linkplain java.util.Collection#add(Object) added}
+ * to the existing collection. In other words, the returned map behaves as a <cite>multi-values map</cite>
+ * for the properties that allow multiple values. If the intend is to unconditionally discard all previous
+ * values, then make sure that the given value is a collection when the associated metadata property expects
+ * such collection.</p>
*
* <p>The default implementation is equivalent to the following method call:</p>
*
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] Fri May 3 16:31:02 2013
@@ -22,6 +22,10 @@ import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Collection;
import java.util.Iterator;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Field;
import net.jcip.annotations.ThreadSafe;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.ExtendedElementInformation;
@@ -81,7 +85,12 @@ import static org.apache.sis.util.Argume
* @module
*/
@ThreadSafe
-public class MetadataStandard {
+public class MetadataStandard implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 7549790450195184843L;
+
/**
* Metadata instances defined in this class. The current implementation does not yet
* contains the user-defined instances. However this may be something we will need to
@@ -115,6 +124,8 @@ public class MetadataStandard {
static {
final String[] prefix = {"Default", "Abstract"};
final String[] acronyms = {"CoordinateSystem", "CS", "CoordinateReferenceSystem", "CRS"};
+
+ // If new StandardImplementation instances are added below, please update StandardImplementation.readResolve().
ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", prefix, acronyms);
ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", prefix, null);
ISO_19119 = new MetadataStandard ("ISO 19119", "org.opengis.service.");
@@ -137,7 +148,7 @@ public class MetadataStandard {
*
* @see #getCitation()
*/
- private final Citation citation;
+ final Citation citation;
/**
* The root packages for metadata interfaces. Must have a trailing {@code '.'}.
@@ -154,7 +165,7 @@ public class MetadataStandard {
* <li>{@link PropertyAccessor} otherwise.</li>
* </ul>
*/
- private final Map<Class<?>, Object> accessors;
+ private final transient Map<Class<?>, Object> accessors; // written by reflection on deserialization.
/**
* Creates a new instance working on implementation of interfaces defined in the specified package.
@@ -170,7 +181,7 @@ public class MetadataStandard {
ensureNonNull("interfacePackage", interfacePackage);
this.citation = citation;
this.interfacePackage = interfacePackage.getName() + '.';
- this.accessors = new IdentityHashMap<Class<?>,Object>();
+ this.accessors = new IdentityHashMap<Class<?>,Object>(); // Also defined in readObject(…)
}
/**
@@ -571,8 +582,23 @@ public class MetadataStandard {
*
* <p>The map supports the {@link Map#put(Object, Object) put(…)} and {@link Map#remove(Object)
* remove(…)} operations if the underlying metadata object contains setter methods.
- * The keys are case-insensitive and can be either the JavaBeans property name or
- * the UML identifier.</p>
+ * The {@code remove(…)} method is implemented by a call to {@code put(…, null)}.
+ * Note that whether the entry appears as effectively removed from the map or just cleared
+ * (i.e. associated to a null value) depends on the {@code valuePolicy} argument.</p>
+ *
+ * <p>The keys are case-insensitive and can be either the JavaBeans property name, the getter method name
+ * or the {@linkplain org.opengis.annotation.UML#identifier() UML identifier}. The value given to a call
+ * to the {@code put(…)} method shall be an instance of the type expected by the corresponding setter method,
+ * or an instance of a type {@linkplain org.apache.sis.util.ObjectConverters#find(Class, Class) convertible}
+ * to the expected type.</p>
+ *
+ * <p>Calls to {@code put(…)} replace the previous value, with one noticeable exception: if the metadata
+ * property associated to the given key is a {@link java.util.Collection} but the given value is a single
+ * element (not a collection), then the given value is {@linkplain java.util.Collection#add(Object) added}
+ * to the existing collection. In other words, the returned map behaves as a <cite>multi-values map</cite>
+ * for the properties that allow multiple values. If the intend is to unconditionally discard all previous
+ * values, then make sure that the given value is a collection when the associated metadata property expects
+ * such collection.</p>
*
* @param metadata The metadata object to view as a map.
* @param keyPolicy Determines the string representation of map keys.
@@ -629,37 +655,6 @@ public class MetadataStandard {
}
/**
- * Copies all metadata from source to target.
- * The source must implements the same metadata interface than the target.
- *
- * <p>If the source contains any null or empty properties, then those properties will
- * <strong>not</strong> overwrite the corresponding properties in the destination metadata.</p>
- *
- * @param source The metadata to copy.
- * @param target The target metadata.
- * @throws ClassCastException if the source or target object don't
- * implements a metadata interface of the expected package.
- * @throws UnmodifiableMetadataException if the target metadata is unmodifiable,
- * or if at least one setter method was required but not found.
- *
- * @see ModifiableMetadata#clone()
- */
- public void shallowCopy(final Object source, final Object target)
- throws ClassCastException, UnmodifiableMetadataException
- {
- ensureNonNull("target", target);
- final PropertyAccessor accessor = getAccessor(target.getClass(), true);
- if (!accessor.type.isInstance(source)) {
- ensureNonNull("source", source);
- throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3,
- "source", accessor.type, source.getClass()));
- }
- if (!accessor.shallowCopy(source, target)) {
- throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
- }
- }
-
- /**
* Compares the two specified metadata objects.
* The two metadata arguments shall be implementations of a metadata interface defined by
* this {@code MetadataStandard}, otherwise an exception will be thrown. However the two
@@ -723,4 +718,26 @@ public class MetadataStandard {
public String toString() {
return Classes.getShortClassName(this) + '[' + citation.getTitle() + ']';
}
+
+ /**
+ * Assigns an {@link IdentityHashMap} instance to the given field.
+ * Used on deserialization only.
+ */
+ final void setMapForField(final Class<?> classe, final String name) {
+ try {
+ final Field field = classe.getDeclaredField(name);
+ field.setAccessible(true);
+ field.set(this, new IdentityHashMap());
+ } catch (Exception e) { // (ReflectiveOperationException) on JDK7 branch.
+ throw new AssertionError(e); // Should never happen (tested by MetadataStandardTest).
+ }
+ }
+
+ /**
+ * Invoked during deserialization for restoring the transient fields.
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ setMapForField(MetadataStandard.class, "accessors");
+ }
}
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java [UTF-8] Fri May 3 16:31:02 2013
@@ -21,7 +21,9 @@ import java.util.Collections;
import java.util.AbstractCollection;
import java.util.NoSuchElementException;
import java.util.ConcurrentModificationException;
+import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.Debug;
// Related to JDK7
@@ -86,7 +88,7 @@ final class MetadataTreeChildren extends
* accessor = parent.table.standard.getAccessor(metadata.getClass(), true);
* }
*/
- private final PropertyAccessor accessor;
+ final PropertyAccessor accessor;
/**
* The children to be returned by this collection. All elements in this collection are
@@ -136,7 +138,7 @@ final class MetadataTreeChildren extends
* @param index The index in the accessor (<em>not</em> the index in this collection).
*/
final void clearAt(final int index) {
- accessor.set(index, metadata, null, false);
+ accessor.set(index, metadata, null, PropertyAccessor.RETURN_NULL);
}
/**
@@ -467,6 +469,60 @@ final class MetadataTreeChildren extends
}
/**
+ * Adds the given node to this list. This method fetches the object from {@link TableColumn#VALUE}
+ * and assigns it to the property identified by {@link TableColumn#IDENTIFIER}. All other columns
+ * are ignored.
+ *
+ * <p>If the identified property is a collection, then this method adds the value to that collection.
+ * Otherwise the new value will be set only if the previous value is null or empty.</p>
+ *
+ * <p>This method does not iterate explicitly through the children list, because adding a metadata
+ * object implicitly adds all its children.</p>
+ *
+ * @param node The node from which to get the values.
+ * @return {@code true} if the metadata changed as a result of this method call.
+ * @throws NullPointerException if the given node is null.
+ * @throws IllegalArgumentException if this list does not have a property for the node identifier.
+ * @throws IllegalStateException if a value already exists and no more value can be added for the node identifier.
+ * @throws UnmodifiableMetadataException if the property for the node identifier is read-only.
+ * @throws ClassCastException if the node value can not be converted to the expected type.
+ * @throws BackingStoreException if the metadata implementation threw a checked exception.
+ */
+ @Override
+ public boolean add(final TreeTable.Node node) throws IllegalStateException {
+ final String identifier = node.getValue(TableColumn.IDENTIFIER);
+ if (identifier == null) {
+ throw new IllegalArgumentException(Errors.format(
+ Errors.Keys.MissingValueInColumn_1, TableColumn.IDENTIFIER.getHeader()));
+ }
+ return add(accessor.indexOf(identifier, true), node.getValue(TableColumn.VALUE));
+ }
+
+ /**
+ * Implementation of {@link #add(TreeTable.Node)}, also invoked by {@link MetadataTreeNode.NewChild}.
+ * This method will attempt to convert the given {@code value} to the expected type.
+ *
+ * @param index The index in the accessor (<em>not</em> the index in this collection).
+ * @param value The property value to add.
+ * @return {@code true} if the metadata changed as a result of this method call.
+ */
+ final boolean add(final int index, final Object value) throws IllegalStateException {
+ if (ValueExistencePolicy.isNullOrEmpty(value)) {
+ return false;
+ }
+ // Conversion attempt happen in the PropertyAccessor.set(…) method.
+ final Boolean changed = (Boolean) accessor.set(index, metadata, value, PropertyAccessor.APPEND);
+ if (changed == null) {
+ throw new IllegalStateException(Errors.format(Errors.Keys.ValueAlreadyDefined_1,
+ accessor.name(index, KeyNamePolicy.UML_IDENTIFIER)));
+ }
+ if (changed) {
+ modCount++;
+ }
+ return changed;
+ }
+
+ /**
* Returns a string representation of this collection for debugging purpose.
* This string representation uses one line per element instead of formatting
* everything on a single line.
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java [UTF-8] Fri May 3 16:31:02 2013
@@ -28,8 +28,10 @@ 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.ObjectConverters;
import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.TreeTable.Node;
+import org.apache.sis.util.collection.CheckedContainer;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
@@ -45,12 +47,16 @@ import org.apache.sis.util.resources.Voc
* If a metadata property is a collection, then there is an instance of the {@link CollectionElement}
* subclass for each element in the collection.</p>
*
+ * <p>The {@link #newChild()} operation is supported if the node is not a leaf. The user shall
+ * set the identifier and the value, in that order, before any other operation on the new child.
+ * See {@code newChild()} javadpc for an example.</p>
+ *
* @author Martin Desruisseaux (Geomatys)
* @since 0.3
* @version 0.3
* @module
*/
-class MetadataTreeNode implements TreeTable.Node, Serializable {
+class MetadataTreeNode implements Node, Serializable {
/**
* For cross-version compatibility.
*/
@@ -64,7 +70,7 @@ class MetadataTreeNode implements TreeTa
* does not implement the {@link List} interface. So we are better to never give to the user
* a collection implementing {@code List} in order to signal incorrect casts sooner.</p>
*/
- private static final Collection<TreeTable.Node> LEAF = Collections.emptySet();
+ private static final Collection<Node> LEAF = Collections.emptySet();
/**
* A sentinel value meaning that the node is known to allow {@linkplain #children}, but
@@ -74,7 +80,7 @@ class MetadataTreeNode implements TreeTa
* <p>Any value distinct than {@link #LEAF} is okay. This value will never be visible
* to the user.</p>
*/
- private static final Collection<TreeTable.Node> PENDING = Collections.emptyList();
+ private static final Collection<Node> PENDING = Collections.emptyList();
/**
* The table for which this node is an element. Contains information like
@@ -126,7 +132,7 @@ class MetadataTreeNode implements TreeTa
*
* @see #getChildren()
*/
- private transient Collection<TreeTable.Node> children;
+ private transient Collection<Node> children;
/**
* Creates the root node of a new metadata tree table.
@@ -310,11 +316,11 @@ class MetadataTreeNode implements TreeTa
}
/**
- * Sets the metadata value for this node.
+ * Sets the property value for this node.
*/
@Override
void setUserObject(final Object value) {
- accessor.set(indexInData, metadata, value, false);
+ accessor.set(indexInData, metadata, value, PropertyAccessor.RETURN_NULL);
}
/**
@@ -423,6 +429,41 @@ class MetadataTreeNode implements TreeTa
throw new ConcurrentModificationException();
}
}
+
+ /**
+ * Sets the property value for this node.
+ */
+ @Override
+ void setUserObject(Object value) {
+ final Collection<?> values = (Collection<?>) super.getUserObject();
+ if (!(values instanceof List<?>)) {
+ // 'setValue' is the public method which invoked this one.
+ throw new UnsupportedOperationException(Errors.format(
+ Errors.Keys.UnsupportedOperation_1, "setValue"));
+ }
+ final Class<?> targetType;
+ if (values instanceof CheckedContainer<?>) {
+ // Typically the same than getElementType(), but let be safe
+ // in case some implementations have stricter requirements.
+ targetType = ((CheckedContainer<?>) values).getElementType();
+ } else {
+ targetType = getElementType();
+ }
+ value = ObjectConverters.convert(value, targetType);
+ try {
+ /*
+ * Unsafe addition into a collection. In SIS implementation, the collection is
+ * actually an instance of CheckedCollection, so the check will be performed at
+ * runtime. However other implementations could use unchecked collection. We have
+ * done our best for converting the type above, there is not much more we can do...
+ */
+ // No @SuppressWarnings because this is a real hole.
+ ((List) values).set(indexInList, value);
+ } catch (IndexOutOfBoundsException e) {
+ // Same rational than in the getUserObject() method.
+ throw new ConcurrentModificationException();
+ }
+ }
}
@@ -433,7 +474,7 @@ class MetadataTreeNode implements TreeTa
* Returns the parent node, or {@code null} if this node is the root of the tree.
*/
@Override
- public final TreeTable.Node getParent() {
+ public final Node getParent() {
return parent;
}
@@ -461,7 +502,7 @@ class MetadataTreeNode implements TreeTa
* Only metadata object can have children.
*/
@Override
- public final Collection<TreeTable.Node> getChildren() {
+ public final Collection<Node> getChildren() {
/*
* 'children' is set to LEAF 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.
@@ -496,12 +537,127 @@ class MetadataTreeNode implements TreeTa
}
/**
- * Unconditionally throws {@link UnsupportedOperationException}, because there is no
- * way we can safely determine which metadata property a new child would be for.
+ * Returns a proxy for a new property to be defined in the metadata object.
+ * The user shall set the identifier and the value, in that order, before
+ * any other operation on the new child. Example:
+ *
+ * {@preformat java
+ * TreeTable.Node node = ...;
+ * TreeTable.Node child = node.newChild();
+ * child.setValue(TableColumn.IDENTIFIER, "title");
+ * child.setValue(TableColumn.VALUE, "Le petit prince");
+ * // Nothing else to do - node has been added.
+ * }
+ *
+ * Do not keep a reference to the returned node for a long time, since it is only
+ * a proxy toward the real node to be created once the identifier is known.
+ *
+ * @throws UnsupportedOperationException If this node {@linkplain #isLeaf() is a leaf}.
*/
@Override
- public final TreeTable.Node newChild() {
- throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnsupportedOperation_1, "newChild"));
+ public final Node newChild() throws UnsupportedOperationException {
+ if (isLeaf()) {
+ throw new UnsupportedOperationException(Errors.format(Errors.Keys.NodeIsLeaf_1, this));
+ }
+ return new NewChild();
+ }
+
+ /**
+ * The proxy to be returned by {@link MetadataTreeNode#newChild()}.
+ * User shall not keep a reference to this proxy for a long time.
+ */
+ private final class NewChild implements Node {
+ /**
+ * Index in the {@link PropertyAccessor} for the property to be set.
+ * This index is known only after a value has been specified for the
+ * {@link TableColumn#IDENTIFIER}.
+ */
+ private int indexInData = -1;
+
+ /**
+ * The real node created after the identifier and the value have been specified.
+ * All operations will be delegated to that node after it has been determined.
+ */
+ private MetadataTreeNode delegate;
+
+ /**
+ * Returns the {@link #delegate} node if non-null, or throw an exception otherwise.
+ *
+ * @throws IllegalStateException if the identifier and value columns have not yet been defined.
+ */
+ private MetadataTreeNode delegate() throws IllegalStateException {
+ if (delegate != null) {
+ return delegate;
+ }
+ throw new IllegalStateException(Errors.format(Errors.Keys.MissingValueInColumn_1,
+ (indexInData < 0 ? TableColumn.IDENTIFIER : TableColumn.VALUE).getHeader()));
+ }
+
+ /**
+ * Returns all children of the parent node. The new child will be added to that list.
+ */
+ private MetadataTreeChildren getSiblings() {
+ return (MetadataTreeChildren) MetadataTreeNode.this.getChildren();
+ }
+
+ /**
+ * If the {@link #delegate} is not yet known, set the identifier or the value.
+ * After the identifier and value have been specified, delegates to the real node.
+ */
+ @Override
+ public <V> void setValue(final TableColumn<V> column, final V value) {
+ if (delegate == null) {
+ /*
+ * For the given identifier, get the index in the property accessor.
+ * This can be done only before the 'delegate' is found - after that
+ * point, the identifier will become unmodifiable.
+ */
+ if (column == TableColumn.IDENTIFIER) {
+ ArgumentChecks.ensureNonNull("value", value);
+ indexInData = getSiblings().accessor.indexOf((String) value, true);
+ return;
+ }
+ /*
+ * Set the value for the property specified by the above identifier,
+ * then get the 'delegate' on the assumption that the new value will
+ * be added at the end of collection (if the property is a collection).
+ */
+ if (column == TableColumn.VALUE) {
+ ArgumentChecks.ensureNonNull("value", value);
+ if (indexInData < 0) {
+ throw new IllegalStateException(Errors.format(Errors.Keys.MissingValueInColumn_1,
+ TableColumn.IDENTIFIER.getHeader()));
+ }
+ final MetadataTreeChildren siblings = getSiblings();
+ final int indexInList;
+ if (siblings.isCollection(indexInData)) {
+ indexInList = ((Collection<?>) siblings.valueAt(indexInData)).size();
+ } else {
+ indexInList = -1;
+ }
+ if (!siblings.add(indexInData, value)) {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.ElementAlreadyPresent_1, value));
+ }
+ delegate = siblings.childAt(indexInData, indexInList);
+ return;
+ }
+ }
+ delegate().setValue(column, value);
+ }
+
+ /**
+ * For all operations other than {@code setValue(…)}, delegates to the {@link #delegate} node
+ * or to some code functionally equivalent.
+ *
+ * @throws IllegalStateException if the identifier and value columns have not yet been defined.
+ */
+ @Override public Node getParent() {return MetadataTreeNode.this;}
+ @Override public boolean isLeaf() {return delegate().isLeaf();}
+ @Override public Collection<Node> getChildren() {return delegate().getChildren();}
+ @Override public Node newChild() {return delegate().newChild();}
+ @Override public <V> V getValue(TableColumn<V> column) {return delegate().getValue(column);}
+ @Override public boolean isEditable(TableColumn<?> column) {return delegate().isEditable(column);}
+ @Override public Object getUserObject() {return delegate().getUserObject();}
}
/**
@@ -533,11 +689,17 @@ class MetadataTreeNode implements TreeTa
/**
* Sets the value if the given column is {@link TableColumn#VALUE}. This method verifies
* the {@code column} argument, then delegates to {@link #setUserObject(Object)}.
+ *
+ * <p>This method does not accept null value, because setting a singleton property to null
+ * with {@link ValueExistencePolicy#NON_EMPTY} is equivalent to removing the property, and
+ * setting a collection element to null is not allowed. Those various behavior are at risk
+ * of causing confusion, so we are better to never allow null.</p>
*/
@Override
public final <V> void setValue(final TableColumn<V> column, final V value) throws UnsupportedOperationException {
ArgumentChecks.ensureNonNull("column", column);
if (column == TableColumn.VALUE) {
+ ArgumentChecks.ensureNonNull("value", value);
setUserObject(value);
} else if (MetadataTreeTable.COLUMNS.contains(column)) {
throw new UnsupportedOperationException(unmodifiableCellValue(column));
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] Fri May 3 16:31:02 2013
@@ -67,8 +67,8 @@ import static org.apache.sis.util.collec
* <ul>
* <li>The standard properties defined by the GeoAPI (or other standard) interfaces.
* Those properties are the only one accessible by most methods in this class,
- * except {@link #equals(Object, Object, ComparisonMode, boolean)},
- * {@link #shallowCopy(Object, Object)} and {@link #freeze(Object)}.</li>
+ * except {@link #equals(Object, Object, ComparisonMode, boolean)} and
+ * {@link #freeze(Object)}.</li>
*
* <li>Extra properties defined by the {@link IdentifiedObject} interface. Those properties
* invisible in the ISO 19115 model, but appears in ISO 19139 XML marshalling. So we do
@@ -104,6 +104,12 @@ final class PropertyAccessor {
static final int COUNT_FIRST=0, COUNT_SHALLOW=1, COUNT_DEEP=2;
/**
+ * Enumeration constants for the {@code mode} argument
+ * in the {@link #set(int, Object, Object, int)} method.
+ */
+ static final int RETURN_NULL=0, RETURN_PREVIOUS=1, APPEND=2;
+
+ /**
* Additional getter to declare in every list of getter methods that do not already provide
* their own {@code getIdentifiers()} method. We handle this method specially because it is
* needed for XML marshalling in ISO 19139 compliant document, while not part of abstract
@@ -207,9 +213,9 @@ final class PropertyAccessor {
* looking for a converter, and also reduce thread contention since it reduces the number
* of calls to the synchronized {@link ObjectConverters#find(Class, Class)} method.
*
- * @see #set(int, Object, Object, boolean)
+ * @see #convert(Object[], Class)
*/
- private transient volatile ObjectConverter<?,?> converter;
+ private transient volatile ObjectConverter<?,?> lastConverter;
/**
* The property information, including the name and restrictions on valid values.
@@ -298,7 +304,7 @@ final class PropertyAccessor {
* If we found no setter method expecting an argument of the same type than the
* argument returned by the GeoAPI method, try again with the type returned by
* the implementation class. It is typically the same type, but sometime it may
- * be a subtype.
+ * be a parent type.
*
* It is a necessary condition that the type returned by the getter is assignable
* to the type expected by the setter. This contract is required by the 'freeze'
@@ -663,10 +669,24 @@ final class PropertyAccessor {
}
/**
- * Sets a value for the specified metadata and returns the old value if {@code getOld} is
- * {@code true}. If the old value was a collection or a map, then this value is copied in
- * a new collection or map before the new value is set, because the setter methods typically
- * copy the new collection in their existing instance.
+ * Sets a value for the specified metadata and returns the old value if {@code mode} is
+ * {@link #RETURN_PREVIOUS}. The {@code mode} argument can be one of the following:
+ *
+ * <ul>
+ * <li>RETURN_NULL: Set the value and returns {@code null}.</li>
+ * <li>RETURN_PREVIOUS: Set the value and returns the previous value. If the previous value was a
+ * collection or a map, then that value is copied in a new collection or map
+ * before the new value is set because the setter methods typically copy the
+ * new collection in their existing instance.</li>
+ * <li>APPEND: Set the value only if it doesn't overwrite an existing value, then returns
+ * {@link Boolean#TRUE} if the metadata changed as a result of this method call,
+ * {@code Boolean#FALSE} if the metadata didn't changed or {@code null} if the
+ * value can not be set because an other value already exists.</li>
+ * </ul>
+ *
+ * <p>The {@code APPEND} mode has an additional side effect: it sets the {@code append} argument to
+ * {@code true} in the call to the {@link #convert(Method, Object, Object, Object[], Class, boolean)}
+ * method. See the {@code convert} javadoc for more information.</p>
*
* <p>If the given index is out of bounds, then this method does nothing and return {@code null}.
* We do that because the {@link ValueMap#remove(Object)} method may invoke this method with
@@ -674,16 +694,17 @@ final class PropertyAccessor {
* However the given value will be silently discarded, so index out-of-bounds shall be used only
* in the context of {@code remove} operations (this is not verified).</p>
*
- * @param index The index of the property to set.
- * @param metadata The metadata object on which to set the value.
- * @param value The new value.
- * @param getOld {@code true} if this method should first fetches the old value.
- * @return The old value, or {@code null} if {@code getOld} was {@code false}.
+ * @param index The index of the property to set.
+ * @param metadata The metadata object on which to set the value.
+ * @param value The new value.
+ * @param mode Whether this method should first fetches the old value,
+ * as one of the {@code RETURN_*} constants.
+ * @return The old value, or {@code null} if {@code returnValue} was {@code RETURN_NULL}.
* @throws UnmodifiableMetadataException if the property for the given key is read-only.
* @throws ClassCastException if the given value is not of the expected type.
* @throws BackingStoreException if the implementation threw a checked exception.
*/
- final Object set(final int index, final Object metadata, final Object value, final boolean getOld)
+ final Object set(final int index, final Object metadata, final Object value, final int mode)
throws UnmodifiableMetadataException, ClassCastException, BackingStoreException
{
if (index < 0 || index >= standardCount) {
@@ -693,28 +714,58 @@ final class PropertyAccessor {
final Method getter = getters[index];
final Method setter = setters[index];
if (setter != null) {
- final Object old;
- final Object copy;
- if (getOld) {
- old = get(getter, metadata);
- if (old instanceof Collection<?>) {
- if (old instanceof List<?>) {
- copy = snapshot((List<?>) old);
+ final Object oldValue;
+ final Object snapshot; // Copy of oldValue before modification.
+ switch (mode) {
+ case RETURN_NULL: {
+ oldValue = null;
+ snapshot = null;
+ break;
+ }
+ case APPEND: {
+ oldValue = get(getter, metadata);
+ snapshot = null;
+ break;
+ }
+ case RETURN_PREVIOUS: {
+ oldValue = get(getter, metadata);
+ if (oldValue instanceof Collection<?>) {
+ if (oldValue instanceof List<?>) {
+ snapshot = snapshot((List<?>) oldValue);
+ } else {
+ snapshot = modifiableCopy((Collection<?>) oldValue);
+ }
+ } else if (oldValue instanceof Map<?,?>) {
+ snapshot = modifiableCopy((Map<?,?>) oldValue);
} else {
- copy = modifiableCopy((Collection<?>) old);
+ snapshot = oldValue;
}
- } else if (old instanceof Map<?,?>) {
- copy = modifiableCopy((Map<?,?>) old);
- } else {
- copy = old;
+ break;
}
- } else {
- copy = old = null;
+ default: throw new AssertionError(mode);
}
+ /*
+ * Converts the new value to a type acceptable for the setter method (if possible).
+ * If the new value is a singleton while the expected type is a collection, then the 'convert'
+ * method added the singleton in the existing collection, which may result in no change if the
+ * collection is a Set and the new value already exists in that Set. If we detect that there is
+ * no change, then we don't need to invoke the setter method. Note that we conservatively assume
+ * that there is always a change in RETURN_NULL mode since we don't know the previous value.
+ */
final Object[] newValues = new Object[] {value};
- converter = convert(getter, metadata, old, newValues, elementTypes[index], converter);
- set(setter, metadata, newValues);
- return copy;
+ Boolean changed = convert(getter, metadata, oldValue, newValues, elementTypes[index], mode == APPEND);
+ if (changed == null) {
+ changed = (mode == RETURN_NULL) || (newValues[0] != oldValue);
+ if (changed && mode == APPEND && !ValueExistencePolicy.isNullOrEmpty(oldValue)) {
+ // If 'convert' did not added the value in a collection and if a value already
+ // exists, do not modify the existing value. Exit now with "no change" status.
+ return null;
+ }
+ }
+ if (changed) {
+ set(setter, metadata, newValues);
+ }
+ return (mode == APPEND) ? changed : snapshot;
}
}
throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.CanNotSetPropertyValue_1, names[index]));
@@ -759,149 +810,176 @@ final class PropertyAccessor {
/**
* Converts a value to the type required by a setter method.
+ * The values are converted in-place in the {@code newValues} array. We use an array instead
+ * of a single argument and return value because an array will be needed anyway for invoking
+ * the {@link #convert(Object[], Class)} and {@link Method#invoke(Object, Object[])} methods.
+ *
+ * {@section The collection special case}
+ * If the metadata property is a collection, then there is a choice:
+ *
+ * <ul>
+ * <li>If {@code append} is {@code true}, then the new value (which may itself be a collection)
+ * is unconditionally added to the previous collection.</li>
+ * <li>If {@code append} is {@code false} and the new value is <strong>not</strong> a collection,
+ * then the new value is added to the existing collection. In other words, we behave as a
+ * <cite>multi-values map</cite> for the properties that allow multi-values.</li>
+ * <li>Otherwise the new collection replaces the previous collection. All previous values
+ * are discarded.</li>
+ * </ul>
+ *
+ * Adding new values to the previous collection may or may not change the original metadata
+ * depending on whether those collections are live or not. In Apache SIS implementation,
+ * those collections are live. However this method can be though as if the collections were
+ * not live, since the caller will invoke the setter method with the collection anyway.
*
* @param getter The method to use for fetching the previous value.
- * @param metadata The metadata object to query.
+ * @param metadata The metadata object to query and modify.
* @param oldValue The value returned by {@code get(getter, metadata)}, or {@code null} if unknown.
* This parameter is only an optimization for avoiding to invoke the getter method
* twice if the value is already known.
- * @param newValues The argument to convert. It must be an array of length 1.
- * The content of this array will be modified in-place.
- * @param elementType The type required by the setter method.
- * @param converter The last converter used, or {@code null} if none.
- * This converter is provided only as a hint and doesn't need to be accurate.
- * @return The last converter used, or {@code null}.
+ * @param newValues The argument to convert. The content of this array will be modified in-place.
+ * Current implementation requires an array of length 1, however this restriction
+ * may be relaxed in a future SIS version if needed.
+ * @param elementType The target type (if singleton) or the type of elements in the collection.
+ * @param append If {@code true} and the value is a collection, then that collection will be added
+ * to any previously existing collection instead of replacing it.
+ * @return If the given value has been added to an existing collection, then whether that existing
+ * collection has been modified as a result of this method call. Otherwise {@code null}.
* @throws ClassCastException if the element of the {@code arguments} array is not of the expected type.
* @throws BackingStoreException If the implementation threw a checked exception.
*/
- private static ObjectConverter<?,?> convert(final Method getter, final Object metadata,
- final Object oldValue, final Object[] newValues, Class<?> elementType,
- ObjectConverter<?,?> converter) throws ClassCastException, BackingStoreException
+ private Boolean convert(final Method getter, final Object metadata, Object oldValue, final Object[] newValues,
+ Class<?> elementType, final boolean append) throws ClassCastException, BackingStoreException
{
assert newValues.length == 1;
Object newValue = newValues[0];
+ Class<?> targetType = getter.getReturnType();
if (newValue == null) {
// Can't test elementType, because it has been converted to the wrapper class.
- final Class<?> type = getter.getReturnType();
- if (type.isPrimitive()) {
- newValues[0] = Numbers.valueOfNil(type);
+ if (targetType.isPrimitive()) {
+ newValues[0] = Numbers.valueOfNil(targetType);
}
+ return null;
+ }
+ Boolean changed = null;
+ if (!Collection.class.isAssignableFrom(targetType)) {
+ /*
+ * We do not expect a collection. The provided argument should not be a
+ * collection neither. It should be some class convertible to targetType.
+ *
+ * If nevertheless the user provided a collection and this collection contains
+ * no more than 1 element, then as a convenience we will extract the singleton
+ * element and process it as if it had been directly provided in argument.
+ */
+ if (newValue instanceof Collection<?>) {
+ final Iterator<?> it = ((Collection<?>) newValue).iterator();
+ if (!it.hasNext()) { // If empty, process like null argument.
+ newValues[0] = null;
+ return null;
+ }
+ final Object next = it.next();
+ if (!it.hasNext()) { // Singleton
+ newValue = next;
+ }
+ // Other cases: let the collection unchanged. It is likely to
+ // cause an exception later. The message should be appropriate.
+ }
+ // Getter type (targetType) shall be the same than the setter type (elementType).
+ assert elementType == Numbers.primitiveToWrapper(targetType) : elementType;
+ targetType = elementType; // Ensure that we use primitive wrapper.
} else {
- Class<?> targetType = getter.getReturnType();
- if (!Collection.class.isAssignableFrom(targetType)) {
- /*
- * We do not expect a collection. The provided argument should not be a
- * collection neither. It should be some class convertible to targetType.
- *
- * If nevertheless the user provided a collection and this collection contains
- * no more than 1 element, then as a convenience we will extract the singleton
- * element and process it as if it had been directly provided in argument.
- */
- if (newValue instanceof Collection<?>) {
- final Iterator<?> it = ((Collection<?>) newValue).iterator();
- if (!it.hasNext()) { // If empty, process like null argument.
- newValues[0] = null;
- return converter;
- }
- final Object next = it.next();
- if (!it.hasNext()) { // Singleton
- newValue = next;
- }
- // Other cases: let the collection unchanged. It is likely to
- // cause an exception later. The message should be appropriate.
- }
- // Getter type (targetType) shall be the same than the setter type (elementType).
- assert elementType == Numbers.primitiveToWrapper(targetType) : elementType;
- targetType = elementType; // Ensure that we use primitive wrapper.
- } else {
- /*
- * We expect a collection. Collections are handled in one of the two ways below:
- *
- * - If the user gives a collection, the user's collection replaces any
- * previous one. The content of the previous collection is discarded.
- *
- * - If the user gives a single value, it will be added to the existing
- * collection (if any). The previous values are not discarded. This
- * allow for incremental filling of a property.
- *
- * The code below prepares an array of elements to be converted and wraps that
- * array in a List (to be converted to a Set after this block if required). It
- * is okay to convert the elements after the List creation since the list is a
- * wrapper.
- */
- final Collection<?> addTo;
- final Object[] elements;
- if (newValue instanceof Collection<?>) {
- elements = ((Collection<?>) newValue).toArray();
- newValue = Arrays.asList(elements); // Content will be converted later.
- addTo = null;
- } else {
- elements = new Object[] {newValue};
- newValue = addTo = (Collection<?>) (oldValue != null ? oldValue : get(getter, metadata));
- if (addTo == null) {
- // No previous collection. Create one.
- newValue = Arrays.asList(elements);
- } else if (addTo instanceof CheckedContainer<?>) {
+ /*
+ * We expect a collection. Collections are handled in one of the two ways below:
+ *
+ * - If the user gives a collection, the user's collection replaces any
+ * previous one. The content of the previous collection is discarded.
+ *
+ * - If the user gives a single value, it will be added to the existing
+ * collection (if any). The previous values are not discarded. This
+ * allow for incremental filling of a property.
+ *
+ * The code below prepares an array of elements to be converted and wraps that
+ * array in a List (to be converted to a Set after this block if required). It
+ * is okay to convert the elements after the List creation since the list is a
+ * wrapper.
+ */
+ final boolean isCollection = (newValue instanceof Collection<?>);
+ final Object[] elements = isCollection ? ((Collection<?>) newValue).toArray() : new Object[] {newValue};
+ final List<Object> elementList = Arrays.asList(elements); // Converted later (see above comment).
+ newValue = elementList; // Still contains the same values, but now guaranteed to be a collection.
+ Collection<?> addTo = null;
+ if (!isCollection || append) {
+ if (oldValue == null) {
+ oldValue = get(getter, metadata);
+ }
+ if (oldValue != null) {
+ addTo = (Collection<?>) oldValue;
+ if (addTo instanceof CheckedContainer<?>) {
// Get the explicitly-specified element type.
elementType = ((CheckedContainer<?>) addTo).getElementType();
}
+ newValue = addTo;
}
- if (elementType != null) {
- converter = convert(elements, elementType, converter);
- }
- /*
- * We now have objects of the appropriate type. If we have a singleton to be added
- * in an existing collection, add it now. In that case the 'newValue' should refer
- * to the 'addTo' collection. We rely on the ModifiableMetadata.writeCollection(…)
- * optimization for detecting that the new collection is the same instance than
- * the old one so there is nothing to do. We could exit from the method, but let
- * it continues in case the user override the 'setFoo(...)' method.
- */
- if (addTo != null) {
- /*
- * Unsafe addition into a collection. In SIS implementation, the collection is
- * actually an instance of CheckedCollection, so the check will be performed at
- * runtime. However other implementations could use unchecked collection.
- * There is not much we can do...
- */
- // No @SuppressWarnings because this is a real hole.
- ((Collection<Object>) addTo).add(elements[0]);
- }
+ }
+ if (elementType != null) {
+ convert(elements, elementType);
}
/*
- * If the expected type was not a collection, the conversion of user value happen
- * here. Otherwise conversion from List to Set (if needed) happen here.
+ * We now have objects of the appropriate type. If we have a singleton to be added
+ * in an existing collection, add it now. In that case the 'newValue' should refer
+ * to the 'addTo' collection. We rely on the ModifiableMetadata.writeCollection(…)
+ * optimization for detecting that the new collection is the same instance than
+ * the old one so there is nothing to do. We could exit from the method, but let
+ * it continues in case the user override the 'setFoo(…)' method.
*/
- newValues[0] = newValue;
- converter = convert(newValues, targetType, converter);
+ if (addTo != null) {
+ /*
+ * Unsafe addition into a collection. In SIS implementation, the collection is
+ * actually an instance of CheckedCollection, so the check will be performed at
+ * runtime. However other implementations could use unchecked collection.
+ * There is not much we can do...
+ */
+ // No @SuppressWarnings because this is a real hole.
+ changed = ((Collection) addTo).addAll(elementList);
+ }
}
- return converter;
+ /*
+ * If the expected type was not a collection, the conversion of user value happen
+ * here. Otherwise conversion from List to Set (if needed) happen here.
+ */
+ newValues[0] = newValue;
+ convert(newValues, targetType);
+ return changed;
}
/**
* Converts values in the specified array to the given type.
- * The given converter will be used if suitable, or a new one fetched otherwise.
+ * The array content is modified in-place. This method accepts an array instead than
+ * a single value because the values to convert may be the content of a collection.
*
* @param elements The array which contains element to convert.
* @param targetType The base type of target elements.
- * @param converter The proposed converter, or {@code null}.
- * @return The last converter used, or {@code null}.
* @throws ClassCastException If an element can't be converted.
*/
@SuppressWarnings({"unchecked","rawtypes"})
- private static ObjectConverter<?,?> convert(final Object[] elements, final Class<?> targetType,
- ObjectConverter<?,?> converter) throws ClassCastException
- {
+ private void convert(final Object[] elements, final Class<?> targetType) throws ClassCastException {
+ boolean hasNewConverter = false;
+ ObjectConverter<?,?> converter = null;
for (int i=0; i<elements.length; i++) {
final Object value = elements[i];
if (value != null) {
final Class<?> sourceType = value.getClass();
if (!targetType.isAssignableFrom(sourceType)) try {
- if (converter == null
- || !converter.getSourceClass().isAssignableFrom(sourceType)
- || !targetType.isAssignableFrom(converter.getTargetClass()))
+ if (converter == null) {
+ converter = lastConverter; // Volatile field - read only if needed.
+ }
+ // Require the exact same classes, not parent or subclass,
+ // otherwise the converter could be stricter than necessary.
+ if (converter == null || converter.getSourceClass() != sourceType
+ || converter.getTargetClass() != targetType)
{
converter = ObjectConverters.find(sourceType, targetType);
+ hasNewConverter = true;
}
elements[i] = ((ObjectConverter) converter).convert(value);
} catch (UnconvertibleObjectException cause) {
@@ -912,7 +990,9 @@ final class PropertyAccessor {
}
}
}
- return converter;
+ if (hasNewConverter) {
+ lastConverter = converter; // Volatile field - store only if needed.
+ }
}
/**
@@ -1005,27 +1085,26 @@ final class PropertyAccessor {
}
/**
- * Copies all non-empty metadata from source to target. The source can be any implementation
+ * Appends all non-empty metadata from source to target. The source can be any implementation
* of the metadata interface, but the target must be the implementation expected by this class.
*
* <p>If the source contains any null or empty properties, then those properties will
- * <strong>not</strong> overwrite the corresponding properties in the destination metadata.</p>
+ * not overwrite the corresponding properties in the destination metadata.</p>
*
* @param source The metadata to copy.
- * @param target The target metadata.
+ * @param target The target metadata where to append.
* @return {@code true} in case of success, or {@code false} if at least
* one setter method was not found.
* @throws UnmodifiableMetadataException if the target metadata is unmodifiable.
* @throws BackingStoreException If the implementation threw a checked exception.
*/
- public boolean shallowCopy(final Object source, final Object target)
+ public boolean append(final Object source, final Object target)
throws UnmodifiableMetadataException, BackingStoreException
{
// Because this PropertyAccesssor is designed for the target, we must
// check if the extra methods are suitable for the source object.
- ObjectConverter<?,?> converter = this.converter;
- boolean success = true;
assert type.isInstance(source) : Classes.getClass(source);
+ boolean success = true;
final Object[] arguments = new Object[1];
for (int i=0; i<standardCount; i++) {
final Method getter = getters[i];
@@ -1036,14 +1115,13 @@ final class PropertyAccessor {
}
final Method setter = setters[i];
if (setter != null) {
- converter = convert(getter, target, null, arguments, elementTypes[i], converter);
+ convert(getter, target, null, arguments, elementTypes[i], true);
set(setter, target, arguments);
} else {
success = false;
}
}
}
- this.converter = converter;
return success;
}
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java [UTF-8] Fri May 3 16:31:02 2013
@@ -18,6 +18,7 @@ package org.apache.sis.metadata;
import java.util.Map;
import java.util.IdentityHashMap;
+import java.io.ObjectStreamException;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
@@ -32,6 +33,11 @@ import org.apache.sis.util.logging.Loggi
*/
final class StandardImplementation extends MetadataStandard {
/**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 855786625369724248L;
+
+ /**
* The root packages for metadata implementations, or {@code null} if none.
* If non-null, then this string must ends with a trailing {@code "."}.
*/
@@ -63,7 +69,7 @@ final class StandardImplementation exten
* less objects since <code>IdentityHashMap</code> implementation doesn't need the chain
* of objects created by <code>HashMap</code>.}
*/
- private final Map<Class<?>,Class<?>> implementations;
+ private final transient Map<Class<?>,Class<?>> implementations; // written by reflection on deserialization.
/**
* Creates a new instance working on implementation of interfaces defined in the
@@ -151,4 +157,19 @@ final class StandardImplementation exten
}
return null;
}
+
+ /**
+ * Invoked on deserialization. Returns one of the pre-existing constants if possible.
+ */
+ Object readResolve() throws ObjectStreamException {
+ if (ISO_19111.citation.equals(citation)) return ISO_19111;
+ if (ISO_19115.citation.equals(citation)) return ISO_19115;
+ /*
+ * Following should not occurs, unless we are deserializing an instance created by a
+ * newer version of the Apache SIS library. The newer version could contains constants
+ * not yet declared in this older SIS version, so we have to use this instance.
+ */
+ setMapForField(StandardImplementation.class, "implementations");
+ return this;
+ }
}
Modified: sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java [UTF-8] Fri May 3 16:31:02 2013
@@ -22,6 +22,9 @@ import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.sis.util.CharSequences;
+import static org.apache.sis.metadata.PropertyAccessor.RETURN_NULL;
+import static org.apache.sis.metadata.PropertyAccessor.RETURN_PREVIOUS;
+
// Related to JDK7
import org.apache.sis.internal.jdk7.Objects;
@@ -112,7 +115,7 @@ final class ValueMap extends PropertyMap
*/
@Override
public Object put(final String key, final Object value) {
- final Object old = accessor.set(accessor.indexOf(key, true), metadata, value, true);
+ final Object old = accessor.set(accessor.indexOf(key, true), metadata, value, RETURN_PREVIOUS);
return valuePolicy.isSkipped(old) ? null : old;
}
@@ -127,7 +130,7 @@ final class ValueMap extends PropertyMap
@Override
public void putAll(final Map<? extends String, ?> map) {
for (final Map.Entry<? extends String, ?> e : map.entrySet()) {
- accessor.set(accessor.indexOf(e.getKey(), true), metadata, e.getValue(), false);
+ accessor.set(accessor.indexOf(e.getKey(), true), metadata, e.getValue(), RETURN_NULL);
}
}
@@ -139,7 +142,7 @@ final class ValueMap extends PropertyMap
@Override
public Object remove(final Object key) throws UnsupportedOperationException {
if (key instanceof String) {
- final Object old = accessor.set(accessor.indexOf((String) key, false), metadata, null, true);
+ final Object old = accessor.set(accessor.indexOf((String) key, false), metadata, null, RETURN_PREVIOUS);
if (!valuePolicy.isSkipped(old)) {
return old;
}
@@ -223,7 +226,7 @@ final class ValueMap extends PropertyMap
*/
@Override
public Object setValue(final Object value) {
- return accessor.set(index, metadata, value, true);
+ return accessor.set(index, metadata, value, RETURN_PREVIOUS);
}
/**
Modified: sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java [UTF-8] Fri May 3 16:31:02 2013
@@ -34,7 +34,7 @@ import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestCase;
import org.junit.Test;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
/**
@@ -89,26 +89,6 @@ public final strictfp class MetadataStan
}
/**
- * Tests the shallow copy. For this test, we need to use a class that doesn't have any {@code getIdentifiers()}
- * method inherited from GeoAPI interfaces. The class will inherit the {@code getIdentifiers()} method defined
- * by SIS in the parent class, which doesn't have corresponding {@code setIdentifiers(...)} method.
- */
- @Test
- public void testShallowCopy() {
- final AbstractCompleteness source = new AbstractCompleteness();
- final AbstractCompleteness target = new AbstractCompleteness();
- source.setMeasureDescription(new SimpleInternationalString("Some description"));
- target.getStandard().shallowCopy(source, target);
- assertEquals("Copy of measureDescription:", "Some description", target.getMeasureDescription().toString());
- assertEquals("Copy of measureDescription:", source, target);
-
- source.setMeasureDescription(null);
- target.getStandard().shallowCopy(source, target);
- assertEquals("Measure description should not have been removed, since we skipped null values.",
- "Some description", target.getMeasureDescription().toString());
- }
-
- /**
* Tests the {@link MetadataStandard#equals(Object, Object, ComparisonMode)} method.
*/
@Test
@@ -215,4 +195,13 @@ public final strictfp class MetadataStan
assertEquals("As elements in a list of dimensions.", Integer.class, types.get("dimension"));
assertEquals("As elements in the list of offset vectors.", double[].class, types.get("offsetVectors"));
}
+
+ /**
+ * Tests serialization of pre-defined constants.
+ */
+ @Test
+ public void testSerialization() {
+ assertSame(MetadataStandard.ISO_19111, assertSerializedEquals(MetadataStandard.ISO_19111));
+ assertSame(MetadataStandard.ISO_19115, assertSerializedEquals(MetadataStandard.ISO_19115));
+ }
}
Modified: sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java [UTF-8] Fri May 3 16:31:02 2013
@@ -251,7 +251,7 @@ public abstract strictfp class MetadataT
assertEquals("isWritable", isWritable, accessor.isWritable(i));
if (isWritable) {
final Object newValue = valueFor(property, elementType);
- final Object oldValue = accessor.set(i, instance, newValue, true);
+ final Object oldValue = accessor.set(i, instance, newValue, PropertyAccessor.RETURN_PREVIOUS);
assertEquals("PropertyAccessor.set(…) shall return the value previously returned by get(…).", value, oldValue);
value = accessor.get(i, instance);
if (isCollection) {
Modified: sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java [UTF-8] Fri May 3 16:31:02 2013
@@ -23,6 +23,8 @@ import java.util.ArrayList;
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.DefaultTreeTable;
+import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
@@ -182,6 +184,55 @@ public final strictfp class MetadataTree
}
/**
+ * Tests the {@link MetadataTreeChildren#add(TreeTable.Node)} method.
+ */
+ @Test
+ @DependsOnMethod("testReadOnlyWithMultiOccurrences")
+ public void testAdd() {
+ final DefaultCitation citation = metadataWithMultiOccurrences();
+ final MetadataTreeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY);
+ final DefaultTreeTable.Node toAdd = new DefaultTreeTable.Node(new DefaultTreeTable(
+ TableColumn.IDENTIFIER,
+ TableColumn.VALUE));
+ final String[] expected = {
+ "Some title",
+ "First alternate title",
+ "Second alternate title",
+ "Third alternate title", // After addition
+ "New edition", // After "addition" (actually change).
+ "PresentationForm[MAP_DIGITAL]",
+ "PresentationForm[MAP_HARDCOPY]",
+ "PresentationForm[IMAGE_DIGITAL]", // After addition
+ "Some other details"
+ };
+ toAdd.setValue(TableColumn.IDENTIFIER, "edition");
+ toAdd.setValue(TableColumn.VALUE, citation.getEdition());
+ assertFalse("Adding the same value shall be a no-op.", children.add(toAdd));
+ toAdd.setValue(TableColumn.VALUE, "New edition");
+ try {
+ children.add(toAdd);
+ fail("Setting a different value shall be refused.");
+ } catch (IllegalStateException e) {
+ assertTrue(e.getMessage().contains("edition"));
+ }
+ citation.setEdition(null); // Clears so we are allowed to add.
+ assertTrue("Setting a new value shall be a change.", children.add(toAdd));
+
+ toAdd.setValue(TableColumn.IDENTIFIER, "presentationForm");
+ toAdd.setValue(TableColumn.VALUE, PresentationForm.MAP_DIGITAL);
+ assertFalse("Adding the same value shall be a no-op.", children.add(toAdd));
+ toAdd.setValue(TableColumn.VALUE, PresentationForm.IMAGE_DIGITAL);
+ assertTrue("Adding a new value shall be a change.", children.add(toAdd));
+
+ toAdd.setValue(TableColumn.IDENTIFIER, "alternateTitle");
+ toAdd.setValue(TableColumn.VALUE, "Third alternate title");
+ assertTrue("Adding a new value shall be a change.", children.add(toAdd));
+
+ assertEquals("size()", expected.length, children.size());
+ assertAllNextEqual(expected, children.iterator());
+ }
+
+ /**
* Tests the {@link Iterator#remove()} operation on a list of properties without collections.
*/
@Test
@@ -236,6 +287,36 @@ public final strictfp class MetadataTree
assertTrue(citation.getAlternateTitles().isEmpty());
}
+ /**
+ * Tests the children list with the {@link ValueExistencePolicy#ALL}.
+ */
+ @Test
+ @DependsOnMethod("testReadOnlyWithMultiOccurrences")
+ public void testShowAll() {
+ final DefaultCitation citation = metadataWithMultiOccurrences();
+ final MetadataTreeChildren children = create(citation, ValueExistencePolicy.ALL);
+ final String[] expected = {
+ "Some title",
+ "First alternate title",
+ "Second alternate title",
+ null, // dates (collection)
+ "Some edition",
+ null, // edition date
+ null, // identifiers (collection)
+ null, // cited responsibly parties (collection)
+ "PresentationForm[MAP_DIGITAL]",
+ "PresentationForm[MAP_HARDCOPY]",
+ null, // series
+ "Some other details",
+ null, // collective title
+ null, // ISBN
+ null // ISSN
+ };
+ assertFalse ("isEmpty()", children.isEmpty());
+ assertEquals("size()", expected.length, children.size());
+ assertAllNextEqual(expected, children.iterator());
+ }
+
// ------------------------ Support methods for the above tests ------------------------
@@ -247,7 +328,8 @@ public final strictfp class MetadataTree
* 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());
+ final Object value = node.getUserObject();
+ return (value != null) ? value.toString() : null;
}
/**
Modified: sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java?rev=1478867&r1=1478866&r2=1478867&view=diff
==============================================================================
--- sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java [UTF-8] Fri May 3 16:31:02 2013
@@ -130,7 +130,7 @@ public final strictfp class MetadataTree
@DependsOnMethod("testRootNode") // Because tested more basic methods than 'getValue(TableColumn)'.
public void testGetNameForSingleton() {
final DefaultCitation citation = MetadataTreeChildrenTest.metadataWithSingletonInCollections();
- assertColumnEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.NAME,
+ assertColumnContentEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.NAME,
"DefaultCitation",
"Title",
"Alternate title",
@@ -147,7 +147,7 @@ public final strictfp class MetadataTree
@DependsOnMethod("testGetNameForSingleton")
public void testGetNameForMultiOccurrences() {
final DefaultCitation citation = MetadataTreeChildrenTest.metadataWithMultiOccurrences();
- assertColumnEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.NAME,
+ assertColumnContentEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.NAME,
"DefaultCitation",
"Title",
"Alternate title (1 of 2)",
@@ -165,7 +165,7 @@ public final strictfp class MetadataTree
@DependsOnMethod("testGetNameForMultiOccurrences")
public void testGetNameForHierarchy() {
final DefaultCitation citation = metadataWithHierarchy();
- assertColumnEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.NAME,
+ assertColumnContentEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.NAME,
"DefaultCitation",
"Title",
"Alternate title (1 of 2)",
@@ -195,7 +195,7 @@ public final strictfp class MetadataTree
@DependsOnMethod("testGetNameForMultiOccurrences") // Because similar to names, which were tested progressively.
public void testGetIdentifier() {
final DefaultCitation citation = metadataWithHierarchy();
- assertColumnEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.IDENTIFIER,
+ assertColumnContentEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.IDENTIFIER,
"CI_Citation",
"title",
"alternateTitle",
@@ -222,7 +222,7 @@ public final strictfp class MetadataTree
@DependsOnMethod("testGetIdentifier") // Because if identifiers are wrong, we are looking at wrong properties.
public void testGetElementType() {
final DefaultCitation citation = metadataWithHierarchy();
- assertColumnEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.TYPE,
+ assertColumnContentEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.TYPE,
Citation.class,
InternationalString.class,
InternationalString.class,
@@ -249,7 +249,7 @@ public final strictfp class MetadataTree
@DependsOnMethod("testGetIdentifier") // Because if identifiers are wrong, we are looking at wrong properties.
public void testGetValue() {
final DefaultCitation citation = metadataWithHierarchy();
- assertColumnEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.VALUE,
+ assertColumnContentEquals(create(citation, ValueExistencePolicy.NON_EMPTY), TableColumn.VALUE,
null, // Citation
"Some title",
"First alternate title",
@@ -270,6 +270,45 @@ public final strictfp class MetadataTree
}
/**
+ * Tests {@link MetadataTreeNode#newChild()}.
+ */
+ @Test
+ @DependsOnMethod("testGetValue")
+ public void testNewChild() {
+ final DefaultCitation citation = metadataWithHierarchy();
+ final MetadataTreeNode node = create(citation, ValueExistencePolicy.NON_EMPTY);
+ /*
+ * Ensure that we can not overwrite existing nodes.
+ */
+ TreeTable.Node child = node.newChild();
+ child.setValue(TableColumn.IDENTIFIER, "title");
+ try {
+ child.setValue(TableColumn.VALUE, "A new title");
+ fail("Attemps to overwrite an existing value shall fail.");
+ } catch (IllegalStateException e) {
+ assertTrue(e.getMessage().contains("title"));
+ }
+ /*
+ * Clear the title and try again. This time, it shall work.
+ */
+ citation.setTitle(null);
+ child = node.newChild();
+ child.setValue(TableColumn.IDENTIFIER, "title");
+ child.setValue(TableColumn.VALUE, "A new title");
+ assertEquals("A new title", citation.getTitle().toString());
+ assertSame(citation.getTitle(), child.getValue(TableColumn.VALUE));
+ /*
+ * Try adding a new element in a collection.
+ * Note that the code below imply a conversion from String to InternationalString.
+ */
+ child = node.newChild();
+ child.setValue(TableColumn.IDENTIFIER, "alternateTitle");
+ child.setValue(TableColumn.VALUE, "Third alternate title");
+ assertEquals(3, citation.getAlternateTitles().size());
+ assertEquals("Third alternate title", child.getValue(TableColumn.VALUE).toString());
+ }
+
+ /**
* Compares the result of the given getter method invoked on the given node, then invoked
* on all children of that given. In the particular case of the {@link #NAME} method,
* international strings are replaced by unlocalized strings before comparisons.
@@ -280,17 +319,17 @@ public final strictfp class MetadataTree
* applied on the given node, and all other values are the result of the
* getter method applied on the children, in iteration order.
*/
- private static void assertColumnEquals(final MetadataTreeNode node,
+ private static void assertColumnContentEquals(final MetadataTreeNode node,
final TableColumn<?> column, final Object... values)
{
assertEquals("Missing values in the tested metadata.", values.length,
- assertColumnEquals(node, column, values, 0));
+ assertColumnContentEquals(node, column, values, 0));
}
/**
* Implementation of the above {@code assertGetterReturns}, to be invoked recursively.
*/
- private static int assertColumnEquals(final TreeTable.Node node, final TableColumn<?> column,
+ private static int assertColumnContentEquals(final TreeTable.Node node, final TableColumn<?> column,
final Object[] values, int index)
{
Object actual = node.getValue(column);
@@ -299,7 +338,7 @@ public final strictfp class MetadataTree
}
assertEquals("values[" + index + ']', values[index++], actual);
for (final TreeTable.Node child : node.getChildren()) {
- index = assertColumnEquals(child, column, values, index);
+ index = assertColumnContentEquals(child, column, values, index);
}
return index;
}
Copied: sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java (from r1478864, sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java?p2=sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java&p1=sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java&r1=1478864&r2=1478867&rev=1478867&view=diff
==============================================================================
--- sis/branches/JDK7/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java [UTF-8] (original)
+++ sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeTableTest.java [UTF-8] Fri May 3 16:31:02 2013
@@ -90,13 +90,19 @@ public final strictfp class MetadataTree
final Object original = create(ValueExistencePolicy.NON_EMPTY);
final Object deserialized;
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- try (ObjectOutputStream out = new ObjectOutputStream(buffer)) {
+ final ObjectOutputStream out = new ObjectOutputStream(buffer);
+ try {
out.writeObject(original);
+ } finally {
+ out.close();
}
// Now reads the object we just serialized.
final byte[] data = buffer.toByteArray();
- try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data))) {
+ final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data));
+ try {
deserialized = in.readObject();
+ } finally {
+ in.close();
}
assertExpectedString((MetadataTreeTable) deserialized);
}