sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/01: Merge branch 'geoapi-3.1'.
Date Tue, 03 Jul 2018 13:39:54 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 969b7d05881b30523939aa8b9d825183ae23195f
Merge: 2063b69 585bddd
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Jul 3 15:37:52 2018 +0200

    Merge branch 'geoapi-3.1'.

 .gitattributes                                     |  44 ++
 .../java/org/apache/sis/console/package-info.java  |   2 +-
 .../src/main/java/org/apache/sis/gui/Main.java     | 204 ++++++
 .../org/apache/sis/gui/dataset/FeatureTable.java   | 169 +++++
 .../org/apache/sis/gui/dataset/ResourceTree.java   |  23 +-
 .../org/apache/sis/gui/metadata/MetadataNode.java  | 228 ++++++
 .../apache/sis/gui/metadata/MetadataOverview.java  | 403 ++++++++++
 .../org/apache/sis/gui/metadata/ResourceView.java  | 399 ++++++++++
 .../sis/gui/{crs => referencing}/CRSButton.java    |   2 +-
 .../sis/gui/{crs => referencing}/CRSChooser.java   |  81 +-
 .../sis/gui/{crs => referencing}/CRSTable.java     |  69 +-
 .../apache/sis/gui/{crs => referencing}/Code.java  |  54 +-
 .../WKTPane.java}                                  |  16 +-
 .../org/apache/sis/internal/gui/FXUtilities.java   |  91 +++
 .../org/apache/sis/internal/gui/FontGlyphs.java    |  13 +-
 .../apache/sis/internal/gui/JavaFxUtilities.java   |  98 ---
 .../apache/sis/{ => internal}/gui/Resources.java   |  94 +--
 .../sis/{ => internal}/gui/Resources.properties    |   9 +-
 .../sis/{ => internal}/gui/Resources_fr.properties |   9 +-
 .../org/apache/sis/internal/gui}/package-info.java |  10 +-
 .../org/apache/sis/gui/crs/CRSChooser.fxml         |  36 -
 .../apache/sis/gui/metadata/WorldMap360x180.png    | Bin 0 -> 1886 bytes
 .../org/apache/sis/gui/referencing/CRSChooser.fxml |  30 +
 .../sis/gui/{crs => referencing}/proj_conic.png    | Bin
 .../sis/gui/{crs => referencing}/proj_geo.png      | Bin
 .../sis/gui/{crs => referencing}/proj_square.png   | Bin
 .../sis/gui/{crs => referencing}/proj_stereo.png   | Bin
 .../sis/gui/{crs => referencing}/proj_utm.png      | Bin
 .../org/apache/sis/test/suite/package-info.txt     |   3 +
 .../apache/sis/feature/AbstractAssociation.java    |   2 -
 .../org/apache/sis/feature/AbstractAttribute.java  |   2 -
 .../org/apache/sis/feature/AbstractOperation.java  |   2 -
 .../apache/sis/feature/DefaultAssociationRole.java |   2 -
 .../apache/sis/feature/DefaultAttributeType.java   |   2 -
 .../java/org/apache/sis/feature/PropertyView.java  |   2 -
 .../apache/sis/feature/builder/TypeBuilder.java    |   2 -
 .../org/apache/sis/feature/CustomAttribute.java    |   2 +-
 .../java/org/apache/sis/internal/jaxb/Context.java |   1 +
 .../sis/internal/jaxb/IdentifierMapAdapter.java    |   3 -
 .../sis/internal/jaxb/NonMarshalledAuthority.java  |  18 +-
 .../sis/internal/jaxb/SpecializedIdentifier.java   |   2 -
 .../apache/sis/internal/jaxb/cat/CodeListUID.java  |   2 +-
 .../internal/jaxb/code/MD_CharacterSetLegacy.java  |   2 +-
 .../sis/internal/jaxb/code/MD_MediumNameCode.java  |   2 +-
 .../sis/internal/jaxb/code/package-info.java       |   2 +-
 .../sis/internal/jaxb/gco/GO_CharacterString.java  |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_DateTime.java  |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Decimal.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Integer.java   |   7 +-
 .../apache/sis/internal/jaxb/gco/GO_Integer64.java |   4 +-
 ...GO_Integer64.java => GO_MultiplicityRange.java} |  53 +-
 .../apache/sis/internal/jaxb/gco/GO_Record.java    |   2 +-
 .../sis/internal/jaxb/gco/GO_RecordType.java       |   2 +-
 ...{GO_Integer64.java => GO_UnlimitedInteger.java} |  46 +-
 .../apache/sis/internal/jaxb/gco/Multiplicity.java |  92 +++
 .../sis/internal/jaxb/gco/MultiplicityRange.java   | 107 +++
 .../sis/internal/jaxb/gco/UnlimitedInteger.java    | 120 +++
 .../sis/internal/jaxb/gmi/LE_ProcessStep.java      |   1 -
 .../apache/sis/internal/jaxb/gmi/LE_Source.java    |   1 -
 .../org/apache/sis/internal/jaxb/gmi/MI_Band.java  |   1 -
 .../internal/jaxb/gmi/MI_CoverageDescription.java  |   1 -
 .../sis/internal/jaxb/gmi/MI_Georectified.java     |   1 -
 .../sis/internal/jaxb/gmi/MI_Georeferenceable.java |   1 -
 .../sis/internal/jaxb/gmi/MI_ImageDescription.java |   1 -
 .../apache/sis/internal/jaxb/gmi/MI_Metadata.java  |   3 +-
 .../apache/sis/internal/jaxb/gmi/package-info.java |   2 +-
 .../apache/sis/internal/jaxb/gml/DateAdapter.java  |   2 +-
 .../org/apache/sis/internal/jaxb/gml/Measure.java  |   2 +-
 .../apache/sis/internal/jaxb/gml/TM_Primitive.java |   2 +-
 .../apache/sis/internal/jaxb/gml/TimeInstant.java  |   2 +-
 .../apache/sis/internal/jaxb/gml/TimePeriod.java   |   2 +-
 .../internal/jaxb/gml/UniversalTimeAdapter.java    |   6 +-
 .../sis/internal/jaxb/gts/TM_PeriodDuration.java   |   2 +-
 .../org/apache/sis/internal/jaxb/lan/Country.java  |   2 +-
 .../jaxb/lan/LocalisedCharacterString.java         |   2 -
 .../apache/sis/internal/jaxb/lan/PT_Locale.java    |   2 +-
 .../apache/sis/internal/jaxb/lan/TextGroup.java    |   2 -
 .../jaxb/metadata/CI_ResponsibleParty.java         |   4 +-
 .../sis/internal/jaxb/metadata/RS_Identifier.java  |  11 +-
 .../metadata/replace/ReferenceSystemMetadata.java  |   2 +-
 .../jaxb/metadata/replace/ServiceParameter.java    |   2 +-
 .../jaxb/metadata/replace/package-info.java        |   2 +-
 .../org/apache/sis/internal/jaxb/package-info.java |   1 +
 .../sis/internal/metadata/MetadataTypes.java       |   4 +-
 .../sis/internal/metadata/ReferencingServices.java |  16 +-
 .../sis/internal/metadata/sql/SQLBuilder.java      |  16 +-
 .../sis/internal/simple/CitationConstant.java      |   2 -
 .../apache/sis/internal/simple/SimpleCitation.java |   2 -
 .../apache/sis/internal/simple/SimpleFormat.java   |   2 -
 .../sis/internal/simple/SimpleIdentifier.java      |   2 -
 .../internal/{jaxb => xml}/LegacyNamespaces.java   |   2 +-
 .../apache/sis/internal/{jaxb => xml}/Schemas.java |   2 +-
 .../sis/internal/xml}/StreamWriterDelegate.java    |   4 +-
 .../sis/internal/{jaxb => xml}/XmlUtilities.java   |   3 +-
 .../org/apache/sis/internal/xml}/package-info.java |  16 +-
 .../main/java/org/apache/sis/io/wkt/Element.java   |   1 -
 .../main/java/org/apache/sis/io/wkt/Formatter.java |   2 -
 .../org/apache/sis/metadata/AbstractMetadata.java  |  29 +-
 .../java/org/apache/sis/metadata/CacheKey.java     |   2 -
 .../java/org/apache/sis/metadata/HashCode.java     | 105 +++
 .../org/apache/sis/metadata/MetadataCopier.java    | 180 +++--
 .../org/apache/sis/metadata/MetadataStandard.java  |  57 +-
 .../org/apache/sis/metadata/MetadataVisitor.java   | 286 ++++++++
 .../sis/metadata/MetadataVisitorException.java     | 100 +++
 .../apache/sis/metadata/ModifiableMetadata.java    | 309 +++++---
 .../java/org/apache/sis/metadata/ObjectPair.java   |   2 -
 .../org/apache/sis/metadata/PropertyAccessor.java  | 205 +++---
 .../main/java/org/apache/sis/metadata/Pruner.java  | 281 +++----
 .../org/apache/sis/metadata/RecursivityGuard.java  |  69 --
 .../sis/metadata/StandardImplementation.java       |   2 +-
 .../metadata/{Freezer.java => StateChanger.java}   | 121 ++-
 .../java/org/apache/sis/metadata/TreeNode.java     |   2 -
 .../org/apache/sis/metadata/TreeNodeChildren.java  |   2 -
 .../metadata/UnmodifiableMetadataException.java    |   5 +-
 .../iso/DefaultApplicationSchemaInformation.java   |   1 -
 .../iso/DefaultExtendedElementInformation.java     |   3 +-
 .../apache/sis/metadata/iso/DefaultIdentifier.java |   1 -
 .../apache/sis/metadata/iso/DefaultMetadata.java   |  18 +-
 .../iso/DefaultMetadataExtensionInformation.java   |   1 -
 .../sis/metadata/iso/DefaultMetadataScope.java     |   1 -
 .../iso/DefaultPortrayalCatalogueReference.java    |   1 -
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |  72 +-
 .../sis/metadata/iso/ImmutableIdentifier.java      |   2 +
 .../sis/metadata/iso/MetadataScopeAdapter.java     |   2 +-
 .../acquisition/DefaultAcquisitionInformation.java |   1 -
 .../acquisition/DefaultEnvironmentalRecord.java    |   1 -
 .../sis/metadata/iso/acquisition/DefaultEvent.java |   9 +-
 .../iso/acquisition/DefaultInstrument.java         |   9 +-
 .../metadata/iso/acquisition/DefaultObjective.java |   1 -
 .../metadata/iso/acquisition/DefaultOperation.java |   9 +-
 .../sis/metadata/iso/acquisition/DefaultPlan.java  |   1 -
 .../metadata/iso/acquisition/DefaultPlatform.java  |   9 +-
 .../iso/acquisition/DefaultPlatformPass.java       |   9 +-
 .../iso/acquisition/DefaultRequestedDate.java      |   1 -
 .../iso/acquisition/DefaultRequirement.java        |   9 +-
 .../sis/metadata/iso/citation/AbstractParty.java   |   1 -
 .../sis/metadata/iso/citation/Citations.java       |   2 +-
 .../sis/metadata/iso/citation/DefaultAddress.java  |   1 -
 .../sis/metadata/iso/citation/DefaultCitation.java |   3 +-
 .../metadata/iso/citation/DefaultCitationDate.java |   1 -
 .../sis/metadata/iso/citation/DefaultContact.java  |   3 +-
 .../metadata/iso/citation/DefaultIndividual.java   |   1 -
 .../iso/citation/DefaultOnlineResource.java        |   1 -
 .../metadata/iso/citation/DefaultOrganisation.java |   1 -
 .../iso/citation/DefaultResponsibility.java        |   2 +-
 .../iso/citation/DefaultResponsibleParty.java      |   3 +-
 .../sis/metadata/iso/citation/DefaultSeries.java   |   1 -
 .../metadata/iso/citation/DefaultTelephone.java    |   5 +-
 .../sis/metadata/iso/citation/package-info.java    |   2 +-
 .../iso/constraint/DefaultConstraints.java         |   1 -
 .../iso/constraint/DefaultLegalConstraints.java    |   1 -
 .../iso/constraint/DefaultReleasability.java       |   1 -
 .../iso/constraint/DefaultSecurityConstraints.java |   1 -
 .../iso/content/AbstractContentInformation.java    |   1 -
 .../iso/content/DefaultAttributeGroup.java         |   1 -
 .../sis/metadata/iso/content/DefaultBand.java      |   1 -
 .../iso/content/DefaultCoverageDescription.java    |   3 +-
 .../DefaultFeatureCatalogueDescription.java        |   3 +-
 .../iso/content/DefaultFeatureTypeInfo.java        |   1 -
 .../iso/content/DefaultImageDescription.java       |   3 +-
 .../iso/content/DefaultRangeDimension.java         |   3 +-
 .../content/DefaultRangeElementDescription.java    |   1 -
 .../iso/content/DefaultSampleDimension.java        |   1 -
 .../sis/metadata/iso/content/package-info.java     |   2 +-
 .../metadata/iso/distribution/DefaultDataFile.java |   3 +-
 .../DefaultDigitalTransferOptions.java             |   1 -
 .../iso/distribution/DefaultDistribution.java      |   1 -
 .../iso/distribution/DefaultDistributor.java       |   1 -
 .../metadata/iso/distribution/DefaultFormat.java   |   3 +-
 .../metadata/iso/distribution/DefaultMedium.java   |  11 +-
 .../distribution/DefaultStandardOrderProcess.java  |   1 -
 .../metadata/iso/distribution/package-info.java    |   2 +-
 .../iso/extent/AbstractGeographicExtent.java       |   1 -
 .../iso/extent/DefaultBoundingPolygon.java         |   1 -
 .../sis/metadata/iso/extent/DefaultExtent.java     |   1 -
 .../iso/extent/DefaultGeographicBoundingBox.java   |   1 -
 .../iso/extent/DefaultGeographicDescription.java   |   1 -
 .../iso/extent/DefaultSpatialTemporalExtent.java   |   1 -
 .../metadata/iso/extent/DefaultTemporalExtent.java |   1 -
 .../metadata/iso/extent/DefaultVerticalExtent.java |   1 -
 .../iso/identification/AbstractIdentification.java |   3 +-
 .../DefaultAggregateInformation.java               |   3 +-
 .../identification/DefaultAssociatedResource.java  |   1 -
 .../iso/identification/DefaultBrowseGraphic.java   |   1 -
 .../iso/identification/DefaultCoupledResource.java |   7 +-
 .../identification/DefaultDataIdentification.java  |   3 +-
 .../iso/identification/DefaultKeywordClass.java    |   1 -
 .../iso/identification/DefaultKeywords.java        |   1 -
 .../DefaultOperationChainMetadata.java             |   1 -
 .../identification/DefaultOperationMetadata.java   |   1 -
 .../iso/identification/DefaultResolution.java      |   1 -
 .../DefaultServiceIdentification.java              |   1 -
 .../metadata/iso/identification/DefaultUsage.java  |   1 -
 .../metadata/iso/identification/package-info.java  |   2 +-
 .../sis/metadata/iso/lineage/DefaultAlgorithm.java |   1 -
 .../sis/metadata/iso/lineage/DefaultLineage.java   |   1 -
 .../iso/lineage/DefaultNominalResolution.java      |   1 -
 .../metadata/iso/lineage/DefaultProcessStep.java   |   3 +-
 .../iso/lineage/DefaultProcessStepReport.java      |   1 -
 .../metadata/iso/lineage/DefaultProcessing.java    |   9 +-
 .../sis/metadata/iso/lineage/DefaultSource.java    |  18 +-
 .../sis/metadata/iso/lineage/package-info.java     |   2 +-
 .../maintenance/DefaultMaintenanceInformation.java |   3 +-
 .../sis/metadata/iso/maintenance/DefaultScope.java |   1 -
 .../iso/maintenance/DefaultScopeDescription.java   |   1 -
 .../sis/metadata/iso/maintenance/package-info.java |   2 +-
 .../org/apache/sis/metadata/iso/package-info.java  |   2 +-
 .../metadata/iso/quality/AbstractCompleteness.java |   1 -
 .../sis/metadata/iso/quality/AbstractElement.java  |   3 +-
 .../iso/quality/AbstractLogicalConsistency.java    |   1 -
 .../iso/quality/AbstractPositionalAccuracy.java    |   1 -
 .../sis/metadata/iso/quality/AbstractResult.java   |   1 -
 .../iso/quality/AbstractTemporalAccuracy.java      |   1 -
 .../iso/quality/AbstractThematicAccuracy.java      |   1 -
 .../DefaultAbsoluteExternalPositionalAccuracy.java |   1 -
 .../quality/DefaultAccuracyOfATimeMeasurement.java |   1 -
 .../iso/quality/DefaultCompletenessCommission.java |   1 -
 .../iso/quality/DefaultCompletenessOmission.java   |   1 -
 .../iso/quality/DefaultConceptualConsistency.java  |   1 -
 .../iso/quality/DefaultConformanceResult.java      |   1 -
 .../iso/quality/DefaultCoverageResult.java         |   1 -
 .../metadata/iso/quality/DefaultDataQuality.java   |   3 +-
 .../iso/quality/DefaultDomainConsistency.java      |   1 -
 .../iso/quality/DefaultFormatConsistency.java      |   1 -
 .../DefaultGriddedDataPositionalAccuracy.java      |   1 -
 .../DefaultNonQuantitativeAttributeAccuracy.java   |   1 -
 .../DefaultQuantitativeAttributeAccuracy.java      |   1 -
 .../iso/quality/DefaultQuantitativeResult.java     |   3 +-
 .../DefaultRelativeInternalPositionalAccuracy.java |   1 -
 .../sis/metadata/iso/quality/DefaultScope.java     |   1 -
 .../iso/quality/DefaultTemporalConsistency.java    |   1 -
 .../iso/quality/DefaultTemporalValidity.java       |   1 -
 .../DefaultThematicClassificationCorrectness.java  |   1 -
 .../iso/quality/DefaultTopologicalConsistency.java |   1 -
 .../sis/metadata/iso/quality/DefaultUsability.java |   3 +-
 .../sis/metadata/iso/quality/package-info.java     |   2 +-
 .../spatial/AbstractGeolocationInformation.java    |   1 -
 .../iso/spatial/AbstractSpatialRepresentation.java |   1 -
 .../sis/metadata/iso/spatial/DefaultDimension.java |   1 -
 .../sis/metadata/iso/spatial/DefaultGCP.java       |   1 -
 .../metadata/iso/spatial/DefaultGCPCollection.java |   1 -
 .../iso/spatial/DefaultGeometricObjects.java       |   1 -
 .../metadata/iso/spatial/DefaultGeorectified.java  |   1 -
 .../iso/spatial/DefaultGeoreferenceable.java       |   1 -
 .../spatial/DefaultGridSpatialRepresentation.java  |   1 -
 .../DefaultVectorSpatialRepresentation.java        |   1 -
 .../java/org/apache/sis/metadata/package-info.java |   2 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |  42 +-
 .../apache/sis/metadata/sql/MetadataWriter.java    |  59 +-
 .../apache/sis/metadata/sql/TableHierarchy.java    | 118 +++
 .../org/apache/sis/util/iso/DefaultNameSpace.java  |   2 -
 .../org/apache/sis/util/iso/DefaultRecord.java     |   2 -
 .../apache/sis/util/iso/DefaultRecordSchema.java   |   2 -
 .../org/apache/sis/util/iso/RecordDefinition.java  |   2 -
 .../main/java/org/apache/sis/xml/InputFactory.java |  29 +-
 .../main/java/org/apache/sis/xml/Namespaces.java   |   5 +-
 .../java/org/apache/sis/xml/OutputFactory.java     |  31 +-
 .../src/main/java/org/apache/sis/xml/Pooled.java   |   2 +-
 .../java/org/apache/sis/xml/TransformVersion.java  |   2 +-
 .../main/java/org/apache/sis/xml/Transformer.java  |  22 +-
 .../org/apache/sis/xml/TransformingReader.java     |  29 +-
 .../org/apache/sis/xml/TransformingWriter.java     |  17 +-
 .../internal/jaxb/cat/CodeListMarshallingTest.java |   4 +-
 .../sis/internal/jaxb/gco/MultiplicityTest.java    | 124 ++++
 .../apache/sis/internal/jaxb/gml/MeasureTest.java  |   2 +-
 .../sis/internal/jaxb/gml/TimePeriodTest.java      |   2 +-
 .../internal/jaxb/lan/FreeTextMarshallingTest.java |   2 +-
 .../sis/internal/jaxb/lan/LanguageCodeTest.java    |   4 +-
 .../sis/internal/jaxb/lan/PT_LocaleTest.java       |  12 +-
 .../internal/{jaxb => xml}/XmlUtilitiesTest.java   |   2 +-
 .../java/org/apache/sis/metadata/HashCodeTest.java | 154 ++++
 .../apache/sis/metadata/PropertyAccessorTest.java  |  23 -
 .../java/org/apache/sis/metadata/PrunerTest.java   |   4 +-
 .../sis/metadata/iso/ImmutableIdentifierTest.java  |   2 +-
 .../metadata/iso/citation/DefaultCitationTest.java |  54 +-
 .../iso/citation/DefaultResponsibilityTest.java    |   2 +-
 .../constraint/DefaultLegalConstraintsTest.java    |   2 +-
 .../DefaultRepresentativeFractionTest.java         |   8 +-
 .../iso/identification/DefaultResolutionTest.java  |   4 +-
 .../metadata/iso/lineage/DefaultLineageTest.java   |   2 +-
 .../java/org/apache/sis/test/MetadataAssert.java   |   2 +-
 .../java/org/apache/sis/test/XMLComparator.java    |   2 +-
 .../test/java/org/apache/sis/test/XMLTestCase.java |  29 +-
 .../FeatureAttributeMock.java}                     |  32 +-
 .../org/apache/sis/test/mock/MetadataMock.java     |   2 +-
 .../org/apache/sis/test/mock/package-info.java     |   5 +-
 .../apache/sis/test/suite/MetadataTestSuite.java   |   4 +-
 .../sis/test/xml/AnnotationConsistencyCheck.java   |   7 +-
 .../org/apache/sis/test/xml/PackageVerifier.java   | 513 -------------
 .../org/apache/sis/test/xml/SchemaCompliance.java  | 566 --------------
 .../apache/sis/util/iso/NameMarshallingTest.java   |   2 +-
 .../sis/xml/CharSequenceSubstitutionTest.java      |  14 +-
 .../org/apache/sis/xml/RenameListGenerator.java    | 213 ------
 .../apache/sis/xml/TransformingNamespacesTest.java |   2 +-
 .../apache/sis/metadata/xml/2007/Multiplicity.xml  |  56 ++
 .../apache/sis/metadata/xml/2016/Multiplicity.xml  |  55 ++
 .../org/apache/sis/test/suite/package-info.txt     |   3 +
 core/sis-raster/pom.xml                            |  16 +-
 .../sis/coverage/grid/GridCoordinatesView.java     | 136 ++++
 .../org/apache/sis/coverage/grid/GridExtent.java   | 491 +++++++++++++
 .../org/apache/sis/coverage/grid/GridGeometry.java | 815 +++++++++++++++++++++
 .../grid/IncompleteGridGeometryException.java      |  70 ++
 .../apache/sis/coverage/grid/PixelTranslation.java | 366 +++++++++
 .../apache/sis/coverage/grid/package-info.java}    |  17 +-
 .../java/org/apache/sis/image/DefaultIterator.java |   4 +-
 .../java/org/apache/sis/image/PixelIterator.java   |   4 +-
 .../java/org/apache/sis/image/TransferType.java    |   4 +-
 .../apache/sis/image/WritablePixelIterator.java    |   4 +-
 .../java/org/apache/sis/image/package-info.java    |   4 +-
 .../org/apache/sis/internal/raster/Resources.java  |  45 +-
 .../sis/internal/raster/Resources.properties       |   4 +
 .../sis/internal/raster/Resources_fr.properties    |   4 +
 .../apache/sis/internal/raster/package-info.java   |   4 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java | 190 +++++
 .../sis/coverage/grid/PixelTranslationTest.java    | 124 ++++
 .../org/apache/sis/image/DefaultIteratorTest.java  |   4 +-
 .../org/apache/sis/test/suite/RasterTestSuite.java |   8 +-
 .../referencing/gazetteer/AbstractLocation.java    |   2 -
 .../gazetteer/MilitaryGridReferenceSystem.java     |   2 -
 .../sis/geometry/AbstractDirectPosition.java       |   3 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   3 +-
 .../org/apache/sis/geometry/DirectPosition1D.java  |   3 +-
 .../org/apache/sis/geometry/DirectPosition2D.java  |   4 +-
 .../org/apache/sis/geometry/GeneralEnvelope.java   |  12 +-
 .../referencing/CC_GeneralOperationParameter.java  |   2 +-
 .../referencing/PositionalAccuracyConstant.java    |   2 -
 .../internal/referencing/ServicesForMetadata.java  |  32 +-
 .../referencing/provider/DatumShiftGridFile.java   |   2 -
 .../sis/parameter/AbstractParameterDescriptor.java |   1 -
 .../java/org/apache/sis/parameter/Parameters.java  |   1 -
 .../org/apache/sis/parameter/TensorParameters.java |   9 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |   5 +-
 .../apache/sis/referencing/IdentifiedObjects.java  |  34 +-
 .../apache/sis/referencing/NamedIdentifier.java    |  60 +-
 .../sis/referencing/cs/DirectionAlongMeridian.java |   2 +-
 .../sis/referencing/datum/BursaWolfParameters.java |   2 +-
 .../referencing/datum/DefaultVerticalDatum.java    |   2 +-
 .../sis/referencing/datum/TimeDependentBWP.java    |   3 +-
 .../factory/AuthorityFactoryIdentifier.java        |   2 -
 .../factory/ConcurrentAuthorityFactory.java        |   4 +-
 .../factory/GeodeticAuthorityFactory.java          |   2 -
 .../referencing/factory/sql/AuthorityCodes.java    |   2 -
 .../sis/referencing/factory/sql/AxisName.java      |   2 -
 .../operation/CoordinateOperationRegistry.java     |   6 +-
 .../operation/DefaultPassThroughOperation.java     |   5 +-
 .../operation/builder/LinearTransformBuilder.java  |   2 -
 .../sis/referencing/operation/matrix/Matrix1.java  |   2 +-
 .../sis/referencing/operation/matrix/Matrix2.java  |   2 +-
 .../operation/projection/NormalizedProjection.java |   2 +-
 .../operation/projection/ZonedGridSystem.java      |   2 +-
 .../operation/transform/ConcatenatedTransform.java |   2 +-
 .../transform/DefaultMathTransformFactory.java     |   2 +-
 .../transform/EllipsoidToCentricTransform.java     |   2 +-
 .../transform/ExponentialTransform1D.java          |   2 +-
 .../operation/transform/LinearTransform1D.java     |   3 +-
 .../transform/LogarithmicTransform1D.java          |   2 +-
 .../operation/transform/MathTransforms.java        |  45 +-
 .../operation/transform/MolodenskyFormula.java     |   2 +-
 .../operation/transform/PassThroughTransform.java  |  64 +-
 .../operation/transform/PowerTransform1D.java      |   2 +-
 .../operation/transform/TransferFunction.java      |   2 -
 .../datum/DefaultPrimeMeridianTest.java            |   2 +-
 .../datum/DefaultVerticalDatumTest.java            |   2 +-
 .../transform/ConcatenatedTransformTest.java       |   2 +-
 .../operation/transform/MathTransformsTest.java    |   2 +-
 .../transform/PassThroughTransformTest.java        |   8 +-
 .../transform/TransformSeparatorTest.java          |   2 +-
 .../apache/sis/test/integration/MetadataTest.java  |   2 +-
 .../apache/sis/internal/converter/ClassPair.java   |   2 -
 .../sis/internal/converter/ConverterRegistry.java  |   2 -
 .../sis/internal/converter/FallbackConverter.java  |   2 -
 .../internal/converter/SurjectiveConverter.java    |   2 -
 .../org/apache/sis/internal/system/Supervisor.java |   2 +-
 .../apache/sis/internal/util/AbstractMapEntry.java |   2 -
 .../java/org/apache/sis/internal/util/Cloner.java  |  33 +-
 .../org/apache/sis/internal/util/DoubleDouble.java |   2 +-
 .../apache/sis/internal/util/FinalFieldSetter.java | 157 ++++
 .../org/apache/sis/internal/util/Numerics.java     |  10 -
 .../sis/internal/util/TemporalUtilities.java       |   2 +-
 .../org/apache/sis/internal/util/Utilities.java    |   2 +-
 .../src/main/java/org/apache/sis/math/Line.java    |   2 +-
 .../src/main/java/org/apache/sis/math/Plane.java   |   2 +-
 .../java/org/apache/sis/measure/AngleFormat.java   |   2 -
 .../org/apache/sis/measure/LinearConverter.java    |   8 +-
 .../java/org/apache/sis/measure/RangeFormat.java   |  20 +-
 .../java/org/apache/sis/measure/UnitFormat.java    |  21 +-
 .../java/org/apache/sis/util/CharSequences.java    |   2 +-
 .../apache/sis/util/CorruptedObjectException.java  |  13 +-
 .../src/main/java/org/apache/sis/util/Debug.java   |  20 +-
 .../main/java/org/apache/sis/util/Exceptions.java  |   4 +
 .../apache/sis/util/logging/WarningListeners.java  |   8 +-
 .../java/org/apache/sis/util/resources/Errors.java |  15 +
 .../apache/sis/util/resources/Errors.properties    |   3 +
 .../apache/sis/util/resources/Errors_fr.properties |   5 +-
 .../sis/util/resources/IndexedResourceBundle.java  |   1 -
 .../org/apache/sis/util/resources/Vocabulary.java  |  17 +-
 .../sis/util/resources/Vocabulary.properties       |   3 +
 .../sis/util/resources/Vocabulary_fr.properties    |   3 +
 .../org/apache/sis/internal/util/CitationMock.java |   2 -
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.xml         |   1 +
 pom.xml                                            |   4 +-
 .../sis/internal/profile/fra/Constraints.java      |   1 -
 .../internal/profile/fra/DataIdentification.java   |   1 -
 .../sis/internal/profile/fra/LegalConstraints.java |   1 -
 .../internal/profile/fra/SecurityConstraints.java  |   1 -
 .../sis/storage/earthobservation/LandsatStore.java |   2 -
 .../org/apache/sis/internal/geotiff/Resources.java |   5 +
 .../sis/internal/geotiff/Resources.properties      |   1 +
 .../sis/internal/geotiff/Resources_fr.properties   |   1 +
 .../org/apache/sis/storage/geotiff/CRSBuilder.java | 148 ++--
 .../org/apache/sis/storage/geotiff/GeoCodes.java   |   8 +-
 .../sis/storage/geotiff/GridGeometryBuilder.java   | 390 ++++++++++
 .../sis/storage/geotiff/ImageFileDirectory.java    | 191 ++---
 .../{GridGeometry.java => Localization.java}       | 123 +---
 .../java/org/apache/sis/storage/geotiff/Type.java  | 259 ++++---
 .../org/apache/sis/storage/geotiff/TypeTest.java   |  23 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  |  29 +-
 .../org/apache/sis/internal/netcdf/Decoder.java    |  25 +-
 .../apache/sis/internal/netcdf/GridGeometry.java   |   7 +-
 .../org/apache/sis/internal/netcdf/Variable.java   |   2 -
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |   2 -
 .../apache/sis/internal/netcdf/impl/Dimension.java |   2 -
 .../sis/internal/netcdf/impl/GridGeometryInfo.java |   2 +-
 .../sis/internal/netcdf/impl/package-info.java     |   2 +-
 .../apache/sis/internal/netcdf/package-info.java   |   6 +-
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |   2 -
 .../sis/internal/netcdf/ucar/package-info.java     |   7 +-
 .../apache/sis/storage/netcdf/MetadataReader.java  | 179 +++--
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |   2 -
 .../apache/sis/storage/netcdf/package-info.java    |   2 +-
 .../internal/netcdf/impl/ChannelDecoderTest.java   |   3 +-
 .../org/apache/sis/test/suite/package-info.txt     |   3 +
 storage/sis-storage/pom.xml                        |   5 +
 .../sis/internal/storage/MetadataBuilder.java      |  92 ++-
 .../sis/internal/storage/StoreUtilities.java       |   2 +-
 .../sis/internal/storage/io/ChannelData.java       |   2 -
 .../sis/internal/storage/io/IOUtilities.java       |   6 +-
 .../sis/internal/storage/xml/StoreProvider.java    |   2 +-
 .../org/apache/sis/storage/DataStoreRegistry.java  | 133 ++--
 .../apache/sis/storage/GridCoverageResource.java   |  21 +-
 .../org/apache/sis/storage/ProbeProviderPair.java  |   5 +-
 .../java/org/apache/sis/storage/ProbeResult.java   |   2 -
 .../org/apache/sis/storage/StorageConnector.java   |   2 -
 .../internal/storage/xml/MimeTypeDetectorTest.java |   2 +-
 .../apache/sis/internal/storage/xml/StoreTest.java |   2 +-
 .../storage/xml/stream/FormattedWriter.java        |   1 +
 .../storage/xml/stream/NamespaceEraser.java        |   1 +
 .../internal/storage/xml/stream/StaxDataStore.java |   2 -
 .../internal/storage/xml/stream/package-info.java  |   2 +-
 .../sis/internal/storage/gpx/WriterTest.java       |   2 -
 451 files changed, 8121 insertions(+), 3758 deletions(-)

