Author: desruisseaux Date: Tue Oct 6 22:31:21 2015 New Revision: 1707160 URL: http://svn.apache.org/viewvc?rev=1707160&view=rev Log: Partial fix of holes in metadata immutability (SIS-107). Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -18,11 +18,13 @@ package org.apache.sis.metadata; import java.util.Map; import java.util.Set; +import java.util.EnumSet; import java.util.Iterator; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import org.apache.sis.util.collection.Containers; +import org.apache.sis.util.collection.CodeListSet; import org.apache.sis.internal.util.CollectionsExt; @@ -33,7 +35,7 @@ import org.apache.sis.internal.util.Coll * * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.3 + * @version 0.7 * @module */ final class Cloner extends org.apache.sis.internal.util.Cloner { @@ -53,6 +55,15 @@ final class Cloner extends org.apache.si } /** + * Recursively clones all elements in the given array. + */ + private void clones(final Object[] array) throws CloneNotSupportedException { + for (int i=0; i) { Collection collection = (Collection) object; final boolean isSet = (collection instanceof Set); - if (collection.isEmpty()) { - if (isSet) { - collection = Collections.EMPTY_SET; - } else { - collection = Collections.EMPTY_LIST; + final Object[] array = collection.toArray(); + switch (array.length) { + case 0: { + collection = isSet ? Collections.EMPTY_SET + : Collections.EMPTY_LIST; + break; } - } else { - final Object[] array = collection.toArray(); - for (int i=0; i) { + collection = Collections.unmodifiableSet(((EnumSet) collection).clone()); + } else if (collection instanceof CodeListSet) { + collection = Collections.unmodifiableSet(((CodeListSet) collection).clone()); + } else { + clones(array); + collection = CollectionsExt.immutableSet(false, array); + } + } else { + /* + * Do not use the SIS Checked* classes since we don't need type checking anymore. + * Conservatively assumes a List if we are not sure to have a Set since the list + * is less destructive (no removal of duplicated values). + */ + clones(array); + collection = Containers.unmodifiableList(array); } + break; } } return collection; Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -104,8 +104,7 @@ public abstract class ModifiableMetadata } /** - * A flag used for {@link #unmodifiable} in order to specify that - * {@link #freeze()} is under way. + * A sentinel value used for {@link #unmodifiable} in order to specify that {@link #freeze()} is under way. */ private static final ModifiableMetadata FREEZING = new Null(); @@ -134,7 +133,7 @@ public abstract class ModifiableMetadata * @see #checkWritePermission() */ public final boolean isModifiable() { - return unmodifiable != this; + return unmodifiable != this && unmodifiable != FREEZING; } /** @@ -240,10 +239,13 @@ public abstract class ModifiableMetadata * @see #freeze() */ protected void checkWritePermission() throws UnmodifiableMetadataException { - if (!isModifiable()) { - throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata)); + if (unmodifiable != null) { + if (unmodifiable == this) { + throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata)); + } else if (unmodifiable != FREEZING) { + unmodifiable = null; + } } - unmodifiable = null; } /** Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -48,7 +48,7 @@ import org.apache.sis.internal.jaxb.NonM * @author Cédric Briançon (Geomatys) * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.3 + * @version 0.7 * @module */ @XmlType(name = "MI_Objective_Type", propOrder = { @@ -175,23 +175,20 @@ public class DefaultObjective extends IS @Override @XmlElement(name = "identifier", required = true) public Collection getIdentifiers() { - return NonMarshalledAuthority.excludeOnMarshalling(super.getIdentifiers()); + return NonMarshalledAuthority.filterOnMarshalling(super.getIdentifiers()); } /** * Sets the code used to identify the objective. * - *

