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 2019/05/04 23:33:30 UTC
[sis] 02/02: In Metadata, java.nio.charset.Charset should be associated to java.util.Locale. This issue is described at https://github.com/opengeospatial/geoapi/issues/28 and is required for https://issues.apache.org/jira/browse/SIS-402 resolution. This work required improvement in many SIS internal classes (ModifiableMetadata, PropertyAccessor, Merger, TreeFormat, PT_Locale, etc…) for allowing the use of java.util.Map in addition of java.util.Collection in metadata properties.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit f15686fe5e57c5c3f5be60d39772fa5886fc1c1b
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun May 5 01:26:36 2019 +0200
In Metadata, java.nio.charset.Charset should be associated to java.util.Locale.
This issue is described at https://github.com/opengeospatial/geoapi/issues/28 and is required for https://issues.apache.org/jira/browse/SIS-402 resolution.
This work required improvement in many SIS internal classes (ModifiableMetadata, PropertyAccessor, Merger, TreeFormat, PT_Locale, etc…) for allowing the use of java.util.Map in addition of java.util.Collection in metadata properties.
---
.../sis/internal/jaxb/lan/LocaleAdapter.java | 62 ++++
.../sis/internal/jaxb/lan/LocaleAndCharset.java | 400 +++++++++++++++++++++
.../apache/sis/internal/jaxb/lan/OtherLocales.java | 150 ++++++++
.../apache/sis/internal/jaxb/lan/PT_Locale.java | 216 ++++++++---
.../internal/metadata/LegacyPropertyAdapter.java | 24 +-
.../org/apache/sis/internal/metadata/Merger.java | 83 ++++-
.../sis/internal/metadata/MetadataUtilities.java | 41 +++
.../apache/sis/internal/metadata/OtherLocales.java | 189 ----------
.../apache/sis/internal/simple/SimpleMetadata.java | 20 +-
.../apache/sis/metadata/ModifiableMetadata.java | 170 +++++++--
.../org/apache/sis/metadata/PropertyAccessor.java | 48 ++-
.../java/org/apache/sis/metadata/SpecialCases.java | 30 +-
.../java/org/apache/sis/metadata/TreeNode.java | 61 +++-
.../org/apache/sis/metadata/TreeNodeChildren.java | 51 +--
.../apache/sis/metadata/iso/DefaultMetadata.java | 221 +++++++-----
.../DefaultFeatureCatalogueDescription.java | 76 ++--
.../sis/metadata/iso/content/package-info.java | 4 +-
.../identification/DefaultDataIdentification.java | 114 +++---
.../metadata/iso/identification/package-info.java | 6 +-
.../org/apache/sis/metadata/iso/package-info.java | 6 +-
.../sis/internal/jaxb/lan/LanguageCodeTest.java | 6 +-
.../sis/internal/jaxb/lan/OtherLocalesTest.java | 112 ++++++
.../sis/internal/jaxb/lan/PT_LocaleTest.java | 12 +-
.../apache/sis/internal/metadata/MergerTest.java | 13 +-
.../internal/metadata/MetadataUtilitiesTest.java | 32 +-
.../sis/internal/metadata/OtherLocalesTest.java | 129 -------
.../apache/sis/metadata/PropertyAccessorTest.java | 9 +-
.../sis/metadata/PropertyConsistencyCheck.java | 17 +-
.../sis/metadata/iso/CustomMetadataTest.java | 1 +
.../sis/metadata/iso/DefaultMetadataTest.java | 10 +-
.../DefaultDataIdentificationTest.java | 40 +--
.../org/apache/sis/test/mock/MetadataMock.java | 15 +-
.../apache/sis/test/suite/MetadataTestSuite.java | 2 +-
.../sis/test/xml/AnnotationConsistencyCheck.java | 17 +-
.../apache/sis/test/integration/MetadataTest.java | 11 +-
.../apache/sis/internal/util/CollectionsExt.java | 21 ++
.../sis/internal/util/TreeFormatCustomization.java | 8 +
.../sis/util/collection/TreeTableFormat.java | 32 +-
.../earthobservation/LandsatReaderTest.java | 2 +-
.../apache/sis/internal/storage/xml/StoreTest.java | 6 +-
40 files changed, 1757 insertions(+), 710 deletions(-)
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAdapter.java
index 30ef6e1..651f797 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAdapter.java
@@ -75,4 +75,66 @@ public final class LocaleAdapter extends XmlAdapter<LanguageCode, Locale> {
public LanguageCode marshal(final Locale value) {
return LanguageCode.create(Context.current(), value);
}
+
+
+
+
+ /**
+ * JAXB adapter for XML {@code <PT_Locale>} elements mapped to {@link Locale}.
+ * This adapter formats the locale like below:
+ *
+ * {@preformat xml
+ * <gmd:locale>
+ * <gmd:PT_Locale>
+ * <gmd:language>
+ * <gmd:LanguageCode codeList="(snip)#LanguageCode" codeListValue="jpn">Japanese</gmd:LanguageCode>
+ * </gmd:language>
+ * </gmd:PT_Locale>
+ * </gmd:locale>
+ * }
+ *
+ * This adapter is used for legacy locales in {@code gmd} namespace.
+ * For locales in the newer {@code lan} namespace, see {@link PT_Locale}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+ public static final class Wrapped extends XmlAdapter<PT_Locale, Locale> {
+ /**
+ * Empty constructor for JAXB.
+ */
+ private Wrapped() {
+ }
+
+ /**
+ * Substitutes the locale by the wrapper to be marshalled into an XML file
+ * or stream. JAXB calls automatically this method at marshalling time.
+ *
+ * @param value the locale value.
+ * @return the wrapper for the locale value.
+ */
+ @Override
+ public PT_Locale marshal(final Locale value) {
+ if (value == null) {
+ return null;
+ }
+ PT_Locale p = new PT_Locale(value);
+ p.setCharacterSet(null); // For forcing creating of child element.
+ return p;
+ }
+
+ /**
+ * Substitutes the wrapped value read from a XML stream by the object which will
+ * contains the value. JAXB calls automatically this method at unmarshalling time.
+ *
+ * @param value the wrapper for this metadata value.
+ * @return a locale which represents the metadata value.
+ */
+ @Override
+ public Locale unmarshal(final PT_Locale value) {
+ return (value != null) ? value.getLocale() : null;
+ }
+ }
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAndCharset.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAndCharset.java
new file mode 100644
index 0000000..c99a62e
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/LocaleAndCharset.java
@@ -0,0 +1,400 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.jaxb.lan;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.AbstractSet;
+import java.util.AbstractList;
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.TreeTable.Node;
+import org.apache.sis.internal.util.CollectionsExt;
+
+
+/**
+ * Utility methods for handling {@code Map<Locale,Charset>} as separated collections.
+ * Locale and character set were separated properties in legacy ISO 19115:2003 but become combined
+ * under a single {@link PT_Locale} entity in ISO 19115:2014. This change is not really convenient
+ * in Java since the standard {@link Locale} and {@link Charset} objects are separated entities.
+ * This class provides two services for managing that:
+ *
+ * <ul>
+ * <li>Static methods, used mostly for JAXB marshalling and unmarshalling.</li>
+ * <li>Implementation of {@link Node} for viewing a {@code Map.Entry<Locale,Charset>} as a
+ * {@link Locale} node with a {@link Charset} child. This is used for providing textual
+ * representation of metadata.</li>
+ * </ul>
+ *
+ * Example:
+ *
+ * {@preformat text
+ * Identification info
+ * ├─Abstract………………………………………………………………………………… Some data.
+ * ├─Locale (1 of 2)……………………………………………………………… en_US
+ * │ └─Character set………………………………………………………… US-ASCII
+ * └─Locale (2 of 2)……………………………………………………………… fr
+ * └─Character set………………………………………………………… ISO-8859-1
+ * }
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public final class LocaleAndCharset implements Node {
+ /**
+ * The node containing a {@code Map.Entry<Locale,Charset>} value.
+ * This is the node to replace by this {@link LocaleAndCharset} view.
+ */
+ private final Node node;
+
+ /**
+ * Creates a new node for the given entry. The user object associated to
+ * the given node must be an instance of {@code Map.Entry<Locale,Charset>}.
+ *
+ * @param node the node to wrap.
+ */
+ public LocaleAndCharset(final Node node) {
+ this.node = node;
+ }
+
+ /**
+ * Delegates to wrapped node since this {@link LocaleAndCharset} is a substitute for that node.
+ */
+ @Override
+ public Node getParent() {
+ return node.getParent();
+ }
+
+ /**
+ * Considers this node as non-editable since it represents the key in a map, and keys can not be modified
+ * through the {@link Map.Entry} interface. However {@link Child} will be editable for the value column.
+ */
+ @Override
+ public boolean isEditable(final TableColumn<?> column) {
+ return false;
+ }
+
+ /**
+ * Returns {@code false} since this node can have a children, which is the {@link Child}.
+ */
+ @Override
+ public boolean isLeaf() {
+ return false;
+ }
+
+ /**
+ * Returns the key or the value of the given {@link Map.Entry}. If the given object is not a map entry
+ * or is null, then it is returned as-is. This later case should never happen (the object shall always be
+ * a non-null map entry), but we nevertheless check for making the code more robust to ill-formed metadata.
+ * We apply this tolerance because this method is used (indirectly) for {@code toString()} implementations,
+ * and failure in those methods make debugging more difficult (string representations are often requested
+ * when the developer knows that there is a problem to investigate).
+ *
+ * @param object the map entry for which to get the key or the value.
+ * @param key {@code true} for fetching the key, or {@code false} for fetching the value.
+ * @return the requested key or value, or the given object itself if it is not a map entry.
+ */
+ private static Object keyOrValue(Object object, final boolean key) {
+ if (object instanceof Map.Entry<?,?>) {
+ final Map.Entry<?,?> entry = (Map.Entry<?,?>) object;
+ object = key ? entry.getKey() : entry.getValue();
+ }
+ return object;
+ }
+
+ /**
+ * Returns the user object associated to this node. For this node, that object is the key (a {@link Locale}) of the
+ * map entry. For the {@link Child}, the user object will be the value (a {@link Charset}) of the same map entry.
+ */
+ @Override
+ public Object getUserObject() {
+ return keyOrValue(node.getUserObject(), true);
+ }
+
+ /**
+ * Returns the value associated to the given column of this node. This method delegates to the wrapped node,
+ * then extract the key component of the map entry if the requested column is the value.
+ */
+ @Override
+ public <V> V getValue(final TableColumn<V> column) {
+ return separateValue(column, true);
+ }
+
+ /**
+ * Implementation of {@link #getValue(TableColumn)} also used by the {@link Child}.
+ */
+ private <V> V separateValue(final TableColumn<V> column, final boolean key) {
+ V value = node.getValue(column);
+ if (TableColumn.VALUE.equals(column)) {
+ value = column.getElementType().cast(keyOrValue(value, key));
+ }
+ return value;
+ }
+
+ /**
+ * Always throws an exception since we can not edit the key of a map entry. Attempts to edit other columns
+ * than the value column will also cause an exception to be thrown, but the error message provided by the
+ * wrapped node is more detailed.
+ */
+ @Override
+ public <V> void setValue(final TableColumn<V> column, final V value) {
+ if (TableColumn.VALUE.equals(column)) {
+ throw new UnsupportedOperationException();
+ } else {
+ node.setValue(column, value);
+ }
+ }
+
+ /**
+ * Returns the list of children, which is implemented by this class itself.
+ * The children are {@link Charset} values associated to the {@link Locale}.
+ * The list contains O or 1 element.
+ */
+ @Override
+ public Collection<Node> getChildren() {
+ return new AbstractList<Node>() {
+ /** Returns the number {@link Charset} associated to the {@link Locale}, which is 0 or 1. */
+ @Override public int size() {
+ return keyOrValue(node.getUserObject(), false) != null ? 1 : 0;
+ }
+
+ /** Returns a child node wrapping the {@link Charset} ad the given index. */
+ @Override public Node get(final int index) {
+ ArgumentChecks.ensureValidIndex(1, index);
+ return new Child();
+ }
+ };
+ }
+
+ /**
+ * Creates a new child only if none exists.
+ */
+ @Override
+ public Node newChild() {
+ if (keyOrValue(node.getUserObject(), false) != null) {
+ throw new UnsupportedOperationException();
+ }
+ return new Child();
+ }
+
+ /**
+ * The only child of the node containing a {@link Locale} value. This child contains the associated {@link Charset} value.
+ * That value is replaceable if the wrapped node is editable, which depends on whether the metadata instance is modifiable
+ * or not.
+ */
+ private final class Child implements Node {
+ @Override public Node getParent() {return LocaleAndCharset.this;}
+ @Override public Object getUserObject() {return keyOrValue(node.getUserObject(), false);}
+ @Override public boolean isEditable(TableColumn<?> c) {return node.isEditable(c);}
+ @Override public boolean isLeaf() {return true;}
+ @Override public Collection<Node> getChildren() {return Collections.emptySet();}
+ @Override public Node newChild() {throw new UnsupportedOperationException();}
+
+ /** Returns the value at the given column, with hard-coded names. */
+ @Override public <V> V getValue(final TableColumn<V> column) {
+ final String value;
+ if (TableColumn.IDENTIFIER.equals(column)) {
+ value = "characterSet";
+ } else if (TableColumn.NAME.equals(column)) {
+ value = "Character set";
+ } else {
+ return separateValue(column, false);
+ }
+ return column.getElementType().cast(value);
+ }
+
+ /** Sets the value in the map entry key wrapped by this node. */
+ @Override public <V> void setValue(final TableColumn<V> column, V value) {
+ if (TableColumn.VALUE.equals(column)) {
+ /*
+ * No @SuppressWarning("unchecked") because following is a real hole.
+ * We rely on Entry.setValue(Object) implementation to perform checks
+ * (this is the case with SIS implementation backed by PropertyAccessor).
+ */
+ ((Map.Entry<?,V>) node.getUserObject()).setValue(value);
+ } else {
+ node.setValue(column, value);
+ }
+ }
+
+ /** Returns a hash code value for this node. */
+ @Override public int hashCode() {
+ return ~getParent().hashCode();
+ }
+
+ /** Tests this node with the given object for equality. */
+ @Override public boolean equals(final Object other) {
+ return (other instanceof Child) && getParent().equals(((Child) other).getParent());
+ }
+ }
+
+ /**
+ * Returns a hash code value for this node.
+ */
+ @Override
+ public int hashCode() {
+ return node.hashCode() ^ 37;
+ }
+
+ /**
+ * Tests this node with the given object for equality.
+ * Two {@link LocaleAndCharset} instances are considered equal if they wrap the same node.
+ */
+ @Override
+ public boolean equals(final Object other) {
+ return (other instanceof LocaleAndCharset) && node.equals(((LocaleAndCharset) other).node);
+ }
+
+ /**
+ * Returns the language(s) used within the resource. The returned collection supports the {@code add(Locale)} method
+ * in order to enable XML unmarshalling of legacy ISO 19157 metadata documents. That hack is not needed for newer XML
+ * documents (ISO 19115-3:2016).
+ *
+ * @param locales the map of locales and character sets, or {@code null}.
+ * @return language(s) used within the resource, or {@code null}.
+ */
+ public static Collection<Locale> getLanguages(final Map<Locale,Charset> locales) {
+ if (locales == null) {
+ return null;
+ }
+ return new AbstractSet<Locale>() {
+ @Override public int size() {
+ return locales.size();
+ }
+ @Override public boolean contains(Object o) {
+ return locales.containsKey(o);
+ }
+ @Override public Iterator<Locale> iterator() {
+ return locales.keySet().iterator();
+ }
+ @Override public boolean add(final Locale locale) {
+ // We need containsKey(…) check because value may be null: Map.putIfAbsent(…) is not sufficient.
+ if (locale == null || locales.containsKey(locale)) {
+ return false;
+ }
+ final Charset encoding = locales.remove(null);
+ locales.put(locale, encoding);
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns the character coding standard used for the resource. The returned collection supports {@code add(Charset)}
+ * method in order to enable XML unmarshalling of legacy ISO 19157 metadata documents. That hack is not be needed for
+ * newer (ISO 19115-3:2016) XML documents.
+ *
+ * @param locales the map of locales and character sets, or {@code null}.
+ * @return character coding standard(s) used, or {@code null}.
+ */
+ public static Collection<Charset> getCharacterSets(final Map<Locale,Charset> locales) {
+ if (locales == null) {
+ return null;
+ }
+ return new AbstractCollection<Charset>() {
+ @Override public int size() {
+ return locales.size();
+ }
+ @Override public boolean contains(Object o) {
+ return locales.containsValue(o);
+ }
+ @Override public Iterator<Charset> iterator() {
+ return locales.values().iterator();
+ }
+ @Override public boolean add(final Charset encoding) {
+ if (encoding == null) {
+ return false;
+ }
+ for (final Map.Entry<Locale,Charset> entry : locales.entrySet()) {
+ if (entry.getValue() == null) {
+ entry.setValue(encoding);
+ return true;
+ }
+ }
+ return locales.putIfAbsent(null, encoding) != encoding;
+ }
+ };
+ }
+
+ /**
+ * Sets the language(s) used within the resource.
+ * This method preserves the character sets if possible.
+ *
+ * @param locales the map of locales and character sets, or {@code null}.
+ * @param newValues the new languages.
+ * @return the given map, or a new map if necessary and the given map was null.
+ */
+ @SuppressWarnings("null")
+ public static Map<Locale,Charset> setLanguages(Map<Locale,Charset> locales, final Collection<? extends Locale> newValues) {
+ final Charset encoding = (locales != null) ? CollectionsExt.first(locales.values()) : null;
+ if (newValues == null || newValues.isEmpty()) {
+ if (locales != null) {
+ locales.clear();
+ }
+ } else {
+ if (locales == null) {
+ locales = new LinkedHashMap<>();
+ }
+ locales.keySet().retainAll(newValues);
+ for (final Locale locale : newValues) {
+ locales.putIfAbsent(locale, null);
+ }
+ }
+ /*
+ * If an encoding was defined before invocation of this method and is not associated to any
+ * locale specified in `newValues`, preserve that encoding in an entry with null locale.
+ */
+ if (encoding != null && !locales.values().contains(encoding)) { // `locales` is non-null if `encoding` is non-null.
+ locales.put(null, encoding);
+ }
+ return locales;
+ }
+
+ /**
+ * Sets the character coding standard(s) used within the resource. Current implementation takes
+ * only the first {@link Charset} and set the encoding of all locales to that character set.
+ * This is suboptimal, but this approach is used only for implementation of deprecated methods.
+ *
+ * @param locales the map of locales and character sets, or {@code null}.
+ * @param newValues the new character coding standard(s).
+ * @return the given map, or a new map if necessary and the given map was null.
+ */
+ public static Map<Locale,Charset> setCharacterSets(Map<Locale,Charset> locales, final Collection<? extends Charset> newValues) {
+ final Charset encoding = CollectionsExt.first(newValues);
+ if (locales != null || encoding != null) {
+ if (locales == null) {
+ locales = new LinkedHashMap<>();
+ }
+ if (locales.isEmpty()) {
+ locales.put(null, encoding);
+ } else {
+ for (final Map.Entry<Locale,Charset> entry : locales.entrySet()) {
+ entry.setValue(encoding);
+ }
+ }
+ }
+ return locales;
+ }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/OtherLocales.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/OtherLocales.java
new file mode 100644
index 0000000..1624b2e
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/OtherLocales.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.jaxb.lan;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.Collection;
+import java.util.AbstractSet;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.nio.charset.Charset;
+
+
+/**
+ * Helper methods for handling the ISO 19115 {@code defaultLocale} and {@code otherLocale} legacy properties.
+ * The ISO standard defines them as two separated properties while GeoAPI handles them in a single collection
+ * for integration with JDK standard API like {@link java.util.Locale#lookup(List, Collection)}.
+ *
+ * <p>The first element of the {@code languages} collection is taken as the {@code defaultLocale}, and all
+ * remaining ones are taken as {@code otherLocale} elements. Instances of this {@code OtherLocales} class
+ * are for those remaining elements and are created by the {@link #filter(Map)} method.</p>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 0.5
+ * @module
+ */
+public final class OtherLocales extends AbstractSet<PT_Locale> {
+ /**
+ * The default locale followed by all other locales.
+ */
+ private final Set<PT_Locale> locales;
+
+ /**
+ * Private constructor for {@link #filter(Map)} only.
+ */
+ private OtherLocales(final Set<PT_Locale> locales) {
+ this.locales = locales;
+ }
+
+ /**
+ * Returns a collection for all elements except the first one from the given collection.
+ *
+ * <p><b>Null values and XML marshalling:</b>
+ * The {@code locales} argument may be {@code null} at XML marshalling time. In such case, this method returns
+ * {@code null} instead than an empty set in order to instruct JAXB to not marshal the {@code otherLocale} element
+ * (an empty set would cause JAXB to marshal an empty element). Since the {@code locales} argument given to this
+ * method should never be null except at XML marshalling time, this rule should not be a violation of public API.</p>
+ *
+ * @param locales the collection containing the default locale followed by the other ones, or {@code null}.
+ * @return a collection containing all {@code languages} elements except the first one, or {@code null}.
+ */
+ public static Set<PT_Locale> filter(final Map<Locale,Charset> locales) {
+ final Set<PT_Locale> s = PT_Locale.wrap(locales);
+ return (s != null) ? new OtherLocales(s) : null;
+ }
+
+ /**
+ * Returns the number of elements in this collection.
+ *
+ * @return number of other locales.
+ */
+ @Override
+ public int size() {
+ int size = locales.size();
+ if (size > 0) size--;
+ return size;
+ }
+
+ /**
+ * Returns an iterator over all elements in this collection except the first one.
+ *
+ * @return iterator over all other locales.
+ */
+ @Override
+ public Iterator<PT_Locale> iterator() {
+ final Iterator<PT_Locale> it = locales.iterator();
+ if (it.hasNext()) it.next(); // Skip the first element.
+ return it;
+ }
+
+ /**
+ * Adds a new element to the collection of "other locales". If we had no "default locale" prior this method call,
+ * then this method will choose one before to add the given locale. This is needed since the other locales begin
+ * only after the first element, so a first element needs to exist.
+ *
+ * <p>The above rule could be a risk of confusion for the users, since it could cause the apparition of a default
+ * locale which has never been specified. However this risk exists only when invoking the deprecated methods, or
+ * when unmarshalling a XML document having a {@code otherLocale} property without {@code defaultLocale} property,
+ * which is probably invalid.</p>
+ *
+ * @param locale the element to add.
+ * @return {@code true} if the "other locales" collection has been modified as a result of this method call.
+ */
+ @Override
+ public boolean add(final PT_Locale locale) {
+ if (locales.isEmpty()) {
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultLocale.equals(locale.getLocale())) {
+ defaultLocale = Locale.ROOT;
+ }
+ locales.add(new PT_Locale(defaultLocale));
+ }
+ return locales.add(locale);
+ }
+
+ /**
+ * Returns a map containing the given {@code PT_Locale} followed by other locales in the given {@code addTo} map.
+ *
+ * @param addTo the map where to set the first locale, or {@code null}.
+ * @param newValue the value to add in the map, or {@code null}.
+ * @return a map containing this locale followed by other locales in the given map.
+ */
+ public static Map<Locale,Charset> setFirst(Map<Locale,Charset> addTo, final PT_Locale newValue) {
+ if (newValue != null) {
+ Object[] keys = null;
+ Object[] values = null;
+ if (addTo == null) {
+ addTo = new LinkedHashMap<>();
+ } else if (!addTo.isEmpty()) {
+ keys = addTo.keySet().toArray();
+ values = addTo.values().toArray();
+ addTo.clear();
+ }
+ newValue.addInto(addTo);
+ if (keys != null) {
+ for (int i=1; i<keys.length; i++) { // Skip first element.
+ addTo.put((Locale) keys[i], (Charset) values[i]);
+ }
+ }
+ }
+ return addTo;
+ }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/PT_Locale.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/PT_Locale.java
index 233ed92..d714ff6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/PT_Locale.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/lan/PT_Locale.java
@@ -16,32 +16,35 @@
*/
package org.apache.sis.internal.jaxb.lan;
+import java.util.Set;
+import java.util.Map;
+import java.util.AbstractSet;
+import java.util.Iterator;
import java.util.Locale;
import java.nio.charset.Charset;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.apache.sis.internal.jaxb.code.MD_CharacterSetCode;
import org.apache.sis.internal.xml.LegacyNamespaces;
import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.util.CollectionsExt;
/**
- * JAXB adapter for {@link Locale}
- * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
+ * A {@link Locale} associated to {@link Charset}.
+ * This class wraps the value in an XML element as specified by ISO 19115-3 standard.
* See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
- *
- * <p>This adapter formats the locale like below:</p>
+ * This wrapper formats the locale like below:
*
* {@preformat xml
* <lan:locale>
* <lan:PT_Locale id="locale-eng">
- * <lan:languageCode>
+ * <lan:language>
* <lan:LanguageCode codeList="./resources/Codelists.xml#LanguageCode" codeListValue="eng">eng</lan:LanguageCode>
- * </lan:languageCode>
+ * </lan:language>
* <lan:country>
* <lan:Country codeList="./resources/Codelists.xml#Country" codeListValue="GB">GB</lan:Country>
* </lan:country>
@@ -66,7 +69,15 @@ import org.apache.sis.internal.jaxb.Context;
* @since 0.3
* @module
*/
-public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
+public final class PT_Locale {
+ /**
+ * The wrapped locale, for information purpose. This object is not marshalled directly.
+ * Instead it will be decomposed in language and country components in {@link Wrapper}.
+ *
+ * @see #getLocale()
+ */
+ private Locale locale;
+
/**
* The attributes wrapped in a {@code "PT_Locale"} element.
*/
@@ -77,13 +88,16 @@ public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
* Wraps the {@code "locale"} attributes in a {@code "PT_Locale"} element.
*/
@XmlType(name = "PT_Locale_Type", propOrder = {
- "languageCode", "language", "country", "characterEncoding"
+ "languageCode", // Legacy ISO 19115:2003
+ "language", // New in ISO 19115:2014
+ "country",
+ "characterEncoding"
})
private static final class Wrapper {
/**
* The language code, or {@code null} if none.
*/
- LanguageCode languageCode;
+ LanguageCode language;
/**
* The country code, or {@code null} if none.
@@ -92,7 +106,8 @@ public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
Country country;
/**
- * The character encoding. The specification said:
+ * The character encoding. If {@code null}, then this property will be set to the encoding of XML file.
+ * The specification said:
*
* <blockquote>Indeed, an XML file can only support data expressed in a single character set, which is generally
* declared in the XML file header. Having all the localized strings stored in a single XML file would limit the
@@ -123,13 +138,16 @@ public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
/**
* Creates a new wrapper for the given locale.
+ *
+ * @param locale the locale to marshal, or {@code null}.
+ * @param encoding the character set, or {@code null} for defaulting to the encoding of XML document.
*/
- Wrapper(final Locale locale) {
+ Wrapper(final Locale locale, final Charset encoding) {
final Context context = Context.current();
- isLegacyMetadata = Context.isFlagSet(context, Context.LEGACY_METADATA);
- languageCode = LanguageCode.create(context, locale);
- country = Country .create(context, locale);
- // The characterEncoding field will be initialized at marshalling time (see method below).
+ isLegacyMetadata = Context.isFlagSet(context, Context.LEGACY_METADATA);
+ language = LanguageCode.create(context, locale);
+ country = Country .create(context, locale);
+ characterEncoding = encoding;
}
/**
@@ -137,7 +155,7 @@ public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
*/
@XmlElement(name = "languageCode", namespace = LegacyNamespaces.GMD)
private LanguageCode getLanguageCode() {
- return isLegacyMetadata ? languageCode : null;
+ return isLegacyMetadata ? language : null;
}
/**
@@ -145,45 +163,50 @@ public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
*/
@SuppressWarnings("unused")
private void setLanguageCode(LanguageCode newValue) {
- languageCode = newValue;
+ language = newValue;
}
/**
- * Gets the language code for this PT_Locale. Used in ISO 19115-3.
+ * Gets the language code for this PT_Locale. Used in ISO 19115:2014 model.
*/
@XmlElement(name = "language", required = true)
private LanguageCode getLanguage() {
- return isLegacyMetadata ? null : languageCode;
+ return isLegacyMetadata ? null : language;
}
/**
- * Sets the language code for this PT_Locale. Used in ISO 19115:2003 model.
+ * Sets the language code for this PT_Locale. Used in ISO 19115:2014 model.
*/
@SuppressWarnings("unused")
private void setLanguage(LanguageCode newValue) {
- languageCode = newValue;
+ language = newValue;
}
/**
* Invoked by JAXB {@link javax.xml.bind.Marshaller} before this object is marshalled to XML.
- * This method sets the {@link #characterEncoding} to the XML encoding.
+ * If the {@link #characterEncoding} is not set, then this method set a default value.
+ * That default is the encoding of the XML document being written.
*
- * <div class="note"><b>Note:</b> This is totally redundant with the encoding declared in the XML header.
- * Unfortunately, the {@code <lan:characterEncoding>} element is mandatory according OGC/ISO schemas.</div>
+ * <div class="note"><b>Note:</b> This is redundant with the encoding declared in the XML header.
+ * But the {@code <lan:characterEncoding>} element is mandatory according OGC/ISO schemas.</div>
*/
public void beforeMarshal(final Marshaller marshaller) {
- final String encoding;
- try {
- encoding = (String) marshaller.getProperty(Marshaller.JAXB_ENCODING);
- } catch (PropertyException | ClassCastException e) {
- // Should never happen. But if it happen anyway, just let the
- // characterEncoding unitialized: it will not be marshalled.
- Context.warningOccured(Context.current(), PT_Locale.class, "beforeMarshal", e, true);
- return;
- }
- if (encoding != null) {
- final Context context = Context.current();
- characterEncoding = Context.converter(context).toCharset(context, encoding);
+ if (characterEncoding == null) {
+ final String encoding;
+ try {
+ encoding = (String) marshaller.getProperty(Marshaller.JAXB_ENCODING);
+ } catch (PropertyException | ClassCastException e) {
+ /*
+ * Should never happen. But if it happen anyway, just let the
+ * characterEncoding unitialized: it will not be marshalled.
+ */
+ Context.warningOccured(Context.current(), PT_Locale.class, "beforeMarshal", e, true);
+ return;
+ }
+ if (encoding != null) {
+ final Context context = Context.current();
+ characterEncoding = Context.converter(context).toCharset(context, encoding);
+ }
}
}
}
@@ -191,43 +214,120 @@ public final class PT_Locale extends XmlAdapter<PT_Locale, Locale> {
/**
* Empty constructor for JAXB only.
*/
- public PT_Locale() {
+ private PT_Locale() {
}
/**
* Creates a new wrapper for the given locale.
+ *
+ * @param locale the language and country components of {@code PT_Locale}.
+ */
+ public PT_Locale(final Locale locale) {
+ this.locale = locale;
+ }
+
+ /**
+ * Creates a new wrapper for the given locale and character set.
+ *
+ * @param entry the locale to marshal together with its charset.
*/
- private PT_Locale(final Locale locale) {
- element = new Wrapper(locale);
+ private PT_Locale(final Map.Entry<Locale,Charset> entry) {
+ locale = entry.getKey();
+ setCharacterSet(entry.getValue());
}
/**
- * Substitutes the locale by the wrapper to be marshalled into an XML file
- * or stream. JAXB calls automatically this method at marshalling time.
+ * Sets the character set to the given value.
+ */
+ final void setCharacterSet(final Charset encoding) {
+ element = new Wrapper(locale, encoding);
+ }
+
+ /**
+ * Returns the Java locale wrapped by this {@link PT_Locale} instance.
+ * This method returns a cached instance if possible.
*
- * @param value the locale value.
- * @return the wrapper for the locale value.
+ * @return the wrapped locale, or {@code null} if none.
*/
- @Override
- public PT_Locale marshal(final Locale value) {
- return (value != null) ? new PT_Locale(value) : null;
+ public Locale getLocale() {
+ if (locale == null && element != null) {
+ locale = Country.getLocale(Context.current(), element.language, element.country, PT_Locale.class);
+ }
+ return locale;
}
/**
- * Substitutes the wrapped value read from a XML stream by the object which will
- * contains the value. JAXB calls automatically this method at unmarshalling time.
+ * Infers a locale and character set from this wrapper and adds them as an entry in the given map.
*
- * @param value the wrapper for this metadata value.
- * @return a locale which represents the metadata value.
+ * @param addTo the map where to add an entry for the locale and character set.
+ * @return whether the given map has been modified.
*/
- @Override
- public Locale unmarshal(final PT_Locale value) {
- if (value != null) {
- final Wrapper element = value.element;
- if (element != null) {
- return Country.getLocale(Context.current(), element.languageCode, element.country, PT_Locale.class);
- }
+ final boolean addInto(final Map<Locale,Charset> addTo) {
+ final Locale locale = getLocale();
+ final Charset encoding = (element != null) ? element.characterEncoding : null;
+ if (locale != null || encoding != null) {
+ // We need a special check if (encoding == null) since put(…) != encoding will not work in that case.
+ final boolean wasAbsent = (encoding == null) && !addTo.containsKey(locale);
+ return (addTo.put(locale, encoding) != encoding) | wasAbsent;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the first element of the given map, or {@code null} if none.
+ *
+ * @param locales the locales and character sets, or {@code null}.
+ * @return the first element of the given map, or {@code null}.
+ */
+ public static PT_Locale first(final Map<Locale,Charset> locales) {
+ if (locales != null) {
+ final Map.Entry<Locale,Charset> first = CollectionsExt.first(locales.entrySet());
+ if (first != null) return new PT_Locale(first);
}
return null;
}
+
+ /**
+ * Wraps all elements of the given map in a sequence of {@link PT_Locale}.
+ *
+ * @param locales the locales and character sets, or {@code null}.
+ * @return the all elements of the given map, or {@code null} if the given map is null or empty.
+ */
+ public static Set<PT_Locale> wrap(final Map<Locale,Charset> locales) {
+ return (locales != null && !locales.isEmpty()) ? new Sequence(locales) : null;
+ }
+
+ /**
+ * A set of {@link PT_Locale} instances backed by a {@code Map<Locale,Charset>}.
+ * This is used at marshalling and unmarshalling time only.
+ */
+ private static final class Sequence extends AbstractSet<PT_Locale> {
+ /** The languages and character sets. */
+ final Map<Locale,Charset> locales;
+
+ /** Creates a new set backed by the given map. */
+ Sequence(final Map<Locale,Charset> locales) {
+ this.locales = locales;
+ }
+
+ /** Returns the number of elements in this set. */
+ @Override public int size() {
+ return locales.size();
+ }
+
+ /** Add the given {@code PT_Locale} in the backing map. */
+ @Override public boolean add(final PT_Locale value) {
+ return (value != null) && value.addInto(locales);
+ }
+
+ /** Returns an iterator over the entries in this set. */
+ @Override public Iterator<PT_Locale> iterator() {
+ final Iterator<Map.Entry<Locale,Charset>> it = locales.entrySet().iterator();
+ return new Iterator<PT_Locale>() {
+ @Override public boolean hasNext() {return it.hasNext();}
+ @Override public PT_Locale next() {return new PT_Locale(it.next());}
+ @Override public void remove() {it.remove();}
+ };
+ }
+ }
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
index 74d1ad2..a07d302 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/LegacyPropertyAdapter.java
@@ -138,7 +138,7 @@ public abstract class LegacyPropertyAdapter<L,N> extends AbstractCollection<L> {
/**
* Returns the singleton value of the given collection, or {@code null} if the given collection is null or empty.
- * If the given collection contains more than one element, then a warning is emitted.
+ * If the given collection contains more than one non-null and distinct element, then a warning is emitted.
*
* @param <L> the kind of legacy values to be returned.
* @param values the collection from which to get the value.
@@ -153,18 +153,24 @@ public abstract class LegacyPropertyAdapter<L,N> extends AbstractCollection<L> {
{
if (values != null) {
final Iterator<? extends L> it = values.iterator();
- if (it.hasNext()) {
+ while (it.hasNext()) {
final L value = it.next();
- if (it.hasNext()) {
- if (caller != null) {
- if (caller.warningOccurred) {
- return value; // Skip the warning.
+ if (value != null) {
+ while (it.hasNext()) {
+ final L next = it.next();
+ if (next != null && !value.equals(next)) {
+ if (caller != null) {
+ if (caller.warningOccurred) {
+ return value; // Skip the warning.
+ }
+ caller.warningOccurred = true;
+ }
+ warnIgnoredExtraneous(valueClass, callerClass, callerMethod);
+ break;
}
- caller.warningOccurred = true;
}
- warnIgnoredExtraneous(valueClass, callerClass, callerMethod);
+ return value;
}
- return value;
}
}
return null;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
index f8bf6ec..ff4a710 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Merger.java
@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.LinkedList;
import java.util.IdentityHashMap;
import java.util.Locale;
+import java.util.function.BiFunction;
import org.apache.sis.metadata.MetadataStandard;
import org.apache.sis.metadata.AbstractMetadata;
import org.apache.sis.metadata.InvalidMetadataException;
@@ -62,7 +63,7 @@ import org.apache.sis.util.Classes;
* @author Johann Sorel (Geomatys)
* @author Benjamin Garcia (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.8
* @module
*/
@@ -219,7 +220,8 @@ public class Merger {
distribute: while (it.hasNext()) {
final Object value = it.next();
switch (resolve(value, (ModifiableMetadata) element)) {
- // case SEPARATE: do nothing.
+ default: throw new UnsupportedOperationException();
+ case SEPARATE: break; // do nothing.
case MERGE: {
/*
* If enabled, copy(…, true) call verified that the merge can be done, including
@@ -257,12 +259,14 @@ distribute: while (it.hasNext()) {
throw new InvalidMetadataException(errors().getString(
Errors.Keys.UnsupportedImplementation_1, Classes.getShortClassName(targetList)));
}
+ } else if (targetValue instanceof Map<?,?>) {
+ success = new ForMap<>(target, propertyName, (Map<?,?>) sourceValue, (Map<?,?>) targetValue).run(dryRun);
} else {
success = targetValue.equals(sourceValue);
if (!success) {
if (dryRun) break;
merge(target, propertyName, sourceValue, targetValue);
- success = true; // If no exception has been thrown by 'merged', assume the conflict solved.
+ success = true; // If no exception has been thrown by 'merged', assume the conflict solved.
}
}
}
@@ -276,6 +280,79 @@ distribute: while (it.hasNext()) {
}
/**
+ * Helper class for merging the content of two maps where values may be other metadata objects.
+ */
+ private final class ForMap<V> implements BiFunction<V,V,V> {
+ /** Used only in case of non-merged values. */ private final ModifiableMetadata parent;
+ /** Used only in case of non-merged values. */ private final String property;
+ /** The map to copy. Will not be modified. */ private final Map<?,?> source;
+ /** Where to write copied or merged values. */ private final Map<?,?> target;
+
+ /** Creates a new merger for maps. */
+ ForMap(final ModifiableMetadata parent, final String property,
+ final Map<?,?> source, final Map<?,?> target)
+ {
+ this.parent = parent;
+ this.property = property;
+ this.source = source;
+ this.target = target;
+ }
+
+ /**
+ * Executes the merge process between the maps specified at construction time.
+ *
+ * @param dryRun {@code true} for verifying if there is a merge conflict
+ * instead that performing the actual merge operation.
+ */
+ final boolean run(final boolean dryRun) {
+ for (final Map.Entry<?,?> pe : source.entrySet()) {
+ final Object newValue = pe.getValue();
+ if (dryRun) {
+ final Object oldValue;
+ if (newValue != null && (oldValue = target.get(pe.getKey())) != null) {
+ if (newValue instanceof ModifiableMetadata && copy(oldValue, (ModifiableMetadata) newValue, true)) {
+ continue;
+ }
+ return false; // Copying maps would overwrite at least one value.
+ }
+ } else {
+ /*
+ * No @SuppressWarnings("unchecked") because this is really unchecked. However since the two maps
+ * have been fetched by calls to the same getter method on two org.apache.sis.metadata.iso objects,
+ * the types should.
+ */
+ ((Map) target).merge(pe.getKey(), newValue, this);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Invoked when an entry is about to be written in the target map, but a value already exists for that entry.
+ *
+ * @param oldValue the metadata value that already exists.
+ * @param newValue the metadata value to copy in the target.
+ * @return the value to copy in the target (merged) map.
+ */
+ @Override
+ public V apply(final V oldValue, final V newValue) {
+ if (newValue instanceof ModifiableMetadata) {
+ switch (resolve(oldValue, (ModifiableMetadata) newValue)) {
+ default: throw new UnsupportedOperationException();
+ case IGNORE: break;
+ case MERGE: {
+ if (!copy(oldValue, (ModifiableMetadata) newValue, false)) {
+ merge(parent, property, oldValue, newValue);
+ }
+ break;
+ }
+ }
+ }
+ return (newValue != null) ? newValue : oldValue;
+ }
+ }
+
+ /**
* The action to perform when a <var>source</var> metadata element is about to be written in an existing
* <var>target</var> element. Many metadata elements defined by ISO 19115 allows multi-occurrence, i.e.
* are stored in {@link Collection}. When a value <var>A</var> is about to be added in an existing collection
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
index 36f6bef..e8a219d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/MetadataUtilities.java
@@ -17,7 +17,10 @@
package org.apache.sis.internal.metadata;
import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import org.apache.sis.xml.NilReason;
import org.apache.sis.xml.IdentifierSpace;
import org.apache.sis.xml.IdentifiedObject;
@@ -198,6 +201,44 @@ public final class MetadataUtilities extends Static {
}
/**
+ * Sets the first element in the given collection to the given value.
+ * Special cases:
+ *
+ * <ul>
+ * <li>If the given collection is null, a new collection will be returned.</li>
+ * <li>If the given new value is null, then the first element in the collection is removed.</li>
+ * <li>Otherwise if the given collection is empty, the given value will be added to it.</li>
+ * </ul>
+ *
+ * @param <T> the type of elements in the collection.
+ * @param values the collection where to add the new value, or {@code null}.
+ * @param newValue the new value to set, or {@code null} for instead removing the first element.
+ * @return the collection (may or may not be the given {@code values} collection).
+ *
+ * @see org.apache.sis.internal.util.CollectionsExt#first(Iterable)
+ */
+ public static <T> Collection<T> setFirst(Collection<T> values, final T newValue) {
+ if (values == null) {
+ return LegacyPropertyAdapter.asCollection(newValue);
+ }
+ if (newValue == null) {
+ final Iterator<T> it = values.iterator();
+ if (it.hasNext()) {
+ it.next();
+ it.remove();
+ }
+ } else if (values.isEmpty()) {
+ values.add(newValue);
+ } else {
+ if (!(values instanceof List<?>)) {
+ values = new ArrayList<>(values);
+ }
+ ((List<T>) values).set(0, newValue);
+ }
+ return values;
+ }
+
+ /**
* Returns the {@code gco:id} or {@code gml:id} value to use for the given object.
* The returned identifier will be unique in the current XML document.
*
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/OtherLocales.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/OtherLocales.java
deleted file mode 100644
index 621208b..0000000
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/OtherLocales.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.internal.metadata;
-
-import java.util.Locale;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.AbstractCollection;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.collection.Containers;
-
-
-/**
- * Helper methods for handling the ISO 19115 {@code defaultLocale} and {@code otherLocale} legacy properties.
- * The ISO standard defines them as two separated properties while GeoAPI handles them in a single collection
- * for integration with JDK standard API like {@link Locale#lookup(List, Collection)}.
- *
- * <p>The first element of the {@code languages} collection is taken as the {@code defaultLocale}, and all
- * remaining ones are taken as {@code otherLocale} elements. Instances of this {@code OtherLocales} class
- * are for those remaining elements and are created by the {@link #filter(Collection)} method.</p>
- *
- * @author Martin Desruisseaux (Geomatys)
- * @version 0.5
- * @since 0.5
- * @module
- */
-public final class OtherLocales extends AbstractCollection<Locale> {
- /**
- * The default locale followed by all other locales.
- */
- private final Collection<Locale> languages;
-
- /**
- * Private constructor for {@link #filter(Collection)} only.
- */
- private OtherLocales(final Collection<Locale> languages) {
- this.languages = languages;
- }
-
- /**
- * Returns a collection for all elements except the first one from the given collection.
- *
- * <p><b>Null values and XML marshalling:</b>
- * The {@code languages} argument may be {@code null} at XML marshalling time. In such case, this method returns
- * {@code null} instead than an empty set in order to instruct JAXB to not marshal the {@code otherLocale} element
- * (an empty set would cause JAXB to marshal an empty element). Since the {@code languages} argument given to this
- * method should never be null except at XML marshalling time, this rule should not be a violation of public API.</p>
- *
- * <p>The converse of this {@code filter} method is {@link #merge(Locale, Collection)}.</p>
- *
- * @param languages the collection containing the default locale followed by the other ones.
- * @return a collection containing all {@code languages} elements except the first one.
- */
- public static Collection<Locale> filter(final Collection<Locale> languages) {
- return (languages != null) ? new OtherLocales(languages) : null;
- }
-
- /**
- * Returns the number of elements in this collection.
- *
- * @return number of other locales.
- */
- @Override
- public int size() {
- int size = languages.size();
- if (size != 0) size--;
- return size;
- }
-
- /**
- * Returns an iterator over all elements in this collection except the first one.
- *
- * @return iterator over all other locales.
- */
- @Override
- public Iterator<Locale> iterator() {
- final Iterator<Locale> it = languages.iterator();
- if (it.hasNext()) it.next(); // Skip the first element.
- return it;
- }
-
- /**
- * Adds a new element to the collection of "other locales". If we had no "default locale" prior this method call,
- * then this method will choose one before to add the given locale. This is needed since the other locales begin
- * only after the first element, so a first element needs to exist.
- *
- * <p>The above rule could be a risk of confusion for the users, since it could cause the apparition of a default
- * locale which has never been specified. However this risk exists only when invoking the deprecated methods, or
- * when unmarshalling a XML document having a {@code otherLocale} property without {@code defaultLocale} property,
- * which is probably invalid.</p>
- *
- * @param locale the element to add.
- * @return {@code true} if the "other locales" collection has been modified as a result of this method call.
- */
- @Override
- public boolean add(final Locale locale) {
- ArgumentChecks.ensureNonNull("locale", locale);
- if (languages.isEmpty()) {
- Locale defaultLocale = Locale.getDefault();
- if (defaultLocale.equals(locale)) {
- defaultLocale = Locale.ROOT; // Same default than merge(Locale, Collection).
- }
- languages.add(defaultLocale);
- }
- return languages.add(locale);
- }
-
- /**
- * Returns a collection containing the given {@code defaultLocale} followed by the {@code otherLocales}.
- *
- * @param defaultLocale the first element in the collection to be returned, or {@code null} if unspecified.
- * @param otherLocales all remaining elements in the collection to be returned, or {@code null} if none.
- * @return a collection containing the default locale followed by all other ones.
- */
- public static Collection<Locale> merge(Locale defaultLocale, final Collection<? extends Locale> otherLocales) {
- final Collection<Locale> merged;
- if (Containers.isNullOrEmpty(otherLocales)) {
- merged = LegacyPropertyAdapter.asCollection(defaultLocale);
- } else {
- merged = new ArrayList<>(otherLocales.size() + 1);
- if (defaultLocale == null) {
- defaultLocale = Locale.getDefault();
- if (otherLocales.contains(defaultLocale)) {
- defaultLocale = Locale.ROOT; // Same default than add(Locale).
- }
- }
- merged.add(defaultLocale);
- merged.addAll(otherLocales);
- }
- return merged;
- }
-
- /**
- * Sets the first element in the given collection to the given value.
- * Special cases:
- *
- * <ul>
- * <li>If the given collection is null, a new collection will be returned.</li>
- * <li>If the given new value is null, then the first element in the collection is removed.</li>
- * <li>Otherwise if the given collection is empty, the given value will be added to it.</li>
- * </ul>
- *
- * <p><b>Note:</b> while defined in {@code OtherLocales} because the primary use for this method is to
- * get the default locale, this method is also opportunistically used for other legacy properties.</p>
- *
- * @param <T> the type of elements in the collection.
- * @param values the collection where to add the new value, or {@code null}.
- * @param newValue the new value to set, or {@code null} for instead removing the first element.
- * @return the collection (may or may not be the given {@code values} collection).
- *
- * @see org.apache.sis.internal.util.CollectionsExt#first(Iterable)
- */
- public static <T> Collection<T> setFirst(Collection<T> values, final T newValue) {
- if (values == null) {
- return LegacyPropertyAdapter.asCollection(newValue);
- }
- if (newValue == null) {
- final Iterator<T> it = values.iterator();
- if (it.hasNext()) {
- it.next();
- it.remove();
- }
- } else if (values.isEmpty()) {
- values.add(newValue);
- } else {
- if (!(values instanceof List<?>)) {
- values = new ArrayList<>(values);
- }
- ((List<T>) values).set(0, newValue);
- }
- return values;
- }
-}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
index 99f2e33..5454a81 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
+import java.util.Map;
import org.opengis.metadata.ApplicationSchemaInformation;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.Metadata;
@@ -79,7 +80,7 @@ import org.opengis.util.InternationalString;
* </ul>
*
* @author Johann Sorel (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.8
* @module
*/
@@ -113,12 +114,21 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati
* Also the language(s) used within the data.
*/
@Override
+ public Map<Locale,Charset> getLocalesAndCharsets() {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * @deprecated As of SIS 1.0, replaced by {@link #getLocalesAndCharsets()}.
+ */
+ @Override
+ @Deprecated
public Collection<Locale> getLanguages() {
return Collections.emptySet(); // We use 'Set' because we handle 'Locale' like a CodeList.
}
/**
- * @deprecated As of SIS 0.5, replaced by {@link #getLanguages()}.
+ * @deprecated As of SIS 1.0, replaced by {@link #getLocalesAndCharsets()}.
*/
@Override
@Deprecated
@@ -127,7 +137,7 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati
}
/**
- * @deprecated As of SIS 0.5, replaced by {@link #getLanguages()}.
+ * @deprecated As of SIS 1.0, replaced by {@link #getLocalesAndCharsets()}.
*/
@Override
@Deprecated
@@ -136,10 +146,10 @@ public class SimpleMetadata implements Metadata, MetadataScope, DataIdentificati
}
/**
- * The character coding standard used for the metadata set.
- * Also the character coding standard(s) used for the dataset.
+ * @deprecated As of SIS 1.0, replaced by {@link #getLocalesAndCharsets()}.
*/
@Override
+ @Deprecated
public Collection<Charset> getCharacterSets() {
return Collections.emptySet(); // We use 'Set' because we handle 'Charset' like a CodeList.
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
index bc7c901..56b5b24 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
@@ -16,11 +16,14 @@
*/
package org.apache.sis.metadata;
+import java.util.Map;
import java.util.Set;
import java.util.List;
+import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Currency;
import java.util.NoSuchElementException;
@@ -539,7 +542,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
if (state == FREEZING) {
/*
* transition(State.FINAL) is under progress. The source collection is already
- * an unmodifiable instance created by StageChanger.
+ * an unmodifiable instance created by StateChanger.
*/
assert (useSet != null) || collectionType(elementType).isInstance(source) : elementType;
return (Collection<E>) source;
@@ -578,6 +581,61 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
}
/**
+ * Writes the content of the {@code source} map into the {@code target} map,
+ * creating it if needed. This method performs the following steps:
+ *
+ * <ul>
+ * <li>Invokes {@link #checkWritePermission(Object)} in order to ensure that this metadata is modifiable.</li>
+ * <li>If {@code source} is null or empty, returns {@code null}
+ * (meaning that the metadata property is not provided).</li>
+ * <li>If {@code target} is null, creates a new {@link Map}.</li>
+ * <li>Copies the content of the given {@code source} into the target.</li>
+ * </ul>
+ *
+ * @param <K> the type of keys represented by the {@code Class} argument.
+ * @param <V> the type of values in the map.
+ * @param source the source map, or {@code null}.
+ * @param target the target map, or {@code null} if not yet created.
+ * @param keyType the base type of keys to put in the map.
+ * @return a map (possibly the {@code target} instance) containing the {@code source} entries,
+ * or {@code null} if the source was null.
+ * @throws UnmodifiableMetadataException if this metadata is unmodifiable.
+ *
+ * @see #nonNullMap(Map, Class)
+ *
+ * @since 1.0
+ */
+ @SuppressWarnings("unchecked")
+ protected final <K,V> Map<K,V> writeMap(final Map<? extends K, ? extends V> source, Map<K,V> target,
+ Class<K> keyType) throws UnmodifiableMetadataException
+ {
+ /*
+ * Code in this method is a copy of write(Collection, Collection, Class) with some calls inlined.
+ * See the comments inside that write(…) method body for more information on the logic.
+ */
+ if (source != target) {
+ if (state == FREEZING) {
+ return (Map<K,V>) source;
+ }
+ checkWritePermission((target == null) || target.isEmpty() ? null : target);
+ if (isNullOrEmpty(source)) {
+ target = null;
+ } else {
+ if (target != null && state != COMPLETABLE) {
+ target.clear();
+ } else {
+ target = createMap(keyType, source);
+ }
+ target.putAll(source);
+ if (state == COMPLETABLE) {
+ target = CollectionsExt.unmodifiableOrCopy(target);
+ }
+ }
+ }
+ return target;
+ }
+
+ /**
* Creates a list with the content of the {@code source} collection,
* or returns {@code null} if the source is {@code null} or empty.
* This is a convenience method for copying fields in subclass copy constructors.
@@ -646,6 +704,29 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
}
/**
+ * Creates a map with the content of the {@code source} map,
+ * or returns {@code null} if the source is {@code null} or empty.
+ * This is a convenience method for copying fields in subclass copy constructors.
+ *
+ * @param <K> the type of keys represented by the {@code Class} argument.
+ * @param <V> the type of values in the map.
+ * @param source the source map, or {@code null}.
+ * @param keyType the base type of keys to put in the map.
+ * @return a map containing the {@code source} entries,
+ * or {@code null} if the source was null or empty.
+ *
+ * @since 1.0
+ */
+ protected final <K,V> Map<K,V> copyMap(final Map<? extends K, ? extends V> source, final Class<K> keyType) {
+ if (isNullOrEmpty(source)) {
+ return null;
+ }
+ final Map<K,V> target = createMap(keyType, source);
+ target.putAll(source);
+ return target;
+ }
+
+ /**
* Creates a singleton list or set containing only the given value, if non-null.
* This is a convenience method for initializing fields in subclass constructors.
*
@@ -684,13 +765,13 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
}
/**
- * Returns the specified list, or a new one if {@code c} is null.
+ * Returns the specified list, or a new one if {@code current} is null.
* This is a convenience method for implementation of {@code getFoo()} methods.
*
* @param <E> the type represented by the {@code Class} argument.
* @param current the existing list, or {@code null} if the list has not yet been created.
- * @param elementType the element type (used only if {@code c} is null).
- * @return {@code c}, or a new list if {@code c} is null.
+ * @param elementType the element type (used only if {@code current} is null).
+ * @return {@code current}, or a new list if {@code current} is null.
*/
protected final <E> List<E> nonNullList(final List<E> current, final Class<E> elementType) {
if (current != null) {
@@ -700,26 +781,19 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
return null;
}
if (state < FREEZING) {
- /*
- * Do not specify an initial capacity, because the list will stay empty in a majority of cases
- * (i.e. the users will want to iterate over the list elements more often than they will want
- * to add elements). JDK implementation of ArrayList has a lazy instantiation mechanism for
- * initially empty lists, but as of JDK8 this lazy instantiation works only for list having
- * the default capacity.
- */
- return new CheckedArrayList<>(elementType);
+ return createList(elementType, current); // `current` given as a matter of principle even if null.
}
return Collections.emptyList();
}
/**
- * Returns the specified set, or a new one if {@code c} is null.
+ * Returns the specified set, or a new one if {@code current} is null.
* This is a convenience method for implementation of {@code getFoo()} methods.
*
* @param <E> the type represented by the {@code Class} argument.
* @param current the existing set, or {@code null} if the set has not yet been created.
- * @param elementType the element type (used only if {@code c} is null).
- * @return {@code c}, or a new set if {@code c} is null.
+ * @param elementType the element type (used only if {@code current} is null).
+ * @return {@code current}, or a new set if {@code current} is null.
*/
protected final <E> Set<E> nonNullSet(final Set<E> current, final Class<E> elementType) {
if (current != null) {
@@ -729,13 +803,13 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
return null;
}
if (state < FREEZING) {
- return createSet(elementType, null);
+ return createSet(elementType, current); // `current` given as a matter of principle even if null.
}
return Collections.emptySet();
}
/**
- * Returns the specified collection, or a new one if {@code c} is null.
+ * Returns the specified collection, or a new one if {@code current} is null.
* This is a convenience method for implementation of {@code getFoo()} methods.
*
* <div class="section">Choosing a collection type</div>
@@ -747,8 +821,8 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
*
* @param <E> the type represented by the {@code Class} argument.
* @param current the existing collection, or {@code null} if the collection has not yet been created.
- * @param elementType the element type (used only if {@code c} is null).
- * @return {@code c}, or a new collection if {@code c} is null.
+ * @param elementType the element type (used only if {@code current} is null).
+ * @return {@code current}, or a new collection if {@code current} is null.
*/
protected final <E> Collection<E> nonNullCollection(final Collection<E> current, final Class<E> elementType) {
if (current != null) {
@@ -761,14 +835,13 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
final boolean isModifiable = (state < FREEZING);
if (useSet(elementType)) {
if (isModifiable) {
- return createSet(elementType, null);
+ return createSet(elementType, current); // `current` given as a matter of principle even if null.
} else {
return Collections.emptySet();
}
} else {
if (isModifiable) {
- // Do not specify an initial capacity for the reason explained in nonNullList(…).
- return new CheckedArrayList<>(elementType);
+ return createList(elementType, current); // `current` given as a matter of principle even if null.
} else {
return Collections.emptyList();
}
@@ -776,6 +849,31 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
}
/**
+ * Returns the specified map, or a new one if {@code current} is null.
+ * This is a convenience method for implementation of {@code getFoo()} methods.
+ *
+ * @param <K> the type of keys represented by the {@code Class} argument.
+ * @param <V> the type of values in the map.
+ * @param current the existing map, or {@code null} if the map has not yet been created.
+ * @param keyType the key type (used only if {@code current} is null).
+ * @return {@code current}, or a new map if {@code current} is null.
+ *
+ * @since 1.0
+ */
+ protected final <K,V> Map<K,V> nonNullMap(final Map<K,V> current, final Class<K> keyType) {
+ if (current != null) {
+ return current.isEmpty() && emptyCollectionAsNull() ? null : current;
+ }
+ if (emptyCollectionAsNull()) {
+ return null;
+ }
+ if (state < FREEZING) {
+ return createMap(keyType, current); // `current` given as a matter of principle even if null.
+ }
+ return Collections.emptyMap();
+ }
+
+ /**
* Creates a modifiable list for elements of the given type. This method is defined mostly
* for consistency with {@link #createSet(Class, Collection)}.
*
@@ -783,12 +881,23 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
* only for computing initial capacity; it does not perform the actual copy.
*/
private static <E> List<E> createList(final Class<E> elementType, final Collection<?> source) {
+ if (source == null) {
+ /*
+ * Do not specify an initial capacity, because the list will stay empty in a majority of cases
+ * (i.e. the users will want to iterate over the list elements more often than they will want
+ * to add elements). JDK implementation of ArrayList has a lazy instantiation mechanism for
+ * initially empty lists, but as of JDK 10 this lazy instantiation works only for list having
+ * the default capacity.
+ */
+ return new CheckedArrayList<>(elementType);
+ }
return new CheckedArrayList<>(elementType, source.size());
}
/**
* Creates a modifiable set for elements of the given type. This method will create an {@link EnumSet},
* {@link CodeListSet} or {@link java.util.LinkedHashSet} depending on the {@code elementType} argument.
+ * The set must have a stable iteration order (this is needed by {@link TreeTableView}).
*
* @param source the collection to be copied in the new set, or {@code null} if unknown.
* This method uses this information only for computing initial capacity;
@@ -811,6 +920,23 @@ public abstract class ModifiableMetadata extends AbstractMetadata {
}
/**
+ * Creates a modifiable map for elements of the given type.
+ * The map must have a stable iteration order (this is needed by {@link TreeTableView}).
+ *
+ * @param source the map to be copied in the new map. This method uses this information
+ * only for computing initial capacity; it does not perform the actual copy.
+ */
+ @SuppressWarnings({"unchecked","rawtypes"})
+ private static <K,V> Map<K,V> createMap(final Class<K> keyType, final Map<?,?> source) {
+ if (Enum.class.isAssignableFrom(keyType)) {
+ return new EnumMap(keyType);
+ } else {
+ // Must be LinkedHashMap, not HashMap, because TreeTableView needs stable iteration order.
+ return new LinkedHashMap<>((source != null) ? Containers.hashMapCapacity(source.size()) : 4);
+ }
+ }
+
+ /**
* Returns {@code true} if we should use a {@link Set} instead than a {@link List}
* for elements of the given type.
*/
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
index 8314aef..5d2e52f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
@@ -28,6 +28,7 @@ import java.lang.reflect.InvocationTargetException;
import org.opengis.annotation.UML;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.ExtendedElementInformation;
+import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.util.Citations;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.measure.ValueRange;
@@ -109,7 +110,7 @@ class PropertyAccessor {
try {
EXTRA_GETTER = IdentifiedObject.class.getMethod("getIdentifiers", (Class<?>[]) null);
} catch (NoSuchMethodException e) {
- throw new AssertionError(e); // Should never happen.
+ throw new AssertionError(e); // Should never happen.
}
}
@@ -192,6 +193,7 @@ class PropertyAccessor {
*
* <p>Notes:</p>
* <ul>
+ * <li>Element type of {@link Map} collection is {@link Map.Entry}.</li>
* <li>Primitive types like {@code double} or {@code int} are converted to their wrapper types.</li>
* <li>This array may contain null values if the type of elements in a collection is unknown
* (i.e. the collection is not parameterized).</li>
@@ -250,11 +252,11 @@ class PropertyAccessor {
int standardCount = allCount;
if (allCount != 0 && getters[allCount-1] == EXTRA_GETTER) {
if (!EXTRA_GETTER.getDeclaringClass().isAssignableFrom(implementation)) {
- allCount--; // The extra getter method does not exist.
+ allCount--; // The extra getter method does not exist.
}
standardCount--;
}
- while (standardCount != 0) { // Skip deprecated methods.
+ while (standardCount != 0) { // Skip deprecated methods.
if (!isDeprecated(standardCount - 1)) {
break;
}
@@ -325,8 +327,10 @@ class PropertyAccessor {
try {
getter = implementation.getMethod(getter.getName(), (Class<?>[]) null);
} catch (NoSuchMethodException error) {
- // Should never happen, since the implementation class
- // implements the interface where the getter come from.
+ /*
+ * Should never happen, since the implementation class
+ * implements the interface where the getter come from.
+ */
throw new AssertionError(error);
}
if (returnType != (returnType = getter.getReturnType())) {
@@ -334,8 +338,10 @@ class PropertyAccessor {
try {
setter = implementation.getMethod(name, arguments);
} catch (NoSuchMethodException ignore) {
- // There is no setter, which may be normal. At this stage
- // the 'setter' variable should still have the null value.
+ /*
+ * There is no setter, which may be normal. At this stage
+ * the 'setter' variable should still have the null value.
+ */
}
}
}
@@ -358,6 +364,8 @@ class PropertyAccessor {
// Subclass has erased parameterized type. Use method declared in the interface.
elementType = Classes.boundOfParameterizedProperty(getters[i]);
}
+ } else if (Map.class.isAssignableFrom(elementType)) {
+ elementType = Map.Entry.class;
}
elementTypes[i] = Numbers.primitiveToWrapper(elementType);
}
@@ -530,7 +538,7 @@ class PropertyAccessor {
* @return the name of the given kind at the given index, or {@code null} if the index is out of bounds.
*/
@SuppressWarnings("fallthrough")
- @Workaround(library="JDK", version="1.7") // Actually apply to String.intern() below.
+ @Workaround(library="JDK", version="10") // Actually apply to String.intern() below.
final String name(final int index, final KeyNamePolicy keyPolicy) {
if (index >= 0 && index < names.length) {
switch (keyPolicy) {
@@ -538,10 +546,10 @@ class PropertyAccessor {
final UML uml = getters[index].getAnnotation(UML.class);
if (uml != null) {
/*
- * Workaround here: I though that annotation strings were interned like any other
- * constants, but it doesn't seem to be the case as of JDK7. To check if a future
- * JDK release still needs this explicit call to String.intern(), try to remove
- * the ".intern()" part and run the NameMapTest.testStringIntern() method.
+ * Workaround here: I though that annotation strings were interned like any other constants,
+ * but it does not seem to be the case as of JDK 10. To check if a future JDK release still
+ * needs this explicit call to String.intern(), try to remove the ".intern()" part and run
+ * the NameMapTest.testStringIntern() method.
*/
return uml.identifier().intern();
}
@@ -606,16 +614,24 @@ class PropertyAccessor {
}
/**
- * Returns {@code true} if the type at the given index is {@link Collection}.
+ * Returns {@code true} if the type at the given index is {@link Collection} or {@link Map}.
*/
- final boolean isCollection(final int index) {
+ final boolean isCollectionOrMap(final int index) {
if (index >= 0 && index < allCount) {
- return Collection.class.isAssignableFrom(getters[index].getReturnType());
+ final Class<?> type = getters[index].getReturnType();
+ return Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type);
}
return false;
}
/**
+ * Returns {@code true} if the type at the given index is {@link Map}.
+ */
+ final boolean isMap(final int index) {
+ return (index >= 0 && index < allCount) && elementTypes[index] == Map.Entry.class;
+ }
+
+ /**
* Returns {@code true} if the property at the given index is deprecated, either in the interface that
* declare the method or in the implementation class. A method may be deprecated in the implementation
* but not in the interface when the implementation has been updated for a new standard
@@ -1132,7 +1148,7 @@ class PropertyAccessor {
* 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;
+ count += isCollectionOrMap(i) ? Math.max(CollectionsExt.size(value), 1) : 1;
break;
}
default: throw new AssertionError(mode);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java
index edcd984..6186158 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/SpecialCases.java
@@ -25,10 +25,9 @@ import org.apache.sis.util.collection.BackingStoreException;
/**
- * Substitute on-the-fly the values of some properties handled in a special way.
- * The current implementation handles only the longitude and latitude bounds of
- * {@link GeographicBoundingBox}, which are returned as {@link Longitude} or
- * {@link Latitude} instances instead of {@link Double}.
+ * Substitute on-the-fly the values of some ISO 19115 properties handled in a special way.
+ * Current implementation handles the longitude and latitude bounds of {@link GeographicBoundingBox},
+ * which are returned as {@link Longitude} or {@link Latitude} instances instead of {@link Double}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
@@ -156,4 +155,27 @@ final class SpecialCases extends PropertyAccessor {
return super.set(index, metadata, value, mode);
}
}
+
+ /**
+ * Returns {@code true} if the property at the given index is a {@code Map<Locale,Charset>}.
+ */
+ static boolean isLocaleAndCharset(final PropertyAccessor accessor, final int indexInData) {
+ return accessor.isMap(indexInData) && accessor.type.getName().startsWith("org.opengis.metadata.")
+ && "localesAndCharsets".equals(accessor.name(indexInData, KeyNamePolicy.JAVABEANS_PROPERTY));
+ }
+
+ /**
+ * Returns the identifier to use in replacement of the identifier given in {@link org.opengis.annotation.UML} annotations.
+ * We usually want to use those identifiers as-is because they were specified by ISO standards, but we may an exception if
+ * the identifier is actually a construction of two or more identifiers like {@code "defaultLocale+otherLocale"}.
+ *
+ * @param name the UML identifier(s) from ISO specification.
+ * @return the potentially simplified identifier to use for displaying purpose.
+ */
+ static String rename(final String name) {
+ if ("defaultLocale+otherLocale".equals(name)) {
+ return "locale";
+ }
+ return name;
+ }
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
index 1cfa971..cdf45e7 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNode.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.metadata;
+import java.util.Map;
import java.util.List;
import java.util.Iterator;
import java.util.Collection;
@@ -23,6 +24,9 @@ import java.util.Collections;
import java.util.Objects;
import java.util.NoSuchElementException;
import java.util.ConcurrentModificationException;
+import java.util.function.Function;
+import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
+import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.util.Classes;
import org.apache.sis.util.iso.Types;
import org.apache.sis.util.CharSequences;
@@ -172,7 +176,7 @@ class TreeNode implements Node {
* @param metadata the metadata object for which this node will be a value.
* @param baseType the return type of the getter method that provides the value encapsulated by this node.
*/
- TreeNode(final TreeNode parent, final Object metadata, final Class<?> baseType) {
+ private TreeNode(final TreeNode parent, final Object metadata, final Class<?> baseType) {
this.table = parent.table;
this.parent = parent;
this.metadata = metadata;
@@ -321,6 +325,19 @@ class TreeNode implements Node {
private final int indexInData;
/**
+ * If tree node should be wrapped in another object before to be returned, the function performing that wrapping.
+ * This is used if we want to render a metadata property in a different way than the way implied by JavaBeans.
+ * The wrapping operation should be cheap because it will be applied every time the user request the node.
+ *
+ * <div class="note"><b>Example:</b>
+ * the {@code "defaultLocale+otherLocale"} property is represented by {@code Map.Entry<Locale,Charset>} values.
+ * The nodes created by this class contain those {@code Map.Entry} values, but we want to show them to users as
+ * as a {@link java.util.Locale} node with a {@link java.nio.charset.Charset} child. This separation is done by
+ * {@link LocaleAndCharset}.</div>
+ */
+ final Function<TreeNode,Node> decorator;
+
+ /**
* Creates a new child for a property of the given metadata at the given index.
*
* @param parent the parent of this node.
@@ -334,6 +351,11 @@ class TreeNode implements Node {
super(parent, metadata, accessor.type(indexInData, TypeValuePolicy.ELEMENT_TYPE));
this.accessor = accessor;
this.indexInData = indexInData;
+ if (SpecialCases.isLocaleAndCharset(accessor, indexInData)) {
+ decorator = LocaleAndCharset::new;
+ } else {
+ decorator = null;
+ }
}
/**
@@ -360,7 +382,8 @@ class TreeNode implements Node {
* node for each element in a collection.
*
* <p>If the property name is equals, ignoring case, to the simple type name, then this method
- * returns the subtype name. For example instead of:</p>
+ * returns the subtype name (<a href="https://issues.apache.org/jira/browse/SIS-298">SIS-298</a>).
+ * For example instead of:</p>
*
* {@preformat text
* Citation
@@ -377,8 +400,6 @@ class TreeNode implements Node {
* └─Individual
* └─Name ……………………………… Jon Smith
* }
- *
- * @see <a href="https://issues.apache.org/jira/browse/SIS-298">SIS-298</a>
*/
@Override
CharSequence getName() {
@@ -392,6 +413,7 @@ class TreeNode implements Node {
}
}
}
+ identifier = SpecialCases.rename(identifier); // Hard-coded special case.
return CharSequences.camelCaseToSentence(identifier).toString();
}
@@ -421,7 +443,7 @@ class TreeNode implements Node {
* Gets remarks about the value in this node, or {@code null} if none.
*/
@Override
- CharSequence getRemarks() {
+ final CharSequence getRemarks() {
return accessor.remarks(indexInData, metadata);
}
@@ -519,12 +541,9 @@ class TreeNode implements Node {
@Override
CharSequence getName() {
CharSequence name = super.getName();
- final Collection<?> values = (Collection<?>) super.getUserObject();
- if (values != null) {
- final int size = values.size();
- if (size >= 2) {
- name = Vocabulary.formatInternational(Vocabulary.Keys.Of_3, name, indexInList+1, size);
- }
+ final int size = CollectionsExt.size(super.getUserObject());
+ if (size >= 2) {
+ name = Vocabulary.formatInternational(Vocabulary.Keys.Of_3, name, indexInList+1, size);
}
return name;
}
@@ -535,7 +554,17 @@ class TreeNode implements Node {
*/
@Override
public Object getUserObject() {
- final Collection<?> values = (Collection<?>) super.getUserObject();
+ final Object collection = super.getUserObject();
+ final Collection<?> values;
+ if (collection instanceof Collection<?>) {
+ values = (Collection<?>) collection;
+ } else {
+ /*
+ * ClassCastException should never happen here unless PropertyAccessor.isCollectionOrMap(…) has
+ * been modified, in which case there is probably many code to update (not only this method).
+ */
+ values = ((Map<?,?>) collection).entrySet();
+ }
/*
* If the collection is null or empty but the value existence policy tells
* us that such elements shall be shown, behave as if the collection was a
@@ -672,8 +701,8 @@ class TreeNode implements Node {
* exists otherwise the call to 'isLeaf()' above would have returned 'true'.
*/
if (children == null || ((TreeNodeChildren) children).metadata != value) {
- children = new TreeNodeChildren(this, value,
- table.standard.getAccessor(new CacheKey(value.getClass(), baseType), true));
+ PropertyAccessor accessor = table.standard.getAccessor(new CacheKey(value.getClass(), baseType), true);
+ children = new TreeNodeChildren(this, value, accessor);
}
}
return children;
@@ -773,8 +802,8 @@ class TreeNode implements Node {
}
final TreeNodeChildren siblings = getSiblings();
final int indexInList;
- if (siblings.isCollection(indexInData)) {
- indexInList = ((Collection<?>) siblings.valueAt(indexInData)).size();
+ if (siblings.isCollectionOrMap(indexInData)) {
+ indexInList = CollectionsExt.size(siblings.valueAt(indexInData));
} else {
indexInList = -1;
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
index 7e82ef2..aade06c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeNodeChildren.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.metadata;
+import java.util.Map;
import java.util.Iterator;
import java.util.Collections;
import java.util.AbstractCollection;
@@ -94,7 +95,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
* <p>Not all elements in this array will be returned by the iterator.
* The value needs to be verified for the {@link ValueExistencePolicy}.</p>
*/
- private final TreeNode[] children;
+ private final TreeNode.Element[] children;
/**
* Index of the property to write in the parent node instead than as a child.
@@ -138,7 +139,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
this.parent = parent;
this.metadata = metadata;
this.accessor = accessor;
- this.children = new TreeNode[accessor.count()];
+ this.children = new TreeNode.Element[accessor.count()];
/*
* Search for something that looks like the main property, to be associated with the parent node
* instead than provided as a child. The intent is to have more compact and easy to read trees.
@@ -225,9 +226,8 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
}
/**
- * 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 collection.
+ * Returns {@code true} if the type at the given index is a collection or a map.
+ * The given {@code index} is relative to the {@link #accessor} indexing, <strong>not</strong> to this collection.
*
* <div class="note"><b>Implementation note:</b>
* We do not test {@code (value instanceof Collection)} because the value could be any user's implementation.
@@ -236,8 +236,8 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
* @param index the index in the accessor (<em>not</em> the index in this collection).
* @return {@code true} if the value at the given index is a collection.
*/
- final boolean isCollection(final int index) {
- return accessor.isCollection(index);
+ final boolean isCollectionOrMap(final int index) {
+ return accessor.isCollectionOrMap(index);
}
/**
@@ -263,13 +263,16 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
* collection (<em>not</em> the index in <em>this</em> collection). Otherwise -1.
* @return the node to be returned by public API.
*/
- final TreeNode childAt(final int index, final int subIndex) {
- TreeNode node = children[index];
+ final TreeNode.Element childAt(final int index, final int subIndex) {
+ TreeNode.Element node = children[index];
if (subIndex >= 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.
+ *
+ * Note: subIndex is ≧ 0 only if node is an instance of CollectionElement.
+ * A ClassCastException below would be a logical error in this class.
*/
if (node == null || ((TreeNode.CollectionElement) node).indexInList != subIndex) {
node = new TreeNode.CollectionElement(parent, metadata, accessor, index, subIndex);
@@ -462,23 +465,25 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
if (nextInAccessor != titleProperty) {
nextValue = valueAt(nextInAccessor);
if (!isSkipped(nextValue)) {
- if (isCollection(nextInAccessor)) {
+ if (isCollectionOrMap(nextInAccessor)) {
+ /*
+ * Null collections are illegal (it shall be empty collections instead),
+ * but we try to keep the iterator robust to ill-formed metadata because
+ * we want AbstractMetadata.toString() to work so we can spot problems.
+ */
+ if (nextValue == null) {
+ subIterator = Collections.emptyIterator();
+ } else if (nextValue instanceof Iterable<?>) {
+ subIterator = ((Iterable<?>) nextValue).iterator();
+ } else {
+ subIterator = ((Map<?,?>) nextValue).entrySet().iterator();
+ }
/*
* 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) {
- subIterator = ((Iterable<?>) nextValue).iterator();
- } else {
- subIterator = 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.
- */
- }
subIndex = 0;
if (subIterator.hasNext()) {
nextValue = subIterator.next();
@@ -502,12 +507,12 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
/**
* 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.
+ * any time if the user modifies the underlying metadata object.
*/
@Override
public TreeTable.Node next() {
if (hasNext()) {
- final TreeNode node = childAt(nextInAccessor, subIndex);
+ final TreeNode.Element node = childAt(nextInAccessor, subIndex);
node.cachedValue = nextValue;
previousInAccessor = nextInAccessor;
if (subIterator == null) {
@@ -521,7 +526,7 @@ final class TreeNodeChildren extends AbstractCollection<TreeTable.Node> {
nextInAccessor++;
}
isNextVerified = false;
- return node;
+ return (node.decorator == null) ? node : node.decorator.apply(node);
}
throw new NoSuchElementException();
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index 4390d69..4347249 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -18,6 +18,9 @@ package org.apache.sis.metadata.iso;
import java.util.Date;
import java.util.Locale;
+import java.util.Set;
+import java.util.EnumSet;
+import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
@@ -63,17 +66,22 @@ import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
import org.apache.sis.metadata.iso.identification.AbstractIdentification;
import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
import org.apache.sis.internal.metadata.LegacyPropertyAdapter;
-import org.apache.sis.internal.metadata.OtherLocales;
+import org.apache.sis.internal.metadata.MetadataUtilities;
import org.apache.sis.internal.metadata.Dependencies;
import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
-import org.apache.sis.internal.xml.LegacyNamespaces;
+import org.apache.sis.internal.jaxb.lan.OtherLocales;
+import org.apache.sis.internal.jaxb.lan.PT_Locale;
import org.apache.sis.internal.jaxb.FilterByVersion;
import org.apache.sis.internal.jaxb.Context;
import org.apache.sis.internal.jaxb.metadata.CI_Citation;
import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
-
-import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+import org.apache.sis.internal.xml.LegacyNamespaces;
+import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.internal.converter.SurjectiveConverter;
+import org.apache.sis.math.FunctionProperty;
/**
@@ -189,17 +197,12 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
/**
* Serial number for inter-operability with different versions.
*/
- private static final long serialVersionUID = -4935599812744534502L;
-
- /**
- * Language(s) used for documenting metadata.
- */
- private Collection<Locale> languages;
+ private static final long serialVersionUID = -1128741312274891545L;
/**
- * Full name of the character coding standard used for the metadata set.
+ * Language(s) and character set(s) used within the dataset.
*/
- private Collection<Charset> characterSets;
+ private Map<Locale,Charset> locales;
/**
* Identification of the parent metadata record.
@@ -345,8 +348,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
if (object != null) {
identifiers = singleton(object.getMetadataIdentifier(), Identifier.class);
parentMetadata = object.getParentMetadata();
- languages = copyCollection(object.getLanguages(), Locale.class);
- characterSets = copyCollection(object.getCharacterSets(), Charset.class);
+ locales = copyMap (object.getLocalesAndCharsets(), Locale.class);
metadataScopes = copyCollection(object.getMetadataScopes(), MetadataScope.class);
contacts = copyCollection(object.getContacts(), Responsibility.class);
dateInfo = copyCollection(object.getDateInfo(), CitationDate.class);
@@ -476,23 +478,77 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
}
/**
- * Returns the language(s) used for documenting metadata.
+ * Returns the language(s) and character set(s) used for documenting metadata.
* The first element in iteration order is the default language.
* All other elements, if any, are alternate language(s) used within the resource.
*
- * <p>Unless an other locale has been specified with the {@link org.apache.sis.xml.XML#LOCALE} property,
+ * <p>Unless another locale has been specified with the {@link org.apache.sis.xml.XML#LOCALE} property,
* this {@code DefaultMetadata} instance and its children will use the first locale returned by this method
* for marshalling {@link org.opengis.util.InternationalString} and {@link org.opengis.util.CodeList} instances
- * in ISO 19115-2 compliant XML documents.
+ * in ISO 19115-2 compliant XML documents.</p>
*
- * @return language(s) used for documenting metadata.
+ * <div class="section">Relationship with ISO 19115</div>
+ * Each ({@link Locale}, {@link Charset}) entry is equivalent to an instance of ISO {@code PT_Locale} class.
+ * ISO 19115-1:2014 represents character sets by references to the
+ * <a href="http://www.iana.org/assignments/character-sets">IANA Character Set register</a>,
+ * which is represented in Java by {@link java.nio.charset.Charset}.
+ * Instances can be obtained by a call to {@link Charset#forName(String)}.
*
- * @since 0.5
+ * <div class="note"><b>Examples:</b>
+ * {@code UCS-2}, {@code UCS-4}, {@code UTF-7}, {@code UTF-8}, {@code UTF-16},
+ * {@code ISO-8859-1} (a.k.a. {@code ISO-LATIN-1}), {@code ISO-8859-2}, {@code ISO-8859-3}, {@code ISO-8859-4},
+ * {@code ISO-8859-5}, {@code ISO-8859-6}, {@code ISO-8859-7}, {@code ISO-8859-8}, {@code ISO-8859-9},
+ * {@code ISO-8859-10}, {@code ISO-8859-11}, {@code ISO-8859-12}, {@code ISO-8859-13}, {@code ISO-8859-14},
+ * {@code ISO-8859-15}, {@code ISO-8859-16},
+ * {@code JIS_X0201}, {@code Shift_JIS}, {@code EUC-JP}, {@code US-ASCII}, {@code EBCDIC}, {@code EUC-KR},
+ * {@code Big5}, {@code GB2312}.
+ * </div>
+ *
+ * @return language(s) and character set(s) used for documenting metadata.
+ *
+ * @since 1.0
*/
@Override
// @XmlElement at the end of this class.
+ public Map<Locale,Charset> getLocalesAndCharsets() {
+ return locales = nonNullMap(locales, Locale.class);
+ }
+
+ /**
+ * Sets the language(s) and character set(s) used within the dataset.
+ * The first element in iteration order should be the default language.
+ * All other elements, if any, are alternate language(s) used within the resource.
+ *
+ * @param newValues the new language(s) and character set(s) used for documenting metadata.
+ *
+ * @see org.apache.sis.xml.XML#LOCALE
+ *
+ * @since 1.0
+ */
+ public void setLocalesAndCharsets(final Map<? extends Locale, ? extends Charset> newValues) {
+ locales = writeMap(newValues, locales, Locale.class);
+ /*
+ * The "magic" applying this language to every children
+ * is performed by the 'beforeMarshal(Marshaller)' method.
+ */
+ }
+
+ /**
+ * Returns the language(s) used for documenting metadata.
+ * The first element in iteration order is the default language.
+ * All other elements, if any, are alternate language(s) used within the resource.
+ *
+ * @return language(s) used for documenting metadata.
+ *
+ * @since 0.5
+ *
+ * @deprecated Replaced by {@code getLocalesAndCharsets().keySet()}.
+ */
+ @Deprecated
+ @Dependencies("getLocalesAndCharsets")
public Collection<Locale> getLanguages() {
- return languages = nonNullCollection(languages, Locale.class);
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getLanguages(getLocalesAndCharsets()) : null;
}
/**
@@ -502,14 +558,14 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*
* @param newValues the new languages.
*
- * @see org.apache.sis.xml.XML#LOCALE
- *
* @since 0.5
+ *
+ * @deprecated Replaced by putting keys in {@link #getLocalesAndCharsets()} map.
*/
+ @Deprecated
public void setLanguages(final Collection<Locale> newValues) {
- languages = writeCollection(newValues, languages, Locale.class);
- // The "magic" applying this language to every children
- // is performed by the 'beforeMarshal(Marshaller)' method.
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ setLocalesAndCharsets(LocaleAndCharset.setLanguages(getLocalesAndCharsets(), newValues));
}
/**
@@ -521,9 +577,8 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Override
@Deprecated
- @Dependencies("getLanguages")
+ @Dependencies("getLocalesAndCharsets")
@XmlElement(name = "language", namespace = LegacyNamespaces.GMD)
- @XmlJavaTypeAdapter(LocaleAdapter.class)
public Locale getLanguage() {
return FilterByVersion.LEGACY_METADATA.accept() ? CollectionsExt.first(getLanguages()) : null;
/*
@@ -547,8 +602,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Deprecated
public void setLanguage(final Locale newValue) {
- checkWritePermission(valueIfDefined(languages));
- setDefaultLocale(newValue);
+ setLocalesAndCharsets(OtherLocales.setFirst(locales, new PT_Locale(newValue)));
}
/**
@@ -560,54 +614,56 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Override
@Deprecated
- @Dependencies("getLanguages")
+ @Dependencies("getLocalesAndCharsets")
@XmlElement(name = "locale", namespace = LegacyNamespaces.GMD)
+ @XmlJavaTypeAdapter(LocaleAdapter.Wrapped.class)
public Collection<Locale> getLocales() {
- return FilterByVersion.LEGACY_METADATA.accept() ? OtherLocales.filter(getLanguages()) : null;
+ if (FilterByVersion.LEGACY_METADATA.accept()) {
+ final Set<PT_Locale> locales = OtherLocales.filter(getLocalesAndCharsets());
+ return Containers.derivedSet(locales, ToLocale.INSTANCE);
+ }
+ return null;
}
/**
- * Sets information about an alternatively used localized character string for a linguistic extension.
- *
- * @param newValues the new locales.
- *
- * @deprecated As of SIS 0.5, replaced by {@link #setLanguages(Collection)}.
+ * Converter from {@link PT_Locale} and {@link Locale}.
*/
- @Deprecated
- public void setLocales(final Collection<? extends Locale> newValues) {
- checkWritePermission(valueIfDefined(languages));
- setOtherLocales(newValues);
+ private static final class ToLocale extends SurjectiveConverter<PT_Locale,Locale> {
+ static final ToLocale INSTANCE = new ToLocale();
+ private ToLocale() {}
+ @Override public Class<PT_Locale> getSourceClass() {return PT_Locale.class;}
+ @Override public Class<Locale> getTargetClass() {return Locale.class;}
+ @Override public Locale apply(PT_Locale p) {return p.getLocale();}
+ @Override public ObjectConverter<Locale, PT_Locale> inverse() {return FromLocale.INSTANCE;}
+ }
+
+ /**
+ * Converter from {@link Locale} and {@link PT_Locale}.
+ */
+ private static final class FromLocale implements ObjectConverter<Locale,PT_Locale> {
+ static final FromLocale INSTANCE = new FromLocale();
+ private FromLocale() {}
+ @Override public Set<FunctionProperty> properties() {return EnumSet.of(FunctionProperty.INJECTIVE);}
+ @Override public Class<Locale> getSourceClass() {return Locale.class;}
+ @Override public Class<PT_Locale> getTargetClass() {return PT_Locale.class;}
+ @Override public PT_Locale apply(Locale o) {return (o != null) ? new PT_Locale(o) : null;}
+ @Override public ObjectConverter<PT_Locale, Locale> inverse() {return ToLocale.INSTANCE;}
}
/**
* Returns the character coding standard used for the metadata set.
- * ISO 19115:2014 represents character sets by references to the
- * <a href="http://www.iana.org/assignments/character-sets">IANA Character Set register</a>,
- * which is represented in Java by {@link java.nio.charset.Charset}.
- * Instances can be obtained by a call to {@link Charset#forName(String)}.
- *
- * <div class="note"><b>Examples:</b>
- * {@code UCS-2}, {@code UCS-4}, {@code UTF-7}, {@code UTF-8}, {@code UTF-16},
- * {@code ISO-8859-1} (a.k.a. {@code ISO-LATIN-1}), {@code ISO-8859-2}, {@code ISO-8859-3}, {@code ISO-8859-4},
- * {@code ISO-8859-5}, {@code ISO-8859-6}, {@code ISO-8859-7}, {@code ISO-8859-8}, {@code ISO-8859-9},
- * {@code ISO-8859-10}, {@code ISO-8859-11}, {@code ISO-8859-12}, {@code ISO-8859-13}, {@code ISO-8859-14},
- * {@code ISO-8859-15}, {@code ISO-8859-16},
- * {@code JIS_X0201}, {@code Shift_JIS}, {@code EUC-JP}, {@code US-ASCII}, {@code EBCDIC}, {@code EUC-KR},
- * {@code Big5}, {@code GB2312}.
- * </div>
*
* @return character coding standards used for the metadata.
*
- * @see #getLanguages()
- * @see org.opengis.metadata.identification.DataIdentification#getCharacterSets()
- * @see Charset#forName(String)
- * @see <a href="https://issues.apache.org/jira/browse/SIS-402">SIS-402</a>
- *
* @since 0.5
+ *
+ * @deprecated Replaced by {@code getLocalesAndCharsets().values()}.
*/
- @Override
+ @Deprecated
+ @Dependencies("getLocalesAndCharsets")
public Collection<Charset> getCharacterSets() {
- return characterSets = nonNullCollection(characterSets, Charset.class);
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getCharacterSets(getLocalesAndCharsets()) : null;
}
/**
@@ -616,9 +672,13 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
* @param newValues the new character coding standards.
*
* @since 0.5
+ *
+ * @deprecated Replaced by putting values in {@link #getLocalesAndCharsets()} map.
*/
+ @Deprecated
public void setCharacterSets(final Collection<? extends Charset> newValues) {
- characterSets = writeCollection(newValues, characterSets, Charset.class);
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ setLocalesAndCharsets(LocaleAndCharset.setCharacterSets(getLocalesAndCharsets(), newValues));
}
/**
@@ -630,7 +690,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Override
@Deprecated
- @Dependencies("getCharacterSets")
+ @Dependencies("getLocalesAndCharsets")
@XmlElement(name = "characterSet", namespace = LegacyNamespaces.GMD)
public CharacterSet getCharacterSet() {
if (FilterByVersion.LEGACY_METADATA.accept()) {
@@ -803,7 +863,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Deprecated
public void setHierarchyLevels(final Collection<? extends ScopeCode> newValues) {
- checkWritePermission(valueIfDefined(metadataScopes));
+ checkWritePermission(MetadataUtilities.valueIfDefined(metadataScopes));
((LegacyPropertyAdapter<ScopeCode,?>) getHierarchyLevels()).setValues(newValues);
}
@@ -854,7 +914,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Deprecated
public void setHierarchyLevelNames(final Collection<? extends String> newValues) {
- checkWritePermission(valueIfDefined(metadataScopes));
+ checkWritePermission(MetadataUtilities.valueIfDefined(metadataScopes));
((LegacyPropertyAdapter<String,?>) getHierarchyLevelNames()).setValues(newValues);
}
@@ -939,7 +999,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@Deprecated
public void setDateStamp(final Date newValue) {
- checkWritePermission(valueIfDefined(dateInfo));
+ checkWritePermission(MetadataUtilities.valueIfDefined(dateInfo));
Collection<CitationDate> newValues = dateInfo; // See "Note about deprecated methods implementation"
if (newValues == null) {
if (newValue == null) {
@@ -1072,7 +1132,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
* {@link #setMetadataStandardVersion(String)} methods.
*/
private void setMetadataStandard(final boolean version, final String newValue) {
- checkWritePermission(valueIfDefined(metadataStandards));
+ checkWritePermission(MetadataUtilities.valueIfDefined(metadataStandards));
final InternationalString i18n = (newValue != null) ? new SimpleInternationalString(newValue) : null;
final List<Citation> newValues = (metadataStandards != null)
? new ArrayList<>(metadataStandards)
@@ -1231,7 +1291,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
public void setDataSetUri(final String newValue) throws URISyntaxException {
final URI uri = new URI(newValue);
Collection<Identification> info = identificationInfo; // See "Note about deprecated methods implementation"
- checkWritePermission(valueIfDefined(info));
+ checkWritePermission(MetadataUtilities.valueIfDefined(info));
AbstractIdentification firstId = AbstractIdentification.castOrCopy(CollectionsExt.first(info));
if (firstId == null) {
firstId = new DefaultDataIdentification();
@@ -1246,10 +1306,10 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
firstOnline = new DefaultOnlineResource();
}
firstOnline.setLinkage(uri);
- onlineResources = OtherLocales.setFirst(onlineResources, firstOnline);
+ onlineResources = MetadataUtilities.setFirst(onlineResources, firstOnline);
citation.setOnlineResources(onlineResources);
firstId.setCitation(citation);
- info = OtherLocales.setFirst(info, firstId);
+ info = MetadataUtilities.setFirst(info, firstId);
setIdentificationInfo(info);
}
@@ -1548,7 +1608,7 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
*/
@SuppressWarnings("unused")
private void beforeMarshal(final Marshaller marshaller) {
- Context.push(CollectionsExt.first(languages));
+ Context.push(CollectionsExt.first(LocaleAndCharset.getLanguages(getLocalesAndCharsets())));
}
/**
@@ -1564,30 +1624,23 @@ public class DefaultMetadata extends ISOMetadata implements Metadata {
* Gets the default locale for this record (used in ISO 19115-3 format).
*/
@XmlElement(name = "defaultLocale")
- private Locale getDefaultLocale() {
- return FilterByVersion.CURRENT_METADATA.accept() ? CollectionsExt.first(getLanguages()) : null;
+ private PT_Locale getDefaultLocale() {
+ return FilterByVersion.CURRENT_METADATA.accept() ? PT_Locale.first(getLocalesAndCharsets()) : null;
}
/**
* Sets the default locale for this record (used in ISO 19115-3 format).
*/
- private void setDefaultLocale(final Locale newValue) {
- setLanguages(OtherLocales.setFirst(languages, newValue)); // See "Note about deprecated methods implementation"
+ private void setDefaultLocale(final PT_Locale newValue) {
+ setLocalesAndCharsets(OtherLocales.setFirst(locales, newValue));
}
/**
* Gets the other locales for this record (used in ISO 19115-3 format).
*/
@XmlElement(name = "otherLocale")
- private Collection<Locale> getOtherLocales() {
- return FilterByVersion.CURRENT_METADATA.accept() ? OtherLocales.filter(getLanguages()) : null;
- }
-
- /**
- * Sets the other locales for this record (used in ISO 19115-3 format).
- */
- private void setOtherLocales(final Collection<? extends Locale> newValues) {
- setLanguages(OtherLocales.merge(CollectionsExt.first(languages), newValues));
+ private Collection<PT_Locale> getOtherLocales() {
+ return FilterByVersion.CURRENT_METADATA.accept() ? OtherLocales.filter(getLocalesAndCharsets()) : null;
}
/**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index ea109f9..1f17382 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@ -16,21 +16,23 @@
*/
package org.apache.sis.metadata.iso.content;
-import java.util.Locale;
+import java.util.Map;
import java.util.Collection;
+import java.util.Locale;
+import java.nio.charset.Charset;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.opengis.util.GenericName;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.content.FeatureCatalogueDescription;
import org.opengis.metadata.content.FeatureTypeInfo;
import org.apache.sis.internal.jaxb.FilterByVersion;
import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
+import org.apache.sis.internal.jaxb.lan.PT_Locale;
import org.apache.sis.internal.metadata.Dependencies;
import org.apache.sis.internal.metadata.LegacyPropertyAdapter;
+import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
@@ -65,7 +67,7 @@ import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
@XmlType(name = "MD_FeatureCatalogueDescription_Type", propOrder = {
"compliant",
"locale", // New in ISO 19115:2014
- "language", // Legacy ISO 19115:2003
+ "languages", // Legacy ISO 19115:2003
"includedWithDataset",
"featureTypesInfo", // New in ISO 19115:2014. Actual name is "featureTypeInfo"
"featureTypes", // Legacy ISO 19115:2003
@@ -78,7 +80,7 @@ public class DefaultFeatureCatalogueDescription extends AbstractContentInformati
/**
* Serial number for inter-operability with different versions.
*/
- private static final long serialVersionUID = 5731044701122380718L;
+ private static final long serialVersionUID = 4637544662644655274L;
/**
* Whether or not the cited feature catalogue complies with ISO 19110.
@@ -90,9 +92,9 @@ public class DefaultFeatureCatalogueDescription extends AbstractContentInformati
private Boolean compliant;
/**
- * Language(s) used within the catalogue
+ * Language(s) and character set(s) used within the catalogue.
*/
- private Collection<Locale> languages;
+ private Map<Locale,Charset> locales;
/**
* Whether or not the feature catalogue is included with the resource.
@@ -129,7 +131,7 @@ public class DefaultFeatureCatalogueDescription extends AbstractContentInformati
if (object != null) {
compliant = object.isCompliant();
includedWithDataset = object.isIncludedWithDataset();
- languages = copyCollection(object.getLanguages(), Locale.class);
+ locales = copyMap(object.getLocalesAndCharsets(), Locale.class);
featureTypes = copyCollection(object.getFeatureTypeInfo(), FeatureTypeInfo.class);
featureCatalogueCitations = copyCollection(object.getFeatureCatalogueCitations(), Citation.class);
}
@@ -182,23 +184,55 @@ public class DefaultFeatureCatalogueDescription extends AbstractContentInformati
}
/**
- * Returns the language(s) used within the catalogue
+ * Returns the language(s) and character set(s) used within the catalogue.
*
- * @return language(s) used within the catalogue.
+ * @return language(s) and character set(s) used within the catalogue.
+ *
+ * @since 1.0
*/
@Override
// @XmlElement at the end of this class.
+ public Map<Locale,Charset> getLocalesAndCharsets() {
+ return locales = nonNullMap(locales, Locale.class);
+ }
+
+ /**
+ * Sets the language(s) and character set(s) used within the catalogue.
+ *
+ * @param newValues the new language(s) and character set(s) used within the catalogue.
+ *
+ * @since 1.0
+ */
+ public void setLocalesAndCharsets(final Map<? extends Locale, ? extends Charset> newValues) {
+ locales = writeMap(newValues, locales, Locale.class);
+ }
+
+ /**
+ * Returns the language(s) used within the catalogue.
+ *
+ * @return language(s) used within the catalogue.
+ *
+ * @deprecated Replaced by {@code getLocalesAndCharsets().keySet()}.
+ */
+ @Override
+ @Deprecated
+ @Dependencies("getLocalesAndCharsets")
+ @XmlElement(name = "language", namespace = LegacyNamespaces.GMD)
public Collection<Locale> getLanguages() {
- return languages = nonNullCollection(languages, Locale.class);
+ return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getLanguages(getLocalesAndCharsets()) : null;
}
/**
- * Sets the language(s) used within the catalogue
+ * Sets the language(s) used within the catalogue.
*
* @param newValues the new languages.
+ *
+ * @deprecated Replaced by putting keys in {@link #getLocalesAndCharsets()} map.
*/
+ @Deprecated
public void setLanguages(final Collection<? extends Locale> newValues) {
- languages = writeCollection(newValues, languages, Locale.class);
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ setLocalesAndCharsets(LocaleAndCharset.setLanguages(getLocalesAndCharsets(), newValues));
}
/**
@@ -339,21 +373,11 @@ public class DefaultFeatureCatalogueDescription extends AbstractContentInformati
}
/**
- * Returns the locale to marshal if the XML document is to be written
+ * Returns the locales and character sets to marshal if the XML document is to be written
* according the new ISO 19115:2014 model.
*/
@XmlElement(name = "locale")
- private Collection<Locale> getLocale() {
- return FilterByVersion.CURRENT_METADATA.accept() ? getLanguages() : null;
- }
-
- /**
- * Returns the locale to marshal if the XML document is to be written
- * according the legacy ISO 19115:2003 model.
- */
- @XmlElement(name = "language", namespace = LegacyNamespaces.GMD)
- @XmlJavaTypeAdapter(LocaleAdapter.class)
- private Collection<Locale> getLanguage() {
- return FilterByVersion.LEGACY_METADATA.accept() ? getLanguages() : null;
+ private Collection<PT_Locale> getLocale() {
+ return FilterByVersion.CURRENT_METADATA.accept() ? PT_Locale.wrap(locales) : null;
}
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/package-info.java
index c3976f7..8292c8f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/package-info.java
@@ -112,10 +112,10 @@
@XmlJavaTypeAdapter(MI_PolarisationOrientationCode.class),
@XmlJavaTypeAdapter(MI_RangeElementDescription.class),
@XmlJavaTypeAdapter(MI_TransferFunctionTypeCode.class),
- @XmlJavaTypeAdapter(PT_Locale.class),
// Java types, primitive types and basic OGC types handling
@XmlJavaTypeAdapter(UnitAdapter.class),
+ @XmlJavaTypeAdapter(LocaleAdapter.class),
@XmlJavaTypeAdapter(InternationalStringAdapter.class),
@XmlJavaTypeAdapter(value=GO_Boolean.class, type=boolean.class)
})
@@ -130,7 +130,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import org.apache.sis.xml.Namespaces;
import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.internal.jaxb.lan.PT_Locale;
+import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
import org.apache.sis.internal.jaxb.gco.*;
import org.apache.sis.internal.jaxb.code.*;
import org.apache.sis.internal.jaxb.metadata.*;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
index 2e6edb2..f7bb052 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
@@ -16,22 +16,24 @@
*/
package org.apache.sis.metadata.iso.identification;
+import java.util.Map;
import java.util.Collection;
+import java.util.Collections;
import java.util.Locale;
import java.nio.charset.Charset;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.opengis.util.InternationalString;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.identification.TopicCategory;
import org.opengis.metadata.identification.DataIdentification;
-import org.apache.sis.internal.metadata.OtherLocales;
-import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
+import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
+import org.apache.sis.internal.jaxb.lan.OtherLocales;
+import org.apache.sis.internal.jaxb.lan.PT_Locale;
import org.apache.sis.internal.xml.LegacyNamespaces;
import org.apache.sis.internal.jaxb.FilterByVersion;
-import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.metadata.Dependencies;
/**
@@ -71,7 +73,7 @@ import org.apache.sis.internal.util.CollectionsExt;
* @module
*/
@XmlType(name = "MD_DataIdentification_Type", propOrder = {
- "language", // Legacy ISO 19115:2003
+ "languages", // Legacy ISO 19115:2003
"characterSets", // Legacy ISO 19115:2003
"defaultLocale", // New in ISO 19115:2014
"otherLocales", // New in ISO 19115:2014
@@ -92,17 +94,12 @@ public class DefaultDataIdentification extends AbstractIdentification implements
/**
* Serial number for compatibility with different versions.
*/
- private static final long serialVersionUID = 6104637930243499851L;
+ private static final long serialVersionUID = 7302901752833238436L;
/**
- * Language(s) used within the dataset.
+ * Language(s) and character set(s) used within the dataset.
*/
- private Collection<Locale> languages;
-
- /**
- * Full name of the character coding standard used for the dataset.
- */
- private Collection<Charset> characterSets;
+ private Map<Locale,Charset> locales;
/**
* Description of the dataset in the producers processing environment, including items
@@ -135,7 +132,9 @@ public class DefaultDataIdentification extends AbstractIdentification implements
final TopicCategory topicCategory)
{
super(citation, abstracts);
- languages = singleton(language, Locale.class);
+ if (language != null) {
+ locales = writeMap(Collections.singletonMap(language, null), null, Locale.class);
+ }
super.setTopicCategories(singleton(topicCategory, TopicCategory.class));
}
@@ -151,10 +150,9 @@ public class DefaultDataIdentification extends AbstractIdentification implements
public DefaultDataIdentification(final DataIdentification object) {
super(object);
if (object != null) {
- languages = copyCollection(object.getLanguages(), Locale.class);
- characterSets = copyCollection(object.getCharacterSets(), Charset.class);
- environmentDescription = object.getEnvironmentDescription();
- supplementalInformation = object.getSupplementalInformation();
+ locales = copyMap(object.getLocalesAndCharsets(), Locale.class);
+ environmentDescription = object.getEnvironmentDescription();
+ supplementalInformation = object.getSupplementalInformation();
}
}
@@ -184,6 +182,34 @@ public class DefaultDataIdentification extends AbstractIdentification implements
}
/**
+ * Returns the language(s) and character set(s) used within the dataset.
+ * The first element in iteration order is the default language.
+ * All other elements, if any, are alternate language(s) used within the resource.
+ *
+ * @return language(s) and character set(s) used within the dataset.
+ *
+ * @since 1.0
+ */
+ @Override
+ // @XmlElement at the end of this class.
+ public Map<Locale,Charset> getLocalesAndCharsets() {
+ return locales = nonNullMap(locales, Locale.class);
+ }
+
+ /**
+ * Sets the language(s) and character set(s) used within the dataset.
+ * The first element in iteration order should be the default language.
+ * All other elements, if any, are alternate language(s) used within the resource.
+ *
+ * @param newValues the new language(s) and character set(s) used within the dataset.
+ *
+ * @since 1.0
+ */
+ public void setLocalesAndCharsets(final Map<? extends Locale, ? extends Charset> newValues) {
+ locales = writeMap(newValues, locales, Locale.class);
+ }
+
+ /**
* Returns the language(s) used within the resource.
* The first element in iteration order shall be the default language.
* All other elements, if any, are alternate language(s) used within the resource.
@@ -193,41 +219,55 @@ public class DefaultDataIdentification extends AbstractIdentification implements
*
* @return language(s) used.
*
- * @see Locale#getISO3Language()
+ * @deprecated Replaced by {@code getLocalesAndCharsets().keySet()}.
*/
@Override
- // @XmlElement at the end of this class.
+ @Deprecated
+ @Dependencies("getLocalesAndCharsets")
+ @XmlElement(name = "language", namespace = LegacyNamespaces.GMD)
public Collection<Locale> getLanguages() {
- return languages = nonNullCollection(languages, Locale.class);
+ return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getLanguages(getLocalesAndCharsets()) : null;
}
/**
- * Sets the language(s) used within the dataset.
+ * Sets the language(s) used within the resource.
*
* @param newValues the new languages.
+ *
+ * @deprecated Replaced by putting keys in {@link #getLocalesAndCharsets()} map.
*/
- public void setLanguages(final Collection<? extends Locale> newValues) {
- languages = writeCollection(newValues, languages, Locale.class);
+ @Deprecated
+ public void setLanguages(final Collection<? extends Locale> newValues) {
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ setLocalesAndCharsets(LocaleAndCharset.setLanguages(getLocalesAndCharsets(), newValues));
}
/**
* Returns the character coding standard used for the dataset.
*
* @return character coding standard(s) used.
+ *
+ * @deprecated Replaced by {@code getLocalesAndCharsets().values()}.
*/
@Override
+ @Deprecated
+ @Dependencies("getLocalesAndCharsets")
@XmlElement(name = "characterSet", namespace = LegacyNamespaces.GMD)
public Collection<Charset> getCharacterSets() {
- return characterSets = nonNullCollection(characterSets, Charset.class);
+ return FilterByVersion.LEGACY_METADATA.accept() ? LocaleAndCharset.getCharacterSets(getLocalesAndCharsets()) : null;
}
/**
* Sets the character coding standard used for the dataset.
*
* @param newValues the new character sets.
+ *
+ * @deprecated Replaced by putting values in {@link #getLocalesAndCharsets()} map.
*/
+ @Deprecated
public void setCharacterSets(final Collection<? extends Charset> newValues) {
- characterSets = writeCollection(newValues, characterSets, Charset.class);
+ // TODO: delete after SIS 1.0 release (method not needed by JAXB).
+ setLocalesAndCharsets(LocaleAndCharset.setCharacterSets(getLocalesAndCharsets(), newValues));
}
/**
@@ -291,33 +331,23 @@ public class DefaultDataIdentification extends AbstractIdentification implements
* Gets the default locale for this record (used in ISO 19115-3 format).
*/
@XmlElement(name = "defaultLocale")
- private Locale getDefaultLocale() {
- return FilterByVersion.CURRENT_METADATA.accept() ? CollectionsExt.first(getLanguages()) : null;
+ private PT_Locale getDefaultLocale() {
+ return FilterByVersion.CURRENT_METADATA.accept() ? PT_Locale.first(getLocalesAndCharsets()) : null;
}
/**
* Sets the default locale for this record (used in ISO 19115-3 format).
*/
@SuppressWarnings("unused")
- private void setDefaultLocale(final Locale newValue) {
- setLanguages(OtherLocales.setFirst(languages, newValue));
+ private void setDefaultLocale(final PT_Locale newValue) {
+ setLocalesAndCharsets(OtherLocales.setFirst(locales, newValue));
}
/**
* Gets the other locales for this record (used in ISO 19115-3 format).
*/
@XmlElement(name = "otherLocale")
- private Collection<Locale> getOtherLocales() {
- return FilterByVersion.CURRENT_METADATA.accept() ? OtherLocales.filter(getLanguages()) : null;
- }
-
- /**
- * Returns the locale to marshal if the XML document is to be written
- * according the legacy ISO 19115:2003 model.
- */
- @XmlElement(name = "language", namespace = LegacyNamespaces.GMD)
- @XmlJavaTypeAdapter(LocaleAdapter.class)
- private Collection<Locale> getLanguage() {
- return FilterByVersion.LEGACY_METADATA.accept() ? getLanguages() : null;
+ private Collection<PT_Locale> getOtherLocales() {
+ return FilterByVersion.CURRENT_METADATA.accept() ? OtherLocales.filter(getLocalesAndCharsets()) : null;
}
}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
index 2497232..dfe8ba0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
@@ -138,7 +138,6 @@
@XmlJavaTypeAdapter(MD_StandardOrderProcess.class),
@XmlJavaTypeAdapter(MD_TopicCategoryCode.class),
@XmlJavaTypeAdapter(MD_Usage.class),
- @XmlJavaTypeAdapter(PT_Locale.class),
@XmlJavaTypeAdapter(SV_CoupledResource.class),
@XmlJavaTypeAdapter(SV_CouplingType.class),
@XmlJavaTypeAdapter(SV_OperationMetadata.class),
@@ -149,7 +148,8 @@
// Java types, primitive types and basic OGC types handling
@XmlJavaTypeAdapter(URIAdapter.class),
@XmlJavaTypeAdapter(StringAdapter.class),
- @XmlJavaTypeAdapter(InternationalStringAdapter.class)
+ @XmlJavaTypeAdapter(InternationalStringAdapter.class),
+ @XmlJavaTypeAdapter(LocaleAdapter.class)
})
package org.apache.sis.metadata.iso.identification;
@@ -162,7 +162,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import org.apache.sis.xml.Namespaces;
import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.internal.jaxb.lan.PT_Locale;
+import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
import org.apache.sis.internal.jaxb.gco.*;
import org.apache.sis.internal.jaxb.gts.*;
import org.apache.sis.internal.jaxb.code.*;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
index 016fafb..70c9f1e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
@@ -123,13 +123,13 @@
@XmlJavaTypeAdapter(MD_ScopeCode.class),
@XmlJavaTypeAdapter(MD_SpatialRepresentation.class),
@XmlJavaTypeAdapter(MI_AcquisitionInformation.class),
- @XmlJavaTypeAdapter(PT_Locale.class),
@XmlJavaTypeAdapter(RS_ReferenceSystem.class),
@XmlJavaTypeAdapter(LegacyCharacterSet.class),
// Java types, primitive types and basic OGC types handling
@XmlJavaTypeAdapter(StringAdapter.class),
- @XmlJavaTypeAdapter(InternationalStringAdapter.class)
+ @XmlJavaTypeAdapter(InternationalStringAdapter.class),
+ @XmlJavaTypeAdapter(LocaleAdapter.class)
})
package org.apache.sis.metadata.iso;
@@ -142,7 +142,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import org.apache.sis.xml.Namespaces;
import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.internal.jaxb.lan.PT_Locale;
+import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
import org.apache.sis.internal.jaxb.gco.*;
import org.apache.sis.internal.jaxb.code.*;
import org.apache.sis.internal.jaxb.metadata.*;
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
index fac59d6..d55cd18 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
@@ -167,7 +167,7 @@ public final strictfp class LanguageCodeTest extends TestCase {
final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
final String xml = getMetadataXML(LANGUAGE_CODE);
final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
- assertEquals(Locale.JAPANESE, getSingleton(metadata.getLanguages()));
+ assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
}
/**
@@ -190,7 +190,7 @@ public final strictfp class LanguageCodeTest extends TestCase {
final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
final String xml = getMetadataXML(LANGUAGE_CODE_WITHOUT_ATTRIBUTE);
final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
- assertEquals(Locale.JAPANESE, getSingleton(metadata.getLanguages()));
+ assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
pool.recycle(unmarshaller);
}
@@ -232,7 +232,7 @@ public final strictfp class LanguageCodeTest extends TestCase {
final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
final String xml = getMetadataXML(CHARACTER_STRING);
final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
- assertEquals(Locale.JAPANESE, getSingleton(metadata.getLanguages()));
+ assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
pool.recycle(unmarshaller);
}
}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/OtherLocalesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/OtherLocalesTest.java
new file mode 100644
index 0000000..4200b9a
--- /dev/null
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/OtherLocalesTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.jaxb.lan;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.nio.charset.Charset;
+import org.junit.Test;
+import org.apache.sis.test.TestCase;
+
+import static java.util.Locale.*;
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link OtherLocales} class.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 0.5
+ * @module
+ */
+public final strictfp class OtherLocalesTest extends TestCase {
+ /**
+ * Returns the locales in an array. Character sets are ignored.
+ */
+ private static Locale[] toArray(final Set<PT_Locale> locales) {
+ final Locale[] languages = new Locale[locales.size()];
+ int i = 0;
+ for (final PT_Locale p : locales) {
+ languages[i++] = p.getLocale();
+ }
+ assertEquals(i, languages.length);
+ return languages;
+ }
+
+ /**
+ * Tests {@link OtherLocales#filter(Map)}.
+ */
+ @Test
+ public void testFilter() {
+ final Map<Locale,Charset> languages = new LinkedHashMap<>();
+ assertNull(OtherLocales.filter(languages));
+ /*
+ * The first locale in the 'languages' list is taken as the default locale.
+ * It shall not appears in the 'other locales' collection.
+ */
+ assertNull(languages.put(ENGLISH, null));
+ final Set<PT_Locale> otherLocales = OtherLocales.filter(languages);
+ assertEquals("size", 0, otherLocales.size());
+ /*
+ * All elements after the first one in the 'language' list are "other locales".
+ */
+ assertNull(languages.put(FRENCH, null));
+ assertEquals("size", 1, otherLocales.size());
+ assertArrayEquals(new Locale[] {FRENCH}, toArray(otherLocales));
+ /*
+ * Adding to the "other locales" collection shall delegate to the 'languages' list.
+ */
+ assertTrue(otherLocales.add(new PT_Locale(JAPANESE)));
+ assertEquals("size", 2, otherLocales.size());
+ assertArrayEquals(new Locale[] {FRENCH, JAPANESE}, toArray(otherLocales));
+ assertArrayEquals(new Locale[] {ENGLISH, FRENCH, JAPANESE}, languages.keySet().toArray());
+ /*
+ * Clearing the "other locales" list shall not remove the default locale.
+ */
+ otherLocales.clear();
+ assertEquals("size", 0, otherLocales.size());
+ assertArrayEquals(new Locale[] {ENGLISH}, languages.keySet().toArray());
+ /*
+ * The first 'add' operation on an empty 'languages' list generates a default locale.
+ * Note that we can not test the first element of 'languages', since it is system-dependent.
+ */
+ languages.clear();
+ assertTrue(otherLocales.add(new PT_Locale(FRENCH)));
+ assertArrayEquals(new Locale[] {FRENCH}, toArray(otherLocales));
+ assertEquals("size", 2, languages.size());
+ }
+
+ /**
+ * Tests {@link OtherLocales#setFirst(Map, PT_Locale)}.
+ */
+ @Test
+ public void testSetFirst() {
+ Map<Locale,Charset> merged = OtherLocales.setFirst(null, null);
+ assertNull(merged);
+
+ merged = OtherLocales.setFirst(null, new PT_Locale(ENGLISH));
+ assertArrayEquals(new Locale[] {ENGLISH}, merged.keySet().toArray());
+
+ merged.put(FRENCH, null);
+ merged.put(JAPANESE, null);
+ merged = OtherLocales.setFirst(merged, new PT_Locale(GERMAN));
+ assertArrayEquals(new Locale[] {GERMAN, FRENCH, JAPANESE}, merged.keySet().toArray());
+ }
+}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/PT_LocaleTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/PT_LocaleTest.java
index f0e3a16..8b6aa6c 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/PT_LocaleTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/PT_LocaleTest.java
@@ -16,8 +16,9 @@
*/
package org.apache.sis.internal.jaxb.lan;
-import java.util.Arrays;
+import java.util.Map;
import java.util.Locale;
+import java.nio.charset.Charset;
import javax.xml.bind.JAXBException;
import org.apache.sis.util.Version;
import org.apache.sis.metadata.iso.DefaultMetadata;
@@ -64,7 +65,10 @@ public final strictfp class PT_LocaleTest extends TestUsingFile {
throws JAXBException
{
final DefaultMetadata metadata = new DefaultMetadata();
- metadata.setLanguages(Arrays.asList(locales));
+ final Map<Locale,Charset> lc = metadata.getLocalesAndCharsets();
+ for (final Locale locale : locales) {
+ lc.put(locale, null);
+ }
assertMarshalEqualsFile(filename, metadata, version, STRICT, ignoredNodes,
new String[] {"xmlns:*", "xsi:*"});
}
@@ -99,7 +103,7 @@ public final strictfp class PT_LocaleTest extends TestUsingFile {
@Test
public void testUnmarshalling() throws JAXBException {
final DefaultMetadata metadata = unmarshalFile(DefaultMetadata.class, XML2016+FILENAME);
- assertArrayEquals(locales, metadata.getLanguages().toArray());
+ assertArrayEquals(locales, metadata.getLocalesAndCharsets().keySet().toArray());
}
/**
@@ -110,6 +114,6 @@ public final strictfp class PT_LocaleTest extends TestUsingFile {
@Test
public void testUnmarshallingLegacy() throws JAXBException {
final DefaultMetadata metadata = unmarshalFile(DefaultMetadata.class, XML2007+FILENAME);
- assertArrayEquals(locales, metadata.getLanguages().toArray());
+ assertArrayEquals(locales, metadata.getLocalesAndCharsets().keySet().toArray());
}
}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
index 6d38226..e3ef4bd 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
@@ -43,7 +43,7 @@ import static org.apache.sis.test.Assert.*;
* Tests the {@link Merger} class.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.8
* @module
*/
@@ -67,8 +67,7 @@ public final strictfp class MergerTest extends TestCase {
image.setCloudCoverPercentage(0.8);
metadata.getContentInfo().add(image);
- metadata.getLanguages().add(Locale.JAPANESE);
- metadata.getCharacterSets().add(StandardCharsets.UTF_16);
+ metadata.setLocalesAndCharsets(Collections.singletonMap(Locale.JAPANESE, StandardCharsets.UTF_16));
return metadata;
}
@@ -87,7 +86,7 @@ public final strictfp class MergerTest extends TestCase {
features.setIncludedWithDataset(Boolean.TRUE);
metadata.getContentInfo().add(features);
- metadata.getLanguages().add(Locale.FRENCH);
+ metadata.setLocalesAndCharsets(Collections.singletonMap(Locale.FRENCH, StandardCharsets.UTF_8));
return metadata;
}
@@ -102,8 +101,10 @@ public final strictfp class MergerTest extends TestCase {
final Merger merger = new Merger(null);
merger.copy(source, target);
- assertSetEquals(Arrays.asList(Locale.JAPANESE, Locale.FRENCH), target.getLanguages());
- assertSetEquals(Collections.singleton(StandardCharsets.UTF_16), target.getCharacterSets());
+ assertSetEquals(Arrays.asList(Locale.JAPANESE, Locale.FRENCH),
+ target.getLocalesAndCharsets().keySet());
+ assertSetEquals(Arrays.asList(StandardCharsets.UTF_16, StandardCharsets.UTF_8),
+ target.getLocalesAndCharsets().values());
final Iterator<ContentInformation> it = target.getContentInfo().iterator();
final ImageDescription image = (ImageDescription) it.next();
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MetadataUtilitiesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MetadataUtilitiesTest.java
index a1271c1..593cf7b 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MetadataUtilitiesTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MetadataUtilitiesTest.java
@@ -17,17 +17,22 @@
package org.apache.sis.internal.metadata;
import java.util.Date;
-import org.junit.Test;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Locale;
import org.apache.sis.test.TestCase;
+import org.junit.Test;
import static org.junit.Assert.*;
+import static java.util.Locale.*;
/**
* Tests the {@link MetadataUtilities} class.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.4
+ * @version 1.0
* @since 0.3
* @module
*/
@@ -49,4 +54,27 @@ public final strictfp class MetadataUtilitiesTest extends TestCase {
assertEquals(new Date(1000), MetadataUtilities.toDate(1000));
assertNull(MetadataUtilities.toDate(Long.MIN_VALUE));
}
+
+ /**
+ * Tests the {@link MetadataUtilities#setFirst(Collection, Object)} method.
+ */
+ @Test
+ public void testSetFirst() {
+ Collection<Locale> locales = MetadataUtilities.setFirst(null, null);
+ assertTrue(locales.isEmpty());
+
+ locales = MetadataUtilities.setFirst(null, GERMAN);
+ assertArrayEquals(new Locale[] {GERMAN}, locales.toArray());
+
+ locales = Arrays.asList(ENGLISH, JAPANESE, FRENCH);
+ assertSame("Shall set value in-place.", locales, MetadataUtilities.setFirst(locales, GERMAN));
+ assertArrayEquals(new Locale[] {GERMAN, JAPANESE, FRENCH}, locales.toArray());
+
+ locales = new LinkedHashSet<>(Arrays.asList(ENGLISH, JAPANESE, FRENCH));
+ locales = MetadataUtilities.setFirst(locales, ITALIAN);
+ assertArrayEquals(new Locale[] {ITALIAN, JAPANESE, FRENCH}, locales.toArray());
+
+ locales = MetadataUtilities.setFirst(locales, null);
+ assertArrayEquals(new Locale[] {JAPANESE, FRENCH}, locales.toArray());
+ }
}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/OtherLocalesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/OtherLocalesTest.java
deleted file mode 100644
index 0921aa8..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/OtherLocalesTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.internal.metadata;
-
-import java.util.Locale;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import org.junit.Test;
-import org.apache.sis.test.TestCase;
-
-import static java.util.Locale.*;
-import static org.junit.Assert.*;
-
-
-/**
- * Tests the {@link OtherLocales} class.
- *
- * @author Martin Desruisseaux (Geomatys)
- * @version 0.5
- * @since 0.5
- * @module
- */
-public final strictfp class OtherLocalesTest extends TestCase {
- /**
- * Tests {@link OtherLocales#filter(Collection)}.
- */
- @Test
- public void testFilter() {
- final Collection<Locale> languages = new LinkedHashSet<>();
- final Collection<Locale> otherLocales = OtherLocales.filter(languages);
- assertEquals("size", 0, otherLocales.size());
- /*
- * The first locale in the 'languages' list is taken as the default locale.
- * It shall not appears in the 'other locales' collection.
- */
- assertTrue(languages.add(ENGLISH));
- assertEquals("size", 0, otherLocales.size());
- /*
- * All elements after the first one in the 'language' list are "other locales".
- */
- assertTrue(languages.add(FRENCH));
- assertEquals("size", 1, otherLocales.size());
- assertArrayEquals(new Locale[] {FRENCH}, otherLocales.toArray());
- /*
- * Adding to the "other locales" collection shall delegate to the 'languages' list.
- */
- assertTrue(otherLocales.add(JAPANESE));
- assertEquals("size", 2, otherLocales.size());
- assertArrayEquals(new Locale[] {FRENCH, JAPANESE}, otherLocales.toArray());
- assertArrayEquals(new Locale[] {ENGLISH, FRENCH, JAPANESE}, languages.toArray());
- /*
- * Clearing the "other locales" list shall not remove the default locale.
- */
- otherLocales.clear();
- assertEquals("size", 0, otherLocales.size());
- assertArrayEquals(new Locale[] {ENGLISH}, languages.toArray());
- /*
- * The first 'add' operation on an empty 'languages' list generates a default locale.
- * Note that we can not test the first element of 'languages', since it is system-dependent.
- */
- languages.clear();
- assertTrue(otherLocales.add(FRENCH));
- assertArrayEquals(new Locale[] {FRENCH}, otherLocales.toArray());
- assertEquals("size", 2, languages.size());
- }
-
- /**
- * Tests {@link OtherLocales#merge(Locale, Collection)}.
- */
- @Test
- public void testMerge() {
- Collection<Locale> merged = OtherLocales.merge(null, null);
- assertTrue(merged.isEmpty());
-
- merged = OtherLocales.merge(ENGLISH, null);
- assertArrayEquals(new Locale[] {ENGLISH}, merged.toArray());
-
- merged = OtherLocales.merge(ENGLISH, Arrays.asList(FRENCH, JAPANESE));
- assertArrayEquals(new Locale[] {ENGLISH, FRENCH, JAPANESE}, merged.toArray());
- /*
- * The tricky case: a default locale will be generated. That locale is system-dependent.
- */
- merged = OtherLocales.merge(null, Arrays.asList(FRENCH, JAPANESE));
- final Iterator<Locale> it = merged.iterator();
- assertNotNull(it.next()); // System-dependent value.
- assertEquals(FRENCH, it.next());
- assertEquals(JAPANESE, it.next());
- assertFalse(it.hasNext());
- }
-
- /**
- * Tests the {@link OtherLocales#setFirst(Collection, Object)} method.
- */
- @Test
- public void testSetFirst() {
- Collection<Locale> locales = OtherLocales.setFirst(null, null);
- assertTrue(locales.isEmpty());
-
- locales = OtherLocales.setFirst(null, GERMAN);
- assertArrayEquals(new Locale[] {GERMAN}, locales.toArray());
-
- locales = Arrays.asList(ENGLISH, JAPANESE, FRENCH);
- assertSame("Shall set value in-place.", locales, OtherLocales.setFirst(locales, GERMAN));
- assertArrayEquals(new Locale[] {GERMAN, JAPANESE, FRENCH}, locales.toArray());
-
- locales = new LinkedHashSet<>(Arrays.asList(ENGLISH, JAPANESE, FRENCH));
- locales = OtherLocales.setFirst(locales, ITALIAN);
- assertArrayEquals(new Locale[] {ITALIAN, JAPANESE, FRENCH}, locales.toArray());
-
- locales = OtherLocales.setFirst(locales, null);
- assertArrayEquals(new Locale[] {JAPANESE, FRENCH}, locales.toArray());
- }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
index 140c7e2..ec01d42 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.metadata;
+import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.Arrays;
@@ -23,7 +24,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import java.util.Date;
-import java.nio.charset.Charset;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.extent.Extent;
@@ -148,6 +148,8 @@ public final strictfp class PropertyAccessorTest extends TestCase {
propertyType = Set.class;
}
}
+ } else if (propertyType == Map.class) {
+ elementType = Map.Entry.class;
}
assertEquals(propertyName, propertyType, accessor.type(index, TypeValuePolicy.PROPERTY_TYPE));
assertEquals(umlIdentifier, elementType, accessor.type(index, TypeValuePolicy.ELEMENT_TYPE));
@@ -221,10 +223,9 @@ public final strictfp class PropertyAccessorTest extends TestCase {
Identification.class, "getResourceSpecificUsages", "resourceSpecificUsages", "resourceSpecificUsage", "Resource specific usages", Usage[].class,
Identification.class, "getResourceConstraints", "resourceConstraints", "resourceConstraints", "Resource constraints", Constraints[].class,
Identification.class, "getAssociatedResources", "associatedResources", "associatedResource", "Associated resources", AssociatedResource[].class,
- DataIdentification.class, "getLanguages", "languages", "language", "Languages", Locale[].class,
- DataIdentification.class, "getCharacterSets", "characterSets", "characterSet", "Character sets", Charset[].class,
DataIdentification.class, "getEnvironmentDescription", "environmentDescription", "environmentDescription", "Environment description", InternationalString.class,
- DataIdentification.class, "getSupplementalInformation", "supplementalInformation", "supplementalInformation", "Supplemental information", InternationalString.class);
+ DataIdentification.class, "getSupplementalInformation", "supplementalInformation", "supplementalInformation", "Supplemental information", InternationalString.class,
+ DataIdentification.class, "getLocalesAndCharsets", "localesAndCharsets", "defaultLocale+otherLocale", "Locales and charsets", Map.class);
}
/**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
index 4c3b9d3..4f73282 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
@@ -242,25 +242,29 @@ public abstract strictfp class PropertyConsistencyCheck extends AnnotationConsis
final Class<?> elementType = Numbers.primitiveToWrapper(accessor.type(i, TypeValuePolicy.ELEMENT_TYPE));
assertNotNull(testingMethod, propertyType);
assertNotNull(testingMethod, elementType);
+ final boolean isMap = Map.class.isAssignableFrom(propertyType);
final boolean isCollection = Collection.class.isAssignableFrom(propertyType);
assertFalse("Element type can not be Collection.", Collection.class.isAssignableFrom(elementType));
assertEquals("Property and element types shall be the same if and only if not a collection.",
- !isCollection, propertyType == elementType);
+ !(isMap | isCollection), propertyType == elementType);
/*
* Try to get a value.
*/
Object value = accessor.get(i, instance);
if (value == null) {
- assertFalse("Null values are not allowed to be collections.", isCollection);
+ assertFalse("Null values are not allowed to be collections.", isMap | isCollection);
} else {
assertTrue("Wrong property type.", propertyType.isInstance(value));
if (value instanceof CheckedContainer<?>) {
assertTrue("Wrong element type in collection.",
elementType.isAssignableFrom(((CheckedContainer<?>) value).getElementType()));
}
- if (isCollection) {
+ if (isMap) {
+ assertTrue("Collections shall be initially empty.", ((Map<?,?>) value).isEmpty());
+ value = CollectionsExt.modifiableCopy((Map<?,?>) value); // Protect from changes.
+ } else if (isCollection) {
assertTrue("Collections shall be initially empty.", ((Collection<?>) value).isEmpty());
- value = CollectionsExt.modifiableCopy((Collection<?>) value); // Protect from changes.
+ value = CollectionsExt.modifiableCopy((Collection<?>) value); // Protect from changes.
}
}
/*
@@ -275,13 +279,16 @@ public abstract strictfp class PropertyConsistencyCheck extends AnnotationConsis
// Dates requires sis-temporal module, which is not available for sis-metadata.
continue;
}
+ if (isMap) {
+ continue;
+ }
final Object newValue = sampleValueFor(property, elementType);
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) {
if (newValue == null) {
- assertTrue("We didn't generated a random value for this type, consequently the "
+ assertTrue("We did not generated a random value for this type, consequently the "
+ "collection should still empty.", ((Collection<?>) value).isEmpty());
value = null;
} else {
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
index 688339b..5dc44ac 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
@@ -136,6 +136,7 @@ public final strictfp class CustomMetadataTest extends TestCase {
@Deprecated @Override public Collection<AggregateInformation> getAggregationInfo() {return null;}
@Override public Collection<AssociatedResource> getAssociatedResources() {return null;}
@Override public Collection<Citation> getAdditionalDocumentations() {return null;}
+ @Override public Map<Locale,Charset> getLocalesAndCharsets() {return null;}
};
final DefaultMetadata data = new DefaultMetadata();
data.setIdentificationInfo(singleton(identification));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
index ea18a01..8fd3d7f 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
@@ -53,7 +53,7 @@ import static org.apache.sis.test.TestUtilities.getSingleton;
* @since 0.3
* @module
*/
-@DependsOn(org.apache.sis.internal.metadata.OtherLocalesTest.class)
+@DependsOn(org.apache.sis.internal.jaxb.lan.OtherLocalesTest.class)
public final strictfp class DefaultMetadataTest extends TestCase {
/**
* A flag for tracing workarounds for allowing some tests to pass despite regression.
@@ -102,9 +102,9 @@ public final strictfp class DefaultMetadataTest extends TestCase {
}
/**
- * Tests {@link DefaultMetadata#getLanguage()}, {@link DefaultMetadata#setLanguage(Locale)},
- * {@link DefaultMetadata#getLocales()} and {@link DefaultMetadata#setLocales(Collection)}
- * legacy methods. Those methods should delegate to newer methods.
+ * Tests {@link DefaultMetadata#getLanguage()}, {@link DefaultMetadata#setLanguage(Locale)}
+ * and {@link DefaultMetadata#getLocales()} legacy methods.
+ * Those methods should delegate to newer methods.
*
* @since 0.5
*/
@@ -123,7 +123,7 @@ public final strictfp class DefaultMetadataTest extends TestCase {
* Add other languages. They should appear as additional entries after the first one.
* The "language" property shall be unmodified by changes in the "other locales" one.
*/
- metadata.setLocales(Arrays.asList(Locale.FRENCH, Locale.ENGLISH));
+ metadata.getLocales().addAll(Arrays.asList(Locale.FRENCH, Locale.ENGLISH));
assertLanguagesEquals(metadata, Locale.JAPANESE, Locale.FRENCH, Locale.ENGLISH);
/*
* Ensure that the "locales" list is modifiable, since JAXB writes directly in it.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java
index 2a2d621..ed5ab1f 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentificationTest.java
@@ -34,7 +34,6 @@ import org.apache.sis.test.TestCase;
import org.apache.sis.test.TestUtilities;
import org.junit.Test;
-import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static org.apache.sis.test.MetadataAssert.*;
@@ -56,11 +55,6 @@ import static org.apache.sis.test.MetadataAssert.*;
})
public final strictfp class DefaultDataIdentificationTest extends TestCase {
/**
- * The locales used in this test.
- */
- private static final Locale[] LOCALES = {Locale.US, Locale.ENGLISH};
-
- /**
* Creates the instance to test.
*/
private static DefaultDataIdentification create() {
@@ -90,7 +84,7 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase {
/*
* Identification info
* ├─(above objects)
- * ├─Abstract………………………………………………………………………………… NCEP SST Global 5.0 x 2.5 degree model data
+ * ├─Abstract………………………………………………………………………………… Global 5.0 x 2.5 degree model data
* ├─Descriptive keywords
* │ ├─Keyword………………………………………………………………………… EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature
* │ ├─Type………………………………………………………………………………… Theme
@@ -99,9 +93,10 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase {
* ├─Resource constraints
* │ └─Use limitation……………………………………………………… Freely available
* ├─Spatial representation type……………………………… Grid
- * ├─Language (1 of 2)………………………………………………………… en_US
- * ├─Language (2 of 2)………………………………………………………… en
- * ├─Character set…………………………………………………………………… US-ASCII
+ * ├─Locale (1 of 2)……………………………………………………………… en_US
+ * │ └─Character set………………………………………………………… US-ASCII
+ * ├─Locale (2 of 2)……………………………………………………………… fr
+ * │ └─Character set………………………………………………………… ISO-8859-1
* └─Extent
* └─Geographic element
* ├─West bound longitude…………………………… 180°W
@@ -111,13 +106,13 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase {
* └─Extent type code……………………………………… true
*/
final DefaultDataIdentification info = new DefaultDataIdentification(citation,
- "NCEP SST Global 5.0 x 2.5 degree model data", null, null);
+ "Global 5.0 x 2.5 degree model data", null, null);
info.setSpatialRepresentationTypes(singleton(SpatialRepresentationType.GRID));
info.setDescriptiveKeywords(singleton(keywords));
info.setResourceConstraints(singleton(new DefaultConstraints("Freely available")));
+ info.getLocalesAndCharsets().put(Locale.US, StandardCharsets.US_ASCII);
+ info.getLocalesAndCharsets().put(Locale.FRENCH, StandardCharsets.ISO_8859_1);
info.setExtents(singleton(Extents.WORLD));
- info.setLanguages(asList(LOCALES));
- info.setCharacterSets(singleton(StandardCharsets.US_ASCII));
return info;
}
@@ -139,7 +134,7 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase {
" │ ├─Date………………………………………………………… 2005-09-22 00:00:00\n" +
" │ │ └─Date type………………………………… Creation\n" +
" │ └─Identifier………………………………………… SST_Global.nc\n" +
- " ├─Abstract………………………………………………………… NCEP SST Global 5.0 x 2.5 degree model data\n" +
+ " ├─Abstract………………………………………………………… Global 5.0 x 2.5 degree model data\n" +
" ├─Spatial representation type……… Grid\n" +
" ├─Extent……………………………………………………………… World\n" +
" │ └─Geographic element\n" +
@@ -154,9 +149,10 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase {
" │ └─Thesaurus name……………………………… GCMD Science Keywords\n" +
" ├─Resource constraints\n" +
" │ └─Use limitation……………………………… Freely available\n" +
- " ├─Language (1 of 2)………………………………… en_US\n" +
- " ├─Language (2 of 2)………………………………… en\n" +
- " └─Character set…………………………………………… US-ASCII\n",
+ " ├─Locale (1 of 2)……………………………………… en_US\n" +
+ " │ └─Character set………………………………… US-ASCII\n" +
+ " └─Locale (2 of 2)……………………………………… fr\n" +
+ " └─Character set………………………………… ISO-8859-1\n",
TestUtilities.formatMetadata(create().asTreeTable()));
}
@@ -168,11 +164,13 @@ public final strictfp class DefaultDataIdentificationTest extends TestCase {
public void testValueMap() {
final DefaultDataIdentification info = create();
final Map<String,Object> map = info.asMap();
- assertEquals("abstract", "NCEP SST Global 5.0 x 2.5 degree model data", map.get("abstract").toString());
+ assertEquals("abstract", "Global 5.0 x 2.5 degree model data", map.get("abstract").toString());
assertTitleEquals("title", "Sea Surface Temperature Analysis Model", (Citation) map.get("citation"));
assertEquals("spatialRepresentationType", singleton(SpatialRepresentationType.GRID), map.get("spatialRepresentationType"));
- assertArrayEquals("language", LOCALES, ((Collection<?>) map.get("language")).toArray());
- assertArrayEquals("languages", LOCALES, ((Collection<?>) map.get("languages")).toArray());
- assertArrayEquals("getLanguages", LOCALES, ((Collection<?>) map.get("getLanguages")).toArray());
+
+ final Locale[] locales = {Locale.US, Locale.FRENCH};
+ assertArrayEquals("language", locales, ((Collection<?>) map.get("language")).toArray());
+ assertArrayEquals("languages", locales, ((Collection<?>) map.get("languages")).toArray());
+ assertArrayEquals("getLanguages", locales, ((Collection<?>) map.get("getLanguages")).toArray());
}
}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
index 5479bc2..634f1e2 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
@@ -16,9 +16,11 @@
*/
package org.apache.sis.test.mock;
-import java.util.Locale;
+import java.util.Map;
import java.util.Collection;
import java.util.Collections;
+import java.util.Locale;
+import java.nio.charset.Charset;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -67,11 +69,22 @@ public final strictfp class MetadataMock extends SimpleMetadata {
}
/**
+ * Returns {@link #language} in a singleton map or an empty map.
+ *
+ * @return {@link #language}
+ */
+ @Override
+ public Map<Locale,Charset> getLocalesAndCharsets() {
+ return (language != null) ? Collections.singletonMap(language, null) : Collections.emptyMap();
+ }
+
+ /**
* Returns {@link #language} in a singleton set or an empty set.
*
* @return {@link #language}
*/
@Override
+ @Deprecated
public Collection<Locale> getLanguages() {
return (language != null) ? Collections.singleton(language) : Collections.emptySet();
}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java b/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
index 90cde37..cc15b61 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
@@ -35,7 +35,6 @@ import org.junit.BeforeClass;
org.apache.sis.internal.metadata.NameMeaningTest.class,
org.apache.sis.internal.metadata.MetadataUtilitiesTest.class,
org.apache.sis.internal.metadata.VerticalDatumTypesTest.class,
- org.apache.sis.internal.metadata.OtherLocalesTest.class,
// Classes using Java reflection.
org.apache.sis.metadata.PropertyInformationTest.class,
@@ -75,6 +74,7 @@ import org.junit.BeforeClass;
org.apache.sis.internal.jaxb.gco.PropertyTypeTest.class,
org.apache.sis.internal.jaxb.gco.MultiplicityTest.class,
org.apache.sis.internal.jaxb.lan.PT_LocaleTest.class,
+ org.apache.sis.internal.jaxb.lan.OtherLocalesTest.class,
org.apache.sis.internal.jaxb.lan.LanguageCodeTest.class,
org.apache.sis.internal.jaxb.lan.FreeTextMarshallingTest.class,
org.apache.sis.internal.jaxb.cat.EnumAdapterTest.class,
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
index 06f4e56..33919ec 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
@@ -209,6 +209,17 @@ public abstract strictfp class AnnotationConsistencyCheck extends TestCase {
}
/**
+ * Returns the identifier specified by the given UML, taking only the first one if it compound.
+ * For example if the identifier is {@code "defaultLocale+otherLocale"}, then this method returns
+ * only {@code "defaultLocale"}.
+ */
+ private static String firstIdentifier(final UML uml) {
+ String identifier = uml.identifier();
+ final int s = identifier.indexOf('+');
+ return (s >= 0) ? identifier.substring(0, s) : identifier;
+ }
+
+ /**
* Returns the beginning of expected namespace for an element defined by the given UML.
* For example the namespace of most types defined by {@link Specification#ISO_19115}
* starts with is {@code "http://standards.iso.org/iso/19115/-3/"}.
@@ -393,7 +404,7 @@ public abstract strictfp class AnnotationConsistencyCheck extends TestCase {
* @see #testMethodAnnotations()
*/
protected String getExpectedXmlElementName(final Class<?> enclosing, final UML uml) {
- String name = uml.identifier();
+ String name = firstIdentifier(uml);
switch (name) {
case "stepDateTime": {
if (org.opengis.metadata.lineage.ProcessStep.class.isAssignableFrom(enclosing)) {
@@ -768,7 +779,7 @@ public abstract strictfp class AnnotationConsistencyCheck extends TestCase {
* for example verifying whether we are marshalling ISO 19139:2007 or ISO 19115-3:2016.
*/
boolean wasPublic = false;
- final String identifier = uml.identifier();
+ final String identifier = firstIdentifier(uml);
for (final Method pm : impl.getDeclaredMethods()) {
final XmlElement e = pm.getAnnotation(XmlElement.class);
if (e != null && identifier.equals(e.name())) {
@@ -784,7 +795,7 @@ public abstract strictfp class AnnotationConsistencyCheck extends TestCase {
}
}
/*
- * If a few case the annotation is not on a getter method, but directly on the field.
+ * In a few case the annotation is not on a getter method, but directly on the field.
* The main case is the "pass" field in DefaultConformanceResult.
*/
if (element == null) try {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
index 1384074..0e7c530 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
@@ -152,8 +152,7 @@ public strictfp class MetadataTest extends TestCase {
private DefaultMetadata createHardCoded() {
final DefaultMetadata metadata = new DefaultMetadata();
metadata.setMetadataIdentifier(new DefaultIdentifier("Apache SIS/Metadata test"));
- metadata.setLanguages(singleton(Locale.ENGLISH));
- metadata.setCharacterSets(singleton(StandardCharsets.UTF_8));
+ metadata.setLocalesAndCharsets(singletonMap(Locale.ENGLISH, StandardCharsets.UTF_8));
metadata.setMetadataScopes(singleton(new DefaultMetadataScope(ScopeCode.DATASET, "Common Data Index record")));
metadata.setDateInfo(singleton(new DefaultCitationDate(TestUtilities.date("2009-01-01 04:00:00"), DateType.CREATION)));
/*
@@ -413,7 +412,7 @@ public strictfp class MetadataTest extends TestCase {
/*
* The <gmd:EX_TemporalExtent> block can not be marshalled yet, since it requires the sis-temporal module.
* We need to instruct the XML comparator to ignore this block during the comparison. We also ignore for
- * now the "gml:id" attribute since SIS generates different values than the ones in oyr test XML file,
+ * now the "gml:id" attribute since SIS generates different values than the ones in our test XML file,
* and those values may change in future SIS version.
*/
final DocumentComparator comparator = new DocumentComparator(getResource(), xml.toString());
@@ -472,11 +471,11 @@ public strictfp class MetadataTest extends TestCase {
final Metadata metadata = unmarshalFile(Metadata.class, VERTICAL_CRS_XML);
if (REGRESSION) {
assertTrue("Maybe SIS-402 has been fixed and this anti-regression hack can be removed?",
- metadata.getCharacterSets().add(StandardCharsets.UTF_8));
+ ((DefaultMetadata) metadata).getCharacterSets().add(StandardCharsets.UTF_8));
}
assertEquals("fileIdentifier", "20090901", metadata.getMetadataIdentifier().getCode());
- assertEquals("language", Locale.ENGLISH, getSingleton(metadata.getLanguages()));
- assertEquals("characterSet", StandardCharsets.UTF_8, getSingleton(metadata.getCharacterSets()));
+ assertEquals("language", Locale.ENGLISH, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+ assertEquals("characterSet", StandardCharsets.UTF_8, getSingleton(metadata.getLocalesAndCharsets().values()));
assertEquals("dateStamp", xmlDate("2014-01-04 00:00:00"), getSingleton(metadata.getDateInfo()).getDate());
/*
* <gmd:contact>
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
index b469435..53f2ed3 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
@@ -102,6 +102,27 @@ public final class CollectionsExt extends Static {
}
/**
+ * Returns the number of elements if the given object is a collection or a map.
+ * Otherwise returns 0 if the given object if null or 1 otherwise.
+ *
+ * @param c the collection or map for which to get the size, or {@code null}.
+ * @return the size or pseudo-size of the given object.
+ *
+ * @since 1.0
+ */
+ public static int size(final Object c) {
+ if (c == null) {
+ return 0;
+ } else if (c instanceof Collection<?>) {
+ return ((Collection<?>) c).size();
+ } else if (c instanceof Map<?,?>) {
+ return ((Map<?,?>) c).size();
+ } else {
+ return 1;
+ }
+ }
+
+ /**
* Returns the first element of the given iterable, or {@code null} if none.
* This method does not emit warning if more than one element is found.
* Consequently, this method should be used only when multi-occurrence is not ambiguous.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java
index 966b998..f270220 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TreeFormatCustomization.java
@@ -26,6 +26,14 @@ import org.apache.sis.util.collection.TreeTableFormat;
* are invoked by {@link TreeTableFormat#format(TreeTable, Appendable)} before to format the tree.
* Non-null return values are merged with the {@code TreeTableFormat} configuration.
*
+ * <div class="note"><b>Design note:</b>
+ * methods in this class are invoked for configuring the formatter before to write the tree.
+ * We do not use this interface as callbacks invoked for individual rows during formatting.
+ * The reason is that functions provided by this interface may need to manage a state
+ * (for example {@linkplain #filter() filtering} may depend on previous rows) but we do not want
+ * to force implementations to store such state in {@code TreeFormatCustomization} instances
+ * since objects implementing this interface may be immutable.</div>
+ *
* <p>This class is not yet in public API. We are waiting for more experience before to decide if it should be
* committed API.</p>
*
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
index f730caa..f6fb420 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
@@ -241,8 +241,8 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
}
/**
- * Returns the table columns to parse and format, or {@code null} for the default list of
- * columns. The default is:
+ * Returns the table columns to parse and format, or {@code null} for the default list of columns.
+ * The default is:
*
* <ul>
* <li>On parsing, a single column containing the node label as a {@link String}.</li>
@@ -701,10 +701,12 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
this.recursivityGuard = recursivityGuard;
Predicate<TreeTable.Node> filter = nodeFilter;
if (tree instanceof TreeFormatCustomization) {
- final Predicate<TreeTable.Node> more = ((TreeFormatCustomization) tree).filter();
+ final TreeFormatCustomization custom = (TreeFormatCustomization) tree;
+ final Predicate<TreeTable.Node> more = custom.filter();
if (more != null) {
filter = (filter != null) ? more.and(filter) : more;
}
+ } else {
}
this.filter = filter;
setTabulationExpanded(true);
@@ -779,6 +781,20 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
} else if (value instanceof Object[]) {
formatCollection(Arrays.asList((Object[]) value), recursive);
return;
+ } else if (value instanceof Map.Entry<?,?>) {
+ final Map.Entry<?,?> entry = (Map.Entry<?,?>) value;
+ final Object k = entry.getKey();
+ final Object v = entry.getValue();
+ if (k == null) {
+ append(null);
+ } else {
+ formatValue(k, recursive);
+ }
+ if (v != null) {
+ append(" → ");
+ formatValue(v, recursive);
+ }
+ return;
} else {
/*
* Check for a value-by-value format only as last resort. If a column-wide format was specified by
@@ -872,14 +888,12 @@ public class TreeTableFormat extends TabularFormat<TreeTable> {
* may be followed by a non-null value, which is why we need to check all of them before to know how many
* columns to omit.
*/
- int n = 0;
for (int i=0; i<columns.length; i++) {
- if ((values[i] = node.getValue(columns[i])) != null) {
- n = i;
- }
+ values[i] = node.getValue(columns[i]);
}
- if (!omitTrailingNulls) {
- n = values.length - 1;
+ int n = values.length - 1;
+ if (omitTrailingNulls) {
+ while (n > 0 && values[n] == null) n--;
}
/*
* Format the values that we fetched in above loop.
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
index aa887c0..c1c13f0 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
@@ -106,7 +106,7 @@ public class LandsatReaderTest extends TestCase {
verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo"); // Very verbose and depends on EPSG connection.
verifier.addMetadataToVerify(actual);
verifier.assertMetadataEquals(
- "language[0]", "en",
+ "defaultLocale+otherLocale[0]", "en",
"metadataIdentifier.code", "LandsatTest",
"metadataScope[0].resourceScope", ScopeCode.COVERAGE,
"dateInfo[0].date", date("2016-06-27 16:48:12"),
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
index c251d74..6155e3b 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
@@ -18,7 +18,6 @@ package org.apache.sis.internal.storage.xml;
import java.util.Locale;
import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
import org.opengis.metadata.Metadata;
import org.opengis.metadata.citation.*;
import org.apache.sis.xml.Namespaces;
@@ -31,7 +30,6 @@ import org.junit.Test;
import static org.opengis.test.Assert.*;
import static org.apache.sis.test.TestUtilities.getSingleton;
-import static org.apache.sis.metadata.iso.DefaultMetadataTest.REGRESSION;
/**
@@ -103,9 +101,7 @@ public final strictfp class StoreTest extends TestCase {
final OnlineResource resource = getSingleton(contact.getOnlineResources());
assertInstanceOf("party", Organisation.class, party);
- assertEquals(Locale.ENGLISH, getSingleton(metadata.getLanguages()));
- if (!REGRESSION)
- assertEquals(StandardCharsets.UTF_8, getSingleton(metadata.getCharacterSets()));
+ assertEquals(Locale.ENGLISH, getSingleton(metadata.getLocalesAndCharsets().keySet()));
assertEquals(Role.PRINCIPAL_INVESTIGATOR, resp.getRole());
assertEquals("Apache SIS", String.valueOf(party.getName()));
assertEquals("http://sis.apache.org", String.valueOf(resource.getLinkage()));