diff --cc application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
index 0000000,fa5338a..dd5ec20
mode 000000,100644..100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
@@@ -1,0 -1,169 +1,169 @@@
+ /*
+  * 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.gui.dataset;
+ 
+ import java.util.Map;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Iterator;
+ import java.util.ResourceBundle;
+ import java.util.MissingResourceException;
+ import java.util.stream.Stream;
+ import java.util.stream.Collectors;
+ import javafx.collections.FXCollections;
+ import javafx.scene.control.Label;
+ import javafx.scene.control.ScrollPane;
+ import javafx.scene.control.TableColumn;
+ import javafx.scene.control.TableView;
+ import javafx.scene.control.TreeItem;
+ import javafx.scene.control.TreeView;
+ import javafx.scene.layout.BorderPane;
+ import javafx.beans.property.SimpleObjectProperty;
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.PropertyType;
++import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.feature.AbstractIdentifiedType;
+ import org.opengis.geometry.Geometry;
+ import org.apache.sis.internal.util.CheckedArrayList;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.storage.FeatureSet;
+ import org.apache.sis.storage.Resource;
+ 
+ 
+ /**
+  *
+  * @author  Johann Sorel (Geomatys)
+  * @author  Smaniotto Enzo
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ public class FeatureTable extends BorderPane {
+     /**
+      * Contains ResourceBundles indexed by table names.
+      */
+     private final Map<String, ResourceBundle> bundles = new HashMap<>();
+ 
+     private String bundlePrefix;
+ 
 -    private String generateFinalColumnName(final PropertyType prop) {
++    private String generateFinalColumnName(final AbstractIdentifiedType prop) {
+         Map<String, Map.Entry<String, String>> labelInfo = (Map) prop.getDesignation();
+         final String labelName = prop.getName().toString();
+         String columnName = labelName;
+         String tableName = null;
+         /*
+          * If exists, explore labelInfo to retrive table and column respect to this label.
+          */
+         if (labelInfo != null) {
+             final Map.Entry<String, String> entry = labelInfo.get(labelName);
+             if (entry != null) {
+                 if (entry.getKey() != null) {
+                     tableName = entry.getKey();
+                 } else {
+                     tableName = null;
+                 }
+                 if (entry.getValue() != null) {
+                     columnName = entry.getValue();
+                 } else {
+                     columnName = labelName;
+                 }
+             }
+         }
+         /*
+          * If table name is not null, try to found resourcebundle for this table.
+          */
+         if (tableName != null) {
+             /*
+              * If there isn't resource bundles (or not for the curruen table), try to generate.
+              */
+             if (bundles.get(tableName) == null) {
+                 if (bundlePrefix != null) {
+                     bundles.put(tableName, ResourceBundle.getBundle(bundlePrefix + tableName));
+                 }
+             }
+         }
+         final ResourceBundle bundle = bundles.get(tableName);
+         String finalColumnName;
+         if (labelName == null) {
+             finalColumnName = "";
+         } else if (bundle == null) {
+             if (!labelName.equals(columnName)) {
+                 finalColumnName = columnName + " as " + labelName;
+             } else {
+                 finalColumnName = columnName;
+             }
+         } else {
+             try {
+                 if (!labelName.equals(columnName)) {
+                     finalColumnName = bundle.getString(columnName) + " as " + labelName;
+                 } else {
+                     finalColumnName = bundle.getString(columnName);
+                 }
+             } catch (MissingResourceException ex) {
+                 if (!labelName.equals(columnName)) {
+                     finalColumnName = columnName + " as " + labelName;
+                 } else {
+                     finalColumnName = columnName;
+                 }
+             }
+         }
+         return finalColumnName;
+     }
+ 
+     public FeatureTable(Resource res, int i) throws DataStoreException {
 -        TableView<Feature> ttv = new TableView<>();
++        TableView<AbstractFeature> ttv = new TableView<>();
+         final ScrollPane scroll = new ScrollPane(ttv);
+         scroll.setFitToHeight(true);
+         scroll.setFitToWidth(true);
+         ttv.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
+         ttv.setTableMenuButtonVisible(true);
+         ttv.setFixedCellSize(100);
+         scroll.setPrefSize(600, 400);
+         scroll.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
+         setCenter(scroll);
 -        final List<Feature> list;
++        final List<AbstractFeature> list;
+         if (res instanceof FeatureSet) {
 -            try (Stream<Feature> stream = ((FeatureSet) res).features(false)) {
++            try (Stream<AbstractFeature> stream = ((FeatureSet) res).features(false)) {
+                 list = stream.collect(Collectors.toList());
+                 ttv.setItems(FXCollections.observableArrayList(list));
 -                for (PropertyType pt : list.get(0).getType().getProperties(false)) {
 -                    final TableColumn<Feature, BorderPane> column = new TableColumn<>(generateFinalColumnName(pt));
 -                    column.setCellValueFactory((TableColumn.CellDataFeatures<Feature, BorderPane> param) -> {
++                for (AbstractIdentifiedType pt : list.get(0).getType().getProperties(false)) {
++                    final TableColumn<AbstractFeature, BorderPane> column = new TableColumn<>(generateFinalColumnName(pt));
++                    column.setCellValueFactory((TableColumn.CellDataFeatures<AbstractFeature, BorderPane> param) -> {
+                         final Object val = param.getValue().getPropertyValue(pt.getName().toString());
+                         if (val instanceof Geometry) {
+                             return new SimpleObjectProperty<>(new BorderPane(new Label("{geometry}")));
+                         } else {
+                             SimpleObjectProperty<BorderPane> sop = new SimpleObjectProperty<>();
+                             if (val instanceof CheckedArrayList<?>) {
+                                 Iterator<String> it = ((CheckedArrayList<String>) val).iterator();
+                                 TreeItem<String> ti = new TreeItem<>(it.next());
+                                 while (it.hasNext()) {
+                                     ti.getChildren().add(new TreeItem<>(it.next()));
+                                 }
+                                 BorderPane bp = new BorderPane(new TreeView<>(ti));
+                                 sop.setValue(bp);
+                                 return sop;
+                             } else {
+                                 sop.setValue(new BorderPane(new Label(String.valueOf(val))));
+                                 return sop;
+                             }
+                         }
+                     });
+                     ttv.getColumns().add(column);
+                 }
+             }
+         }
+     }
+ }
diff --cc application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java
index 0000000,77df9a5..b556230
mode 000000,100644..100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java
@@@ -1,0 -1,430 +1,403 @@@
+ /*
+  * 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.gui.metadata;
+ 
+ import java.io.IOException;
+ import java.io.InputStream;
+ import java.text.DateFormat;
+ import java.text.NumberFormat;
+ import java.util.Collection;
+ import java.util.HashMap;
+ import java.util.Iterator;
+ import java.util.Locale;
+ import java.util.Map;
+ import javafx.beans.value.ObservableValue;
+ import javafx.collections.FXCollections;
+ import javafx.collections.ObservableList;
+ import javafx.event.ActionEvent;
+ import javafx.event.Event;
+ import javafx.geometry.Insets;
+ import javafx.scene.Node;
+ import javafx.scene.canvas.Canvas;
+ import javafx.scene.control.ComboBox;
+ import javafx.scene.control.Label;
+ import javafx.scene.control.TitledPane;
+ import javafx.scene.control.Toggle;
+ import javafx.scene.control.ToggleButton;
+ import javafx.scene.control.ToggleGroup;
+ import javafx.scene.image.Image;
+ import javafx.scene.layout.GridPane;
+ import javafx.scene.layout.HBox;
+ import javafx.scene.layout.Priority;
+ import javafx.scene.layout.StackPane;
+ import javafx.scene.layout.VBox;
+ import javafx.scene.paint.Color;
+ import org.opengis.metadata.Metadata;
+ import org.opengis.metadata.citation.CitationDate;
 -import org.opengis.metadata.citation.Party;
 -import org.opengis.metadata.citation.Responsibility;
+ import org.opengis.metadata.extent.Extent;
+ import org.opengis.metadata.extent.GeographicBoundingBox;
+ import org.opengis.metadata.extent.GeographicDescription;
+ import org.opengis.metadata.extent.GeographicExtent;
+ import org.opengis.metadata.identification.DataIdentification;
+ import org.opengis.metadata.identification.Identification;
+ import org.opengis.metadata.identification.TopicCategory;
+ import org.opengis.metadata.spatial.Dimension;
+ import org.opengis.metadata.spatial.SpatialRepresentation;
+ import org.opengis.metadata.spatial.SpatialRepresentationType;
+ import org.opengis.referencing.ReferenceSystem;
+ import org.opengis.util.InternationalString;
+ import org.apache.sis.metadata.iso.DefaultMetadata;
 -import org.apache.sis.metadata.iso.citation.DefaultIndividual;
 -import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
+ import org.apache.sis.metadata.iso.spatial.DefaultGridSpatialRepresentation;
+ import org.apache.sis.util.iso.Types;
+ 
+ 
+ /**
+  * Metadata Viewer.
+  *
+  * @author  Smaniotto Enzo
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ class MetadataOverview extends StackPane {
+ 
+     private final Metadata metadata;
+     final String fromFile;
+     private final Locale locale = Locale.getDefault();
+ 
+     public MetadataOverview(final DefaultMetadata md, final String fromFile) {
+         this.metadata = md;
+         this.fromFile = fromFile;
+         VBox root = new VBox();
+         root.setStyle("-fx-background-color: linear-gradient(to bottom right, #aeb7c4, #fafafa);");
+ 
+         // Creation of the differents views.
+         VBox summaryView = createSummaryView();
+         MetadataNode advancedView = new MetadataNode(md.asTreeTable());
+         advancedView.setMaxHeight(Double.MAX_VALUE);
+         VBox.setVgrow(advancedView, Priority.ALWAYS);
+ 
+         // Create and configure view selection buttons.
+         ToggleGroup buttonGroup = new ToggleGroup();
+         ToggleButton tb1 = new ToggleButton("Summary");
+         ToggleButton tb2 = new ToggleButton("Advanced");
+         tb1.setStyle("-fx-text-fill: white; -fx-font-family: Arial Narrow;-fx-font-weight: bold; -fx-background-color: linear-gradient(#61a2b1, #2A5058); -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 ); -fx-padding: 0.8em;");
+         tb2.setStyle("-fx-text-fill: white; -fx-font-family: Arial Narrow;-fx-font-weight: bold; -fx-background-color: linear-gradient(#61a2b1, #2A5058); -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 ); -fx-padding: 0.8em;");
+ 
+         tb1.setToggleGroup(buttonGroup);
+         tb1.setSelected(true);
+         tb1.setDisable(true);
+         tb2.setToggleGroup(buttonGroup);
+         buttonGroup.selectToggle(tb1);
+         buttonGroup.selectedToggleProperty().addListener((ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> {
+             if (tb2.isSelected()) {
+                 tb2.setDisable(true);
+                 root.getChildren().remove(summaryView);
+                 root.getChildren().add(advancedView);
+                 tb1.setDisable(false);
+             } else {
+                 tb1.setDisable(true);
+                 root.getChildren().add(summaryView);
+                 root.getChildren().remove(advancedView);
+                 tb2.setDisable(false);
+             }
+         });
+ 
+         HBox toggleGroupLayout = new HBox();
+         toggleGroupLayout.getChildren().addAll(tb1, tb2);
+         toggleGroupLayout.setPadding(new Insets(0, 0, 10, 0));
+ 
+         root.getChildren().add(toggleGroupLayout);
+         root.getChildren().add(summaryView);
+ 
+         this.getChildren().add(root);
+     }
+ 
+     private VBox createSummaryView() {
+         VBox vb = new VBox();
+         TitledPane idPane = new TitledPane("Identification info", createIdGridPane());
+         GridPane createSpatialGridPane = createSpatialGridPane();
+         vb.getChildren().add(idPane);
+         if (!createSpatialGridPane.getChildren().isEmpty()) {
+             TitledPane spatialPane = new TitledPane("Spatial representation", createSpatialGridPane);
+             vb.getChildren().add(spatialPane);
+         }
+         return vb;
+     }
+ 
+     private GridPane createIdGridPane() {
+         GridPane gp = new GridPane();
+         gp.setHgap(10.00);
+         int j = 0, k = 1;
+ 
+         HashMap<String, Identification> m = new HashMap<>();
+         ComboBox<String> comboBox = createComboBox(m);
+         comboBox.setStyle("-fx-font-weight: bold; -fx-font-size: 2em;");
+         if (!comboBox.isVisible()) {
+             Label la = new Label(comboBox.getValue());
+             la.setStyle("-fx-font-weight: bold; -fx-font-size: 2em;");
+             gp.add(la, j, k++, 2, 1);
+         } else {
+             gp.add(comboBox, j, k++, 2, 1);
+         }
+ 
 -        // Show author information.
 -        Collection<? extends Responsibility> contacts = this.metadata.getContacts();
 -        if (!contacts.isEmpty()) {
 -            Responsibility contact = contacts.iterator().next();
 -            Collection<? extends Party> parties = contact.getParties();
 -            if (!parties.isEmpty()) {
 -                Party party = parties.iterator().next();
 -                if (party.getName() != null) {
 -                    Label partyType = new Label("Party");
 -                    Label partyValue = new Label(party.getName().toString());
 -                    partyValue.setWrapText(true);
 -                    if (party instanceof DefaultOrganisation) {
 -                        partyType.setText("Organisation");
 -                    } else if (party instanceof DefaultIndividual) {
 -                        partyType.setText("Author");
 -                    }
 -                    gp.add(partyType, j, k);
 -                    gp.add(partyValue, ++j, k++);
 -                    j = 0;
 -                }
 -            }
 -        }
 -
+         GridPane gpi = new GridPane();
+         gpi.setHgap(10.00);
+ 
+         comboBox.setOnAction(e -> {
+             gpi.getChildren().clear();
 -            Identification id = m.get(comboBox.getValue());
++            DataIdentification id = (DataIdentification) m.get(comboBox.getValue());
+             if (comboBox.getValue().equals("No data to show")) {
+                 return;
+             }
+ 
+             // Show the abstract or the credit, the topic category, the creation date, the type of data, the representation system info and also the geographical area.
+             Object ab = id.getAbstract();
+             if (ab != null) {
+                 InternationalString abs = (InternationalString) ab;
+                 Label crd = new Label("Abstract");
+                 Label crdValue = new Label(abs.toString(locale));
+                 crdValue.setWrapText(true);
+                 gpi.add(crd, 0, 1);
+                 gpi.add(crdValue, 1, 1);
+             } else {
+                 Collection<String> credits = id.getCredits();
+                 if (!credits.isEmpty()) {
+                     String credit = credits.iterator().next();
+                     Label crd = new Label("Credit");
+                     Label crdValue = new Label(credit.toString());
+                     crdValue.setWrapText(true);
+                     gpi.add(crd, 0, 1);
+                     gpi.add(crdValue, 1, 1);
+                 }
+             }
+ 
+             Collection<TopicCategory> tcs = id.getTopicCategories();
+             if (!tcs.isEmpty()) {
+                 TopicCategory tc = tcs.iterator().next();
+                 Label topicC = new Label("Topic Category");
+                 Label topicValue = new Label(tc.toString());
+                 topicValue.setWrapText(true);
+                 gpi.add(topicC, 0, 2);
+                 gpi.add(topicValue, 1, 2);
+             }
+ 
+             if (!id.getCitation().getDates().isEmpty()) {
+                 CitationDate dateAndType = id.getCitation().getDates().iterator().next();
+                 DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, locale);
+                 String dateStr = dateFormat.format(dateAndType.getDate());
+                 String s = dateAndType.getDateType().toString();
+                 s = s.replace("DateType[", "");
+                 s = s.replace("]", "");
+                 Label dt = new Label("Date type: " + s.toLowerCase());
+                 Label dtValue = new Label(dateStr);
+                 dtValue.setWrapText(true);
+                 gpi.add(dt, 0, 3);
+                 gpi.add(dtValue, 1, 3);
+             }
+ 
+             if (id instanceof DataIdentification) {
+                 Label topicC = new Label("Object type");
+                 Label topicValue = new Label("Data");
+                 topicValue.setWrapText(true);
+                 gpi.add(topicC, 0, 4);
+                 gpi.add(topicValue, 1, 4);
+             } else {
+                 Label topicC = new Label("Object type");
+                 Label topicValue = new Label("Service");
+                 topicValue.setWrapText(true);
+                 gpi.add(topicC, 0, 4);
+                 gpi.add(topicValue, 1, 4);
+             }
+ 
+             Collection<SpatialRepresentationType> spatialRepresentationTypes = id.getSpatialRepresentationTypes();
+             Iterator<SpatialRepresentationType> its = spatialRepresentationTypes.iterator();
+             String typeList = "Spatial representation type: ";
+             while (its.hasNext()) {
+                 SpatialRepresentationType spatialRepresentationType = its.next();
+                 typeList += spatialRepresentationType.toString().toLowerCase(locale).replace("spatialrepresentationtype[", "").replace(']', '\0') + ", ";
+             }
+             if (!typeList.equals("Spatial representation type: ")) {
+                 Label list = new Label(typeList.substring(0, typeList.length() - 2));
+                 list.setWrapText(true);
+                 gpi.add(list, 0, 5, 2, 1);
+             }
+ 
+             Collection<? extends Extent> exs = id.getExtents();
+             if (!exs.isEmpty()) {
+                 Extent ex = exs.iterator().next();
+                 Collection<? extends GeographicExtent> ges = ex.getGeographicElements();
+                 Iterator<? extends GeographicExtent> it = ges.iterator();
+                 while (it.hasNext()) {
+                     GeographicExtent ge = it.next();
+                     Label geoEx = new Label("Zone");
+                     Label geoExValue = new Label(ge.toString());
+                     geoExValue.setWrapText(true);
+                     if (ge instanceof GeographicBoundingBox) {
+                         geoEx.setText("");
+                         GeographicBoundingBox gbd = (GeographicBoundingBox) ge;
+                         geoExValue.setText("");
+                         Canvas c = createMap(gbd.getNorthBoundLatitude(), gbd.getEastBoundLongitude(), gbd.getSouthBoundLatitude(), gbd.getWestBoundLongitude());
+                         if (c != null) {
+                             gpi.add(c, 0, 6, 2, 1);
+                         } else {
+                             geoEx.setText("Impossible to load the map.");
+                             gpi.add(geoEx, 0, 6);
+                             gpi.add(geoExValue, 1, 6);
+                         }
+                     } else if (ge instanceof GeographicDescription) {
+                         geoEx.setText("Geographic description");
+                         GeographicDescription gd = (GeographicDescription) ge;
+                         geoExValue.setText(gd.getGeographicIdentifier().getCode());
+                     }
+                 }
+             }
+         });
+ 
+         Event.fireEvent(comboBox, new ActionEvent());
+         gp.add(gpi, j, k++, 2, 1);
+ 
+         int ind = 0;
+         for (Node n : gp.getChildren()) {
+             if (ind++ != 0) {
+                 n.setStyle("-fx-padding: 0 83 10 0;");
+             } else {
+                 n.setStyle("-fx-padding: 0 0 10 0; -fx-font-weight: bold; -fx-font-size: 2em;");
+             }
+         }
+         gpi.getChildren().forEach(n -> n.setStyle("-fx-padding: 0 0 10 0;"));
+ 
+         return gp;
+     }
+ 
+     private Canvas createMap(double north, double east, double south, double west) {
+         Canvas can = new Canvas();
+         Image image = null;
+         try (InputStream in = MetadataOverview.class.getResourceAsStream("WorldMap360x180.png")) {
+             image = new Image(in);
+         } catch (IOException e) {
+             // TODO
+         }
+         if (image.errorProperty().getValue()) {
+             return null;
+         }
+ 
+         double height = image.getHeight();
+         double width = image.getWidth();
+ 
+         can.setHeight(height);
+         can.setWidth(width);
+         can.getGraphicsContext2D().drawImage(image, 0, 0, width, height);
+         can.getGraphicsContext2D().setStroke(Color.DARKBLUE);
+         can.getGraphicsContext2D().setGlobalAlpha(0.1);
+         double x = west + width / 2, y = height / 2 - north, w = east - west, h = north - south;
+         can.getGraphicsContext2D().strokeRect(x, y, w, h);
+         final double minRectSize = 6.0;
+         if (w < minRectSize) {
+             double difX = minRectSize - w;
+             x -= difX / 2;
+             w = minRectSize;
+         }
+         if (h < minRectSize) {
+             double difY = minRectSize - h;
+             y -= difY / 2;
+             h = minRectSize;
+         }
+         can.getGraphicsContext2D().fillRect(x, y, w, h);
+         can.getGraphicsContext2D().setGlobalAlpha(1.0);
+         can.getGraphicsContext2D().setStroke(Color.DARKBLUE);
+         can.getGraphicsContext2D().strokeRect(x, y, w, h);
+ 
+         return can;
+     }
+ 
+     private GridPane createSpatialGridPane() {
+         GridPane gp = new GridPane();
+         gp.setHgap(10.00);
+         gp.setVgap(10.00);
+         int j = 0, k = 1;
+ 
+         Collection<? extends ReferenceSystem> referenceSystemInfos = metadata.getReferenceSystemInfo();
+         if (!referenceSystemInfos.isEmpty()) {
+             ReferenceSystem referenceSystemInfo = referenceSystemInfos.iterator().next();
+             Label rsiValue = new Label("Reference system infos: " + referenceSystemInfo.getName().toString());
+             rsiValue.setWrapText(true);
+             gp.add(rsiValue, j, k++);
+         }
+ 
+         Collection<? extends SpatialRepresentation> sris = this.metadata.getSpatialRepresentationInfo();
+         if (sris.isEmpty()) {
+             return gp;
+         }
+         NumberFormat numberFormat = NumberFormat.getIntegerInstance(locale);
+         for (SpatialRepresentation sri : sris) {
+             String currentValue = "• ";
+             if (sri instanceof DefaultGridSpatialRepresentation) {
+                 DefaultGridSpatialRepresentation sr = (DefaultGridSpatialRepresentation) sri;
+ 
+                 Iterator<? extends Dimension> it = sr.getAxisDimensionProperties().iterator();
+                 while (it.hasNext()) {
+                     Dimension dim = it.next();
+                     currentValue += numberFormat.format(dim.getDimensionSize()) + " " + Types.getCodeTitle(dim.getDimensionName()) + " * ";
+                 }
+                 currentValue = currentValue.substring(0, currentValue.length() - 3);
+                 Label spRep = new Label(currentValue);
+                 gp.add(spRep, j, k++, 2, 1);
+                 if (sr.getCellGeometry() != null) {
+                     Label cellGeo = new Label("Cell geometry:");
+                     Label cellGeoValue = new Label(Types.getCodeTitle(sr.getCellGeometry()).toString());
+                     cellGeoValue.setWrapText(true);
+                     gp.add(cellGeo, j, k);
+                     gp.add(cellGeoValue, ++j, k++);
+                     j = 0;
+                 }
+             }
+         }
+         return gp;
+     }
+ 
+     private ComboBox<String> createComboBox(final Map<String, Identification> m) {
+         ComboBox<String> cb = new ComboBox<>();
+         Collection<? extends Identification> ids = this.metadata.getIdentificationInfo();
+         ObservableList<String> options = FXCollections.observableArrayList();
+         int i = 1;
+         if (ids.size() > 1) {
+             for (Identification id : ids) {
+                 String currentName;
+                 if (id.getCitation() != null) {
+                     currentName = id.getCitation().getTitle().toString();
+                 } else {
+                     currentName = Integer.toString(i);
+                 }
+                 options.add(currentName);
+                 m.put(currentName, id);
+             }
+             cb.setItems(options);
+             cb.setValue(ids.iterator().next().getCitation().getTitle().toString());
+         } else if (ids.size() == 1) {
+             if (ids.iterator().next().getCitation() != null) {
+                 m.put(ids.iterator().next().getCitation().getTitle().toString(), ids.iterator().next());
+                 cb.setValue(ids.iterator().next().getCitation().getTitle().toString());
+                 cb.setVisible(false);
+             }
+         } else {
+             cb.setValue("No data to show");
+             cb.setVisible(false);
+         }
+         return cb;
+     }
+ }
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
index a5959f1,fa35d1a..52ec3d5
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractOperation.java
@@@ -30,9 -30,16 +30,8 @@@ import org.opengis.parameter.GeneralPar
  import org.opengis.parameter.ParameterDescriptorGroup;
  import org.opengis.parameter.ParameterValueGroup;
  import org.apache.sis.util.Classes;
- import org.apache.sis.util.Debug;
  
  // Branch-dependent imports
 -import org.opengis.feature.Attribute;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureAssociation;
 -import org.opengis.feature.FeatureOperationException;
 -import org.opengis.feature.IdentifiedType;
 -import org.opengis.feature.Operation;
 -import org.opengis.feature.Property;
  
  
  /**
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java
index 975ccf6,c8e414b..bffabc3
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/PropertyView.java
@@@ -25,9 -25,17 +25,8 @@@ import org.opengis.util.GenericName
  import org.apache.sis.util.collection.CheckedContainer;
  import org.apache.sis.util.resources.Errors;
  import org.apache.sis.util.Classes;
- import org.apache.sis.util.Debug;
  import org.apache.sis.internal.feature.Resources;
  
 -// Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.Property;
 -import org.opengis.feature.Operation;
 -import org.opengis.feature.PropertyType;
 -import org.opengis.feature.AttributeType;
 -import org.opengis.feature.FeatureAssociationRole;
 -import org.opengis.feature.MultiValuedPropertyException;
 -
  
  /**
   * An attribute or association implementation which delegate its work to the parent feature.
@@@ -149,11 -189,26 +148,10 @@@ final class PropertyView 
      }
  
      /**
 -     * Compares this attribute with the given object for equality.
 -     */
 -    @Override
 -    public final boolean equals(final Object obj) {
 -        if (obj == this) {
 -            return true;
 -        }
 -        if (obj != null && obj.getClass() == getClass()) {
 -            final PropertyView<?> that = (PropertyView<?>) obj;
 -            return feature == that.feature && Objects.equals(name, that.name);
 -        }
 -        return false;
 -    }
 -
 -    /**
       * Returns a string representation of this property for debugging purposes.
       */
-     @Debug
 -    @Override
 -    public final String toString() {
 -        return FieldType.toString(false, getClass().getSimpleName(), getName(),
 -                Classes.getShortName(getValueClass()), getValues().iterator()).toString();
 +    static String toString(final Class<?> classe, final Class<?> valueClass, final GenericName name, final Collection<?> values) {
 +        return FieldType.toString(false, classe.getSimpleName(), name,
 +                Classes.getShortName(valueClass), values.iterator()).toString();
      }
  }
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
index 90e2c18,3dffc1b..345db73
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
@@@ -32,8 -32,11 +32,7 @@@ import org.apache.sis.util.NullArgument
  import org.apache.sis.util.Deprecable;
  import org.apache.sis.util.Localized;
  import org.apache.sis.util.Classes;
- import org.apache.sis.util.Debug;
  
 -// Branch-dependent imports
 -import org.opengis.feature.IdentifiedType;
 -import org.opengis.feature.PropertyNotFoundException;
 -
  
  /**
   * Information common to all kind of types (feature, association, characteristics).
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
index 316281b,05d8053..7570f1d
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
@@@ -23,9 -23,10 +23,9 @@@ import javax.xml.bind.annotation.XmlAtt
  import javax.xml.bind.annotation.XmlType;
  import javax.xml.bind.annotation.XmlValue;
  import org.opengis.util.CodeList;
 -import org.opengis.util.ControlledVocabulary;
  import org.apache.sis.util.iso.Types;
  import org.apache.sis.internal.jaxb.Context;
- import org.apache.sis.internal.jaxb.Schemas;
+ import org.apache.sis.internal.xml.Schemas;
  
  
  /**
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
index 47d05bf,9903cd0..546ced4
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
@@@ -18,9 -18,11 +18,9 @@@ package org.apache.sis.internal.jaxb.gm
  
  import java.util.Date;
  import javax.xml.bind.annotation.XmlElement;
 -import org.opengis.temporal.Period;
 -import org.opengis.temporal.Instant;
  import org.opengis.temporal.TemporalPrimitive;
+ import org.apache.sis.internal.xml.XmlUtilities;
  import org.apache.sis.internal.jaxb.Context;
- import org.apache.sis.internal.jaxb.XmlUtilities;
  import org.apache.sis.internal.jaxb.gco.PropertyType;
  import org.apache.sis.internal.util.TemporalUtilities;
  import org.apache.sis.util.resources.Errors;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
index 28da4df,611e527..d6164e4
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
@@@ -22,12 -22,10 +22,12 @@@ import javax.xml.bind.annotation.XmlEle
  import javax.xml.bind.annotation.XmlRootElement;
  import javax.xml.datatype.XMLGregorianCalendar;
  import javax.xml.datatype.DatatypeConfigurationException;
 -import org.opengis.temporal.Instant;
  import org.apache.sis.internal.jaxb.Context;
- import org.apache.sis.internal.jaxb.XmlUtilities;
+ import org.apache.sis.internal.xml.XmlUtilities;
  
 +// Branch-dependent imports
 +import org.apache.sis.internal.geoapi.temporal.Instant;
 +
  
  /**
   * Encapsulates a {@code gml:TimeInstant}. This element may be used alone, or included in a
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
index 9addb31,5a66b04..0b8bc63
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
@@@ -20,10 -20,10 +20,10 @@@ import javax.xml.bind.annotation.XmlTyp
  import javax.xml.bind.annotation.XmlElement;
  import javax.xml.bind.annotation.XmlElements;
  import javax.xml.bind.annotation.XmlRootElement;
 -import org.opengis.temporal.Period;
  import org.apache.sis.internal.jaxb.Context;
 +import org.apache.sis.internal.geoapi.temporal.Period;
  
- import static org.apache.sis.internal.jaxb.LegacyNamespaces.VERSION_3_0;
+ import static org.apache.sis.internal.xml.LegacyNamespaces.VERSION_3_0;
  
  
  /**
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
index c937985,4d561f8..4e297c8
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
@@@ -23,10 -23,10 +23,10 @@@ import javax.xml.datatype.DatatypeFacto
  import javax.xml.bind.annotation.XmlElement;
  import javax.xml.datatype.DatatypeConfigurationException;
  import org.opengis.temporal.PeriodDuration;
 -import org.opengis.temporal.TemporalFactory;
 +import org.apache.sis.internal.geoapi.temporal.TemporalFactory;
  import org.opengis.util.InternationalString;
  import org.apache.sis.internal.jaxb.Context;
- import org.apache.sis.internal.jaxb.XmlUtilities;
+ import org.apache.sis.internal.xml.XmlUtilities;
  import org.apache.sis.internal.jaxb.gco.PropertyType;
  import org.apache.sis.internal.util.TemporalUtilities;
  import org.apache.sis.util.iso.SimpleInternationalString;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
index a21ec8b,16aece8..8c315ac
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
@@@ -59,6 -60,7 +59,6 @@@ import javax.xml.bind.annotation.XmlAcc
  import javax.xml.bind.annotation.XmlAccessorType;
  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
 -import org.apache.sis.internal.jaxb.code.SV_ParameterDirection;
  import org.apache.sis.internal.jaxb.gco.*;
  import org.apache.sis.xml.Namespaces;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
index 115a66f,3e68625..94908a5
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
@@@ -18,10 -18,11 +18,9 @@@ package org.apache.sis.internal.simple
  
  import java.util.Collection;
  import java.util.Collections;
- import org.apache.sis.util.Debug;
  import org.opengis.util.InternationalString;
 -import org.opengis.metadata.citation.Citation;
  import org.opengis.metadata.distribution.Distributor;
  import org.opengis.metadata.distribution.Format;
 -import org.opengis.metadata.distribution.Medium;
  
  
  /**
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
index 928f7be,69ca60e..04840de
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
@@@ -30,8 -31,6 +31,7 @@@ import java.security.AccessController
  import org.opengis.metadata.Identifier;
  import org.opengis.metadata.citation.Citation;
  import org.opengis.metadata.ExtendedElementInformation;
 +import org.opengis.referencing.ReferenceIdentifier;
- import org.apache.sis.util.Debug;
  import org.apache.sis.util.Classes;
  import org.apache.sis.util.ComparisonMode;
  import org.apache.sis.util.collection.TreeTable;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
index 898a5ea,b2fbc85..801eee0
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
@@@ -16,10 -16,9 +16,9 @@@
   */
  package org.apache.sis.metadata;
  
- import java.util.Map;
  import java.util.Iterator;
  import java.util.Collection;
 -import org.opengis.util.ControlledVocabulary;
 +import org.opengis.util.CodeList;
  import org.apache.sis.util.Emptiable;
  import org.apache.sis.internal.util.CollectionsExt;
  
@@@ -110,128 -89,106 +89,106 @@@ final class Pruner extends MetadataVisi
      }
  
      /**
-      * {@link #isEmpty(AbstractMetadata, Class, boolean, boolean)} implementation, potentially
-      * invoked recursively for inspecting child metadata and optionally removing empty ones.
-      * The map given in argument is a safety guard against infinite recursivity.
+      * Marks a metadata instance as empty before we start visiting its non-null properties.
+      * If the metadata does not contain any property, then the {@link #isEmpty} field will stay {@code true}.
       *
-      * @param  accessor    the accessor that provided the metadata {@code properties}.
-      * @param  properties  the metadata properties.
-      * @param  tested      an initially singleton map, to be filled with tested metadata.
-      * @param  prune       {@code true} for removing empty properties.
-      * @return {@code true} if all metadata properties are null or empty.
+      * @return {@link Filter#NON_EMPTY} since this visitor is not restricted to writable properties.
+      *         We need to visit all readable properties even for pruning operation since we need to
+      *         determine if the metadata is empty.
       */
-     private static boolean isEmpty(final PropertyAccessor accessor, final Map<String,Object> properties,
-             final Map<Object,Boolean> tested, final boolean prune)
-     {
-         boolean isEmpty = true;
-         for (final Map.Entry<String,Object> entry : properties.entrySet()) {
-             final Object value = entry.getValue();
-             /*
-              * No need to check for null values, because the ValueExistencePolicy argument
-              * given to asMap(…) asked for non-null values. If nevertheless a value is null,
-              * following code should be robust to that.
-              *
-              * We use the 'tested' map in order to avoid computing the same value twice, but
-              * also as a check against infinite recursivity - which is why a value needs to be
-              * set before to iterate over children. The default value is 'false' because if we
-              * test the same object through a "A → B → A" dependency chain, this means that A
-              * was not empty (since it contains B).
-              */
-             final Boolean isEntryEmpty = tested.put(value, Boolean.FALSE);
-             if (isEntryEmpty != null) {
-                 if (isEntryEmpty) {                     // If a value was already set, restore the original value.
-                     tested.put(value, Boolean.TRUE);
-                 } else {
-                     isEmpty = false;
-                     if (!prune) break;                  // No need to continue if we are not pruning the metadata.
-                 }
-             } else {
+     @Override
+     Filter preVisit(final PropertyAccessor accessor) {
+         isEmpty = true;
+         return Filter.NON_EMPTY;
+     }
+ 
+     /**
+      * Invoked for each element in the metadata to test or prune. This method is invoked only for new elements
+      * not yet processed by {@code Pruner}. The element may be a value object or a collection. For convenience
+      * we will proceed as if we had only collections, wrapping value object in a singleton collection.
+      *
+      * @param  type   the type of elements. Note that this is not necessarily the type
+      *                of given {@code element} argument if the later is a collection.
+      * @param  value  value of the metadata element being visited.
+      */
+     @Override
+     Object visit(final Class<?> type, final Object value) {
+         final boolean isEmptyMetadata = isEmpty;    // Save the value in case it is overwritten by recursive invocations.
+         boolean isEmptyValue = true;
+         final Collection<?> values = CollectionsExt.toCollection(value);
+         for (final Iterator<?> it = values.iterator(); it.hasNext();) {
+             final Object element = it.next();
+             if (!isNullOrEmpty(element)) {
                  /*
-                  * At this point, 'value' is a new instance not yet processed by Pruner. The value may
-                  * be a data object or a collection. For convenience we will proceed as if we had only
-                  * collections, wrapping data object in a singleton collection if necessary.
+                  * At this point, 'element' is not an empty CharSequence, Collection or array.
+                  * It may be an other metadata, a Java primitive type or user-defined object.
+                  *
+                  *  - For AbstractMetadata, delegate to the public API in case it has been overriden.
+                  *  - For user-defined Emptiable, delegate to the user's isEmpty() method. Note that
+                  *    we test at different times depending if 'prune' is true of false.
                   */
-                 Class<?> elementType = null;                    // To be computed when first needed.
-                 boolean allElementsAreEmpty = true;
-                 final Collection<?> values = CollectionsExt.toCollection(value);
-                 for (final Iterator<?> it = values.iterator(); it.hasNext();) {
-                     final Object element = it.next();
-                     if (!isNullOrEmpty(element)) {
+                 boolean isEmptyElement = false;
+                 if (element instanceof AbstractMetadata) {
+                     final AbstractMetadata md = (AbstractMetadata) element;
+                     if (prune) md.prune();
+                     isEmptyElement = md.isEmpty();
+                 } else if (!prune && element instanceof Emptiable) {
+                     isEmptyElement = ((Emptiable) element).isEmpty();
+                     // If 'prune' is true, we will rather test for Emptiable after our pruning attempt.
 -                } else if (!(element instanceof ControlledVocabulary)) {
++                } else if (!(element instanceof Enum<?>) && !(element instanceof CodeList<?>)) {
+                     final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
+                     if (standard != null) {
                          /*
-                          * At this point, 'element' is not an empty CharSequence, Collection or array.
-                          * It may be an other metadata, a Java primitive type or user-defined object.
-                          *
-                          *  - For AbstractMetadata, delegate to the public API in case it has been overriden.
-                          *  - For user-defined Emptiable, delegate to the user's isEmpty() method. Note that
-                          *    we test at different times depending if 'prune' is true of false.
+                          * For implementation that are not subtype of AbstractMetadata but nevertheless
+                          * implement some metadata interfaces, we will invoke recursively this method.
                           */
-                         boolean isEmptyElement = false;
-                         if (element instanceof AbstractMetadata) {
-                             final AbstractMetadata md = (AbstractMetadata) element;
-                             if (prune) md.prune();
-                             isEmptyElement = md.isEmpty();
-                         } else if (!prune && element instanceof Emptiable) {
-                             isEmptyElement = ((Emptiable) element).isEmpty();
-                             // If 'prune' is true, we will rather test for Emptiable after our pruning attempt.
-                         } else if (!(element instanceof Enum<?>) && !(element instanceof CodeList<?>)) {
-                             final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
-                             if (standard != null) {
-                                 /*
-                                  * For implementation that are not subtype of AbstractMetadata but nevertheless
-                                  * implement some metadata interface, we will invoke recursively this method.
-                                  * But since a class may implement more than one interface, we need to get the
-                                  * type of the value returned by the getter method in order to take in account
-                                  * only that type.
-                                  */
-                                 if (elementType == null) {
-                                     elementType = accessor.type(accessor.indexOf(entry.getKey(), false),
-                                                                 TypeValuePolicy.ELEMENT_TYPE);
-                                 }
-                                 final PropertyAccessor elementAccessor = standard.getAccessor(
-                                         new CacheKey(element.getClass(), elementType), false);
-                                 if (elementAccessor != null) {
-                                     isEmptyElement = isEmpty(elementAccessor, asMap(element, elementAccessor, prune), tested, prune);
-                                     if (!isEmptyElement && element instanceof Emptiable) {
-                                         isEmptyElement = ((Emptiable) element).isEmpty();
-                                     }
-                                 }
-                             } else if (isPrimitive(entry)) {
-                                 if (value instanceof Number) {
-                                     isEmptyElement = Double.isNaN(((Number) value).doubleValue());
-                                 } else {
-                                     // Typically methods of the kind 'isFooAvailable()'.
-                                     isEmptyElement = Boolean.FALSE.equals(value);
-                                 }
-                             }
-                         }
-                         if (!isEmptyElement) {
-                             // At this point, we have determined that the property is not empty.
-                             // If we are not removing empty nodes, there is no need to continue.
-                             if (!prune) {
-                                 return false;
+                         final Boolean r = walk(standard, type, value, false);
+                         if (r != null) {
+                             isEmptyElement = r;
+                             if (!isEmptyElement && element instanceof Emptiable) {
+                                 isEmptyElement = ((Emptiable) element).isEmpty();
                              }
-                             allElementsAreEmpty = false;
-                             continue;
                          }
-                     }
-                     // Found an empty element. Remove it if we are
-                     // allowed to do so, then check next elements.
-                     if (prune && values == value) {
-                         it.remove();
+                     } else if (value instanceof Number) {
+                         isEmptyElement = Double.isNaN(((Number) value).doubleValue());
+                     } else if (value instanceof Boolean) {
+                         // Typically methods of the kind 'isFooAvailable()'.
+                         isEmptyElement = !((Boolean) value);
                      }
                  }
-                 // If all elements were empty, set the whole property to 'null'.
-                 isEmpty &= allElementsAreEmpty;
-                 if (allElementsAreEmpty) {
-                     tested.put(value, Boolean.TRUE);
-                     if (prune) try {
-                         entry.setValue(null);
-                     } catch (UnsupportedOperationException e) {
-                         // Entry is read only - ignore.
+                 if (!isEmptyElement) {
+                     /*
+                      * At this point, we have determined that the property is not empty.
+                      * If we are not removing empty nodes, there is no need to continue.
+                      */
+                     if (!prune) {
+                         isEmpty = false;
+                         return SKIP_SIBLINGS;
                      }
+                     isEmptyValue = false;
+                     continue;
                  }
              }
+             /*
+              * Found an empty element. Remove it if the element is part of a collection,
+              * then move to the next element in the collection (not yet the next property).
+              */
+             if (prune && values == value) {
+                 it.remove();
+             }
          }
+         /*
+          * If all elements were empty, set the whole property to 'null'.
+          */
+         isEmpty = isEmptyMetadata & isEmptyValue;
+         return isEmptyValue & prune ? null : value;
+     }
+ 
+     /**
+      * Returns the result of visiting all elements in the metadata.
+      */
+     @Override
+     Boolean result() {
          return isEmpty;
      }
  }
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index 98b578e,b16b150..81c3a4f
--- 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
@@@ -366,36 -364,7 +360,36 @@@ public class DefaultMetadata extends IS
              applicationSchemaInfo         = copyCollection(object.getApplicationSchemaInfo(),         ApplicationSchemaInformation.class);
              metadataMaintenance           = object.getMetadataMaintenance();
              acquisitionInformation        = copyCollection(object.getAcquisitionInformation(),        AcquisitionInformation.class);
 -            resourceLineages              = copyCollection(object.getResourceLineages(),              Lineage.class);
 +            if (object instanceof DefaultMetadata) {
 +                final DefaultMetadata c = (DefaultMetadata) object;
-                 metadataIdentifier            = c.getMetadataIdentifier();
++                identifiers                   = singleton(c.getMetadataIdentifier(), Identifier.class);
 +                parentMetadata                = c.getParentMetadata();
 +                languages                     = copyCollection(c.getLanguages(),                     Locale.class);
 +                characterSets                 = copyCollection(c.getCharacterSets(),                 Charset.class);
 +                metadataScopes                = copyCollection(c.getMetadataScopes(),                DefaultMetadataScope.class);
 +                dateInfo                      = copyCollection(c.getDateInfo(),                      CitationDate.class);
 +                metadataStandards             = copyCollection(c.getMetadataStandards(),             Citation.class);
 +                metadataProfiles              = copyCollection(c.getMetadataProfiles(),              Citation.class);
 +                alternativeMetadataReferences = copyCollection(c.getAlternativeMetadataReferences(), Citation.class);
 +                metadataLinkages              = copyCollection(c.getMetadataLinkages(),              OnlineResource.class);
 +                resourceLineages              = copyCollection(c.getResourceLineages(),              Lineage.class);
 +            } else {
 +                setFileIdentifier         (object.getFileIdentifier());
 +                setParentIdentifier       (object.getParentIdentifier());
 +                setLanguage               (object.getLanguage());
 +                setLocales                (object.getLocales());
 +                setCharacterSet           (object.getCharacterSet());
 +                setHierarchyLevels        (object.getHierarchyLevels());
 +                setHierarchyLevelNames    (object.getHierarchyLevelNames());
 +                setDateStamp              (object.getDateStamp());
 +                setMetadataStandardName   (object.getMetadataStandardName());
 +                setMetadataStandardVersion(object.getMetadataStandardVersion());
 +                try {
 +                    setDataSetUri(object.getDataSetUri());
 +                } catch (URISyntaxException e) {
 +                    throw new IllegalArgumentException(e);
 +                }
 +            }
          }
      }
  
@@@ -444,11 -413,11 +438,11 @@@
       *
       * @since 0.5
       */
 -    @Override
      @XmlElement(name = "metadataIdentifier")
      @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
 +    @UML(identifier="metadataIdentifier", obligation=OPTIONAL, specification=ISO_19115)
      public Identifier getMetadataIdentifier() {
-         return metadataIdentifier;
+         return super.getIdentifier();
      }
  
      /**
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
index 889a097,03dde0f..3a59bb1
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ImmutableIdentifier.java
@@@ -194,18 -189,16 +194,20 @@@ public class ImmutableIdentifier extend
       * get the code, codespace, authority and version from the given identifier.
       *
       * @param identifier  the identifier to copy.
+      *
 -     * @see #castOrCopy(Identifier)
++     * @see #castOrCopy(ReferenceIdentifier)
       */
 -    public ImmutableIdentifier(final Identifier identifier) {
 +    public ImmutableIdentifier(final ReferenceIdentifier identifier) {
          ensureNonNull("identifier", identifier);
 -        code        = identifier.getCode();
 -        codeSpace   = identifier.getCodeSpace();
 -        authority   = identifier.getAuthority();
 -        version     = identifier.getVersion();
 -        description = identifier.getDescription();
 +        code      = identifier.getCode();
 +        codeSpace = identifier.getCodeSpace();
 +        authority = identifier.getAuthority();
 +        version   = identifier.getVersion();
 +        if (identifier instanceof DefaultIdentifier) {
 +            description = ((DefaultIdentifier) identifier).getDescription();
 +        } else {
 +            description = null;
 +        }
          validate(null);
      }
  
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index 63ffa27,66889d0..c7f6e8c
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@@ -63,10 -64,12 +63,10 @@@ abstract class MetadataScopeAdapter<L> 
               * But if the metadata is not modifiable, then we will need to clone it and replaces the element in
               * the collection.
               */
-             if (!scope.isModifiable()) {
 -            if (!(scope instanceof DefaultMetadataScope) ||
 -                    ((DefaultMetadataScope) scope).state() == DefaultMetadataScope.State.FINAL)
 -            {
++            if (scope.state() == DefaultMetadataScope.State.FINAL) {
                  scope = new DefaultMetadataScope(scope);
                  if (elements instanceof List<?>) {
 -                    ((List<MetadataScope>) elements).set(n, scope);
 +                    ((List<DefaultMetadataScope>) elements).set(n, scope);
                  } else {
                      /*
                       * Not a list. Delete all the remaining parts, substitute the value
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
index 78b2e3d,a0bf497..0d7df56
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
@@@ -56,7 -57,7 +56,6 @@@ import org.apache.sis.internal.metadata
   * @since   0.3
   * @module
   */
- @SuppressWarnings("CloneableClassWithoutClone")                 // ModifiableMetadata needs shallow clones.
 -@Deprecated
  @XmlType(name = "CI_ResponsibleParty_Type", namespace = LegacyNamespaces.GMD, propOrder = {
      "individualName",
      "organisationName",
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
index 081ad61,13e22f9..ab014b5
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
@@@ -33,14 -34,9 +33,14 @@@ import org.opengis.util.RecordType
  import org.apache.sis.internal.metadata.Dependencies;
  import org.apache.sis.internal.metadata.LegacyPropertyAdapter;
  import org.apache.sis.internal.jaxb.FilterByVersion;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Information about the content of a grid data cell.
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index 987b4b5,8398143..bfc40ba
--- 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
@@@ -25,8 -25,9 +25,8 @@@ import javax.xml.bind.annotation.adapte
  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.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  import org.apache.sis.internal.jaxb.lan.LocaleAdapter;
  import org.apache.sis.internal.metadata.Dependencies;
  import org.apache.sis.internal.metadata.LegacyPropertyAdapter;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
index 26e29d3,d8d92df..61c8f8a
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
@@@ -357,11 -371,11 +355,11 @@@ public class DefaultMedium extends ISOM
       *
       * @since 0.5
       */
 -    @Override
      @XmlElement(name = "identifier")
      @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
 +    @UML(identifier="identifier", obligation=OPTIONAL, specification=ISO_19115)
      public Identifier getIdentifier() {
-         return NonMarshalledAuthority.getMarshallable(identifiers);
+         return super.getIdentifier();
      }
  
      /**
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
index d3dee84,9d38770..b7525b1
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
@@@ -71,8 -66,10 +71,7 @@@ import org.apache.sis.internal.jaxb.cod
   * @version 1.0
   * @since   0.3
   * @module
 - *
 - * @deprecated As of ISO 19115:2014, replaced by {@link DefaultAssociatedResource}.
   */
- @SuppressWarnings("CloneableClassWithoutClone")                 // ModifiableMetadata needs shallow clones.
 -@Deprecated
  @XmlType(name = "MD_AggregateInformation_Type", namespace = LegacyNamespaces.GMD, propOrder = {
      "aggregateDataSetName",
      "aggregateDataSetIdentifier",
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
index 711f7e0,99fe75b..33e8949
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
@@@ -437,13 -425,16 +436,16 @@@ public class DefaultSource extends ISOM
      public Collection<Extent> getSourceExtents() {
          if (FilterByVersion.LEGACY_METADATA.accept()) {
              Scope scope = getScope();
-             if (!(scope instanceof DefaultScope)) {
-                 if (isModifiable()) {
-                     scope = new DefaultScope(scope);
-                     this.scope = scope;
-                 } else {
-                     return Collections.singleton(scope.getExtent());
+             if (scope != null) {
+                 if (!(scope instanceof DefaultScope)) {
+                     if (super.state() != State.FINAL) {
+                         scope = new DefaultScope(scope);
+                         this.scope = scope;
+                     } else {
 -                        return Collections.unmodifiableCollection(scope.getExtents());
++                        return Collections.singleton(scope.getExtent());
+                     }
                  }
+                 return ((DefaultScope) scope).getExtents();
              }
          }
          return null;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
index 420e962,c8f75ed..45116f9
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
@@@ -38,13 -38,8 +38,13 @@@ import org.apache.sis.metadata.iso.cita
  import org.apache.sis.internal.metadata.LegacyPropertyAdapter;
  import org.apache.sis.internal.metadata.Dependencies;
  import org.apache.sis.internal.jaxb.FilterByVersion;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Information about the scope and frequency of updating.
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
index 1aa9d92,862495b..895be98
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
@@@ -26,9 -26,8 +26,9 @@@ import org.opengis.metadata.quality.Ele
  import org.opengis.metadata.quality.Scope;
  import org.opengis.metadata.maintenance.ScopeCode;
  import org.apache.sis.metadata.iso.ISOMetadata;
 +import org.apache.sis.metadata.iso.maintenance.DefaultScope;
  import org.apache.sis.internal.jaxb.FilterByVersion;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  
  
  /**
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
index a5dca40,e268e80..009ac61
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
@@@ -46,6 -46,8 +46,7 @@@ import java.sql.SQLNonTransientExceptio
  import java.sql.PreparedStatement;
  import org.opengis.annotation.UML;
  import org.opengis.util.CodeList;
 -import org.opengis.util.ControlledVocabulary;
+ import org.opengis.util.FactoryException;
  import org.apache.sis.metadata.MetadataStandard;
  import org.apache.sis.metadata.KeyNamePolicy;
  import org.apache.sis.metadata.ValueExistencePolicy;
@@@ -929,8 -901,8 +903,8 @@@ public class MetadataSource implements 
           * If the identifier is prefixed with a table name as in "{CI_Organisation}identifier",
           * the name between bracket is a subtype of the given 'type' argument.
           */
-         final Class<?> type           = subType(info.getMetadataType(), toSearch.identifier);
+         final Class<?> type           = TableHierarchy.subType(info.getMetadataType(), toSearch.identifier);
 -        final Class<?> returnType     = method.getReturnType();
 +        final Class<?> returnType     = Interim.getReturnType(method);
          final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
          final Class<?> elementType    = wantCollection ? Classes.boundOfParameterizedProperty(method) : returnType;
          final boolean  isMetadata     = standard.isMetadata(elementType);
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
index 6685e34,f205d0f..3f672c2
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
@@@ -31,9 -33,9 +33,10 @@@ import java.sql.SQLException
  import javax.sql.DataSource;
  import java.lang.reflect.Modifier;
  
 +import org.opengis.util.CodeList;
  import org.opengis.metadata.Identifier;
  import org.opengis.metadata.citation.Citation;
+ import org.opengis.util.FactoryException;
  
  import org.apache.sis.util.Exceptions;
  import org.apache.sis.util.ArgumentChecks;
@@@ -49,9 -51,10 +52,10 @@@ import org.apache.sis.metadata.ValueExi
  import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.iso.citation.Citations;
  import org.apache.sis.internal.metadata.sql.SQLBuilder;
+ import org.apache.sis.xml.IdentifiedObject;
  
  // Branch-dependent imports
 -import org.opengis.util.ControlledVocabulary;
 +import org.opengis.referencing.ReferenceIdentifier;
  
  
  /**
@@@ -641,9 -646,11 +647,9 @@@ public class MetadataWriter extends Met
      /**
       * Adds a code list if it is not already present. This is used only in order to enforce
       * foreigner key constraints in the database. The value of CodeList tables are not used
 -     * at parsing time. Enumerations are handled as if they were CodeLists; we do not use
 -     * the native SQL {@code ENUM} type for making easier to add new values when a standard
 -     * is updated.
 +     * at parsing time.
       */
-     private String addCode(final Statement stmt, final CodeList<?> code) throws SQLException {
 -    private String addCode(final Statement stmt, final ControlledVocabulary code) throws SQLException, FactoryException {
++    private String addCode(final Statement stmt, final CodeList<?> code) throws SQLException, FactoryException {
          assert Thread.holdsLock(this);
          final String table = getTableName(code.getClass());
          final Set<String> columns = getExistingColumns(table);
@@@ -687,13 -694,22 +693,24 @@@
       */
      protected String suggestIdentifier(final Object metadata, final Map<String,Object> asValueMap) throws SQLException {
          String identifier = null;
+         final Collection<? extends Identifier> identifiers;
          if (metadata instanceof Identifier) {
-             identifier = nonEmpty(((Identifier) metadata).getCode());
-             if (metadata instanceof ReferenceIdentifier) {
-                 final String cs = nonEmpty(((ReferenceIdentifier) metadata).getCodeSpace());
-                 if (cs != null) {
-                     identifier = (identifier != null) ? (cs + DefaultNameSpace.DEFAULT_SEPARATOR + identifier) : cs;
+             identifiers = Collections.singleton((Identifier) metadata);
+         } else if (metadata instanceof IdentifiedObject) {
+             identifiers = ((IdentifiedObject) metadata).getIdentifiers();
+         } else {
+             identifiers = Collections.emptySet();
+         }
+         for (final Identifier id : identifiers) {
+             identifier = nonEmpty(id.getCode());
+             if (identifier != null) {
 -                final String cs = nonEmpty(id.getCodeSpace());
 -                if (cs != null) {
 -                    identifier = cs + DefaultNameSpace.DEFAULT_SEPARATOR + identifier;
++                if (id instanceof ReferenceIdentifier) {
++                    final String cs = nonEmpty(((ReferenceIdentifier) id).getCodeSpace());
++                    if (cs != null) {
++                        identifier = cs + DefaultNameSpace.DEFAULT_SEPARATOR + identifier;
++                    }
                  }
+                 break;
              }
          }
          if (identifier == null && metadata instanceof Citation) {
diff --cc core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
index 07cfd5d,694008c..201b55d
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
@@@ -29,7 -29,7 +29,6 @@@ import org.opengis.util.NameFactory
  import org.opengis.util.NameSpace;
  import org.opengis.util.RecordSchema;
  import org.opengis.util.RecordType;
- import org.apache.sis.util.Debug;
 -import org.opengis.feature.AttributeType;
  import org.apache.sis.util.ArgumentChecks;
  import org.apache.sis.util.ObjectConverter;
  import org.apache.sis.util.ObjectConverters;
diff --cc core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
index eb0183b,2efa1f8..0fe49b3
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
@@@ -26,7 -26,7 +26,6 @@@ import javax.xml.bind.annotation.XmlTra
  import org.opengis.util.Type;
  import org.opengis.util.RecordType;
  import org.opengis.util.MemberName;
- import org.apache.sis.util.Debug;
 -import org.opengis.feature.AttributeType;
  import org.apache.sis.util.Classes;
  import org.apache.sis.util.Numbers;
  import org.apache.sis.util.CharSequences;
diff --cc core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
index fb3ec87,e0e91b5..115249a
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
@@@ -24,11 -24,11 +24,11 @@@ import javax.xml.bind.JAXBException
  import org.opengis.metadata.citation.Role;
  import org.opengis.metadata.citation.DateType;
  import org.opengis.metadata.citation.CitationDate;
 -import org.opengis.metadata.citation.Responsibility;
 +import org.opengis.metadata.citation.ResponsibleParty;
  import org.opengis.metadata.citation.PresentationForm;
  import org.apache.sis.metadata.iso.citation.DefaultCitation;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
- import org.apache.sis.internal.jaxb.Schemas;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
+ import org.apache.sis.internal.xml.Schemas;
  import org.apache.sis.xml.XML;
  import org.apache.sis.xml.Namespaces;
  import org.apache.sis.xml.MarshallerPool;
diff --cc core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
index 0000000,6e49ec9..a85ef32
mode 000000,100644..100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
@@@ -1,0 -1,155 +1,154 @@@
+ /*
+  * 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.Arrays;
+ import org.opengis.util.InternationalString;
+ import org.opengis.metadata.Identifier;
+ import org.opengis.metadata.citation.Role;
+ import org.opengis.metadata.citation.Citation;
 -import org.opengis.metadata.citation.Individual;
+ import org.opengis.metadata.citation.ResponsibleParty;
+ import org.opengis.metadata.acquisition.Instrument;
+ import org.opengis.metadata.acquisition.Platform;
+ import org.apache.sis.util.iso.SimpleInternationalString;
+ import org.apache.sis.metadata.iso.DefaultIdentifier;
+ import org.apache.sis.metadata.iso.citation.DefaultCitation;
+ import org.apache.sis.metadata.iso.citation.DefaultIndividual;
+ import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
+ import org.apache.sis.metadata.iso.acquisition.DefaultInstrument;
+ import org.apache.sis.metadata.iso.acquisition.DefaultPlatform;
+ import org.apache.sis.test.DependsOnMethod;
+ import org.apache.sis.test.DependsOn;
+ import org.apache.sis.test.TestCase;
+ import org.junit.Test;
+ 
+ import static java.util.Collections.singleton;
+ import static org.junit.Assert.*;
+ 
+ 
+ /**
+  * Tests the {@link HashCode} class. This is also used as a relatively simple {@link MetadataVisitor} test.
+  * The entry point is the {@link HashCode#walk(MetadataStandard, Class, Object, boolean)} method.
+  *
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ @DependsOn(PropertyAccessorTest.class)
+ public final strictfp class HashCodeTest extends TestCase {
+     /**
+      * Computes the hash code value of the given object.
+      */
+     private static Integer hash(final Object metadata) {
+         return HashCode.getOrCreate().walk(MetadataStandard.ISO_19115, null, metadata, true);
+     }
+ 
+     /**
+      * Tests hash code computation of an object that do not contain other metadata.
+      */
+     @Test
+     public void testSimple() {
+         final DefaultCitation instance = new DefaultCitation();
+         final int baseCode = Citation.class.hashCode();
+         assertEquals("Empty metadata.", Integer.valueOf(baseCode), hash(instance));
+ 
+         final InternationalString title = new SimpleInternationalString("Some title");
+         instance.setTitle(title);
+         assertEquals("Metadata with a single value.", Integer.valueOf(baseCode + title.hashCode()), hash(instance));
+ 
+         final InternationalString alternateTitle = new SimpleInternationalString("An other title");
+         instance.setAlternateTitles(singleton(alternateTitle));
+         assertEquals("Metadata with two values.",
+                      Integer.valueOf(baseCode + title.hashCode() + Arrays.asList(alternateTitle).hashCode()),
+                      hash(instance));
+     }
+ 
+     /**
+      * Tests hash code computation of an object containing another metadata object.
+      */
+     @Test
+     @DependsOnMethod("testSimple")
+     public void testNested() {
+         final InternationalString   title    = new SimpleInternationalString("Some title");
+         final InternationalString   person   = new SimpleInternationalString("Illustre inconnu");
+         final DefaultIndividual     party    = new DefaultIndividual(person, null, null);
+         final DefaultResponsibleParty resp   = new DefaultResponsibleParty(Role.AUTHOR);
+         final DefaultCitation       instance = new DefaultCitation(title);
+         resp.getParties().add(party);
+         instance.getCitedResponsibleParties().add(resp);
+         /*
+          * Individual hash code is the sum of all its properties, none of them being a collection.
+          */
 -        int expected = Individual.class.hashCode() + person.hashCode();
++        int expected = DefaultIndividual.class.hashCode() + person.hashCode();
+         assertEquals("Individual", Integer.valueOf(expected), hash(party));
+         /*
+          * The +31 below come from java.util.List contract, since above Individual is a list member.
+          */
+         expected += ResponsibleParty.class.hashCode() + Role.AUTHOR.hashCode() + 31;
+         assertEquals("Responsibility", Integer.valueOf(expected), hash(resp));
+         /*
+          * The +31 below come from java.util.List contract, since above Responsibility is a list member.
+          */
+         expected += Citation.class.hashCode() + title.hashCode() + 31;
+         assertEquals("Citation", Integer.valueOf(expected), hash(instance));
+     }
+ 
+     /**
+      * Tests hash code computation of an object graph containing a cycle.
+      */
+     @Test
+     @DependsOnMethod("testNested")
+     public void testCycle() {
+         /*
+          * We will create a Platform and an Instrument, both of them with no other property than an identifier.
+          * The assertions verifying Identifier hash codes are not the main purpose of this test, but we perform
+          * those verifications for making sure that the assertion done at the end of this method has good premises.
+          */
+         final DefaultIdentifier platformID   = new DefaultIdentifier("P1");
+         final DefaultIdentifier instrumentID = new DefaultIdentifier("I1");
+         int platformHash   = Identifier.class.hashCode() +   platformID.getCode().hashCode();
+         int instrumentHash = Identifier.class.hashCode() + instrumentID.getCode().hashCode();
+         assertEquals("platformID",   Integer.valueOf(platformHash),   hash(platformID));
+         assertEquals("instrumentID", Integer.valueOf(instrumentHash), hash(instrumentID));
+         /*
+          * Verify Platform and Instrument hash codes before we link them together.
+          */
+         final DefaultPlatform   platform   = new DefaultPlatform();
+         final DefaultInstrument instrument = new DefaultInstrument();
+         platform  .setIdentifier(platformID);
+         instrument.setIdentifier(instrumentID);
+         platformHash   +=   Platform.class.hashCode();
+         instrumentHash += Instrument.class.hashCode();
+         assertEquals("Platform",   Integer.valueOf(platformHash),   hash(platform));
+         assertEquals("Instrument", Integer.valueOf(instrumentHash), hash(instrument));
+         /*
+          * Add the instrument to the platform. The +31 below come from java.util.List contract,
+          * since the Instrument is contained in a list.
+          */
+         platform.getInstruments().add(instrument);
+         platformHash += instrumentHash + 31;
+         assertEquals("Platform", Integer.valueOf(platformHash), hash(platform));
+         /*
+          * Add a reference from the instrument back to the platform. This is where the graph become cyclic.
+          * The hash code computation is expected to behave as if the platform was not specified.
+          */
+         instrument.setMountedOn(platform);
+         assertEquals("Platform", Integer.valueOf(platformHash), hash(platform));
+     }
+ }
diff --cc core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
index a52d205,985c525..664cdeb
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
@@@ -27,11 -27,10 +27,11 @@@ import org.apache.sis.io.wkt.Symbols
  import org.apache.sis.io.wkt.WKTFormat;
  import org.apache.sis.io.wkt.Convention;
  import org.apache.sis.xml.Namespaces;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  
  // Branch-specific imports
 -import org.opengis.metadata.citation.Responsibility;
 +import org.apache.sis.metadata.iso.citation.DefaultCitation;
 +import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
  
  
  /**
diff --cc core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
index 14a709f,04fd9c5..c4827b2
--- 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
@@@ -34,10 -34,13 +34,10 @@@ import org.opengis.annotation.UML
  import org.opengis.annotation.Obligation;
  import org.opengis.annotation.Specification;
  import org.opengis.util.CodeList;
 -import org.opengis.util.ControlledVocabulary;
  import org.apache.sis.util.ArraysExt;
  import org.apache.sis.xml.Namespaces;
- import org.apache.sis.internal.jaxb.Schemas;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.Schemas;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  import org.apache.sis.test.DependsOnMethod;
  import org.apache.sis.test.TestUtilities;
  import org.apache.sis.test.TestCase;
@@@ -65,7 -68,7 +65,6 @@@ import junit.framework.AssertionFailedE
   * </ul>
   *
   * This class does not verify JAXB annotations against a XSD file.
-- * For such verification, see {@link SchemaCompliance}.
   *
   * @author  Cédric Briançon (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
@@@ -218,7 -222,7 +217,7 @@@ public abstract strictfp class Annotati
       * with a hard-coded list of exceptions to the general rule.
       * Subclasses shall override this method if they need to support more namespaces.</p>
       *
--     * <p>Note that a more complete verification is done by {@link SchemaCompliance}.
++     * <p>Note that a more complete verification is done by {@code SchemaCompliance}.
       * But the test done in this {@link AnnotationConsistencyCheck} class can be run without network access.</p>
       *
       * <p>The prefix for the given namespace will be fetched by
diff --cc core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
index bf52a98,a964214..0000000
deleted file mode 100644,100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
+++ /dev/null
@@@ -1,513 -1,514 +1,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.test.xml;
--
--import java.util.Map;
--import java.util.Set;
--import java.util.HashMap;
--import java.util.Collection;
--import java.util.Collections;
--import java.io.IOException;
--import java.lang.reflect.Type;
--import java.lang.reflect.Field;
--import java.lang.reflect.Method;
--import java.lang.reflect.AnnotatedElement;
--import java.lang.reflect.ParameterizedType;
--import javax.xml.bind.annotation.XmlNs;
--import javax.xml.bind.annotation.XmlType;
--import javax.xml.bind.annotation.XmlSchema;
--import javax.xml.bind.annotation.XmlElement;
--import javax.xml.bind.annotation.XmlRootElement;
--import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
--import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
--import javax.xml.parsers.ParserConfigurationException;
--import org.xml.sax.SAXException;
--import org.opengis.annotation.UML;
 -import org.opengis.geoapi.SchemaException;
--import org.apache.sis.util.Classes;
--import org.apache.sis.internal.system.Modules;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
 -import org.apache.sis.internal.xml.LegacyNamespaces;
--import org.apache.sis.xml.Namespaces;
--
--
--/**
-- * Verify JAXB annotations in a single package. A new instance of this class is created by
-- * {@link SchemaCompliance#verify(java.nio.file.Path)} for each Java package to be verified.
-- *
-- * @author  Martin Desruisseaux (Geomatys)
-- * @version 1.0
-- * @since   1.0
-- * @module
-- */
--final strictfp class PackageVerifier {
--    /**
--     * Sentinel value used in {@link #LEGACY_NAMESPACES} for meaning "all properties in that namespace".
--     */
--    @SuppressWarnings("unchecked")
--    private static final Set<String> ALL = InfiniteSet.INSTANCE;
--
--    /**
--     * Classes or properties having a JAXB annotation in this namespace should be deprecated.
--     * Deprecated namespaces are enumerated as keys. If the associated value is {@link #ALL},
--     * the whole namespace is deprecated. If the value is not ALL, then only the enumerated
--     * properties are deprecated.
--     *
--     * <p>Non-ALL values are rare. They happen in a few cases where a property is legacy despite its namespace.
--     * Those "properties" are errors in the legacy ISO 19139:2007 schema; they were types without their property
--     * wrappers. For example in {@code SV_CoupledResource}, {@code <gco:ScopedName>} was marshalled without its
--     * {@code <srv:scopedName>} wrapper — note the upper and lower-case "s". Because {@code ScopedName} is a type,
--     * we had to keep the namespace declared in {@link org.apache.sis.util.iso.DefaultScopedName}
--     * (the replacement is performed by {@code org.apache.sis.xml.TransformingWriter}).</p>
--     */
--    private static final Map<String, Set<String>> LEGACY_NAMESPACES;
--    static {
--        final Map<String, Set<String>> m = new HashMap<>(8);
--        m.put(LegacyNamespaces.GMD, ALL);
--        m.put(LegacyNamespaces.GMI, ALL);
--        m.put(LegacyNamespaces.GMX, ALL);
--        m.put(LegacyNamespaces.SRV, ALL);
--        m.put(Namespaces.GCO, Collections.singleton("ScopedName"));     // Not to be confused with standard <srv:scopedName>
--        LEGACY_NAMESPACES = Collections.unmodifiableMap(m);
--    }
--
--    /**
--     * Types declared in JAXB annotations to be considered as equivalent to types in XML schemas.
--     */
--    private static final Map<String,String> TYPE_EQUIVALENCES;
--    static {
--        final Map<String,String> m = new HashMap<>();
--        m.put("PT_FreeText",             "CharacterString");
--        m.put("Abstract_Citation",       "CI_Citation");
--        m.put("AbstractCI_Party",        "CI_Party");
--        m.put("Abstract_Responsibility", "CI_Responsibility");
--        m.put("Abstract_Extent",         "EX_Extent");
--        TYPE_EQUIVALENCES = Collections.unmodifiableMap(m);
--    }
--
--    /**
--     * The schemas to compare with the JAXB annotations.
--     * Additional schemas will be loaded as needed.
--     */
--    private final SchemaCompliance schemas;
--
--    /**
--     * The package name, for reporting error.
--     */
--    private final String packageName;
--
--    /**
--     * The default namespace to use if a class does not define explicitely a namespace.
--     */
--    private final String packageNS;
--
--    /**
--     * The namespace of the class under examination.
--     * This field must be updated for every class found in a package.
--     */
--    private String classNS;
--
--    /**
--     * The class under examination, used in error messages.
--     * This field must be updated for every class found in a package.
--     */
--    private Class<?> currentClass;
--
--    /**
--     * Whether the class under examination is defined in a legacy namespace.
--     * In such case, some checks may be skipped because we didn't loaded schemas for legacy properties.
--     */
--    private boolean isDeprecatedClass;
--
--    /**
--     * The schema definition for the class under examination.
--     *
-      * @see SchemaCompliance#typeDefinition(String)
 -     * @see SchemaCompliance#getTypeDefinition(String)
--     */
-     private Map<String, SchemaCompliance.Info> properties;
 -    private Map<String, SchemaCompliance.Element> properties;
--
--    /**
--     * Whether a namespace is actually used of not.
--     * We use this map for identifying unnecessary prefix declarations.
--     */
--    private final Map<String,Boolean> namespaceIsUsed;
--
--    /**
--     * Whether adapters declared in {@code package-info.java} are used or not.
--     */
--    private final Map<Class<?>,Boolean> adapterIsUsed;
--
--    /**
--     * Creates a new verifier for the given package.
--     */
--    PackageVerifier(final SchemaCompliance schemas, final Package pkg)
--            throws IOException, ParserConfigurationException, SAXException, SchemaException
--    {
--        this.schemas = schemas;
--        namespaceIsUsed = new HashMap<>();
--        adapterIsUsed = new HashMap<>();
--        String name = "?", namespace = "";
--        if (pkg != null) {
--            name = pkg.getName();
--            final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
--            if (schema != null) {
--                namespace = schema.namespace();
--                String location = schema.location();
--                if (!XmlSchema.NO_LOCATION.equals(location)) {
--                    if (!location.startsWith(schema.namespace())) {
--                        throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
--                    }
--                    schemas.loadSchema(location);
--                }
--                for (final XmlNs xmlns : schema.xmlns()) {
--                    final String pr = xmlns.prefix();
--                    final String ns = xmlns.namespaceURI();
--                    final String cr = schemas.allXmlNS.put(pr, ns);
--                    if (cr != null && !cr.equals(ns)) {
--                        throw new SchemaException(String.format("Prefix \"%s\" associated to two different namespaces:%n%s%n%s", pr, cr, ns));
--                    }
--                    if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
--                        throw new SchemaException(String.format("Duplicated namespace in package %s:%n%s", name, ns));
--                    }
--                }
--            }
--            /*
--             * Lists the type of all values for which an adapter is declared in package-info.
--             * If the type is not explicitely declared, then it is inferred from class signature.
--             */
--            final XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class);
--            if (adapters != null) {
--                for (final XmlJavaTypeAdapter adapter : adapters.value()) {
--                    Class<?> propertyType = adapter.type();
--                    if (propertyType == XmlJavaTypeAdapter.DEFAULT.class) {
--                        for (Class<?> c = adapter.value(); ; c = c.getSuperclass()) {
--                            final Type type = c.getGenericSuperclass();
--                            if (type == null) {
--                                throw new SchemaException(String.format(
--                                        "Can not infer type for %s adapter.", adapter.value().getName()));
--                            }
--                            if (type instanceof ParameterizedType) {
--                                final Type[] p = ((ParameterizedType) type).getActualTypeArguments();
--                                if (p.length == 2) {
--                                    Type pt = p[1];
--                                    if (pt instanceof ParameterizedType) {
--                                        pt = ((ParameterizedType) pt).getRawType();
--                                    }
--                                    if (pt instanceof Class<?>) {
--                                        propertyType = (Class<?>) pt;
--                                        break;
--                                    }
--                                }
--                            }
--                        }
--                    }
--                    if (adapterIsUsed.put((Class<?>) propertyType, Boolean.FALSE) != null) {
--                        throw new SchemaException(String.format(
--                                "More than one adapter for %s in package %s", propertyType, name));
--                    }
--                }
--            }
--        }
--        packageName = name;
--        packageNS = namespace;
--    }
--
--    /**
--     * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
--     * (type name should be same as root element name with {@value SchemaCompliance#TYPE_SUFFIX} suffix appended),
--     * ensures that the name exists in the schema, and checks the namespace.
--     *
--     * @param  type  the class on which to verify annotations.
--     */
--    final void verify(final Class<?> type)
--            throws IOException, ParserConfigurationException, SAXException, SchemaException
--    {
--        /*
--         * Reinitialize fields to be updated for each class.
--         */
--        classNS           = null;
--        currentClass      = type;
--        isDeprecatedClass = false;
--        properties        = Collections.emptyMap();
--
--        final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
--        final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
--        XmlElement codeList = null;
--        /*
--         * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
--         * If both of them are present, verify that they are consistent (same namespace and
--         * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
--         * assume that it is the same than the class name (this is what Apache SIS 0.8 does
--         * in its org.apache.sis.internal.jaxb.code package for CodeList adapters).
--         */
--        final String isoName;       // ISO class name (not the same than Java class name).
--        if (xmlRoot != null) {
--            classNS = xmlRoot.namespace();
--            isoName = xmlRoot.name();
--            if (xmlType != null) {
--                if (!classNS.equals(xmlType.namespace())) {
--                    throw new SchemaException(errorInClassMember(null)
-                             .append("Mismatched namespace in @XmlType and @XmlRootElement."));
 -                            .append("Mismatched namespace in @XmlType and @XmlRootElement.").toString());
--                }
--                SchemaCompliance.verifyNamingConvention(type.getName(), isoName, xmlType.name(), SchemaCompliance.TYPE_SUFFIX);
--            }
--        } else if (xmlType != null) {
--            classNS = xmlType.namespace();
--            final String name = xmlType.name();
--            isoName = SchemaCompliance.trim(name, SchemaCompliance.TYPE_SUFFIX);
--        } else {
--            /*
--             * If there is neither @XmlRootElement or @XmlType annotation, it may be a code list as implemented
--             * in the org.apache.sis.internal.jaxb.code package. Those adapters have a single @XmlElement which
--             * is to be interpreted as if it was the actual type.
--             */
--            for (final Method method : type.getDeclaredMethods()) {
--                final XmlElement e = method.getDeclaredAnnotation(XmlElement.class);
--                if (e != null) {
--                    if (codeList != null) return;
--                    codeList = e;
--                }
--            }
--            if (codeList == null) return;
--            classNS = codeList.namespace();
--            isoName = codeList.name();
--        }
--        /*
--         * Verify that the namespace declared on the class is not redundant with the namespace
--         * declared in the package. Actually redundant namespaces are not wrong, but we try to
--         * reduce code size.
--         */
--        if (classNS.equals(AnnotationConsistencyCheck.DEFAULT)) {
--            classNS = packageNS;
--        } else if (classNS.equals(packageNS)) {
--            throw new SchemaException(errorInClassMember(null)
-                     .append("Redundant namespace declaration: ").append(classNS));
 -                    .append("Redundant namespace declaration: ").append(classNS).toString());
--        }
--        /*
--         * Verify that the namespace has a prefix associated to it in the package-info file.
--         */
--        if (namespaceIsUsed.put(classNS, Boolean.TRUE) == null) {
--            throw new SchemaException(errorInClassMember(null)
-                     .append("No prefix in package-info for ").append(classNS));
 -                    .append("No prefix in package-info for ").append(classNS).toString());
--        }
--        /*
--         * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
--         * is already available or not. However properties in other namespaces should not be deprecated.
--         * Some validations of deprecated properties are skipped because we didn't loaded their schema.
--         */
--        isDeprecatedClass = (LEGACY_NAMESPACES.get(classNS) == ALL);
--        if (!isDeprecatedClass) {
--            if (type.isAnnotationPresent(Deprecated.class)) {
--                throw new SchemaException(errorInClassMember(null)
-                         .append("Unexpected @Deprecated annotation."));
 -                        .append("Unexpected @Deprecated annotation.").toString());
--            }
--            /*
--             * Verify that class name exists, then verify its namespace (associated to the null key by convention).
--             */
-             properties = schemas.typeDefinition(isoName);
 -            properties = schemas.getTypeDefinition(isoName);
--            if (properties == null) {
--                throw new SchemaException(errorInClassMember(null)
-                         .append("Unknown name declared in @XmlRootElement: ").append(isoName));
 -                        .append("Unknown name declared in @XmlRootElement: ").append(isoName).toString());
--            }
--            final String expectedNS = properties.get(null).namespace;
--            if (!classNS.equals(expectedNS)) {
--                throw new SchemaException(errorInClassMember(null)
-                         .append(isoName).append(" shall be associated to namespace ").append(expectedNS));
 -                        .append(isoName).append(" shall be associated to namespace ").append(expectedNS).toString());
--            }
--            if (codeList != null) return;                   // If the class was a code list, we are done.
--        }
--        /*
--         * At this point the classNS, className, isDeprecatedClass and properties field have been set.
--         * We can now loop over the XML elements, which may be on fields or on methods (public or private).
--         */
--        for (final Field field : type.getDeclaredFields()) {
--            Class<?> valueType = field.getType();
--            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
--            if (isCollection) {
--                valueType = Classes.boundOfParameterizedProperty(field);
--            }
--            verify(field, field.getName(), valueType, isCollection);
--        }
--        for (final Method method : type.getDeclaredMethods()) {
--            Class<?> valueType = method.getReturnType();
--            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
--            if (isCollection) {
--                valueType = Classes.boundOfParameterizedProperty(method);
--            }
--            verify(method, method.getName(), valueType, isCollection);
--        }
--    }
--
--    /**
--     * Validate a field or a method against the expected schema.
--     *
--     * @param  property      the field or method to validate.
--     * @param  javaName      the field name or method name in Java code.
--     * @param  valueType     the field type or the method return type, or element type in case of collection.
--     * @param  isCollection  whether the given value type is the element type of a collection.
--     */
--    private void verify(final AnnotatedElement property, final String javaName,
--            final Class<?> valueType, final boolean isCollection) throws SchemaException
--    {
--        final XmlElement element = property.getDeclaredAnnotation(XmlElement.class);
--        if (element == null) {
--            return;                               // No @XmlElement annotation - skip this property.
--        }
--        String name = element.name();
--        if (name.equals(AnnotationConsistencyCheck.DEFAULT)) {
--            name = javaName;
--        }
--        String ns = element.namespace();
--        if (ns.equals(AnnotationConsistencyCheck.DEFAULT)) {
--            ns = classNS;
--        }
--        if (namespaceIsUsed.put(ns, Boolean.TRUE) == null) {
--            throw new SchemaException(errorInClassMember(javaName)
-                     .append("Missing @XmlNs for namespace ").append(ns));
 -                    .append("Missing @XmlNs for namespace ").append(ns).toString());
--        }
--        /*
--         * Remember that we need an adapter for this property, unless the method or field defines its own adapter.
--         * In theory we do not need to report missing adapter since JAXB performs its own check, but we do anyway
--         * because JAXB has default adapters for String, Double, Boolean, Date, etc. which do not match the way
--         * OGC/ISO marshal those elements.
--         */
--        if (!property.isAnnotationPresent(XmlJavaTypeAdapter.class) && (valueType != null)
--                && !valueType.getName().startsWith(Modules.CLASSNAME_PREFIX))
--        {
--            Class<?> c = valueType;
--            while (adapterIsUsed.replace(c, Boolean.TRUE) == null) {
--                final Class<?> parent = c.getSuperclass();
--                if (parent != null) {
--                    c = parent;
--                } else {
--                    final Class<?>[] p = c.getInterfaces();
--                    if (p.length == 0) {
--                        throw new SchemaException(errorInClassMember(javaName)
-                                 .append("Missing @XmlJavaTypeAdapter for ").append(valueType));
 -                                .append("Missing @XmlJavaTypeAdapter for ").append(valueType).toString());
--                    }
--                    c = p[0];   // Take only the first interface, which should be the "main" parent.
--                }
--            }
--        }
--        /*
--         * We do not verify fully the properties in legacy namespaces because we didn't loaded their schemas.
--         * However we verify at least that those properties are not declared as required.
--         */
--        if (LEGACY_NAMESPACES.getOrDefault(ns, Collections.emptySet()).contains(name)) {
--            if (!isDeprecatedClass && element.required()) {
--                throw new SchemaException(errorInClassMember(javaName)
-                         .append("Legacy property should not be required."));
 -                        .append("Legacy property should not be required.").toString());
--            }
--        } else {
--            /*
--             * Property in non-legacy namespaces should not be deprecated. Verify also their namespace
--             * and whether the property is required or optional, and whether it should be a collection.
--             */
--            if (property.isAnnotationPresent(Deprecated.class)) {
--                throw new SchemaException(errorInClassMember(javaName)
-                         .append("Unexpected deprecation status."));
 -                        .append("Unexpected deprecation status.").toString());
--            }
-             final SchemaCompliance.Info info = properties.get(name);
 -            final SchemaCompliance.Element info = properties.get(name);
--            if (info == null) {
--                throw new SchemaException(errorInClassMember(javaName)
-                         .append("Unexpected XML element: ").append(name));
 -                        .append("Unexpected XML element: ").append(name).toString());
--            }
--            if (info.namespace != null && !ns.equals(info.namespace)) {
--                throw new SchemaException(errorInClassMember(javaName)
--                        .append("Declared namespace: ").append(ns).append(System.lineSeparator())
-                         .append("Expected namespace: ").append(info.namespace));
 -                        .append("Expected namespace: ").append(info.namespace).toString());
--            }
--            if (element.required() != info.isRequired) {
--                throw new SchemaException(errorInClassMember(javaName)
-                         .append("Expected @XmlElement(required = ").append(info.isRequired).append(')'));
 -                        .append("Expected @XmlElement(required = ").append(info.isRequired).append(')').toString());
--            }
--            /*
--             * Following is a continuation of our check for cardinality, but also the beginning of the check
--             * for return value type. The return type should be an interface with a UML annotation; we check
--             * that this annotation contains the name of the expected type.
--             */
--            if (isCollection) {
--                if (!info.isCollection) {
--                    if (false)  // Temporarily disabled because require GeoAPI modifications.
-                     throw new SchemaException(errorInClassMember(javaName).append("Value should be a singleton."));
 -                    throw new SchemaException(errorInClassMember(javaName).append("Value should be a singleton.").toString());
--                }
--            } else if (info.isCollection) {
--                if (false)  // Temporarily disabled because require GeoAPI modifications.
-                 throw new SchemaException(errorInClassMember(javaName).append("Value should be a collection."));
 -                throw new SchemaException(errorInClassMember(javaName).append("Value should be a collection.").toString());
--            }
--            if (valueType != null) {
--                final UML valueUML = valueType.getAnnotation(UML.class);
--                if (valueUML != null) {
--                    String expected = info.typeName;
--                    String actual   = valueUML.identifier();
--                    expected = TYPE_EQUIVALENCES.getOrDefault(expected, expected);
--                    actual   = TYPE_EQUIVALENCES.getOrDefault(actual,   actual);
--                    if (!expected.equals(actual)) {
--                        if (false)  // Temporarily disabled because require GeoAPI modifications.
--                        throw new SchemaException(errorInClassMember(javaName)
--                                .append("Declared value type: ").append(actual).append(System.lineSeparator())
-                                 .append("Expected value type: ").append(expected));
 -                                .append("Expected value type: ").append(expected).toString());
--                    }
--                }
--            }
--            /*
--             * Verify if we have a @XmlNs for the type of the value. This is probably not required, but we
--             * do that as a safety. A common namespace added by this check is Metadata Common Classes (MCC).
--             */
-             final Map<String, SchemaCompliance.Info> valueInfo = schemas.typeDefinition(info.typeName);
 -            final Map<String, SchemaCompliance.Element> valueInfo = schemas.getTypeDefinition(info.typeName);
--            if (valueInfo != null) {
--                final String valueNS = valueInfo.get(null).namespace;
--                if (namespaceIsUsed.put(valueNS, Boolean.TRUE) == null) {
--                    throw new SchemaException(errorInClassMember(javaName)
-                             .append("Missing @XmlNs for property value namespace: ").append(valueNS));
 -                            .append("Missing @XmlNs for property value namespace: ").append(valueNS).toString());
--                }
--            }
--        }
--    }
--
--    /**
--     * Returns a message beginning with "Error in …", to be completed by the caller.
--     * This is an helper method for exception messages.
--     *
--     * @param  name  the property name, or {@code null} if none.
--     */
--    private StringBuilder errorInClassMember(final String name) {
--        final StringBuilder builder = new StringBuilder(80).append("Error in ");
--        if (isDeprecatedClass) {
--            builder.append("legacy ");
--        }
--        builder.append(currentClass.getCanonicalName());
--        if (name != null) {
--            builder.append('.').append(name);
--        }
--        return builder.append(':').append(System.lineSeparator());
--    }
--
--    /**
--     * Verifies if there is any unused namespace or adapter in package-info file.
--     */
--    final void reportUnused() throws SchemaException {
--        for (final Map.Entry<String,Boolean> entry : namespaceIsUsed.entrySet()) {
--            if (!entry.getValue()) {
--                throw new SchemaException(String.format("Unused namespace in package %s:%n%s", packageName, entry.getKey()));
--            }
--        }
--        for (final Map.Entry<Class<?>,Boolean> entry : adapterIsUsed.entrySet()) {
--            if (!entry.getValue()) {
--                throw new SchemaException(String.format("Unused adapter in package %s for %s.", packageName, entry.getKey()));
--            }
--        }
--    }
--}
diff --cc core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
index 1280c3a,ad6edcb..0000000
deleted file mode 100644,100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
+++ /dev/null
@@@ -1,566 -1,200 +1,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.test.xml;
--
--import java.io.IOException;
- import java.io.InputStream;
- import java.net.URL;
--import java.nio.file.Path;
--import java.nio.file.Files;
--import java.nio.file.DirectoryStream;
--import java.nio.file.DirectoryIteratorException;
--import java.util.Map;
- import java.util.Deque;
--import java.util.HashMap;
- import java.util.ArrayDeque;
- import java.util.Objects;
- import java.util.Collections;
- import javax.xml.XMLConstants;
--import javax.xml.bind.annotation.XmlNs;
--import javax.xml.bind.annotation.XmlElement;
- import javax.xml.parsers.DocumentBuilderFactory;
--import javax.xml.parsers.ParserConfigurationException;
- import org.w3c.dom.Node;
- import org.w3c.dom.Document;
- import org.w3c.dom.NamedNodeMap;
--import org.xml.sax.SAXException;
 -import org.opengis.geoapi.Departures;
 -import org.opengis.geoapi.DocumentationStyle;
 -import org.opengis.geoapi.SchemaInformation;
 -import org.opengis.geoapi.SchemaException;
--import org.apache.sis.util.StringBuilders;
--
--
--/**
-- * Compares JAXB annotations against the ISO 19115 schemas. This test requires a connection to
-- * <a href="http://standards.iso.org/iso/19115/-3/">http://standards.iso.org/iso/19115/-3/</a>.
-- * All classes in a given directory are scanned.
-- *
-- * <div class="section">Limitations</div>
-- * Current implementation ignores the XML prefix (e.g. {@code "cit:"} in {@code "cit:CI_Citation"}).
-- * We assume that there is no name collision, especially given that {@code "CI_"} prefix in front of
-- * most OGC/ISO class names have the effect of a namespace. If a collision nevertheless happen, then
-- * an exception will be thrown.
-- *
-- * <p>Current implementation assumes that XML element name, type name, property name and property type
-- * name follow some naming convention. For example type names are suffixed with {@code "_Type"} in OGC
-- * schemas, while property type names are suffixed with {@code "_PropertyType"}.  This class throws an
-- * exception if a type does not follow the expected naming convention. This requirement makes
-- * implementation easier, by reducing the amount of {@link Map}s that we need to manage.</p>
-- *
-- * @author  Martin Desruisseaux (Geomatys)
-- * @version 1.0
-- * @since   1.0
-- * @module
-- */
- public final strictfp class SchemaCompliance {
-     /**
-      * The root of ISO schemas. May be replaced by {@link #schemaRootDirectory} if a local copy
-      * is available for faster tests.
-      */
-     private static final String SCHEMA_ROOT_DIRECTORY = "http://standards.iso.org/iso/";
- 
-     /**
-      * ISO 19115-2 classes to merge with ISO 19115-1 classes. For example ISO 19115-2 defines {@code MI_Band}
-      * as an extension of ISO 19115-1 {@code MD_Band}, but GeoAPI and Apache SIS merges those two types in a
-      * single class for simplicity. Consequently when reading the schema, we rename some {@code MI_*} types
-      * as {@code MD_*} in order to store properties together.
-      */
-     private static final Map<String,String> TYPES_TO_MERGE;
-     static {
-         final Map<String,String> m = new HashMap<>();
-         // ………Merge what…………………………………………………………Into……………………………………………
-         m.put("MI_Band_Type",                 "MD_Band_Type");
-         m.put("MI_CoverageDescription_Type",  "MD_CoverageDescription_Type");
-         m.put("MI_Georectified_Type",         "MD_Georectified_Type");
-         m.put("MI_Georeferenceable_Type",     "MD_Georeferenceable_Type");
-         m.put("LE_Source_Type",               "LI_Source_Type");
-         m.put("LE_ProcessStep_Type",          "LI_ProcessStep_Type");
-         m.put("AbstractMX_File_Type",         "MX_DataFile_Type");
-         m.put("Abstract_DataQuality_Type",    "DQ_DataQuality_Type");
-         m.put("Abstract_QualityElement_Type", "AbstractDQ_Element_Type");
-         TYPES_TO_MERGE = Collections.unmodifiableMap(m);
-     }
- 
 -public final strictfp class SchemaCompliance extends SchemaInformation {
--    /**
--     * The prefix of XML type names for properties. In ISO/OGC schemas, this prefix does not appear
--     * in the definition of class types but may appear in the definition of property types.
--     */
--    private static final String ABSTRACT_PREFIX = "Abstract_";
--
--    /**
--     * The suffix of XML type names for classes.
--     * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
--     */
--    static final String TYPE_SUFFIX = "_Type";
- 
-     /**
-      * The suffix of XML property type names in a given class.
-      * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
-      */
-     private static final String PROPERTY_TYPE_SUFFIX = "_PropertyType";
- 
-     /**
-      * XML type to ignore because of key collisions in {@link #typeDefinitions}.
-      * Those collisions occur because code lists are defined as links to the same file,
-      * with only different anchor positions.
-      */
-     private static final String CODELIST_TYPE = "gco:CodeListValue_Type";
--
--    /**
--     * Separator between XML prefix and the actual name.
--     */
--    private static final char PREFIX_SEPARATOR = ':';
- 
-     /**
-      * If the computer contains a local copy of ISO schemas, path to that directory. Otherwise {@code null}.
-      * If non-null, the {@code "http://standards.iso.org/iso/"} prefix in URL will be replaced by that path.
-      * This field is usually {@code null}, but can be set to a non-null value for making tests faster.
-      */
-     private final Path schemaRootDirectory;
--
--    /**
--     * Root directory from which to search for classes.
--     */
--    private final Path classRootDirectory;
- 
-     /**
-      * A temporary buffer for miscellaneous string operations.
-      * Valid only in a local scope since the content may change at any time.
-      */
-     private final StringBuilder buffer;
- 
-     /**
-      * The DOM factory used for reading XSD schemas.
-      */
-     private final DocumentBuilderFactory factory;
- 
-     /**
-      * URL of schemas loaded, for avoiding loading the same schema many time.
-      * The last element on the queue is the schema in process of being loaded,
-      * used for resolving relative paths in {@code <xs:include>} elements.
-      */
-     private final Deque<String> schemaLocations;
- 
-     /**
-      * The type and namespace of a property or class. Used in {@link #typeDefinitions} map.
-      */
-     static final class Info {
-         final String  typeName;
-         final String  namespace;
-         final boolean isRequired;
-         final boolean isCollection;
- 
-         Info(final String typeName, final String namespace, final boolean isRequired, final boolean isCollection) {
-             this.typeName     = typeName;
-             this.namespace    = namespace;
-             this.isRequired   = isRequired;
-             this.isCollection = isCollection;
-         }
- 
-         boolean equal(final Info other) {
-             return Objects.equals(typeName,  other.typeName)
-                 && Objects.equals(namespace, other.namespace)
-                 && isRequired   == other.isRequired
-                 && isCollection == other.isCollection;
-         }
- 
-         @Override public String toString() {
-             return typeName;
-         }
-     }
- 
-     /**
-      * Definitions of XML type for each class. In OGC/ISO schemas, those definitions have the {@value #TYPE_SUFFIX}
-      * suffix in their name (which is omitted). The value is another map, where keys are property names and values
-      * are their types, having the {@link #PROPERTY_TYPE_SUFFIX} suffix in their name (which is omitted).
-      */
-     private final Map<String, Map<String,Info>> typeDefinitions;
- 
-     /**
-      * Notifies that we are about to define the XML type for each property. In OGC/ISO schemas, those definitions
-      * have the {@value #PROPERTY_TYPE_SUFFIX} suffix in their name (which is omitted). After this method call,
-      * properties can be defined by calls to {@link #addProperty(String, String, boolean, boolean)}.
-      */
-     private void preparePropertyDefinitions(final String type) throws SchemaException {
-         currentProperties = typeDefinitions.computeIfAbsent(trim(type, TYPE_SUFFIX).intern(), (k) -> new HashMap<>());
-     }
- 
-     /**
-      * The properties of the XML type under examination, or {@code null} if none.
-      * If non-null, this is one of the values in the {@link #typeDefinitions} map.
-      * By convention, the {@code null} key is associated to information about the class.
-      */
-     private Map<String,Info> currentProperties;
- 
-     /**
-      * A single property type under examination, or {@code null} if none.
-      * If non-null, this is a value ending with the {@value #PROPERTY_TYPE_SUFFIX} suffix.
-      */
-     private String currentPropertyType;
- 
-     /**
-      * Default value for the {@code required} attribute of {@link XmlElement}. This default value should
-      * be {@code true} for properties declared inside a {@code <sequence>} element, and {@code false} for
-      * properties declared inside a {@code <choice>} element.
-      */
-     private boolean requiredByDefault;
- 
-     /**
-      * Namespace of the type or properties being defined.
-      * This is specified by {@code <xs:schema targetNamespace="(…)">}.
-      */
-     private String targetNamespace;
--
--    /**
--     * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations.
--     * Used for verifying that no prefix is defined twice for different namespaces.
--     *
--     * <p>This field is not really related to schema loading process. But we keep it in this class for
--     * {@link PackageVerifier} convenience, as a way to share a single map for all verifier instances.</p>
--     */
--    final Map<String,String> allXmlNS;
--
--    /**
--     * Creates a new verifier for classes under the given directory. The given directory shall be the
--     * root of {@code "*.class"} files. For example if the {@code mypackage.MyClass} class is compiled
--     * in the {@code "MyProject/target/classes/mypackage/MyClass.class"} file, then the root directory
--     * shall be {@code "MyProject/target/classes/"}.
--     *
--     * @param  classRootDirectory   the root of compiled class files.
--     * @param  schemaRootDirectory  if the computer contains a local copy of ISO schemas, path to that directory.
--     *                              Otherwise {@code null}. This is only for making tests faster.
--     */
--    public SchemaCompliance(final Path classRootDirectory, final Path schemaRootDirectory) {
-         this.classRootDirectory  = classRootDirectory;
-         this.schemaRootDirectory = schemaRootDirectory;
-         factory = DocumentBuilderFactory.newInstance();
-         factory.setNamespaceAware(true);
-         buffer = new StringBuilder(100);
-         typeDefinitions = new HashMap<>();
-         schemaLocations = new ArrayDeque<>();
 -        super(schemaRootDirectory, new Departures(), DocumentationStyle.NONE);
 -        this.classRootDirectory = classRootDirectory;
--        allXmlNS = new HashMap<>();
--    }
--
--    /**
--     * Verifies {@link XmlElement} annotations on all {@code *.class} files in the given directory and sub-directories.
--     * The given directory must be a sub-directory of the root directory given at construction time.
--     * This method will invoke itself for scanning sub-directories.
--     *
--     * @param  directory  the directory to scan for classes, relative to class root directory.
--     * @throws IOException if an error occurred while reading files or schemas.
--     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
--     * @throws ParserConfigurationException if {@link javax.xml.parsers.DocumentBuilder} can not be created.
--     * @throws SAXException if an error occurred while parsing the XSD file.
--     * @throws SchemaException if a XSD file does not comply with our assumptions,
--     *         or a JAXB annotation failed a compliance check.
--     */
--    public void verify(final Path directory)
--            throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException, SchemaException
--    {
--        PackageVerifier verifier = null;
 -        final StringBuilder buffer = new StringBuilder();
--        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
--            for (Path path : stream) {
--                final String filename = path.getFileName().toString();
--                if (!filename.startsWith(".")) {
--                    if (Files.isDirectory(path)) {
--                        verify(path);
--                    } else if (filename.endsWith(".class")) {
--                        path = classRootDirectory.relativize(path);
--                        buffer.setLength(0);
--                        buffer.append(path.toString()).setLength(buffer.length() - 6);      // Remove ".class" suffix.
--                        StringBuilders.replace(buffer, '/', '.');
--                        final Class<?> c = Class.forName(buffer.toString());
--                        if (verifier == null) {
--                            verifier = new PackageVerifier(this, c.getPackage());
--                        }
--                        verifier.verify(c);
--                    }
--                }
--            }
--        } catch (DirectoryIteratorException e) {
--            throw e.getCause();
--        }
--        if (verifier != null) {
--            verifier.reportUnused();
-         }
-     }
- 
-     /**
-      * Loads the XSD file at the given URL. Definitions are stored in the {@link #typeDefinitions} map.
-      * Only information of interest are stored, and we assume that the XSD follows OGC/ISO conventions.
-      * This method may be invoked recursively if the XSD contains {@code <xs:include>} elements.
-      *
-      * @param  location  URL to the XSD file to load.
-      */
-     final void loadSchema(String location)
-             throws IOException, ParserConfigurationException, SAXException, SchemaException
-     {
-         if (schemaRootDirectory != null && location.startsWith(SCHEMA_ROOT_DIRECTORY)) {
-             location = schemaRootDirectory.resolve(location.substring(SCHEMA_ROOT_DIRECTORY.length())).toUri().toString();
-         }
-         if (!schemaLocations.contains(location)) {
-             final Document doc;
-             try (final InputStream in = new URL(location).openStream()) {
-                 doc = factory.newDocumentBuilder().parse(in);
-             }
-             schemaLocations.addLast(location);
-             storeClassDefinition(doc);
-         }
-     }
- 
-     /**
-      * Stores information about classes in the given node and children. This method invokes itself
-      * for scanning children, until we reach sub-nodes about properties (in which case we continue
-      * with {@link #storePropertyDefinition(Node)}).
-      */
-     private void storeClassDefinition(final Node node)
-             throws IOException, ParserConfigurationException, SAXException, SchemaException
-     {
-         if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) {
-             switch (node.getNodeName()) {
-                 case "schema": {
-                     targetNamespace = getMandatoryAttribute(node, "targetNamespace").intern();
-                     break;
-                 }
-                 /*
-                  * <xs:include schemaLocation="(…).xsd">
-                  * Load the schema at the given URL, which is assumed relative.
-                  */
-                 case "include": {
-                     final String oldTarget = targetNamespace;
-                     final String location = schemaLocations.getLast();
-                     buffer.setLength(0);
-                     buffer.append(location, 0, location.lastIndexOf('/') + 1).append(getMandatoryAttribute(node, "schemaLocation"));
-                     loadSchema(buffer.toString());
-                     targetNamespace = oldTarget;
-                     return;                             // Skip children (normally, there is none).
-                 }
-                 /*
-                  * <xs:element name="(…)" type="(…)_Type">
-                  * Verify that the names comply with our assumptions.
-                  */
-                 case "element": {
-                     final String name = getMandatoryAttribute(node, "name");
-                     final String type = getMandatoryAttribute(node, "type");
-                     if (CODELIST_TYPE.equals(type)) {
-                         final Map<String,Info> properties = new HashMap<>(4);
-                         final Info info = new Info(null, targetNamespace, false, false);
-                         properties.put(null, info);     // Remember namespace of the code list.
-                         properties.put(name, info);     // Pseudo-property used in our CodeList adapters.
-                         if (typeDefinitions.put(name, properties) != null) {
-                             throw new SchemaException(String.format("Code list \"%s\" is defined twice.", name));
-                         }
-                     } else {
-                         verifyNamingConvention(schemaLocations.getLast(), name, type, TYPE_SUFFIX);
-                         preparePropertyDefinitions(type);
-                         addProperty(null, type, false, false);
-                         currentProperties = null;
-                     }
-                     return;                             // Ignore children (they are about documentation).
-                 }
-                 /*
-                  * <xs:complexType name="(…)_Type">
-                  * <xs:complexType name="(…)_PropertyType">
-                  */
-                 case "complexType": {
-                     String name = getMandatoryAttribute(node, "name");
-                     if (name.endsWith(PROPERTY_TYPE_SUFFIX)) {
-                         currentPropertyType = name;
-                         verifyPropertyType(node);
-                         currentPropertyType = null;
-                     } else {
-                         /*
-                          * In the case of "(…)_PropertyType", replace some ISO 19115-2 types by ISO 19115-1 types.
-                          * For example "MI_Band_Type" is renamed as "MD_Band_Type". We do that because we use only
-                          * one class for representing those two distincts ISO types. Note that not all ISO 19115-2
-                          * types extend an ISO 19115-1 type, so we need to apply a case-by-case approach.
-                          */
-                         requiredByDefault = true;
-                         name = TYPES_TO_MERGE.getOrDefault(name, name);
-                         preparePropertyDefinitions(name);
-                         storePropertyDefinition(node);
-                         currentProperties = null;
-                     }
-                     return;                             // Skip children since they have already been examined.
-                 }
-             }
-         }
-         for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
-             storeClassDefinition(child);
-         }
-     }
- 
-     /**
-      * Stores information about properties in the current class. The {@link #currentProperties} field must be
-      * set to the map of properties for the class defined by the enclosing {@code <xs:complexType>} element.
-      * This method parses elements of the following form:
-      *
-      * {@preformat xml
-      *   <xs:element name="(…)" type="(…)_PropertyType" minOccurs="(…)" maxOccurs="(…)">
-      * }
-      */
-     private void storePropertyDefinition(final Node node) throws SchemaException {
-         if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) {
-             switch (node.getNodeName()) {
-                 case "sequence": {
-                     requiredByDefault = true;
-                     break;
-                 }
-                 case "choice": {
-                     requiredByDefault = false;
-                     break;
-                 }
-                 case "element": {
-                     boolean isRequired = requiredByDefault;
-                     boolean isCollection = false;
-                     final NamedNodeMap attributes = node.getAttributes();
-                     if (attributes != null) {
-                         Node attr = attributes.getNamedItem("minOccurs");
-                         if (attr != null) {
-                             final String value = attr.getNodeValue();
-                             if (value != null) {
-                                 isRequired = Integer.parseInt(value) > 0;
-                             }
-                         }
-                         attr = attributes.getNamedItem("maxOccurs");
-                         if (attr != null) {
-                             final String value = attr.getNodeValue();
-                             if (value != null) {
-                                 isCollection = value.equals("unbounded") || Integer.parseInt(value) >  1;
-                             }
-                         }
-                     }
-                     addProperty(getMandatoryAttribute(node, "name").intern(),
-                            trim(getMandatoryAttribute(node, "type"), PROPERTY_TYPE_SUFFIX).intern(), isRequired, isCollection);
-                     return;
-                 }
-             }
-         }
-         for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
-             storePropertyDefinition(child);
-         }
-     }
- 
-     /**
-      * Verifies the naming convention of property defined by the given node. The {@link #currentPropertyType}
-      * field must be set to the type of the property defined by the enclosing {@code <xs:complexType>} element.
-      * This method parses elements of the following form:
-      *
-      * {@preformat xml
-      *   <xs:element ref="(…)">
-      * }
-      */
-     private void verifyPropertyType(final Node node) throws SchemaException {
-         if (XMLConstants.W3C_XML_SCHEMA_NS_URI.equals(node.getNamespaceURI())) {
-             if ("element".equals(node.getNodeName())) {
-                 verifyNamingConvention(schemaLocations.getLast(),
-                         getMandatoryAttribute(node, "ref"), currentPropertyType, PROPERTY_TYPE_SUFFIX);
-                 return;
-             }
-         }
-         for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
-             verifyPropertyType(child);
--        }
--    }
--
--    /**
--     * Verifies that the relationship between the name of the given entity and its type are consistent with
--     * OGC/ISO conventions. This method ignores the prefix (e.g. {@code "mdb:"} in {@code "mdb:MD_Metadata"}).
--     *
--     * @param  enclosing  schema or other container where the error happened.
--     * @param  name       the class or property name. Example: {@code "MD_Metadata"}, {@code "citation"}.
--     * @param  type       the type of the above named object. Example: {@code "MD_Metadata_Type"}, {@code "CI_Citation_PropertyType"}.
--     * @param  suffix     the expected suffix at the end of {@code type}.
--     * @throws SchemaException if the given {@code name} and {@code type} are not compliant with expected convention.
--     */
--    static void verifyNamingConvention(final String enclosing,
--            final String name, final String type, final String suffix) throws SchemaException
--    {
--        if (type.endsWith(suffix)) {
--            int nameStart = name.indexOf(PREFIX_SEPARATOR) + 1;        // Skip "mdb:" or similar prefix.
--            int typeStart = type.indexOf(PREFIX_SEPARATOR) + 1;
--            final int plg = ABSTRACT_PREFIX.length();
--            if (name.regionMatches(nameStart, ABSTRACT_PREFIX, 0, plg)) nameStart += plg;
--            if (type.regionMatches(typeStart, ABSTRACT_PREFIX, 0, plg)) typeStart += plg;
--            final int length = name.length() - nameStart;
--            if (type.length() - typeStart - suffix.length() == length &&
--                    type.regionMatches(typeStart, name, nameStart, length))
--            {
--                return;
--            }
--        }
--        throw new SchemaException(String.format("Error in %s:%n" +
--                "The type name should be the name with \"%s\" suffix, but found name=\"%s\" and type=\"%s\">.",
--                enclosing, suffix, name, type));
-     }
- 
-     /**
-      * Adds a property of the current name and type. This method is invoked during schema parsing.
-      * The property namespace is assumed to be {@link #targetNamespace}.
-      */
-     private void addProperty(final String name, final String type, final boolean isRequired, final boolean isCollection) throws SchemaException {
-         final Info info = new Info(type, targetNamespace, isRequired, isCollection);
-         final Info old = currentProperties.put(name, info);
-         if (old != null && !old.equal(info)) {
-             throw new SchemaException(String.format("Error while parsing %s:%n" +
-                     "Property \"%s\" is associated to type \"%s\", but that property was already associated to \"%s\".",
-                     schemaLocations.getLast(), name, type, old));
-         }
--    }
--
--    /**
--     * Removes leading and trailing spaces if any, then the prefix and the suffix in the given name.
--     * The prefix is anything before the first {@value #PREFIX_SEPARATOR} character.
--     * The suffix must be the given string, otherwise an exception is thrown.
--     *
--     * @param  name     the name from which to remove prefix and suffix.
--     * @param  suffix   the suffix to remove.
--     * @return the given name without prefix and suffix.
--     * @throws SchemaException if the given name does not end with the given suffix.
--     */
--    static String trim(String name, final String suffix) throws SchemaException {
--        name = name.trim();
--        if (name.endsWith(suffix)) {
--            return name.substring(name.indexOf(PREFIX_SEPARATOR) + 1, name.length() - suffix.length());
--        }
--        throw new SchemaException(String.format("Expected a name ending with \"%s\" but got \"%s\".", suffix, name));
-     }
- 
-     /**
-      * Returns the attribute of the given name in the given node,
-      * or throws an exception if the attribute is not present.
-      */
-     private static String getMandatoryAttribute(final Node node, final String name) throws SchemaException {
-         final NamedNodeMap attributes = node.getAttributes();
-         if (attributes != null) {
-             final Node attr = attributes.getNamedItem(name);
-             if (attr != null) {
-                 final String value = attr.getNodeValue();
-                 if (value != null) {
-                     return value;
-                 }
-             }
-         }
-         throw new SchemaException(String.format("Node \"%s\" should have a '%s' attribute.", node.getNodeName(), name));
-     }
- 
-     /**
-      * Returns the type definitions for a class of the given name.
-      *
-      * @param  className  ISO identifier of a class (e.g. {@code "MD_Metadata"}).
-      */
-     final Map<String, SchemaCompliance.Info> typeDefinition(final String className) {
-         return typeDefinitions.get(className);
--    }
--}
diff --cc core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
index 063cc36,025bad7..a67a265
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
@@@ -21,8 -21,8 +21,8 @@@ import org.opengis.metadata.citation.Ad
  import org.opengis.metadata.acquisition.Instrument;
  import org.opengis.metadata.identification.DataIdentification;
  import org.opengis.metadata.identification.InitiativeType;
 +import org.apache.sis.metadata.iso.DefaultIdentifier;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
+ import org.apache.sis.internal.xml.LegacyNamespaces;
  import org.apache.sis.internal.metadata.SensorType;
  import org.apache.sis.util.iso.Types;
  import org.apache.sis.test.DependsOnMethod;
@@@ -87,15 -84,15 +87,15 @@@ public final strictfp class CharSequenc
                                    " xmlns:gcx=\""   + Namespaces.GCX + '"' +
                                    " xmlns:gco=\""   + Namespaces.GCO + '"' +
                                    " xmlns:xlink=\"" + Namespaces.XLINK + "\">\n" +
-                 "  <mdb:code>\n" +
+                 "  <mcc:code>\n" +
                  "    <gcx:Anchor xlink:href=\"SDN:L101:2:4326\">EPSG:4326</gcx:Anchor>\n" +
-                 "  </mdb:code>\n" +
-                 "  <mdb:codeSpace>\n" +
+                 "  </mcc:code>\n" +
+                 "  <mcc:codeSpace>\n" +
                  "    <gco:CharacterString>L101</gco:CharacterString>\n" +
-                 "  </mdb:codeSpace>\n" +
-                 "</mdb:MD_Identifier>";
+                 "  </mcc:codeSpace>\n" +
+                 "</mcc:MD_Identifier>";
  
 -        final Identifier id = unmarshal(Identifier.class, expected);
 +        final DefaultIdentifier id = unmarshal(DefaultIdentifier.class, expected);
          assertEquals("codespace", "L101", id.getCodeSpace());
          assertEquals("code", "EPSG:4326", id.getCode());
      }
diff --cc core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java
index 1c5fb4b,98d6c26..0000000
deleted file mode 100644,100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java
+++ /dev/null
@@@ -1,213 -1,213 +1,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.xml;
--
--import java.util.Map;
--import java.util.Set;
--import java.util.TreeMap;
--import java.util.TreeSet;
--import java.nio.file.Path;
--import java.nio.file.Files;
--import java.nio.file.DirectoryStream;
--import java.nio.file.DirectoryIteratorException;
--import java.io.IOException;
--import java.util.Arrays;
--import java.util.HashSet;
--import java.util.Collections;
--import java.lang.reflect.Method;
--import javax.xml.bind.annotation.XmlSchema;
--import javax.xml.bind.annotation.XmlElement;
--import javax.xml.bind.annotation.XmlRootElement;
- import org.apache.sis.test.xml.SchemaException;
- import org.apache.sis.internal.jaxb.LegacyNamespaces;
 -import org.opengis.geoapi.SchemaException;
 -import org.apache.sis.internal.xml.LegacyNamespaces;
--
--
--/**
-- * Creates the {@value TransformingReader#FILENAME} file. This class needs to be executed only when the content
-- * has changed, or for verifying the current file. Output format contains namespaces first, then classes,
-- * then properties. Example:
-- *
-- * {@preformat
-- * http://standards.iso.org/iso/19115/-3/cit/1.0
-- *   CI_Address
-- *     administrativeArea
-- *     city
-- *   CI_Citation
-- *     citedResponsibleParty
-- * }
-- */
--public final class RenameListGenerator {
--    /**
--     * Properties in those namespaces do not have older namespaces to map from.
--     */
--    private static final Set<String> LEGACY_NAMESPACES = Collections.unmodifiableSet(new HashSet<>(
--            Arrays.asList(LegacyNamespaces.GMD, LegacyNamespaces.GMI, LegacyNamespaces.SRV)));
--
--    /**
--     * The {@value} string used in JAXB annotations for default names or namespaces.
--     */
--    private static final String DEFAULT = "##default";
--
--    /**
--     * Root directory from which to search for classes.
--     */
--    private final Path classRootDirectory;
--
--    /**
--     * The content to write. Keys in the first (outer) map are namespaces. Keys in the enclosed maps
--     * are class names. Keys in the enclosed set are property names.
--     */
--    private final Map<String, Map<String, Set<String>>> content;
--
--    /**
--     * Creates a new {@value TransformingReader#FILENAME} generator for classes under the given directory.
--     * The given directory shall be the root of {@code "*.class"} files.
--     *
--     * @param  classRootDirectory   the root of compiled class files.
--     */
--    public RenameListGenerator(final Path classRootDirectory) {
--        this.classRootDirectory = classRootDirectory;
--        content = new TreeMap<>();
--    }
--
--    /**
--     * Gets the namespaces, types and properties for all class files in the given directory and sub-directories.
--     * Those information are memorized for future listing with {@link #print(Appendable)}.
--     *
--     * @param  directory  the directory to scan for classes, relative to class root directory.
--     * @throws IOException if an error occurred while reading files or schemas.
--     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
--     * @throws SchemaException if two properties have the same name in the same class and namespace.
--     */
--    public void add(final Path directory) throws IOException, ClassNotFoundException, SchemaException {
--        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
--            for (Path path : stream) {
--                final String filename = path.getFileName().toString();
--                if (!filename.startsWith(".")) {
--                    if (Files.isDirectory(path)) {
--                        add(path);
--                    } else if (filename.endsWith(".class")) {
--                        path = classRootDirectory.relativize(path);
--                        String classname = path.toString();
--                        classname = classname.substring(0, classname.length() - 6).replace('/', '.');
--                        add(Class.forName(classname));
--                    }
--                }
--            }
--        } catch (DirectoryIteratorException e) {
--            throw e.getCause();
--        }
--    }
--
--    /**
--     * Gets the namespaces, types and properties for the given class.
--     * Properties defined in super-classes will be copied as if they were declared in-line.
--     * Those information are memorized for future listing with {@link #print(Appendable)}.
--     *
--     * @throws SchemaException if two properties have the same name in the same class and namespace.
--     */
--    private void add(Class<?> classe) throws SchemaException {
--        XmlRootElement root = classe.getDeclaredAnnotation(XmlRootElement.class);
--        if (root != null) {
--            /*
--             * Add the following entry:
--             *
--             *     http://a.namespace
--             *      PX_AClass
--             *       …
--             *
--             * Then list all properties below "PX_AClass". Note that the namespace may change because properties
--             * may be declared in different namespaces, but the class name stay the same. If the same properties
--             * are inherited by many classes, they will be repeated in each subclass.
--             */
--            final String topLevelTypeName = root.name();
--            String classNS = namespace(classe, root.namespace());
--            for (;; classNS = namespace(classe, root.namespace())) {
--                for (final Method method : classe.getDeclaredMethods()) {
--                    if (!method.isBridge()) {
--                        final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
--                        if (xe != null) {
--                            String namespace = xe.namespace();
--                            if (namespace.equals(DEFAULT)) {
--                                namespace = classNS;
--                            }
--                            add(namespace, topLevelTypeName, xe.name());
--                        }
--                    }
--                }
--                classe = classe.getSuperclass();
--                root = classe.getDeclaredAnnotation(XmlRootElement.class);
--                if (root == null) break;
--            }
--        } else {
--            /*
--             * In Apache SIS implementation, classes without JAXB annotation except on a single method are
--             * code lists or enumerations. Those classes have exactly one method annotated with @XmlElement,
--             * and that method actually gives a type, not a property (because of the way OGC/ISO wrap every
--             * properties in a type).
--             */
--            XmlElement singleton = null;
--            for (final Method method : classe.getDeclaredMethods()) {
--                final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
--                if (xe != null) {
--                    if (singleton != null) return;
--                    singleton = xe;
--                }
--            }
--        }
--    }
--
--    private static String namespace(final Class<?> classe, String classNS) {
--        if (classNS.equals(DEFAULT)) {
--            classNS = classe.getPackage().getDeclaredAnnotation(XmlSchema.class).namespace();
--        }
--        return classNS;
--    }
--
--    /**
--     * Adds a property in the given class in the given namespace.
--     */
--    private void add(final String namespace, final String typeName, final String property) throws SchemaException {
--        if (!LEGACY_NAMESPACES.contains(namespace)) {
--            if (!content.computeIfAbsent(namespace, (k) -> new TreeMap<>())
--                        .computeIfAbsent(typeName,  (k) -> new TreeSet<>())
--                        .add(property))
--            {
--                if (typeName.equals("Integer")) return;     // Exception because of GO_Integer and GO_Integer64.
--                throw new SchemaException(String.format("Duplicated property %s.%s in:%n%s", typeName, property, namespace));
--            }
--        }
--    }
--
--    /**
--     * Prints the {@value TransformingReader#FILENAME} file.
--     *
--     * @param  out  where to print the content.
--     * @throws IOException if an error occurred while printing the content.
--     */
--    public void print(final Appendable out) throws IOException {
--        for (final Map.Entry<String, Map<String, Set<String>>> e : content.entrySet()) {
--            out.append(e.getKey()).append('\n');                                            // Namespace
--            for (final Map.Entry<String, Set<String>> c : e.getValue().entrySet()) {
--                out.append(' ').append(c.getKey()).append('\n');                            // Class
--                for (final String p : c.getValue()) {
--                    out.append("  ").append(p).append('\n');                                // Property
--                }
--            }
--        }
--    }
--}
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 0000000,9d972de..c30c444
mode 000000,100644..100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@@ -1,0 -1,510 +1,491 @@@
+ /*
+  * 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.coverage.grid;
+ 
+ import java.util.Arrays;
+ import java.util.Optional;
+ import java.io.Serializable;
+ import org.opengis.geometry.DirectPosition;
+ import org.opengis.metadata.spatial.DimensionNameType;
+ import org.opengis.referencing.operation.MathTransform;
+ import org.opengis.referencing.operation.TransformException;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.util.collection.WeakValueHashMap;
+ import org.apache.sis.internal.raster.Resources;
+ import org.apache.sis.geometry.GeneralEnvelope;
+ import org.apache.sis.geometry.Envelopes;
+ import org.apache.sis.geometry.GeneralDirectPosition;
+ import org.apache.sis.io.TableAppender;
+ import org.apache.sis.util.iso.Types;
+ 
 -// Branch-dependent imports
 -import org.opengis.coverage.grid.GridEnvelope;
 -
+ 
+ /**
+  * A range of grid coverage coordinates, also known as "grid envelope".
+  * {@code GridExtent} are defined by {@linkplain #getLow() low} coordinates (often all zeros)
+  * and {@linkplain #getHigh() high} coordinates, <strong>inclusive</strong>.
+  * For example a grid with a width of 512 cells can have a low coordinate of 0 and high coordinate of 511.
+  *
+  * <div class="note"><b>Note:</b>
+  * The inclusiveness of {@linkplain #getHigh() high} coordinates come from ISO 19123.
+  * We follow this specification for all getters methods, but developers should keep in mind
+  * that this is the opposite of Java2D usage where {@link java.awt.Rectangle} maximal values are exclusive.</div>
+  *
+  * {@code GridExtent} instances are unmodifiable, so they can be shared between different {@link GridGeometry} instances.
+  *
+  * <div class="note"><b>Upcoming API generalization:</b>
+  * this class may implement the {@code GridEnvelope} interface in a future Apache SIS version.
+  * This is pending <a href="https://github.com/opengeospatial/geoapi/issues/36">GeoAPI update</a>.</div>
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ public class GridExtent implements Serializable {
+     /**
+      * Serial number for inter-operability with different versions.
+      */
+     private static final long serialVersionUID = -4717353677844056017L;
+ 
+     /**
+      * Default axis types for the two-dimensional cases.
+      */
+     private static final DimensionNameType[] DEFAULT_TYPES = new DimensionNameType[] {
+         DimensionNameType.COLUMN,
+         DimensionNameType.ROW
+     };
+ 
+     /**
+      * A pool of shared {@link DimensionNameType} arrays. We use a pool
+      * because a small amount of arrays is shared by most grid extent.
+      */
+     private static final WeakValueHashMap<DimensionNameType[],DimensionNameType[]> POOL = new WeakValueHashMap<>(DimensionNameType[].class);
+ 
+     /**
+      * Type of each axis (vertical, temporal, …) or {@code null} if unspecified.
+      * If non-null, the array length shall be equal to {@link #getDimension()}.
+      * Any array element may be null if unspecified for that particular axis.
+      * The same array may be shared by many {@code GridExtent} instances.
+      *
+      * @see #getAxisType(int)
+      */
+     private final DimensionNameType[] types;
+ 
+     /**
+      * Minimum and maximum grid ordinates. The first half contains minimum ordinates (inclusive),
+      * while the last half contains maximum ordinates (<strong>inclusive</strong>). Note that the
+      * later is the opposite of Java2D usage but conform to ISO specification.
+      */
+     private final long[] ordinates;
+ 
+     /**
+      * Creates a new array of coordinates with the given number of dimensions.
+      *
+      * @throws IllegalArgumentException if the given number of dimensions is excessive.
+      */
+     private static long[] allocate(final int dimension) throws IllegalArgumentException {
+         if (dimension > Integer.MAX_VALUE / 2) {
+             throw new IllegalArgumentException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1, dimension));
+         }
+         return new long[dimension << 1];
+     }
+ 
+     /**
+      * Checks if ordinate values in the low part are less than or
+      * equal to the corresponding ordinate value in the high part.
+      *
+      * @throws IllegalArgumentException if a coordinate value in the low part is
+      *         greater than the corresponding coordinate value in the high part.
+      */
+     private static void checkCoherence(final long[] ordinates) throws IllegalArgumentException {
+         final int dimension = ordinates.length >>> 1;
+         for (int i=0; i<dimension; i++) {
+             final long lower = ordinates[i];
+             final long upper = ordinates[i + dimension];
+             if (lower > upper) {
+                 throw new IllegalArgumentException(Resources.format(
+                         Resources.Keys.IllegalGridEnvelope_3, i, lower, upper));
+             }
+         }
+     }
+ 
+     /**
+      * Verifies that the given array (if non-null) contains no duplicated values, then returns a copy of that array.
+      * The returned copy may be shared by many {@code GridExtent} instances. Consequently it shall not be modified.
+      */
+     @SuppressWarnings("ReturnOfCollectionOrArrayField")
+     private static DimensionNameType[] validateAxisTypes(DimensionNameType[] types) {
+         if (types == null) {
+             return null;
+         }
+         if (Arrays.equals(DEFAULT_TYPES, types)) {          // Common case verified before POOL synchronized lock.
+             return DEFAULT_TYPES;
+         }
+         DimensionNameType[] shared = POOL.get(types);
+         if (shared == null) {
+             /*
+              * Verify the array only if it was not found in the pool. Arrays in the pool were already validated,
+              * so do not need to be verified again. The check performed here is inefficient (nested loop), but it
+              * should be okay since the arrays are usually small (less than 5 elements) and the checks should not
+              * be done often (because of the pool).
+              */
+             types = types.clone();
+             for (int i=1; i<types.length; i++) {
+                 final DimensionNameType t = types[i];
+                 if (t != null) {
+                     for (int j=i; --j >= 0;) {
+                         if (t.equals(types[j])) {
+                             throw new IllegalArgumentException(Errors.format(Errors.Keys.DuplicatedElement_1, t));
+                         }
+                     }
+                 }
+             }
+             shared = POOL.putIfAbsent(types, types);
+             if (shared == null) {
+                 return types;
+             }
+         }
+         return shared;
+     }
+ 
+     /**
+      * Creates an initially empty grid envelope with the given number of dimensions.
+      * All grid coordinate values are initialized to zero. This constructor is private
+      * since {@code GridExtent} coordinate values can not be modified by public API.
+      *
+      * @param dimension  number of dimensions.
+      * @param axisTypes  the axis types, or {@code null} if unspecified.
+      */
+     private GridExtent(final int dimension, final DimensionNameType[] axisTypes) {
+         ordinates = allocate(dimension);
+         types = validateAxisTypes(axisTypes);
+     }
+ 
+     /**
+      * Creates a new grid extent for an image or matrix of the given size.
+      * The {@linkplain #getLow() low} grid coordinates are zeros and the axis types are
+      * {@link DimensionNameType#COLUMN} and {@link DimensionNameType#ROW ROW} in that order.
+      *
+      * @param  width   number of pixels in each row.
+      * @param  height  number of pixels in each column.
+      */
+     public GridExtent(final long width, final long height) {
+         ArgumentChecks.ensureStrictlyPositive("width",  width);
+         ArgumentChecks.ensureStrictlyPositive("height", height);
+         ordinates = new long[4];
+         ordinates[2] = width  - 1;
+         ordinates[3] = height - 1;
+         types = DEFAULT_TYPES;
+     }
+ 
+     /**
+      * Constructs a new grid envelope set to the specified coordinates.
+      * The given arrays contain a minimum (inclusive) and maximum value for each dimension of the grid coverage.
+      * The lowest valid grid coordinates are often zero, but this is not mandatory.
+      * As a convenience for this common case, a null {@code low} array means that all low coordinates are zero.
+      *
+      * <p>An optional (nullable) {@code axisTypes} argument can be used for attaching a label to each grid axis.
+      * For example if this {@code GridExtent} is four-dimensional, then the axis types may be
+      * {{@linkplain DimensionNameType#COLUMN   column}  (<var>x</var>),
+      *  {@linkplain DimensionNameType#ROW      row}     (<var>y</var>),
+      *  {@linkplain DimensionNameType#VERTICAL vertical (<var>z</var>),
+      *  {@linkplain DimensionNameType#TIME     time}    (<var>t</var>)},
+      * which means that the last axis is for the temporal dimension, the third axis is for the vertical dimension, <i>etc.</i>
+      * This information is related to the "real world" coordinate reference system axes, but not necessarily in the same order;
+      * it is caller responsibility to ensure that the grid axes are consistent with the CRS axes.
+      * The {@code axisTypes} array shall not contain duplicated elements,
+      * but may contain {@code null} elements if the type of some axes are unknown.</p>
+      *
+      * @param  axisTypes  the type of each grid axis, or {@code null} if unspecified.
+      * @param  low    the valid minimum grid coordinate (always inclusive), or {@code null} for all zeros.
+      * @param  high   the valid maximum grid coordinate, inclusive or exclusive depending on the next argument.
+      * @param  isHighIncluded  {@code true} if the {@code high} values are inclusive (as in ISO 19123 specification),
+      *         or {@code false} if they are exclusive (as in Java2D usage).
+      *         This argument does not apply to {@code low} values, which are always inclusive.
+      * @throws IllegalArgumentException if a coordinate value in the low part is
+      *         greater than the corresponding coordinate value in the high part.
+      *
+      * @see #getLow()
+      * @see #getHigh()
+      */
+     public GridExtent(final DimensionNameType[] axisTypes, final long[] low, final long[] high, final boolean isHighIncluded) {
+         ArgumentChecks.ensureNonNull("high", high);
+         final int dimension = high.length;
+         if (low != null && low.length != dimension) {
+             throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedDimension_2, low.length, dimension));
+         }
+         if (axisTypes != null && axisTypes.length != dimension) {
+             throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLengths));
+         }
+         ordinates = allocate(dimension);
+         if (low != null) {
+             System.arraycopy(low, 0, ordinates, 0, dimension);
+         }
+         System.arraycopy(high, 0, ordinates, dimension, dimension);
+         if (!isHighIncluded) {
+             for (int i=dimension; i < ordinates.length; i++) {
+                 ordinates[i] = Math.decrementExact(ordinates[i]);
+             }
+         }
+         checkCoherence(ordinates);
+         types = validateAxisTypes(axisTypes);
+     }
+ 
+     /**
+      * Creates a new grid envelope as a copy of the given one.
+      *
+      * @param  extent  the grid envelope to copy.
+      * @throws IllegalArgumentException if a coordinate value in the low part is
+      *         greater than the corresponding coordinate value in the high part.
+      */
 -    protected GridExtent(final GridEnvelope extent) {
++    protected GridExtent(final GridExtent extent) {
+         ArgumentChecks.ensureNonNull("extent", extent);
+         final int dimension = extent.getDimension();
+         ordinates = allocate(dimension);
+         for (int i=0; i<dimension; i++) {
+             ordinates[i] = extent.getLow(i);
+             ordinates[i + dimension] = extent.getHigh(i);
+         }
+         checkCoherence(ordinates);
 -        types = (extent instanceof GridExtent) ? ((GridExtent) extent).types : null;
 -    }
 -
 -    /**
 -     * Returns the given grid envelope as a {@code GridExtent} implementation.
 -     * If the given extent is already a {@code GridExtent} instance or is null, then it is returned as-is.
 -     * Otherwise a new extent is created using the {@linkplain #GridExtent(GridEnvelope) copy constructor}.
 -     *
 -     * @param  extent  the grid envelope to cast or copy, or {@code null}.
 -     * @return the grid envelope as a {@code GridExtent}, or {@code null} if the given extent was null.
 -     */
 -    public static GridExtent castOrCopy(final GridEnvelope extent) {
 -        if (extent == null || extent instanceof GridExtent) {
 -            return (GridExtent) extent;
 -        } else {
 -            return new GridExtent(extent);
 -        }
++        types = extent.types;
+     }
+ 
+     /**
+      * Returns the number of dimensions.
+      *
+      * @return the number of dimensions.
+      */
+     public final int getDimension() {
+         return ordinates.length >>> 1;
+     }
+ 
+     /**
+      * Returns the valid minimum grid coordinates, inclusive.
+      * The sequence contains a minimum value for each dimension of the grid coverage.
+      *
+      * @return the valid minimum grid coordinates, inclusive.
+      *
+      * @todo Pending resolution of <a href="https://github.com/opengeospatial/geoapi/issues/36">GeoAPI update</a>
+      *       before to become public API, in order to use the interface in return type.
+      */
+     GridCoordinatesView getLow() {
+         return new GridCoordinatesView(ordinates, 0);
+     }
+ 
+     /**
+      * Returns the valid maximum grid coordinates, <strong>inclusive</strong>.
+      * The sequence contains a maximum value for each dimension of the grid coverage.
+      *
+      * @return the valid maximum grid coordinates, <strong>inclusive</strong>.
+      *
+      * @todo Pending resolution of <a href="https://github.com/opengeospatial/geoapi/issues/36">GeoAPI update</a>
+      *       before to become public API, in order to use the interface in return type.
+      */
+     GridCoordinatesView getHigh() {
+         return new GridCoordinatesView(ordinates, getDimension());
+     }
+ 
+     /**
+      * Returns the valid minimum inclusive grid coordinate along the specified dimension.
+      *
+      * @param  index  the dimension for which to obtain the coordinate value.
+      * @return the low coordinate value at the given dimension, inclusive.
+      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
+      *         than the {@linkplain #getDimension() grid dimension}.
+      *
+      * @see #getLow()
+      * @see #getHigh(int)
+      */
+     public long getLow(final int index) {
+         ArgumentChecks.ensureValidIndex(getDimension(), index);
+         return ordinates[index];
+     }
+ 
+     /**
+      * Returns the valid maximum <strong>inclusive</strong> grid coordinate along the specified dimension.
+      *
+      * @param  index  the dimension for which to obtain the coordinate value.
+      * @return the high coordinate value at the given dimension, <strong>inclusive</strong>.
+      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
+      *         than the {@linkplain #getDimension() grid dimension}.
+      *
+      * @see #getHigh()
+      * @see #getLow(int)
+      */
+     public long getHigh(final int index) {
+         final int dimension = getDimension();
+         ArgumentChecks.ensureValidIndex(dimension, index);
+         return ordinates[index + dimension];
+     }
+ 
+     /**
+      * Returns the number of integer grid coordinates along the specified dimension.
+      * This is equal to {@code getHigh(dimension) - getLow(dimension) + 1}.
+      *
+      * @param  index  the dimension for which to obtain the size.
+      * @return the number of integer grid coordinates along the given dimension.
+      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
+      *         than the {@linkplain #getDimension() grid dimension}.
+      * @throws ArithmeticException if the size is too large for the {@code long} primitive type.
+      *
+      * @see #getLow(int)
+      * @see #getHigh(int)
+      */
+     public long getSize(final int index) {
+         final int dimension = getDimension();
+         ArgumentChecks.ensureValidIndex(dimension, index);
+         return Math.incrementExact(Math.subtractExact(ordinates[dimension + index], ordinates[index]));
+     }
+ 
+     /**
+      * Returns the grid coordinates of a representative point.
+      * This point may be used for estimating a {@linkplain GridGeometry#getResolution(boolean) grid resolution}.
+      * The default implementation returns the median (or center) coordinates of this grid extent,
+      * but subclasses can override this method if another point is considered more representative.
+      *
+      * @return the grid coordinates of a representative point.
+      */
+     public DirectPosition getCentroid() {
+         final int dimension = getDimension();
+         final GeneralDirectPosition center = new GeneralDirectPosition(dimension);
+         for (int i=0; i<dimension; i++) {
+             center.setOrdinate(i, ((double) ordinates[i] + (double) ordinates[i + dimension] + 1.0) * 0.5);
+         }
+         return center;
+     }
+ 
+     /**
+      * Returns the type (vertical, temporal, …) of grid axis at given dimension.
+      * This information is provided because the grid axis type can not always be inferred from the context.
+      * Some examples are:
+      *
+      * <ul>
+      *   <li>{@code getAxisType(0)} may return {@link DimensionNameType#COLUMN},
+      *       {@link DimensionNameType#TRACK TRACK} or {@link DimensionNameType#LINE LINE}.</li>
+      *   <li>{@code getAxisType(1)} may return {@link DimensionNameType#ROW},
+      *       {@link DimensionNameType#CROSS_TRACK CROSS_TRACK} or {@link DimensionNameType#SAMPLE SAMPLE}.</li>
+      *   <li>{@code getAxisType(2)} may return {@link DimensionNameType#VERTICAL}.</li>
+      *   <li>{@code getAxisType(3)} may return {@link DimensionNameType#TIME}.</li>
+      * </ul>
+      *
+      * Above are only examples; there is no constraint on axis order. In particular grid axes do not need to be in the same
+      * order than the corresponding {@linkplain GridGeometry#getCoordinateReferenceSystem() coordinate reference system} axes.
+      *
+      * @param  index  the dimension for which to obtain the axis type.
+      * @return the axis type at the given dimension. May be absent if the type is unknown.
+      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
+      *         than the {@linkplain #getDimension() grid dimension}.
+      */
+     public Optional<DimensionNameType> getAxisType(final int index) {
+         ArgumentChecks.ensureValidIndex(getDimension(), index);
+         return Optional.ofNullable((types != null) ? types[index] : null);
+     }
+ 
+     /**
+      * Transforms this grid extent to a "real world" envelope using the given transform.
+      * The transform shall map <em>pixel corner</em> to real world coordinates.
+      *
+      * @param  gridToCRS  a transform from <em>pixel corner</em> to real world coordinates
+      * @return this grid extent in real world coordinates.
+      */
+     final GeneralEnvelope toCRS(final MathTransform gridToCRS) throws TransformException {
+         final int dimension = getDimension();
+         final GeneralEnvelope envope = new GeneralEnvelope(dimension);
+         for (int i=0; i<dimension; i++) {
+             envope.setRange(i, ordinates[i], ordinates[i + dimension] + 1.0);
+         }
+         return Envelopes.transform(gridToCRS, envope);
+     }
+ 
+     /**
+      * Returns a new grid envelope that encompass only some dimensions of this grid envelope.
+      * This method copies this grid envelope into a new grid envelope, beginning at dimension
+      * {@code lower} and extending to dimension {@code upper-1} inclusive. Thus the dimension
+      * of the sub grid envelope is {@code upper - lower}.
+      *
+      * @param  lower  the first dimension to copy, inclusive.
+      * @param  upper  the last  dimension to copy, exclusive.
+      * @return the sub-envelope, or {@code this} if [{@code lower} … {@code upper}] is [0 … {@link #getDimension() dimension}].
+      * @throws IndexOutOfBoundsException if an index is out of bounds.
+      */
+     public GridExtent subExtent(final int lower, final int upper) {
+         final int dimension = getDimension();
+         ArgumentChecks.ensureValidIndexRange(dimension, lower, upper);
+         final int newDim = upper - lower;
+         if (newDim == dimension) {
+             return this;
+         }
+         DimensionNameType[] axisTypes = types;
+         if (axisTypes != null) {
+             axisTypes = Arrays.copyOfRange(axisTypes, lower, upper);
+         }
+         final GridExtent sub = new GridExtent(newDim, axisTypes);
+         System.arraycopy(ordinates, lower,           sub.ordinates, 0,      newDim);
+         System.arraycopy(ordinates, lower+dimension, sub.ordinates, newDim, newDim);
+         return sub;
+     }
+ 
+     /**
+      * Returns a hash value for this grid envelope. This value need not remain
+      * consistent between different implementations of the same class.
+      *
+      * @return a hash value for this grid envelope.
+      */
+     @Override
+     public int hashCode() {
+         return Arrays.hashCode(ordinates) + Arrays.hashCode(types) ^ (int) serialVersionUID;
+     }
+ 
+     /**
+      * Compares the specified object with this grid envelope for equality.
+      *
+      * @param  object  the object to compare with this grid envelope for equality.
+      * @return {@code true} if the given object is equal to this grid envelope.
+      */
+     @Override
+     public boolean equals(final Object object) {
+         if (object != null && object.getClass() == GridExtent.class) {
+             final GridExtent other = (GridExtent) object;
+             return Arrays.equals(ordinates, other.ordinates) && Arrays.equals(types, other.types);
+         }
+         return false;
+     }
+ 
+     /**
+      * Returns a string representation of this grid envelope. The returned string
+      * is implementation dependent and is provided for debugging purposes only.
+      */
+     @Override
+     public String toString() {
+         final TableAppender table = new TableAppender(" ");
+         final int dimension = getDimension();
+         for (int i=0; i<dimension; i++) {
+             String name;
+             if ((types == null) || (name = Types.getCodeLabel(types[i])) == null) {
+                 name = Integer.toString(i);
+             }
+             table.append(name).append(':').nextColumn();
+             table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+             table.append(Long.toString(ordinates[i])).nextColumn();
+             table.append("to").nextColumn();
+             table.append(Long.toString(ordinates[i + dimension])).nextLine();
+             table.setCellAlignment(TableAppender.ALIGN_LEFT);
+         }
+         return table.toString();
+     }
+ }
diff --cc core/sis-raster/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
index 0000000,e4498f3..f6f4f15
mode 000000,100644..100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
@@@ -1,0 -1,124 +1,124 @@@
+ /*
+  * 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.coverage.grid;
+ 
+ import org.opengis.metadata.spatial.PixelOrientation;
+ import org.opengis.referencing.datum.PixelInCell;
+ import org.opengis.referencing.operation.MathTransform;
+ import org.opengis.referencing.operation.TransformException;
+ import org.apache.sis.referencing.operation.transform.MathTransforms;
+ import org.apache.sis.referencing.operation.matrix.Matrix3;
+ import org.apache.sis.referencing.operation.matrix.Matrix4;
+ import org.apache.sis.test.TestCase;
+ import org.junit.Test;
+ 
 -import static org.opengis.test.Assert.*;
++import static org.apache.sis.test.Assert.*;
+ 
+ 
+ /**
+  * Tests {@link PixelTranslation}.
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ public final strictfp class PixelTranslationTest extends TestCase {
+     /**
+      * Returns a transform from center to corner with the given number of dimensions.
+      */
+     private static MathTransform centerToCorner(final int dimension) {
+         return PixelTranslation.translate(MathTransforms.identity(dimension), PixelInCell.CELL_CENTER, PixelInCell.CELL_CORNER);
+     }
+ 
+     /**
+      * Returns a transform from center to corner in the two-dimensional case.
+      */
+     private static MathTransform centerToCorner2D() {
+         return PixelTranslation.translate(MathTransforms.identity(2), PixelOrientation.CENTER, PixelOrientation.UPPER_LEFT, 0, 1);
+     }
+ 
+     /**
+      * Tests {@link PixelTranslation#translate(MathTransform, PixelInCell, PixelInCell)} with an identity transform.
+      * If grid coordinates (0,0) in "pixel center" convention map to (0,0) in "real world" coordinates,
+      * then grid coordinates (0,0) in "pixel corner" convention shall map to (-½, -½) in real world.
+      * That way, grid coordinates (½,½) in "pixel corner" convention still map to (0,0) in real world.
+      *
+      * @throws TransformException if an error occurred while transforming a test point.
+      */
+     @Test
+     public void testTranslatePixelInCell() throws TransformException {
+         final MathTransform mt = centerToCorner(3);
+         assertMatrixEquals("center → corner", new Matrix4(
+                 1, 0, 0, -0.5,
+                 0, 1, 0, -0.5,
+                 0, 0, 1, -0.5,
+                 0, 0, 0,  1), MathTransforms.getMatrix(mt), STRICT);
+         /*
+          * Just for making clear what we explained in javadoc comment: the real world (0,0,0) coordinates was in the center
+          * of cell (0,0,0). After we switched to "cell corner" convention, that center is (½,½,½) in grid coordinates but
+          * should still map (0,0,0) in "real world" coordinates.
+          */
+         final double[] coordinates = new double[] {0.5, 0.5, 0.5};
+         mt.transform(coordinates, 0, coordinates, 0, 1);
+         assertArrayEquals(new double[3], coordinates, STRICT);
+     }
+ 
+     /**
+      * Tests {@link PixelTranslation#translate(MathTransform, PixelOrientation, PixelOrientation, int, int)}.
+      * See {@link #testTranslatePixelInCell()} for discussion on expected values.
+      */
+     @Test
+     public void testTranslatePixelOrientation() {
+         MathTransform mt = centerToCorner2D();
+         assertMatrixEquals("center → corner", new Matrix3(
+                 1, 0, -0.5,
+                 0, 1, -0.5,
+                 0, 0,  1), MathTransforms.getMatrix(mt), STRICT);
+ 
+         mt = PixelTranslation.translate(MathTransforms.identity(3), PixelOrientation.LOWER_LEFT, PixelOrientation.CENTER, 1, 2);
+         assertMatrixEquals("corner → center", new Matrix4(
+                 1, 0, 0,  0.0,
+                 0, 1, 0, +0.5,
+                 0, 0, 1, -0.5,
+                 0, 0, 0,  1), MathTransforms.getMatrix(mt), STRICT);
+     }
+ 
+     /**
+      * Verifies consistency of cached transforms when using translations inferred from {@link PixelInCell}.
+      */
+     @Test
+     public void testCache() {
+         MathTransform previous = null;
+         for (int dimension = 1; dimension <= 5; dimension++) {
+             final MathTransform mt = centerToCorner(dimension);
+             assertNotSame("Transforms with different number of dimensions.", previous, mt);
+             assertSame("Transforms with same number of dimensions", mt, centerToCorner(dimension));
+             previous = mt;
+         }
+     }
+ 
+     /**
+      * Verifies consistency of cached transforms when using translations inferred from {@link PixelOrientation}.
+      */
+     @Test
+     public void testCache2D() {
+         assertSame(centerToCorner(2), centerToCorner2D());
+         assertSame(PixelTranslation.translate(MathTransforms.identity(2), PixelInCell.CELL_CORNER, PixelInCell.CELL_CENTER),
+                    PixelTranslation.translate(MathTransforms.identity(2), PixelOrientation.UPPER_LEFT, PixelOrientation.CENTER, 0, 1));
+     }
+ }
diff --cc core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
index 666d2d3,e8d02c7..cedbe55
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
@@@ -27,10 -27,11 +27,9 @@@ import org.opengis.util.InternationalSt
  import org.apache.sis.util.iso.Types;
  import org.apache.sis.geometry.Envelope2D;
  import org.apache.sis.geometry.GeneralDirectPosition;
