blob: eea35a771e5dff2ecf1870988351a14a7006c6c6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.metadata;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import java.util.Date;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.citation.Series;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.citation.CitationDate;
import org.opengis.metadata.citation.OnlineResource;
import org.opengis.metadata.citation.PresentationForm;
import org.opengis.metadata.citation.ResponsibleParty;
import org.opengis.metadata.distribution.Format;
import org.opengis.metadata.constraint.Constraints;
import org.opengis.metadata.content.AttributeGroup;
import org.opengis.metadata.content.CoverageContentType;
import org.opengis.metadata.content.CoverageDescription;
import org.opengis.metadata.identification.*; // Really using almost everything.
import org.opengis.metadata.maintenance.MaintenanceInformation;
import org.opengis.metadata.spatial.SpatialRepresentationType;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.ReferenceSystem;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.GeodeticCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.util.InternationalString;
import org.opengis.util.GenericName;
import org.opengis.temporal.Duration;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.iso.SimpleInternationalString;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.citation.HardCodedCitations;
import org.apache.sis.metadata.iso.content.DefaultCoverageDescription;
import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestCase;
import org.junit.Test;
import static org.apache.sis.test.MetadataAssert.*;
import static org.apache.sis.test.TestUtilities.getSingleton;
import static org.apache.sis.metadata.PropertyAccessor.APPEND;
import static org.apache.sis.metadata.PropertyAccessor.RETURN_NULL;
import static org.apache.sis.metadata.PropertyAccessor.RETURN_PREVIOUS;
/**
* Tests the {@link PropertyAccessor} class. Every tests in this class instantiates directly a
* {@link PropertyAccessor} object by invoking the {@link #createPropertyAccessor()} method.
* This class shall not test accessors created indirectly (e.g. the accessors created
* by {@link MetadataStandard}).
*
* <p>This test case uses the {@link Citation} and {@link GeographicCRS} types. If those types
* are modified in a future GeoAPI version, then some hard-coded values in this test may need
* to be updated.</p>
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.3
* @module
*/
@SuppressWarnings("OverlyStrongTypeCast")
@DependsOn(PropertyInformationTest.class)
public final strictfp class PropertyAccessorTest extends TestCase {
/**
* Creates a new property accessor for the {@link DefaultCitation} class.
*/
private static PropertyAccessor createPropertyAccessor() {
return new PropertyAccessor(Citation.class, DefaultCitation.class, DefaultCitation.class);
}
/**
* Asserts that the properties found by the given {@code accessor} have the given names and types.
* The {@code expected} array shall be a sequence of tuples having the following components:
*
* <ul>
* <li>The interface that declare the method.</li>
* <li>Name of the getter method as specified by {@link KeyNamePolicy#METHOD_NAME}.</li>
* <li>Name of the JavaBeans property as specified by {@link KeyNamePolicy#JAVABEANS_PROPERTY}.</li>
* <li>ISO 19115 UML identifier as specified by {@link KeyNamePolicy#UML_IDENTIFIER}.</li>
* <li>A sentence as specified by {@link KeyNamePolicy#SENTENCE}.</li>
* <li>The type of elements. By convention, an array type stands for {@link Collection}
* (we have to do this replacement because of parameterized types erasure).</li>
* </ul>
*
* The tuples shall be ordered according the {@link PropertyComparator}.
*
* @param accessor the accessor to test.
* @param expected the expected names and types as described above.
*
* @see PropertyAccessor#mapping
*/
private static void assertMappingEquals(final PropertyAccessor accessor, final Object... expected) {
int i = 0;
while (i < expected.length) {
final int index = i / 6;
final Class<?> declaringType = (Class<?>) expected[i++];
final String methodName = (String) expected[i++];
final String propertyName = (String) expected[i++];
final String umlIdentifier = (String) expected[i++];
final String sentence = (String) expected[i++];
assertEquals("methodName", methodName, accessor.name(index, KeyNamePolicy.METHOD_NAME));
assertEquals("propertyName", propertyName, accessor.name(index, KeyNamePolicy.JAVABEANS_PROPERTY));
assertEquals("umlIdentifier", umlIdentifier, accessor.name(index, KeyNamePolicy.UML_IDENTIFIER));
assertEquals("sentence", sentence, accessor.name(index, KeyNamePolicy.SENTENCE));
assertEquals("declaringType", declaringType, accessor.type(index, TypeValuePolicy.DECLARING_INTERFACE));
assertEquals(methodName, index, accessor.indexOf(methodName, false));
assertEquals(propertyName, index, accessor.indexOf(propertyName, false));
assertEquals(umlIdentifier, index, accessor.indexOf(umlIdentifier, false));
assertEquals(propertyName, index, accessor.indexOf(propertyName .toLowerCase(Locale.ROOT), false));
assertEquals(umlIdentifier, index, accessor.indexOf(umlIdentifier.toLowerCase(Locale.ROOT), false));
/*
* Verifies the type of values. This need special handling for collections.
*/
Class<?> propertyType = (Class<?>) expected[i++];
Class<?> elementType = propertyType;
if (propertyType.isArray()) {
elementType = propertyType.getComponentType();
propertyType = Collection.class;
if (IdentifiedObject.class.isAssignableFrom(accessor.type)) {
// Special cases
if (propertyName.equals("identifiers")) {
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));
}
assertEquals("Count of 'get' methods.", i/6, accessor.count());
}
/**
* Tests the constructor with the {@link DefaultCitation} implementation.
* The order of properties shall be the order declared in the {@code XmlType.propOrder} annotation.
* This test may need to be updated if a future GeoAPI release modifies the {@link Citation} interface.
* Other tests that depends on {@link Citation} property order are {@link NameMapTest#testEntrySet()},
* {@link TypeMapTest#testEntrySet()} and most tests in {@link ValueMapTest}.
*
* @see NameMapTest#testEntrySet()
* @see TypeMapTest#testEntrySet()
* @see ValueMapTest
*/
@Test
public void testConstructor() {
assertMappingEquals(createPropertyAccessor(),
//……Declaring type………Method………………………………………………………………JavaBeans………………………………………………UML identifier……………………………Sentence………………………………………………………Type………………………………………………………………
Citation.class, "getTitle", "title", "title", "Title", InternationalString.class,
Citation.class, "getAlternateTitles", "alternateTitles", "alternateTitle", "Alternate titles", InternationalString[].class,
Citation.class, "getDates", "dates", "date", "Dates", CitationDate[].class,
Citation.class, "getEdition", "edition", "edition", "Edition", InternationalString.class,
Citation.class, "getEditionDate", "editionDate", "editionDate", "Edition date", Date.class,
Citation.class, "getIdentifiers", "identifiers", "identifier", "Identifiers", Identifier[].class,
Citation.class, "getCitedResponsibleParties", "citedResponsibleParties", "citedResponsibleParty", "Cited responsible parties", ResponsibleParty[].class,
Citation.class, "getPresentationForms", "presentationForms", "presentationForm", "Presentation forms", PresentationForm[].class,
Citation.class, "getSeries", "series", "series", "Series", Series.class,
Citation.class, "getOtherCitationDetails", "otherCitationDetails", "otherCitationDetails", "Other citation details", InternationalString.class,
// Citation.class, "getCollectiveTitle", "collectiveTitle", "collectiveTitle", "Collective title", InternationalString.class, -- deprecated as of ISO 19115:2014
Citation.class, "getISBN", "ISBN", "ISBN", "ISBN", String.class,
Citation.class, "getISSN", "ISSN", "ISSN", "ISSN", String.class,
Citation.class, "getOnlineResources", "onlineResources", "onlineResource", "Online resources", OnlineResource[].class,
Citation.class, "getGraphics", "graphics", "graphic", "Graphics", BrowseGraphic[].class);
}
/**
* Tests the constructor with the {@link DefaultDataIdentification} implementation.
* The purpose of this test is to ensure that the properties defined in the parent
* class are sorted first.
*
* <div class="note"><b>Note:</b> if there is any element not declared as JAXB elements,
* those ones will be last in alphabetical order. Such situation is usually temporary
* until the JAXB annotations are completed.</div>
*/
@Test
@DependsOnMethod("testConstructor")
public void testConstructorWithInheritance() {
assertMappingEquals(new PropertyAccessor(DataIdentification.class, DefaultDataIdentification.class, DefaultDataIdentification.class),
//……Declaring type………………………Method………………………………………………………………………JavaBeans………………………………………………………UML identifier………………………………………Sentence……………………………………………………………Type………………………………………………………………
Identification.class, "getCitation", "citation", "citation", "Citation", Citation.class,
Identification.class, "getAbstract", "abstract", "abstract", "Abstract", InternationalString.class,
Identification.class, "getPurpose", "purpose", "purpose", "Purpose", InternationalString.class,
Identification.class, "getCredits", "credits", "credit", "Credits", String[].class,
Identification.class, "getStatus", "status", "status", "Status", Progress[].class,
Identification.class, "getPointOfContacts", "pointOfContacts", "pointOfContact", "Point of contacts", ResponsibleParty[].class,
Identification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
Identification.class, "getSpatialResolutions", "spatialResolutions", "spatialResolution", "Spatial resolutions", Resolution[].class,
Identification.class, "getTemporalResolutions", "temporalResolutions", "temporalResolution", "Temporal resolutions", Duration[].class,
Identification.class, "getTopicCategories", "topicCategories", "topicCategory", "Topic categories", TopicCategory[].class,
Identification.class, "getExtents", "extents", "extent", "Extents", Extent[].class,
Identification.class, "getAdditionalDocumentations", "additionalDocumentations", "additionalDocumentation", "Additional documentations", Citation[].class,
Identification.class, "getProcessingLevel", "processingLevel", "processingLevel", "Processing level", Identifier.class,
Identification.class, "getResourceMaintenances", "resourceMaintenances", "resourceMaintenance", "Resource maintenances", MaintenanceInformation[].class,
Identification.class, "getGraphicOverviews", "graphicOverviews", "graphicOverview", "Graphic overviews", BrowseGraphic[].class,
Identification.class, "getResourceFormats", "resourceFormats", "resourceFormat", "Resource formats", Format[].class,
Identification.class, "getDescriptiveKeywords", "descriptiveKeywords", "descriptiveKeywords", "Descriptive keywords", Keywords[].class,
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, "getEnvironmentDescription", "environmentDescription", "environmentDescription", "Environment description", InternationalString.class,
DataIdentification.class, "getSupplementalInformation", "supplementalInformation", "supplementalInformation", "Supplemental information", InternationalString.class,
DataIdentification.class, "getLocalesAndCharsets", "localesAndCharsets", "defaultLocale+otherLocale", "Locales and charsets", Map.class);
}
/**
* Tests the constructor with a method which override an other method with covariant return type.
* This test may need to be updated if a future GeoAPI release modifies the {@link GeographicCRS} interface.
*/
@Test
@DependsOnMethod("testConstructorWithInheritance")
public void testConstructorWithCovariantReturnType() {
final Class<?> type = GeographicCRS.class;
assertMappingEquals(new PropertyAccessor(type, type, type),
//……Declaring type……………………………Method……………………………………………JavaBeans……………………………UML identifier………………Sentence…………………………………Type…………………………………………………………
GeographicCRS.class, "getCoordinateSystem", "coordinateSystem", "coordinateSystem", "Coordinate system", EllipsoidalCS.class, // Covariant return type
GeodeticCRS.class, "getDatum", "datum", "datum", "Datum", GeodeticDatum.class, // Covariant return type
IdentifiedObject.class, "getName", "name", "name", "Name", ReferenceIdentifier.class,
IdentifiedObject.class, "getAlias", "alias", "alias", "Alias", GenericName[].class,
ReferenceSystem.class, "getDomainOfValidity", "domainOfValidity", "domainOfValidity", "Domain of validity", Extent.class,
IdentifiedObject.class, "getIdentifiers", "identifiers", "identifier", "Identifiers", ReferenceIdentifier[].class,
IdentifiedObject.class, "getRemarks", "remarks", "remarks", "Remarks", InternationalString.class,
ReferenceSystem.class, "getScope", "scope", "SC_CRS.scope", "Scope", InternationalString.class);
}
/**
* Tests the {@link PropertyAccessor#information(Citation, int)} method.
* This method delegates to some {@link PropertyInformationTest} methods.
*/
@Test
@DependsOnMethod("testConstructor")
public void testInformation() {
final PropertyAccessor accessor = createPropertyAccessor();
PropertyInformationTest.validateTitle (accessor.information(HardCodedCitations.ISO_19115, accessor.indexOf("title", true)));
PropertyInformationTest.validatePresentationForm(accessor.information(HardCodedCitations.ISO_19115, accessor.indexOf("presentationForm", true)));
}
/**
* Tests the {@link PropertyAccessor#get(int, Object)} method on the {@link HardCodedCitations#ISO_19111} constant.
* The metadata object read by this test is:
*
* {@preformat text
* DefaultCitation
*   ├─Title…………………………………… International Organization for Standardization
*   ├─Alternate title………… ISO 19111
*   ├─Identifier
*   │   ├─Code…………………………… 19111
*   │   └─Code space…………… ISO
*   └─Presentation form…… Document digital
* }
*/
@Test
@DependsOnMethod("testConstructor")
public void testGet() {
final DefaultCitation instance = HardCodedCitations.ISO_19111;
final PropertyAccessor accessor = createPropertyAccessor();
// Singleton value (not a collection)
final Object title = accessor.get(accessor.indexOf("title", true), instance);
assertInstanceOf("title", InternationalString.class, title);
assertEquals("title", "Spatial referencing by coordinates", title.toString());
// Collection of InternationalStrings
final Object alternateTitles = accessor.get(accessor.indexOf("alternateTitles", true), instance);
assertInstanceOf("alternateTitles", Collection.class, alternateTitles);
assertEquals("alternateTitles", "ISO 19111", getSingleton((Collection<?>) alternateTitles).toString());
// Collection of Identifiers
final Object identifiers = accessor.get(accessor.indexOf("identifiers", true), instance);
assertEquals("19111", getSingletonCode(identifiers));
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method
* with a value to be stored <cite>as-is</cite> (without conversion).
* The metadata object created by this test is:
*
* {@preformat text
* DefaultCitation
*   ├─Title………………………… Some title
*   ├─Identifier
*   │   ├─Code………………… Some ISBN code
*   │   └─Authority
*   │       └─Title…… ISBN
*   └─ISBN…………………………… Some ISBN code
* }
*/
@Test
@DependsOnMethod("testGet")
public void testSet() {
final DefaultCitation instance = new DefaultCitation();
final PropertyAccessor accessor = createPropertyAccessor();
Object newValue;
int index;
newValue = new SimpleInternationalString("Some title");
index = accessor.indexOf("title", true);
assertNull("title", accessor.set(index, instance, newValue, RETURN_PREVIOUS));
assertSame("title", newValue, accessor.get(index, instance));
assertSame("title", newValue, instance.getTitle());
newValue = "Some ISBN code";
index = accessor.indexOf("ISBN", true);
assertNull("ISBN", accessor.set(index, instance, newValue, RETURN_PREVIOUS));
assertSame("ISBN", newValue, accessor.get(index, instance));
assertSame("ISBN", newValue, instance.getISBN());
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method with a {@code null} value.
* Setting a property to {@code null} is equivalent to removing that property value.
* The metadata object used by this test (before removal) is:
*
* {@preformat text
* DefaultCitation
*   └─Title………………………… Some title
* }
*/
@Test
@DependsOnMethod("testSet")
public void testSetNull() {
final DefaultCitation instance = new DefaultCitation("Some title");
final PropertyAccessor accessor = createPropertyAccessor();
final InternationalString title = instance.getTitle();
final int index = accessor.indexOf("title", true);
assertEquals("Some title", title.toString()); // Sanity check before to continue.
assertNull("title", accessor.set(index, instance, null, RETURN_NULL));
assertNull("title", instance.getTitle());
instance.setTitle(title);
assertSame("title", title, accessor.set(index, instance, null, RETURN_PREVIOUS));
assertNull("title", instance.getTitle());
}
/**
* Tests setting a deprecated properties. This properties should not be visible in the map,
* but still be accepted by the map views.
*/
@Test
@DependsOnMethod("testSet")
public void testSetDeprecated() {
final PropertyAccessor accessor = new PropertyAccessor(CoverageDescription.class,
DefaultCoverageDescription.class, DefaultCoverageDescription.class);
final int indexOfDeprecated = accessor.indexOf("contentType", true);
final int indexOfReplacement = accessor.indexOf("attributeGroup", true);
assertTrue("Deprecated elements shall be sorted after non-deprecated ones.",
indexOfDeprecated > indexOfReplacement);
/*
* Writes a value using the deprecated property.
*/
final DefaultCoverageDescription instance = new DefaultCoverageDescription();
assertNull("Shall be initially empty.", accessor.set(indexOfDeprecated, instance,
CoverageContentType.IMAGE, PropertyAccessor.RETURN_PREVIOUS));
assertEquals(CoverageContentType.IMAGE, accessor.get(indexOfDeprecated, instance));
/*
* Compares with the non-deprecated property.
*/
final Collection<AttributeGroup> groups = instance.getAttributeGroups();
assertSame(groups, accessor.get(indexOfReplacement, instance));
assertEquals(CoverageContentType.IMAGE, getSingleton(getSingleton(groups).getContentTypes()));
/*
* While we can read/write the value through two properties,
* only one should be visible.
*/
assertEquals("Deprecated property shall not be visible.", 1, accessor.count(
instance, ValueExistencePolicy.NON_EMPTY, PropertyAccessor.COUNT_SHALLOW));
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method
* with a value that will need to be converted. The conversion will be from
* {@link String} to {@link InternationalString}. The created metadata object is:
*
* {@preformat text
* DefaultCitation
*   └─Title……………… Some title
* }
*/
@Test
@DependsOnMethod("testSet")
public void testSetWithConversion() {
final String expected = "Some title";
final DefaultCitation instance = new DefaultCitation();
final PropertyAccessor accessor = createPropertyAccessor();
final int index = accessor.indexOf("title", true);
final Object oldValue = accessor.set(index, instance, expected, RETURN_PREVIOUS);
final Object value = accessor.get(index, instance);
assertNull ("title", oldValue);
assertInstanceOf("title", InternationalString.class, value);
assertSame ("title", expected, value.toString());
assertSame ("title", value, instance.getTitle());
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method when the value
* is a collection. The new collection shall replace the previous one (no merge expected).
* The metadata object created by this test after the replacement is:
*
* {@preformat text
* DefaultCitation
*   ├─Title……………………………………………………… Ignored title
*   ├─Alternate title (1 of 2)…… New title 1
*   └─Alternate title (2 of 2)…… New title 2
* }
*
* @see #testSetInAppendMode()
*/
@Test
@DependsOnMethod("testSet")
public void testSetCollection() {
final DefaultCitation instance = new DefaultCitation("Ignored title");
final List<InternationalString> oldTitles = Arrays.<InternationalString>asList(
new SimpleInternationalString("Old title 1"),
new SimpleInternationalString("Old title 2"));
final List<InternationalString> newTitles = Arrays.<InternationalString>asList(
new SimpleInternationalString("New title 1"),
new SimpleInternationalString("New title 2"));
// Set the alternate titles.
instance.setAlternateTitles(oldTitles);
final PropertyAccessor accessor = createPropertyAccessor();
final int index = accessor.indexOf("alternateTitles", true);
final Object oldValue = accessor.set(index, instance, newTitles, RETURN_PREVIOUS);
final Object newValue = accessor.get(index, instance);
// Verify the values.
assertEquals("set(…, RETURN_PREVIOUS)", oldTitles, oldValue);
assertEquals("get(…)", newTitles, newValue);
assertSame ("alternateTitles", newValue, instance.getAlternateTitles());
assertTitleEquals("title", "Ignored title", instance);
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method
* when adding elements in a collection, without conversion of type.
* The metadata object created by this test is:
*
* {@preformat text
* DefaultCitation
*   ├─Title……………………………………………………… Ignored title
*   ├─Alternate title (1 of 2)…… An other title
*   └─Alternate title (2 of 2)…… Yet an other title
* }
*/
@Test
@DependsOnMethod("testSet")
public void testSetIntoCollection() {
testSetIntoCollection(false);
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method
* when adding elements in a collection, with conversion of type.
* The metadata object created by this test is:
*
* {@preformat text
* DefaultCitation
*   ├─Title……………………………………………………… Ignored title
*   ├─Alternate title (1 of 2)…… An other title
*   └─Alternate title (2 of 2)…… Yet an other title
* }
*/
@Test
@DependsOnMethod("testSetIntoCollection")
public void testSetIntoCollectionWithConversion() {
testSetIntoCollection(true);
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method
* when adding elements in a collection, with or without conversion of type.
*/
private static void testSetIntoCollection(final boolean conversion) {
final String text1 = "An other title";
final String text2 = "Yet an other title";
final InternationalString title1 = new SimpleInternationalString(text1);
final InternationalString title2 = new SimpleInternationalString(text2);
final DefaultCitation instance = new DefaultCitation("Ignored title");
final PropertyAccessor accessor = createPropertyAccessor();
final int index = accessor.indexOf("alternateTitles", true);
// Insert the first value. Old collection shall be empty.
Object oldValue = accessor.set(index, instance, conversion ? text1 : title1, RETURN_PREVIOUS);
assertInstanceOf("alternateTitles", Collection.class, oldValue);
assertTrue("alternateTitles", ((Collection<?>) oldValue).isEmpty());
// Insert the second value. Old collection shall contain the first value.
oldValue = accessor.set(index, instance, conversion ? text2 : title2, RETURN_PREVIOUS);
assertInstanceOf("alternateTitles", Collection.class, oldValue);
oldValue = getSingleton((Collection<?>) oldValue);
assertSame("alternateTitles", text1, oldValue.toString());
if (!conversion) {
assertSame("InternationalString should have been stored as-is.", title1, oldValue);
}
// Check final collection content.
final List<InternationalString> expected = Arrays.asList(title1, title2);
assertEquals("alternateTitles", expected, accessor.get(index, instance));
assertTitleEquals("title", "Ignored title", instance);
}
/**
* Tests the {@link PropertyAccessor#set(int, Object, Object, int)} method in
* {@link PropertyAccessor#APPEND} mode. In this mode, new collections
* are added into existing collections instead than replacing them.
* The metadata object created by this test after the merge is:
*
* {@preformat text
* DefaultCitation
*   ├─Title……………………………………………………… Added title
*   ├─Alternate title (1 of 4)…… Old title 1
*   ├─Alternate title (2 of 4)…… Old title 2
*   ├─Alternate title (3 of 4)…… New title 1
*   └─Alternate title (4 of 4)…… New title 2
* }
*
* @see #testSetCollection()
*/
public void testSetInAppendMode() {
final DefaultCitation instance = new DefaultCitation();
final List<InternationalString> oldTitles = Arrays.<InternationalString>asList(
new SimpleInternationalString("Old title 1"),
new SimpleInternationalString("Old title 2"));
final List<InternationalString> newTitles = Arrays.<InternationalString>asList(
new SimpleInternationalString("New title 1"),
new SimpleInternationalString("New title 2"));
final List<InternationalString> merged = new ArrayList<>(oldTitles);
assertTrue(merged.addAll(newTitles));
// Set the title.
instance.setAlternateTitles(oldTitles);
final PropertyAccessor accessor = createPropertyAccessor();
final int titleIndex = accessor.indexOf("title", true);
Object titleChanged = accessor.set(titleIndex, instance, "Added title", APPEND);
// Set the alternate titles.
final int index = accessor.indexOf("alternateTitles", true);
final Object changed = accessor.set(index, instance, newTitles, APPEND);
final Object newValue = accessor.get(index, instance);
// Verify the values.
assertEquals("set(…, APPEND)", Boolean.TRUE, titleChanged);
assertEquals("set(…, APPEND)", Boolean.TRUE, changed);
assertEquals("get(…)", merged, newValue);
assertSame ("alternateTitles", newValue, instance.getAlternateTitles());
assertTitleEquals("title", "Added title", instance);
// Test setting again the title to the same value.
titleChanged = accessor.set(titleIndex, instance, "Added title", APPEND);
assertEquals("set(…, APPEND)", Boolean.FALSE, titleChanged);
assertTitleEquals("title", "Added title", instance);
// Test setting the title to a different value.
titleChanged = accessor.set(titleIndex, instance, "Different title", APPEND);
assertNull("set(…, APPEND)", titleChanged); // Operation shall be refused.
assertTitleEquals("title", "Added title", instance);
}
/**
* Tests the equals methods.
*/
@Test
public void testEquals() {
DefaultCitation citation = HardCodedCitations.EPSG;
final PropertyAccessor accessor = createPropertyAccessor();
assertFalse(accessor.equals(citation, HardCodedCitations.SIS, ComparisonMode.STRICT));
assertTrue (accessor.equals(citation, HardCodedCitations.EPSG, ComparisonMode.STRICT));
// Same test than above, but on a copy of the EPSG constant.
citation = new DefaultCitation(HardCodedCitations.EPSG);
assertFalse(accessor.equals(citation, HardCodedCitations.SIS, ComparisonMode.STRICT));
assertTrue (accessor.equals(citation, HardCodedCitations.EPSG, ComparisonMode.STRICT));
// Identifiers shall be stored in different collection instances with equal content.
final int index = accessor.indexOf("identifiers", true);
final Object source = accessor.get(index, HardCodedCitations.EPSG);
final Object target = accessor.get(index, citation);
assertInstanceOf("identifiers", Collection.class, source);
assertInstanceOf("identifiers", Collection.class, target);
assertNotSame("Distinct objects shall have distinct collections.", source, target);
assertEquals ("The two collections shall have the same content.", source, target);
assertEquals ("EPSG", getSingletonCode(target));
// Set the identifiers to null, which should clear the collection.
assertEquals("Expected the previous value.", source, accessor.set(index, citation, null, RETURN_PREVIOUS));
final Object value = accessor.get(index, citation);
assertNotNull("Should have replaced null by an empty collection.", value);
assertTrue("Should have replaced null by an empty collection.", ((Collection<?>) value).isEmpty());
}
/**
* Tests {@link PropertyAccessor#toString()}. The {@code toString()}
* method is only for debugging purpose, but we test it anyway.
*/
@Test
public void testToString() {
final PropertyAccessor accessor = createPropertyAccessor();
assertEquals("PropertyAccessor[14 getters (+1 ext.) & 15 setters in DefaultCitation:Citation]", accessor.toString());
}
/**
* Returns the code of the singleton identifier found in the given collection.
* This method verifies that the object is of the expected type.
*
* @param identifiers a singleton {@code Collection<Identifier>}.
* @return {@link Identifier#getCode()}.
*/
static String getSingletonCode(final Object identifiers) {
assertInstanceOf("identifiers", Collection.class, identifiers);
final Object identifier = getSingleton((Collection<?>) identifiers);
assertInstanceOf("identifier", Identifier.class, identifier);
return ((Identifier) identifier).getCode();
}
}