| /* |
| * 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.Date; |
| import java.util.Random; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ArrayList; |
| import org.opengis.metadata.citation.Citation; |
| import org.opengis.metadata.citation.DateType; |
| import org.opengis.metadata.citation.PresentationForm; |
| import org.apache.sis.metadata.iso.citation.DefaultCitation; |
| import org.apache.sis.metadata.iso.citation.DefaultCitationDate; |
| import org.apache.sis.util.SimpleInternationalString; |
| import org.apache.sis.util.collection.DefaultTreeTable; |
| import org.apache.sis.util.collection.TableColumn; |
| import org.apache.sis.util.collection.TreeTable; |
| |
| // Test dependencies |
| import org.junit.jupiter.api.Test; |
| import static org.junit.jupiter.api.Assertions.*; |
| import org.apache.sis.test.TestUtilities; |
| import org.apache.sis.test.TestCase; |
| |
| // Specific to the geoapi-4.0 branch: |
| import java.util.Set; |
| |
| |
| /** |
| * Tests the {@link TreeNodeChildren} class. |
| * Unless otherwise specified, all tests use the {@link MetadataStandard#ISO_19115} constant. |
| * |
| * <h2>Test dependency</h2> |
| * This class uses the {@link TreeNode#getUserObject()} method for comparing the values. |
| * We can hardly avoid to use some {@code TreeNode} methods because of the cross-dependencies. |
| * However, we try to use nothing else than {@code getUserObject()} because the purpose of this |
| * class is not to test {@link TreeNode}. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| */ |
| public final class TreeNodeChildrenTest extends TestCase { |
| /** |
| * Creates a new test case. |
| */ |
| public TreeNodeChildrenTest() { |
| } |
| |
| /** |
| * Creates a shallow metadata object without collections. |
| * |
| * <pre class="text"> |
| * DefaultCitation |
| * ├─Title………………………………………………… Some title |
| * ├─Edition…………………………………………… Some edition |
| * └─Other citation details…… Some other details</pre> |
| */ |
| static DefaultCitation metadataWithoutCollections() { |
| final DefaultCitation citation = new DefaultCitation("Some title"); |
| citation.setEdition(new SimpleInternationalString("Some edition")); |
| citation.setOtherCitationDetails(Set.of(new SimpleInternationalString("Some other details"))); |
| return citation; |
| } |
| |
| /** |
| * Creates a shallow metadata object with singleton value in collections. |
| * This method creates the following metadata: |
| * |
| * <pre class="text"> |
| * DefaultCitation |
| * ├─Title………………………………………………… Some title |
| * ├─Alternate title……………………… First alternate title |
| * ├─Edition…………………………………………… Some edition |
| * ├─Presentation form………………… Map digital |
| * └─Other citation details…… Some other details</pre> |
| */ |
| static DefaultCitation metadataWithSingletonInCollections() { |
| final DefaultCitation citation = metadataWithoutCollections(); |
| assertTrue(citation.getAlternateTitles().add(new SimpleInternationalString("First alternate title"))); |
| assertTrue(citation.getPresentationForms().add(PresentationForm.MAP_DIGITAL)); |
| return citation; |
| } |
| |
| /** |
| * Creates a shallow metadata object with multi-occurrences (i.e. more than one value in collections). |
| * This method creates the following metadata: |
| * |
| * <pre class="text"> |
| * DefaultCitation |
| * ├─Title………………………………………………………… Some title |
| * ├─Alternate title (1 of 2)……… First alternate title |
| * ├─Alternate title (2 of 2)……… Second alternate title |
| * ├─Edition…………………………………………………… Some edition |
| * ├─Presentation form (1 of 2)… Map digital |
| * ├─Presentation form (2 of 2)… map hardcopy |
| * └─Other citation details…………… Some other details</pre> |
| */ |
| static DefaultCitation metadataWithMultiOccurrences() { |
| final DefaultCitation citation = metadataWithSingletonInCollections(); |
| assertTrue(citation.getAlternateTitles().add(new SimpleInternationalString("Second alternate title"))); |
| assertTrue(citation.getPresentationForms().add(PresentationForm.MAP_HARDCOPY)); |
| return citation; |
| } |
| |
| /** |
| * Creates a metadata object with a property that can be simplified. |
| * Strictly speaking, the metadata is: |
| * |
| * <pre class="text"> |
| * DefaultCitation |
| * └─Date |
| * ├─Date………………… 2012-01-01 |
| * └─Date type…… Creation</pre> |
| * |
| * However, the tree view should simplify as: |
| * |
| * <pre class="text"> |
| * DefaultCitation |
| * └─Date………………………… 2012-01-01 |
| * └─Date type…… Creation</pre> |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/SIS-298">SIS-298</a> |
| */ |
| static DefaultCitation metadataSimplifiable() { |
| final DefaultCitation citation = new DefaultCitation(); |
| final DefaultCitationDate date = new DefaultCitationDate(TestUtilities.date("2012-01-01 00:00:00"), DateType.CREATION); |
| assertTrue(citation.getDates().add(date)); |
| return citation; |
| } |
| |
| /** |
| * Creates a collection to be tested for the given metadata object and value policy. |
| */ |
| private static TreeNodeChildren create(final DefaultCitation citation, final ValueExistencePolicy valuePolicy) { |
| final MetadataStandard standard = MetadataStandard.ISO_19115; |
| final TreeTableView table = new TreeTableView(standard, citation, Citation.class, valuePolicy); |
| final TreeNode node = (TreeNode) table.getRoot(); |
| final PropertyAccessor accessor = standard.getAccessor(new CacheKey(citation.getClass()), true); |
| return new TreeNodeChildren(node, citation, accessor); |
| } |
| |
| /** |
| * Tests read-only operations on a list of properties for a shallow metadata object without collections. |
| */ |
| @Test |
| public void testReadOnlyWithoutCollections() { |
| final DefaultCitation citation = metadataWithoutCollections(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| final String[] expected = { |
| "Some title", |
| "Some edition", |
| "Some other details" |
| }; |
| assertEquals(-1, children.titleProperty); |
| assertFalse (children.isEmpty()); |
| assertEquals(expected.length, children.size()); |
| assertAllNextEqual(expected, children.iterator()); |
| } |
| |
| /** |
| * Tests read-only operations on a list of properties for a shallow metadata object with singleton |
| * values in collections. |
| */ |
| @Test |
| public void testReadOnlyWithSingletonInCollections() { |
| final DefaultCitation citation = metadataWithSingletonInCollections(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| final String[] expected = { |
| "Some title", |
| "First alternate title", |
| "Some edition", |
| "PresentationForm.MAP_DIGITAL", |
| "Some other details" |
| }; |
| assertEquals(-1, children.titleProperty); |
| assertFalse (children.isEmpty()); |
| assertEquals(expected.length, children.size()); |
| assertAllNextEqual(expected, children.iterator()); |
| } |
| |
| /** |
| * Tests read-only operations on a list of properties for a shallow metadata object with more |
| * than one values in collections. |
| */ |
| @Test |
| public void testReadOnlyWithMultiOccurrences() { |
| final DefaultCitation citation = metadataWithMultiOccurrences(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| final String[] expected = { |
| "Some title", |
| "First alternate title", |
| "Second alternate title", |
| "Some edition", |
| "PresentationForm.MAP_DIGITAL", |
| "PresentationForm.MAP_HARDCOPY", |
| "Some other details" |
| }; |
| assertEquals(-1, children.titleProperty); |
| assertFalse (children.isEmpty()); |
| assertEquals(expected.length, children.size()); |
| assertAllNextEqual(expected, children.iterator()); |
| } |
| |
| /** |
| * Tests a metadata that can be simplified by displaying a child property value directly as the parent value. |
| */ |
| @Test |
| public void testSimplifiable() { |
| final DefaultCitation citation = metadataSimplifiable(); |
| /* |
| * DefaultCitation |
| * └─Date |
| * ├─Date………………… 2012-01-01 |
| * └─Date type…… Creation |
| * |
| * We need to perform the tests on the "Date" node, not on the "DefaultCitation" node. |
| */ |
| final TreeTable.Node node = TestUtilities.getSingleton(create(citation, ValueExistencePolicy.COMPACT)); |
| assertEquals(1325376000000L, ((Date) node.getValue(TableColumn.VALUE)).getTime()); |
| final TreeNodeChildren children = (TreeNodeChildren) node.getChildren(); |
| final String[] expected = { |
| // The "Date" node should be omitted because merged with the parent "Date" node. |
| "DateType.CREATION" |
| }; |
| assertEquals(0, children.titleProperty); |
| assertFalse (children.isEmpty()); |
| assertEquals(expected.length, children.size()); |
| assertAllNextEqual(expected, children.iterator()); |
| } |
| |
| /** |
| * Tests the {@link TreeNodeChildren#add(TreeTable.Node)} method. |
| */ |
| @Test |
| public void testAdd() { |
| final DefaultCitation citation = metadataWithMultiOccurrences(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| assertEquals(-1, children.titleProperty); |
| |
| final DefaultTreeTable.Node toAdd = new DefaultTreeTable.Node(new DefaultTreeTable( |
| TableColumn.IDENTIFIER, |
| TableColumn.VALUE)); |
| final String[] expected = { |
| "Some title", |
| "First alternate title", |
| "Second alternate title", |
| "Third alternate title", // After addition |
| "New edition", // After "addition" (actually change). |
| "PresentationForm.IMAGE_DIGITAL", // After addition |
| "PresentationForm.MAP_DIGITAL", |
| "PresentationForm.MAP_HARDCOPY", |
| "Some other details" |
| }; |
| toAdd.setValue(TableColumn.IDENTIFIER, "edition"); |
| toAdd.setValue(TableColumn.VALUE, citation.getEdition()); |
| assertFalse(children.add(toAdd),"Adding the same value shall be a no-op."); |
| toAdd.setValue(TableColumn.VALUE, "New edition"); |
| try { |
| children.add(toAdd); |
| fail("Setting a different value shall be refused."); |
| } catch (IllegalStateException e) { |
| assertTrue(e.getMessage().contains("edition")); |
| } |
| citation.setEdition(null); // Clears so we are allowed to add. |
| assertTrue(children.add(toAdd), "Setting a new value shall be a change."); |
| |
| toAdd.setValue(TableColumn.IDENTIFIER, "presentationForm"); |
| toAdd.setValue(TableColumn.VALUE, PresentationForm.MAP_DIGITAL); |
| assertFalse(children.add(toAdd), "Adding the same value shall be a no-op."); |
| toAdd.setValue(TableColumn.VALUE, PresentationForm.IMAGE_DIGITAL); |
| assertTrue(children.add(toAdd), "Adding a new value shall be a change."); |
| |
| toAdd.setValue(TableColumn.IDENTIFIER, "alternateTitle"); |
| toAdd.setValue(TableColumn.VALUE, "Third alternate title"); |
| assertTrue(children.add(toAdd), "Adding a new value shall be a change."); |
| |
| assertEquals(expected.length, children.size()); |
| assertAllNextEqual(expected, children.iterator()); |
| } |
| |
| /** |
| * Tests the {@link Iterator#remove()} operation on a list of properties without collections. |
| */ |
| @Test |
| public void testRemoveWithoutCollections() { |
| final DefaultCitation citation = metadataWithoutCollections(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| assertEquals(-1, children.titleProperty); |
| testRemove(TestUtilities.createRandomNumberGenerator(), children); |
| } |
| |
| /** |
| * Tests the {@link Iterator#remove()} operation on a list of properties with |
| * collections containing only one element. |
| */ |
| @Test |
| public void testRemoveWithSingletonInCollections() { |
| final DefaultCitation citation = metadataWithSingletonInCollections(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| assertEquals(-1, children.titleProperty); |
| testRemove(TestUtilities.createRandomNumberGenerator(), children); |
| } |
| |
| /** |
| * Tests the {@link Iterator#remove()} operation on a list of properties with |
| * collections containing more than one element. |
| */ |
| @Test |
| public void testRemoveWithMultiOccurrences() { |
| final DefaultCitation citation = metadataWithSingletonInCollections(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| assertEquals(-1, children.titleProperty); |
| testRemove(TestUtilities.createRandomNumberGenerator(), children); |
| } |
| |
| /** |
| * Tests the {@link TreeNodeChildren#clear()} method. |
| */ |
| @Test |
| public void testClear() { |
| final DefaultCitation citation = metadataWithSingletonInCollections(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.NON_EMPTY); |
| assertEquals(-1, children.titleProperty); |
| assertFalse(children.isEmpty()); |
| children.clear(); |
| assertTrue(children.isEmpty()); |
| assertNull(citation.getTitle()); |
| assertTrue(citation.getAlternateTitles().isEmpty()); |
| } |
| |
| /** |
| * Tests the children list with the {@link ValueExistencePolicy#ALL}. |
| */ |
| @Test |
| public void testShowAll() { |
| final DefaultCitation citation = metadataWithMultiOccurrences(); |
| final TreeNodeChildren children = create(citation, ValueExistencePolicy.ALL); |
| final String[] expected = { |
| "Some title", |
| "First alternate title", |
| "Second alternate title", |
| null, // dates (collection) |
| "Some edition", |
| null, // edition date |
| null, // identifiers (collection) |
| null, // cited responsibly parties (collection) |
| "PresentationForm.MAP_DIGITAL", |
| "PresentationForm.MAP_HARDCOPY", |
| null, // series |
| "Some other details", |
| // null, // collective title -- deprecated as of ISO 19115:2014. |
| null, // ISBN |
| null, // ISSN |
| null, // onlineResources (collection) |
| null // graphics (collection) |
| }; |
| assertEquals(-1, children.titleProperty); |
| assertFalse (children.isEmpty()); |
| assertEquals(expected.length, children.size()); |
| assertAllNextEqual(expected, children.iterator()); |
| } |
| |
| |
| // ------------------------ Support methods for the above tests ------------------------ |
| |
| /** |
| * Returns the string representation of the user object in the given node. |
| * This is the value that we are going compare in the assertion methods below. |
| * |
| * <p>We use only {@link TreeNode#getUserObject()}, nothing else, |
| * because the purpose of this class is not to test {@link TreeNode}.</p> |
| */ |
| private static String valueOf(final TreeTable.Node node) { |
| final Object value = node.getUserObject(); |
| return (value != null) ? value.toString() : null; |
| } |
| |
| /** |
| * Asserts that the string representation of user objects of all next element are equal |
| * to the expected strings. |
| */ |
| private static void assertAllNextEqual(final String[] expected, final Iterator<TreeTable.Node> it) { |
| for (final String e : expected) { |
| assertTrue(it.hasNext(), e); |
| assertEquals(e, valueOf(it.next())); |
| } |
| assertFalse(it.hasNext()); |
| } |
| |
| /** |
| * Asserts that all next elements traversed by the {@code actual} iterator are equal |
| * to the next elements traversed by {@code expected}. |
| * |
| * @param expected the iterator over expected values. |
| * @param actual the iterator over actual values. |
| */ |
| private static void assertAllNextEqual(final Iterator<?> expected, final Iterator<?> actual) { |
| while (expected.hasNext()) { |
| assertTrue(actual.hasNext()); |
| assertEquals(expected.next(), actual.next()); |
| } |
| assertFalse(actual.hasNext()); |
| } |
| |
| /** |
| * Tests the {@link Iterator#remove()} operation on the given collection of children. |
| * Elements are removed randomly until the collection is empty. After each removal, |
| * the remaining elements are compared with the content of a standard Java collection. |
| * |
| * @param random a random number generator. |
| * @param children the collection from which to remove elements. |
| */ |
| private static void testRemove(final Random random, final TreeNodeChildren children) { |
| final List<TreeTable.Node> reference = new ArrayList<>(children); |
| assertFalse(reference.isEmpty()); |
| do { |
| final Iterator<TreeTable.Node> rit = reference.iterator(); // The reference iterator. |
| final Iterator<TreeTable.Node> cit = children .iterator(); // The children iterator to be tested. |
| while (rit.hasNext()) { |
| assertTrue(cit.hasNext()); |
| assertSame(rit.next(), cit.next()); |
| if (random.nextInt(3) == 0) { // Remove only 1/3 of entries on each pass. |
| rit.remove(); |
| cit.remove(); |
| assertAllNextEqual(reference.iterator(), children.iterator()); |
| } |
| } |
| } while (!reference.isEmpty()); |
| assertTrue(children.isEmpty()); |
| } |
| } |