- import org.apache.sis.util.Debug;
  
  // Branch-dependent imports
 -import org.opengis.metadata.citation.Party;
 -import org.opengis.referencing.gazetteer.Location;
 -import org.opengis.referencing.gazetteer.LocationType;
 +import org.apache.sis.metadata.iso.citation.AbstractParty;
  
  
  /**
diff --cc core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
index ee1496b,23bb329..8cc42b3
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
@@@ -56,7 -58,7 +56,6 @@@ import org.apache.sis.util.ArgumentChec
  import org.apache.sis.util.StringBuilders;
  import org.apache.sis.util.Utilities;
  import org.apache.sis.util.Workaround;
- import org.apache.sis.util.Debug;
 -import org.apache.sis.util.logging.Logging;
  import org.apache.sis.util.resources.Errors;
  import org.apache.sis.util.resources.Vocabulary;
  import org.apache.sis.geometry.Shapes2D;
diff --cc core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
index 496e874,9a2000d..3c402b8
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
@@@ -53,6 -53,7 +53,7 @@@ import org.opengis.referencing.operatio
  import org.opengis.referencing.operation.SingleOperation;
  import org.opengis.referencing.operation.CoordinateOperation;
  import org.opengis.referencing.operation.CoordinateOperationFactory;
 -import org.opengis.metadata.Identifier;
++import org.opengis.referencing.ReferenceIdentifier;
  import org.opengis.metadata.citation.Citation;
  import org.opengis.metadata.citation.OnLineFunction;
  import org.opengis.metadata.citation.OnlineResource;
@@@ -97,16 -98,8 +98,17 @@@ import org.apache.sis.util.resources.Vo
  import org.apache.sis.util.resources.Errors;
  import org.apache.sis.util.Exceptions;
  import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.iso.DefaultNameSpace;
  
 +// Branch-dependent imports
 +import org.apache.sis.metadata.iso.citation.DefaultCitation;
 +import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 +import org.opengis.referencing.datum.Datum;
 +import org.opengis.referencing.datum.DatumFactory;
 +
  
  /**
   * Implements the referencing services needed by the {@code "sis-metadata"} module.
@@@ -420,6 -413,34 +422,34 @@@ public final class ServicesForMetadata 
          return new DirectPosition2D(CommonCRS.defaultGeographic(), λ, φ);
      }
  
+     /**
+      * Returns an identifier for the given object, giving precedence to EPSG identifier if available.
+      * The returned string should be of the form {@code "AUTHORITY:CODE"} if possible (no guarantees).
+      *
+      * @param  object  the object for which to get an identifier.
+      * @return an identifier for the given object, with preference given to EPSG codes.
+      * @throws FactoryException if an error occurred while searching for the EPSG code.
+      *
+      * @since 1.0
+      */
+     @Override
+     public String getPreferredIdentifier(final IdentifiedObject object) throws FactoryException {
+         final Integer code = IdentifiedObjects.lookupEPSG(object);
+         if (code != null) {
+             return Constants.EPSG + DefaultNameSpace.DEFAULT_SEPARATOR + code;
+         }
+         /*
+          * If above code did not found an EPSG code, discard EPSG codes that
+          * we may find in the loop below because they are probably invalid.
+          */
 -        for (final Identifier id : object.getIdentifiers()) {
++        for (final ReferenceIdentifier id : object.getIdentifiers()) {
+             if (!Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
+                 return IdentifiedObjects.toString(id);
+             }
+         }
+         return IdentifiedObjects.getSimpleNameOrIdentifier(object);
+     }
+ 
  
  
  
diff --cc core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
index 0c906a0,24f2e27..ee6df97
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
@@@ -768,12 -772,9 +778,12 @@@ public final class IdentifiedObjects ex
              return identifier.toString();
          }
          final String code = identifier.getCode();
 -        String cs = identifier.getCodeSpace();
 +        String cs = null;
 +        if (identifier instanceof ReferenceIdentifier) {
 +            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
 +        }
          if (cs == null || cs.isEmpty()) {
-             cs = org.apache.sis.internal.util.Citations.getIdentifier(identifier.getAuthority(), true);
+             cs = Citations.getUnicodeIdentifier(identifier.getAuthority());
          }
          if (cs != null) {
              return cs + DefaultNameSpace.DEFAULT_SEPARATOR + code;
diff --cc core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
index 3c50bea,d1a89e9..517acd9
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
@@@ -122,8 -122,10 +122,10 @@@ public class NamedIdentifier extends Im
       * to that name.</p>
       *
       * @param  identifier  the identifier to copy.
+      *
 -     * @see #castOrCopy(Identifier)
++     * @see #castOrCopy(ReferenceIdentifier)
       */
 -    public NamedIdentifier(final Identifier identifier) {
 +    public NamedIdentifier(final ReferenceIdentifier identifier) {
          super(identifier);
          if (identifier instanceof GenericName) {
              name = (GenericName) identifier;
@@@ -137,9 -139,11 +139,11 @@@
       * {@link #head()} and {@link #scope()} will delegate to the given name.
       *
       * @param  name  the name to wrap.
+      *
+      * @see #castOrCopy(GenericName)
       */
      public NamedIdentifier(final GenericName name) {
 -        super(name instanceof Identifier ? (Identifier) name : new NameToIdentifier(name));
 +        super(name instanceof ReferenceIdentifier ? (ReferenceIdentifier) name : new NameToIdentifier(name));
          this.name = name;
          isNameSupplied = true;
      }
@@@ -329,6 -333,60 +333,60 @@@
      }
  
      /**
+      * Returns a SIS identifier implementation with the values of the given arbitrary implementation.
+      * This method performs the first applicable action in the following choices:
+      *
+      * <ul>
+      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
+      *   <li>Otherwise if the given object is already an instance of
+      *       {@code NamedIdentifier}, then it is returned unchanged.</li>
+      *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
 -     *       {@linkplain #NamedIdentifier(Identifier) copy constructor} and returned.
++     *       {@linkplain #NamedIdentifier(ReferenceIdentifier) copy constructor} and returned.
+      *       Note that this is a <cite>shallow</cite> copy operation, since the other
+      *       metadata contained in the given object are not recursively copied.</li>
+      * </ul>
+      *
+      * @param  object  the object to get as a SIS implementation, or {@code null} if none.
+      * @return a SIS implementation containing the values of the given object (may be the
+      *         given object itself), or {@code null} if the argument was null.
+      *
+      * @since 1.0
+      */
 -    public static NamedIdentifier castOrCopy(final Identifier object) {
++    public static NamedIdentifier castOrCopy(final ReferenceIdentifier object) {
+         if (object == null || object instanceof NamedIdentifier) {
+             return (NamedIdentifier) object;
+         }
+         return new NamedIdentifier(object);
+     }
+ 
+     /**
+      * Returns a SIS name implementation with the values of the given arbitrary implementation.
+      * This method performs the first applicable action in the following choices:
+      *
+      * <ul>
+      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
+      *   <li>Otherwise if the given object is already an instance of
+      *       {@code NamedIdentifier}, then it is returned unchanged.</li>
+      *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
+      *       {@linkplain #NamedIdentifier(GenericName) copy constructor} and returned.
+      *       Note that this is a <cite>shallow</cite> copy operation, since the other
+      *       metadata contained in the given object are not recursively copied.</li>
+      * </ul>
+      *
+      * @param  object  the object to get as a SIS implementation, or {@code null} if none.
+      * @return a SIS implementation containing the values of the given object (may be the
+      *         given object itself), or {@code null} if the argument was null.
+      *
+      * @since 1.0
+      */
+     public static NamedIdentifier castOrCopy(final GenericName object) {
+         if (object == null || object instanceof NamedIdentifier) {
+             return (NamedIdentifier) object;
+         }
+         return new NamedIdentifier(object);
+     }
+ 
+     /**
       * The last element in the sequence of {@linkplain #getParsedNames() parsed names}.
       * By default, this is the same value than the {@linkplain #getCode() code} provided as a local name.
       *
diff --cc core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index fa58c7a,67ac1d2..c05d871
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@@ -38,13 -38,7 +38,12 @@@ import org.apache.sis.util.iso.Abstract
  import org.apache.sis.util.resources.Errors;
  import org.apache.sis.util.CharSequences;
  import org.apache.sis.util.Classes;
- import org.apache.sis.util.Debug;
  
 +// Branch-dependent imports
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.crs.DefaultParametricCRS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +
  
  /**
   * Creates geodetic objects from codes defined by an authority.
diff --cc core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
index e3a7a88,81358b6..3d49ba6
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
@@@ -60,9 -60,9 +60,9 @@@ public final class Supervisor extends S
       * Whatever JMX agent is enabled. Setting this variable to {@code false} allows the
       * Java compiler to omit any dependency to this {@code Supervisor} class.
       *
-      * @see <a href="http://sis.apache.org/branches.html#master">Differences between SIS master and branches</a>
+      * @see <a href="http://sis.apache.org/source.html#master">Differences between SIS master and branches</a>
       */
 -    static final boolean ENABLED = true;
 +    static final boolean ENABLED = false;
  
      /**
       * The JMX object name for the {@code Supervisor} service.
diff --cc core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
index 78b952d,9e55e7f..5511255
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
@@@ -44,9 -44,9 +44,9 @@@ public final class TemporalUtilities ex
       *
       * This constant will be removed after SIS release a sis-temporal module.
       *
-      * @see <a href="http://sis.apache.org/branches.html#master">Differences between SIS master and branches</a>
+      * @see <a href="http://sis.apache.org/source.html#master">Differences between SIS master and branches</a>
       */
 -    public static final boolean REPORT_MISSING_MODULE = true;
 +    public static final boolean REPORT_MISSING_MODULE = false;
  
      /**
       * Do not allow instantiation of this class.
diff --cc ide-project/NetBeans/nbproject/genfiles.properties
index 7867cbe3,11e6a4b..e75ddfb
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@@ -3,6 -3,6 +3,6 @@@
  build.xml.data.CRC32=58e6b21c
  build.xml.script.CRC32=462eaba0
  build.xml.stylesheet.CRC32=28e38971@1.53.1.46
- nbproject/build-impl.xml.data.CRC32=846ff049
 -nbproject/build-impl.xml.data.CRC32=5c9f3b18
 -nbproject/build-impl.xml.script.CRC32=fb8d24ed
++nbproject/build-impl.xml.data.CRC32=e3512adb
 +nbproject/build-impl.xml.script.CRC32=3e1070bc
  nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48
diff --cc storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
index b347e51,6a2061d..ee458b0
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CRSBuilder.java
@@@ -309,8 -320,9 +322,9 @@@ final class CRSBuilder 
       * The factory is fetched when first needed.
       *
       * @return the operation factory (never {@code null}).
+      * @see <a href="https://issues.apache.org/jira/browse/SIS-102">SIS-102</a>
       */
 -    private CoordinateOperationFactory operationFactory() {
 +    private DefaultCoordinateOperationFactory operationFactory() {
          if (operationFactory == null) {
              operationFactory = CoordinateOperations.factory();
          }
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index d5c775d,f9d9c9c..1aacd21
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@@ -48,8 -49,9 +49,10 @@@ import org.opengis.referencing.cs.AxisD
  import org.opengis.referencing.crs.VerticalCRS;
  
  import org.apache.sis.util.iso.Types;
 +import org.apache.sis.util.iso.DefaultNameFactory;
  import org.apache.sis.util.iso.SimpleInternationalString;
+ import org.apache.sis.util.logging.WarningListeners;
+ import org.apache.sis.storage.DataStore;
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.metadata.iso.DefaultMetadata;
  import org.apache.sis.metadata.iso.citation.*;
@@@ -613,14 -632,11 +629,11 @@@ split:  while ((start = CharSequences.s
                  addCitedResponsibleParty(contributor, null);
              }
              final ResponsibleParty r = createResponsibleParty(PUBLISHER, false);
--            if (r != null) {
++            if (r instanceof DefaultResponsibility) {
                  addDistributor(r);
-                 /*
-                  * TODO: There is some transfert option, etc. that we could set there.
-                  * See UnidataDD2MI.xsl for options for OPeNDAP, THREDDS, etc.
-                  */
-                 publisher = addIfNonNull(publisher, r.getOrganisationName());
-                 publisher = addIfNonNull(publisher, toInternationalString(r.getIndividualName()));
 -                for (final Party party : r.getParties()) {
++                for (final AbstractParty party : ((DefaultResponsibility) r).getParties()) {
+                     publisher = addIfNonNull(publisher, party.getName());
+                 }
              }
          }
          decoder.setSearchPath(searchPath);


Mime
View raw message