This method overwrites all previous identifiers with the given new values, - * except the XML identifiers ({@linkplain IdentifierSpace#ID ID}, - * {@linkplain IdentifierSpace#UUID UUID}, etc.), if any. We do not overwrite - * the XML identifiers because they are usually associated to object identity.

+ *

XML identifiers ({@linkplain IdentifierSpace#ID ID}, {@linkplain IdentifierSpace#UUID UUID}, etc.), + * are not affected by this method, unless they are explicitely provided in the given collection.

* * @param newValues The new identifiers values. */ - public void setIdentifiers(final Collection newValues) { - final Collection oldIds = NonMarshalledAuthority.filteredCopy(identifiers); + public void setIdentifiers(Collection newValues) { + newValues = NonMarshalledAuthority.setMarshallables(identifiers, newValues); identifiers = writeCollection(newValues, identifiers, Identifier.class); - NonMarshalledAuthority.replace(identifiers, oldIds); } /** Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -63,7 +63,7 @@ import static org.apache.sis.internal.me * @author Cédric Briançon (Geomatys) * @author Rémi Maréchal (Geomatys) * @since 0.3 - * @version 0.5 + * @version 0.7 * @module */ @XmlType(name = "CI_Citation_Type", propOrder = { @@ -364,29 +364,25 @@ public class DefaultCitation extends ISO @Override @XmlElement(name = "identifier") public Collection getIdentifiers() { - return NonMarshalledAuthority.excludeOnMarshalling(super.getIdentifiers()); + return NonMarshalledAuthority.filterOnMarshalling(super.getIdentifiers()); } /** * Sets the unique identifier for the resource. * Example: Universal Product Code (UPC), National Stock Number (NSN). * - *

This method overwrites all previous identifiers with the given new values, - * except the XML identifiers ({@linkplain IdentifierSpace#ID ID}, - * {@linkplain IdentifierSpace#UUID UUID}, etc.), ISBN and ISSN codes, if any. - * We do not overwrite the XML identifiers because they are usually associated to object - * identity, and we do not overwrite ISBN/ISSN codes because they have dedicated setters - * for compliance with the ISO 19115 model.

+ *

XML identifiers ({@linkplain IdentifierSpace#ID ID}, {@linkplain IdentifierSpace#UUID UUID}, etc.), + * {@linkplain #getISBN() ISBN} and {@linkplain #getISSN() ISSN} codes are not affected by this method, unless + * they are explicitely provided in the given collection.

* * @param newValues The new identifiers, or {@code null} if none. * * @see #setISBN(String) * @see #setISSN(String) */ - public void setIdentifiers(final Collection newValues) { - final Collection oldIds = NonMarshalledAuthority.filteredCopy(identifiers); + public void setIdentifiers(Collection newValues) { + newValues = NonMarshalledAuthority.setMarshallables(identifiers, newValues); identifiers = writeCollection(newValues, identifiers, Identifier.class); - NonMarshalledAuthority.replace(identifiers, oldIds); } /** Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -20,14 +20,13 @@ import java.util.Arrays; import javax.measure.unit.SI; import org.opengis.metadata.citation.Role; import org.opengis.metadata.citation.PresentationForm; -import org.opengis.util.InternationalString; import org.apache.sis.util.collection.TableColumn; import org.apache.sis.util.collection.TreeTableFormat; import org.apache.sis.util.iso.SimpleInternationalString; import org.apache.sis.metadata.iso.content.DefaultBand; import org.apache.sis.metadata.iso.content.DefaultImageDescription; import org.apache.sis.metadata.iso.citation.DefaultCitation; -import org.apache.sis.metadata.iso.citation.DefaultIndividual; +import org.apache.sis.metadata.iso.citation.DefaultCitationTest; import org.apache.sis.metadata.iso.citation.DefaultResponsibility; import org.apache.sis.metadata.iso.content.DefaultAttributeGroup; import org.apache.sis.metadata.iso.identification.DefaultDataIdentification; @@ -76,53 +75,41 @@ public final strictfp class TreeTableFor } /** - * Creates the citation to use for testing purpose. - */ - private static DefaultCitation createCitation() { - final DefaultCitation citation = new DefaultCitation(); - final InternationalString title = new SimpleInternationalString("Undercurrent"); - citation.setTitle(title); - citation.setISBN("9782505004509"); - citation.setPresentationForms(asList( - PresentationForm.DOCUMENT_HARDCOPY, - PresentationForm.IMAGE_HARDCOPY)); - citation.setAlternateTitles(asList( - new SimpleInternationalString("Alt A"), - new SimpleInternationalString("Alt B"))); - citation.setCitedResponsibleParties(asList( - new DefaultResponsibility(Role.AUTHOR, null, new DefaultIndividual("Testsuya Toyoda", null, null)), - new DefaultResponsibility(null, null, new DefaultIndividual("A japanese author", null, null)))); - return citation; - } - - /** * Tests the formatting of a {@link DefaultCitation} object. */ @Test public void testCitation() { - final DefaultCitation citation = createCitation(); + final DefaultCitation citation = DefaultCitationTest.create(); final String text = format.format(citation.asTreeTable()); assertMultilinesEquals( "Citation\n" + - "  ├─Title……………………………………………………………………… Undercurrent\n" + - "  ├─Alternate title (1 of 2)…………………… Alt A\n" + - "  ├─Alternate title (2 of 2)…………………… Alt B\n" + + "  ├─Title…………………………………………………………………………… Undercurrent\n" + + "  ├─Alternate title………………………………………………… Andākarento\n" + "  ├─Identifier\n" + - "  │   ├─Code……………………………………………………………… 9782505004509\n" + + "  │   ├─Code…………………………………………………………………… 9782505004509\n" + "  │   ├─Authority\n" + - "  │   │   ├─Title………………………………………………… International Standard Book Number\n" + - "  │   │   └─Alternate title……………………… ISBN\n" + - "  │   └─Code space……………………………………………… ISBN\n"+ + "  │   │   ├─Title……………………………………………………… International Standard Book Number\n" + + "  │   │   └─Alternate title…………………………… ISBN\n" + + "  │   └─Code space…………………………………………………… ISBN\n"+ "  ├─Cited responsible party (1 of 2)\n" + "  │   ├─Party\n" + - "  │   │   └─Name…………………………………………………… Testsuya Toyoda\n" + - "  │   └─Role……………………………………………………………… Author\n" + + "  │   │   └─Name………………………………………………………… Testsuya Toyoda\n" + + "  │   └─Role…………………………………………………………………… Author\n" + "  ├─Cited responsible party (2 of 2)\n" + - "  │   └─Party\n" + - "  │       └─Name…………………………………………………… A japanese author\n" + - "  ├─Presentation form (1 of 2)……………… Document hardcopy\n" + - "  ├─Presentation form (2 of 2)……………… Image hardcopy\n" + - "  └─ISBN………………………………………………………………………… 9782505004509\n", text); + "  │   ├─Party\n" + + "  │   │   └─Name………………………………………………………… Kōdansha\n" + + "  │   ├─Role…………………………………………………………………… Editor\n" + + "  │   └─Extent\n" + + "  │       ├─Description……………………………………… World\n" + + "  │       └─Geographic element\n" + + "  │           ├─West bound longitude…… 180°W\n" + + "  │           ├─East bound longitude…… 180°E\n" + + "  │           ├─South bound latitude…… 90°S\n" + + "  │           ├─North bound latitude…… 90°N\n" + + "  │           └─Extent type code……………… true\n" + + "  ├─Presentation form (1 of 2)…………………… Document digital\n" + + "  ├─Presentation form (2 of 2)…………………… Document hardcopy\n" + + "  └─ISBN……………………………………………………………………………… 9782505004509\n", text); } /** Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -18,12 +18,22 @@ package org.apache.sis.metadata.iso.cita import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Locale; import org.opengis.metadata.Identifier; +import org.opengis.metadata.citation.Role; +import org.opengis.metadata.citation.Responsibility; +import org.opengis.metadata.citation.PresentationForm; +import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.xml.IdentifierMap; +import org.apache.sis.metadata.iso.extent.Extents; import org.apache.sis.metadata.iso.DefaultIdentifier; +import org.apache.sis.util.iso.SimpleInternationalString; +import org.apache.sis.util.iso.DefaultInternationalString; import org.apache.sis.test.TestCase; import org.junit.Test; +import static org.apache.sis.test.TestUtilities.getSingleton; import static org.junit.Assert.*; @@ -32,11 +42,36 @@ import static org.junit.Assert.*; * * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.3 + * @version 0.7 * @module */ public final strictfp class DefaultCitationTest extends TestCase { /** + * Creates a citation with an arbitrary title, presentation form and other properties. + * + * @return An arbitrary citation. + * + * @since 0.7 + */ + public static DefaultCitation create() { + final DefaultCitation citation = new DefaultCitation(); + final DefaultInternationalString title = new DefaultInternationalString(); + title.add(Locale.JAPANESE, "アンダーカレント"); + title.add(Locale.ENGLISH, "Undercurrent"); + citation.setTitle(title); + citation.setISBN("9782505004509"); + citation.setPresentationForms(Arrays.asList( + PresentationForm.DOCUMENT_HARDCOPY, + PresentationForm.DOCUMENT_DIGITAL)); + citation.setAlternateTitles(Collections.singleton( + new SimpleInternationalString("Andākarento"))); // Actually a different script of the Japanese title. + citation.setCitedResponsibleParties(Arrays.asList( + new DefaultResponsibility(Role.AUTHOR, null, new DefaultIndividual("Testsuya Toyoda", null, null)), + new DefaultResponsibility(Role.EDITOR, Extents.WORLD, new DefaultOrganisation("Kōdansha", null, null, null)))); + return citation; + } + + /** * Tests the identifier map, which handles ISBN and ISSN codes in a special way. */ @Test @@ -61,11 +96,67 @@ public final strictfp class DefaultCitat citation.setIdentifiers(Arrays.asList( new DefaultIdentifier(Citations.NETCDF, "MyNetCDF"), new DefaultIdentifier(Citations.EPSG, "MyEPSG"), - new DefaultIdentifier(Citations.ISBN, "MyIgnored"), + new DefaultIdentifier(Citations.ISBN, "NewISBN"), new DefaultIdentifier(Citations.ISSN, "MyISSN"))); - assertEquals("The ISBN value shall not have been overwritten.", "MyISBN", citation.getISBN()); + assertEquals("The ISBN value shall have been overwritten.", "NewISBN", citation.getISBN()); assertEquals("The ISSN value shall have been added, because new.", "MyISSN", citation.getISSN()); - assertEquals("{NetCDF=“MyNetCDF”, EPSG=“MyEPSG”, ISSN=“MyISSN”, ISBN=“MyISBN”}", identifierMap.toString()); + assertEquals("{NetCDF=“MyNetCDF”, EPSG=“MyEPSG”, ISBN=“NewISBN”, ISSN=“MyISSN”}", identifierMap.toString()); + } + + /** + * Tests {@link DefaultCitation#freeze()}, which is needed for the constants defined in {@link Citations}. + */ + @Test + public void testFreeze() { + final DefaultCitation original = create(); + final DefaultCitation clone = (DefaultCitation) original.unmodifiable(); // This will invoke 'freeze()'. + assertNotSame(original, clone); + assertTrue ("original.isModifiable", original.isModifiable()); + assertFalse( "clone.isModifiable", clone.isModifiable()); + assertSame ("original.unmodifiable", clone, original.unmodifiable()); + assertSame ( "clone.unmodifiable", clone, clone.unmodifiable()); + + assertSame ("ISBN", original.getISBN(), clone.getISBN()); + assertSame ("title", original.getTitle(), clone.getTitle()); + assertSame ("alternateTitle", getSingleton(original.getAlternateTitles()), + getSingleton(clone.getAlternateTitles())); + + assertCopy(original.getIdentifiers(), clone.getIdentifiers()); + assertCopy(original.getCitedResponsibleParties(), clone.getCitedResponsibleParties()); + assertCopy(original.getPresentationForms(), clone.getPresentationForms()); + /* + * Verify the unique identifier, which is the ISBN code. ISBN and ISSN codes are handled + * in a special way by DefaultCitation (they are instances of SpecializedIdentifier), but + * the should nevertheless be cloned. + */ + final Identifier ide = getSingleton(original.getIdentifiers()); + final Identifier ida = getSingleton( clone.getIdentifiers()); + assertNotSame("identifier", ide, ida); + assertSame("code", ide.getCode(), ida.getCode()); + assertSame("authority", ide.getAuthority(), ida.getAuthority()); + /* + * Verify the author metadata. + */ + final Responsibility re = CollectionsExt.first(original.getCitedResponsibleParties()); + final Responsibility ra = CollectionsExt.first(clone .getCitedResponsibleParties()); + assertNotSame("citedResponsibleParty", re, ra); + assertSame("role", re.getRole(), ra.getRole()); + assertSame("name", getSingleton(re.getParties()).getName(), + getSingleton(ra.getParties()).getName()); + } + + /** + * Verifies that {@code actual} is an unmodifiable copy of {@code expected}. + */ + private static void assertCopy(final Collection expected, final Collection actual) { + assertNotSame("ModifiableMetadata.freeze() shall have copied the collection.", expected, actual); + assertEquals("The copied collection shall have the same content than the original.", expected, actual); + try { + actual.add(null); + fail("The copied collection shall be unmodifiable."); + } catch (UnsupportedOperationException e) { + // This is the expected exception. + } } } Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -17,12 +17,18 @@ package org.apache.sis.internal.jaxb; import java.util.Iterator; +import java.util.List; import java.util.ArrayList; +import java.util.Map; +import java.util.IdentityHashMap; import java.util.Collection; +import java.util.Collections; import org.opengis.metadata.Identifier; import org.opengis.metadata.citation.Citation; import org.apache.sis.internal.simple.CitationConstant; +import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.internal.util.UnmodifiableArrayList; +import org.apache.sis.util.collection.Containers; import org.apache.sis.xml.IdentifierSpace; @@ -57,7 +63,7 @@ import org.apache.sis.xml.IdentifierSpac * * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.6 + * @version 0.7 * @module * * @see IdentifierSpace @@ -73,6 +79,7 @@ public final class NonMarshalledAuthorit * mirror the constants defined in the {@link IdentifierSpace} interface * and {@link org.apache.sis.metadata.iso.citation.DefaultCitation} class. */ + @SuppressWarnings("FieldNameHidesFieldInSuperclass") public static final byte ID=0, UUID=1, HREF=2, XLINK=3, ISSN=4, ISBN=5; // If more codes are added, please update readResolve() below. @@ -103,6 +110,9 @@ public final class NonMarshalledAuthorit * "special" identifiers (ISO 19139 attributes, ISBN codes...), which are recognized by * the implementation class of their authority. * + *

This method is used for implementation of {@code getIdentifier()} methods (singular form) + * in public metadata objects.

+ * * @param The type of object used as identifier values. * @param identifiers The collection from which to get identifiers, or {@code null}. * @return The first identifier, or {@code null} if none. @@ -124,23 +134,26 @@ public final class NonMarshalledAuthorit * method is used when the given collection is expected to contains only one ISO 19115 * identifier. * - * @param The type of object used as identifier values. + *

This method is used for implementation of {@code setIdentifier(Identifier)} methods + * in public metadata objects.

+ * + * @param The type of object used as identifier values. * @param identifiers The collection in which to add the identifier. - * @param id The identifier to add, or {@code null}. + * @param newValue The identifier to add, or {@code null}. + * + * @see #setMarshallables(Collection, Collection) */ - public static void setMarshallable(final Collection identifiers, final T id) { + public static void setMarshallable(final Collection identifiers, final T newValue) { final Iterator it = identifiers.iterator(); while (it.hasNext()) { final T old = it.next(); - if (old != null) { - if (old.getAuthority() instanceof NonMarshalledAuthority) { - continue; // Don't touch this identifier. - } + if (old != null && old.getAuthority() instanceof NonMarshalledAuthority) { + continue; // Don't touch this identifier. } it.remove(); } - if (id != null) { - identifiers.add(id); + if (newValue != null) { + identifiers.add(newValue); } } @@ -149,10 +162,15 @@ public final class NonMarshalledAuthorit * for which the authority is an instance of {@code NonMarshalledAuthority}. This should exclude * all {@link org.apache.sis.xml.IdentifierSpace} constants. * + *

This method is used for implementation of {@code getIdentifiers()} methods (plural form) + * in public metadata objects. Note that those methods override + * {@link org.apache.sis.xml.IdentifiedObject#getIdentifiers()}, which is expected to return + * all identifiers in normal (non-marshalling) usage.

+ * * @param identifiers The identifiers to filter, or {@code null}. * @return The identifiers to marshal, or {@code null} if none. */ - public static Collection excludeOnMarshalling(Collection identifiers) { + public static Collection filterOnMarshalling(Collection identifiers) { if (identifiers != null && Context.isFlagSet(Context.current(), Context.MARSHALLING)) { int count = identifiers.size(); if (count != 0) { @@ -170,69 +188,72 @@ public final class NonMarshalledAuthorit } /** - * Returns a collection containing only the identifiers having a {@code NonMarshalledAuthority}. - * This method is invoked for saving the identifiers that are conceptually stored in distinct fields - * (XML identifier, UUID, ISBN, ISSN) before to overwrite the collection of all identifiers in - * a metadata object. - * - *

This method is invoked from {@code setIdentifiers(Collection)} implementation - * in {@link org.apache.sis.metadata.iso.ISOMetadata} subclasses as below:

- * - * {@preformat java - * final Collection oldIds = NonMarshalledAuthority.filteredCopy(identifiers); - * identifiers = writeCollection(newValues, identifiers, Identifier.class); - * NonMarshalledAuthority.replace(identifiers, oldIds); - * } + * Returns a collection containing all marshallable values of {@code newValues}, together with unmarshallable + * values of {@code identifiers}. This method is invoked for preserving the identifiers that are conceptually + * stored in distinct fields (XML identifier, UUID, ISBN, ISSN) when setting the collection of all identifiers + * in a metadata object. + * + *

This method is used for implementation of {@code setIdentifiers(Collection)} methods + * in public metadata objects.

* - * @param The type of object used as identifier values. * @param identifiers The metadata internal identifiers collection, or {@code null} if none. - * @return The new list containing the filtered identifiers, or {@code null} if none. + * @param newValues The identifiers to add, or {@code null}. + * @return The collection to set (may be {@code newValues}. + * + * @see #setMarshallable(Collection, Identifier) */ - public static Collection filteredCopy(final Collection identifiers) { - Collection filtered = null; - if (identifiers != null) { - int remaining = identifiers.size(); - for (final T candidate : identifiers) { - if (candidate != null && candidate.getAuthority() instanceof NonMarshalledAuthority) { - if (filtered == null) { - filtered = new ArrayList<>(remaining); - } - filtered.add(candidate); + @SuppressWarnings("null") + public static Collection setMarshallables( + final Collection identifiers, final Collection newValues) + { + int remaining; + if (identifiers == null || (remaining = identifiers.size()) == 0) { + return newValues; + } + /* + * If there is any identifiers that need to be preserved (XML identifier, UUID, ISBN, etc.), + * remember them. Otherwise there is nothing special to do and we can return the new values directly. + */ + List toPreserve = null; + for (final Identifier id : identifiers) { + if (id != null && id.getAuthority() instanceof NonMarshalledAuthority) { + if (toPreserve == null) { + toPreserve = new ArrayList<>(remaining); } - remaining--; + toPreserve.add(id); } + remaining--; } - return filtered; - } - - /** - * Replaces all identifiers in the {@code identifiers} collection having the same - * {@linkplain Identifier#getAuthority() authority} than the ones in {@code oldIds}. - * More specifically: - * - *
    - *
  • First, remove all {@code identifiers} elements having the same authority - * than one of the elements in {@code oldIds}.
  • - *
  • Next, add all {@code oldIds} elements to {@code identifiers}.
  • - *
- * - * @param The type of object used as identifier values. - * @param identifiers The metadata internal identifiers collection, or {@code null} if none. - * @param oldIds The previous filtered identifiers returned by {@link #filteredCopy(Collection)}, - * or {@code null} if none. - */ - public static void replace(final Collection identifiers, final Collection oldIds) { - if (oldIds != null && identifiers != null) { - for (final T old : oldIds) { - final Citation authority = old.getAuthority(); - for (final Iterator it=identifiers.iterator(); it.hasNext();) { - final T id = it.next(); - if (id == null || id.getAuthority() == authority) { - it.remove(); - } + if (toPreserve == null) { + return newValues; + } + /* + * We find at least one identifier that may need to be preserved. + * We need to create a combination of the two collections. + */ + final Map authorities = new IdentityHashMap<>(4); + final List merged = new ArrayList<>(newValues.size()); + for (final Identifier id : newValues) { + merged.add(id); + if (id != null) { + final Citation authority = id.getAuthority(); + if (authority instanceof NonMarshalledAuthority) { + authorities.put(authority, id); } } - identifiers.addAll(oldIds); + } + for (final Identifier id : toPreserve) { + if (!authorities.containsKey(id.getAuthority())) { + merged.add(id); + } + } + /* + * Wraps in an unmodifiable list in case the caller is creating an unmodifiable metadata. + */ + switch (merged.size()) { + case 0: return Collections.emptyList(); + case 1: return Collections.singletonList(merged.get(0)); + default: return Containers.unmodifiableList(CollectionsExt.toArray(merged, Identifier.class)); } } Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -45,10 +45,10 @@ import java.util.Objects; * * @author Martin Desruisseaux (Geomatys) * @since 0.3 - * @version 0.5 + * @version 0.7 * @module */ -public final class SpecializedIdentifier implements Identifier, Serializable { +public final class SpecializedIdentifier implements Identifier, Cloneable, Serializable { /** * For cross-version compatibility. */ @@ -247,6 +247,20 @@ public final class SpecializedIdentifier } /** + * Returns a clone of this identifier. + * + * @return A shallow clone of this identifier. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); // Should never happen, since we are cloneable. + } + } + + /** * Returns a string representation of this identifier. * Example: {@code Identifier[gco:uuid=“42924124-032a-4dfe-b06e-113e3cb81cf0”]}. * Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -547,7 +547,6 @@ public class CodeListSet clone() { - @SuppressWarnings("unchecked") final CodeListSet clone; try { clone = (CodeListSet) super.clone(); Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java?rev=1707160&r1=1707159&r2=1707160&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java [UTF-8] Tue Oct 6 22:31:21 2015 @@ -89,8 +89,7 @@ public final class Containers extends St * * @param The base type of elements in the list. * @param array The array to wrap, or {@code null} if none. - * @return The given array wrapped in an unmodifiable list, or {@code null} if the given - * array was null. + * @return The given array wrapped in an unmodifiable list, or {@code null} if the given array was null. * * @see java.util.Arrays#asList(Object[]) */ @@ -149,10 +148,9 @@ public final class Containers extends St * @param The type of elements in the storage (original) set. * @param The type of elements in the derived set. * @param storage The storage set containing the original elements, or {@code null}. - * @param converter The converter from the elements in the storage set to the elements - * in the derived set. - * @return A view over the {@code storage} set containing all elements converted by the given - * converter, or {@code null} if {@code storage} was null. + * @param converter The converter from the elements in the storage set to the elements in the derived set. + * @return A view over the {@code storage} set containing all elements converted by the given converter, + * or {@code null} if {@code storage} was null. * * @see org.apache.sis.util.ObjectConverters#derivedSet(Set, ObjectConverter) */ @@ -196,8 +194,8 @@ public final class Containers extends St * @param storage The storage map containing the original entries, or {@code null}. * @param keyConverter The converter from the keys in the storage map to the keys in the derived map. * @param valueConverter The converter from the values in the storage map to the values in the derived map. - * @return A view over the {@code storage} map containing all entries converted by the given - * converters, or {@code null} if {@code storage} was null. + * @return A view over the {@code storage} map containing all entries converted by the given converters, + * or {@code null} if {@code storage} was null. * * @see org.apache.sis.util.ObjectConverters#derivedMap(Map, ObjectConverter, ObjectConverter) * @see org.apache.sis.util.ObjectConverters#derivedKeys(Map, ObjectConverter, Class)