sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/01: Upgrade from GeoAPI 3.0 to GeoAPI 3.1-pending. We did not had a branch for that GeoAPI version previously. The content of this branch is an intermediate between GeoAPI 3.0 (master) and GeoAPI 4.0 (was named "JDK8" branch on Subversion repository). The content is actually more similar to the GeoAPI 4.0 branch, except where GeoAPI has an incompatible changes. In those cases, GeoAPI 3.1 is same as GeoAPI 3.0.
Date Sat, 16 Jun 2018 14:25:33 GMT
This is an automated email from the ASF dual-hosted git repository.

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

commit 1d290503bcd04be1dcc4c572169326e80f86e083
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Jun 16 15:47:42 2018 +0200

    Upgrade from GeoAPI 3.0 to GeoAPI 3.1-pending. We did not had a branch for that GeoAPI version previously. The content of this branch is an intermediate between GeoAPI 3.0 (master) and GeoAPI 4.0 (was named "JDK8" branch on Subversion repository). The content is actually more similar to the GeoAPI 4.0 branch, except where GeoAPI has an incompatible changes. In those cases, GeoAPI 3.1 is same as GeoAPI 3.0.
---
 application/pom.xml                                |   3 +-
 application/sis-console/pom.xml                    |   2 +-
 .../org/apache/sis/console/IdentifierCommand.java  |  12 +-
 .../apache/sis/console/MetadataCommandTest.java    |   8 +-
 .../sis-raster => application/sis-javafx}/pom.xml  |  43 +-
 .../org/apache/sis/gui/dataset/ResourceTree.java   | 246 ++++++
 .../org/apache/sis/gui/referencing/CRSButton.java  |  79 ++
 .../org/apache/sis/gui/referencing/CRSChooser.java | 154 ++++
 .../org/apache/sis/gui/referencing/CRSTable.java   | 349 ++++++++
 .../java/org/apache/sis/gui/referencing/Code.java  |  89 ++
 .../org/apache/sis/gui/referencing/WKTPane.java    |  68 ++
 .../org/apache/sis/internal/gui/FXUtilities.java   |  94 +++
 .../org/apache/sis/internal/gui/FontGlyphs.java    | 143 ++++
 .../org/apache/sis/internal/gui/Resources.java     | 216 +++++
 .../apache/sis/internal/gui/Resources.properties   |  27 +
 .../sis/internal/gui/Resources_fr.properties       |  32 +
 .../org/apache/sis/internal/gui}/package-info.java |  10 +-
 .../org/apache/sis/gui/referencing/CRSChooser.fxml |  36 +
 .../org/apache/sis/gui/referencing/proj_conic.png  | Bin 0 -> 591 bytes
 .../org/apache/sis/gui/referencing/proj_geo.png    | Bin 0 -> 834 bytes
 .../org/apache/sis/gui/referencing/proj_square.png | Bin 0 -> 519 bytes
 .../org/apache/sis/gui/referencing/proj_stereo.png | Bin 0 -> 959 bytes
 .../org/apache/sis/gui/referencing/proj_utm.png    | Bin 0 -> 681 bytes
 application/sis-openoffice/pom.xml                 |   4 +-
 application/sis-webapp/pom.xml                     |   2 +-
 core/pom.xml                                       |   4 +-
 core/sis-build-helper/pom.xml                      |   2 +-
 core/sis-feature/pom.xml                           |   2 +-
 .../apache/sis/feature/AbstractAssociation.java    |  58 +-
 .../org/apache/sis/feature/AbstractAttribute.java  |  63 +-
 .../org/apache/sis/feature/AbstractFeature.java    | 250 +++---
 .../apache/sis/feature/AbstractIdentifiedType.java |  18 +-
 .../org/apache/sis/feature/AbstractOperation.java  |  44 +-
 .../org/apache/sis/feature/AssociationView.java    |  85 +-
 .../java/org/apache/sis/feature/AttributeView.java |  79 +-
 .../org/apache/sis/feature/CharacteristicMap.java  |  78 +-
 .../apache/sis/feature/CharacteristicTypeMap.java  |  33 +-
 .../apache/sis/feature/DefaultAssociationRole.java | 104 ++-
 .../apache/sis/feature/DefaultAttributeType.java   |  25 +-
 .../org/apache/sis/feature/DefaultFeatureType.java | 272 +++---
 .../java/org/apache/sis/feature/DenseFeature.java  |  42 +-
 .../org/apache/sis/feature/EnvelopeOperation.java  |  37 +-
 .../java/org/apache/sis/feature/FeatureFormat.java | 113 ++-
 .../org/apache/sis/feature/FeatureOperations.java  |  33 +-
 .../java/org/apache/sis/feature/FeatureType.java   |  41 -
 .../main/java/org/apache/sis/feature/Features.java |  58 +-
 .../main/java/org/apache/sis/feature/Field.java    |  20 +-
 .../java/org/apache/sis/feature/FieldType.java     |   5 +-
 .../sis/feature/InvalidFeatureException.java       |  12 +-
 .../java/org/apache/sis/feature/LinkOperation.java |  16 +-
 .../apache/sis/feature/MultiValuedAssociation.java |  36 +-
 .../apache/sis/feature/MultiValuedAttribute.java   |  12 +-
 .../org/apache/sis/feature/NamedFeatureType.java   |  69 +-
 .../main/java/org/apache/sis/feature/Property.java |  38 -
 .../java/org/apache/sis/feature/PropertyView.java  | 107 ++-
 .../apache/sis/feature/SingletonAssociation.java   |  17 +-
 .../org/apache/sis/feature/SingletonAttribute.java |   5 +-
 .../java/org/apache/sis/feature/SparseFeature.java |  46 +-
 .../apache/sis/feature/StringJoinOperation.java    |  68 +-
 .../java/org/apache/sis/feature/Validator.java     |  50 +-
 .../feature/builder/AssociationRoleBuilder.java    |  24 +-
 .../sis/feature/builder/AttributeTypeBuilder.java  |  24 +-
 .../feature/builder/CharacteristicTypeBuilder.java |  10 +-
 .../sis/feature/builder/FeatureTypeBuilder.java    | 135 ++-
 .../sis/feature/builder/OperationWrapper.java      |   8 +-
 .../sis/feature/builder/PropertyTypeBuilder.java   |  16 +-
 .../apache/sis/feature/builder/TypeBuilder.java    |  13 +-
 .../apache/sis/filter/AbstractBinaryOperator.java  | 114 +++
 .../sis/filter/AbstractComparisonOperator.java     | 114 +++
 .../org/apache/sis/filter/AbstractExpression.java  |  65 ++
 .../apache/sis/filter/AbstractUnaryOperator.java   |  94 +++
 .../apache/sis/filter/DefaultFilterFactory.java    | 883 +++++++++++++++++++
 .../java/org/apache/sis/filter/DefaultLiteral.java | 148 ++++
 .../sis/filter/DefaultPropertyIsEqualTo.java       |  96 +++
 .../apache/sis/filter/DefaultPropertyIsNull.java   |  73 ++
 .../org/apache/sis/filter/DefaultPropertyName.java | 156 ++++
 .../java/org/apache/sis/filter/DefaultSortBy.java  | 102 +++
 .../java/org/apache/sis/filter/package-info.java}  |  24 +-
 .../sis/internal/feature/AttributeConvention.java  |  81 +-
 .../sis/internal/feature/FeatureExpression.java    |  12 +-
 .../sis/internal/feature/FeatureUtilities.java     |  10 +-
 .../sis/internal/feature/GeometryWrapper.java      |  51 ++
 .../apache/sis/internal/feature/MovingFeature.java |  21 +-
 .../apache/sis/feature/AbstractFeatureTest.java    |  27 +-
 .../apache/sis/feature/CharacteristicMapTest.java  |  39 +-
 .../sis/feature/CharacteristicTypeMapTest.java     |   9 +-
 .../org/apache/sis/feature/CustomAttribute.java    |   5 +-
 .../sis/feature/DefaultAssociationRoleTest.java    |  16 +-
 .../apache/sis/feature/DefaultFeatureTypeTest.java |  18 +-
 .../apache/sis/feature/EnvelopeOperationTest.java  |   7 +-
 .../org/apache/sis/feature/FeatureFormatTest.java  |   5 +-
 .../apache/sis/feature/FeatureMemoryBenchmark.java |   3 +-
 .../org/apache/sis/feature/FeatureTestCase.java    |  23 +-
 .../java/org/apache/sis/feature/FeaturesTest.java  |  14 +-
 .../java/org/apache/sis/feature/NoOperation.java   |  13 +-
 .../sis/feature/SingletonAssociationTest.java      |  12 +-
 .../sis/feature/StringJoinOperationTest.java       |  34 +-
 .../builder/AssociationRoleBuilderTest.java        |   4 +-
 .../feature/builder/AttributeTypeBuilderTest.java  |   8 +-
 .../builder/CharacteristicTypeBuilderTest.java     |   4 +-
 .../feature/builder/FeatureTypeBuilderTest.java    |  94 ++-
 .../org/apache/sis/filter/DefaultLiteralTest.java  |  74 ++
 .../apache/sis/filter/DefaultPropertyNameTest.java |  80 ++
 .../internal/feature/AttributeConventionTest.java  |  16 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   2 +
 core/sis-metadata/pom.xml                          |   2 +-
 .../sis/internal/geoapi/evolution/Interim.java     |  49 --
 .../sis/internal/geoapi/evolution/InterimType.java |  45 -
 .../geoapi/evolution/UnsupportedCodeList.java      | 100 ---
 .../evolution/UnsupportedCodeListAdapter.java      | 165 ----
 .../internal/geoapi/evolution/package-info.java    |  36 -
 .../geoapi/evolution/warning-templates.txt         |  33 -
 .../sis/internal/jaxb/IdentifierMapEntry.java      |  19 +-
 .../sis/internal/jaxb/SpecializedIdentifier.java   |  19 +-
 .../apache/sis/internal/jaxb/cat/CodeListUID.java  |   6 +-
 .../apache/sis/internal/jaxb/cat/EnumAdapter.java  |   8 +-
 .../internal/jaxb/code/CI_TelephoneTypeCode.java   |  14 +-
 .../org/apache/sis/internal/jaxb/code/DCPList.java |  34 +-
 .../sis/internal/jaxb/code/MD_RestrictionCode.java |   4 +-
 .../sis/internal/jaxb/code/SV_CouplingType.java    |  11 +-
 ...ouplingType.java => SV_ParameterDirection.java} |  62 +-
 .../sis/internal/jaxb/gco/GO_CharacterString.java  |   3 +-
 .../apache/sis/internal/jaxb/gml/TM_Primitive.java |   6 +-
 .../apache/sis/internal/jaxb/gml/TimeInstant.java  |   4 +-
 .../apache/sis/internal/jaxb/gml/TimePeriod.java   |   2 +-
 .../sis/internal/jaxb/gml/TimePeriodBound.java     |   4 +-
 .../apache/sis/internal/jaxb/gts/TM_Duration.java  |   4 +-
 .../sis/internal/jaxb/gts/TM_PeriodDuration.java   |   8 +-
 .../sis/internal/jaxb/metadata/CI_Party.java       |  17 +-
 .../internal/jaxb/metadata/CI_Responsibility.java  |  23 +-
 ...ureTypeInfo.java => MD_AssociatedResource.java} |  36 +-
 .../internal/jaxb/metadata/MD_AttributeGroup.java  |  17 +-
 .../internal/jaxb/metadata/MD_FeatureTypeInfo.java |  13 +-
 .../sis/internal/jaxb/metadata/MD_Identifier.java  |   5 +-
 .../internal/jaxb/metadata/MD_KeywordClass.java    |  15 +-
 .../internal/jaxb/metadata/MD_MetadataScope.java   |  17 +-
 .../internal/jaxb/metadata/MD_Releasability.java   |  15 +-
 .../sis/internal/jaxb/metadata/MD_Scope.java       |   2 +-
 .../sis/internal/jaxb/metadata/RS_Identifier.java  |  18 +-
 .../internal/jaxb/metadata/SV_CoupledResource.java |  17 +-
 .../jaxb/metadata/SV_OperationChainMetadata.java   |  17 +-
 .../jaxb/metadata/SV_OperationMetadata.java        |  19 +-
 .../jaxb/metadata/replace/ServiceParameter.java    |  36 +-
 .../jaxb/metadata/replace/package-info.java        |   2 +
 .../sis/internal/metadata/AxisDirections.java      |   8 +-
 .../sis/internal/metadata/NameToIdentifier.java    |  14 +-
 .../sis/internal/metadata/ReferencingServices.java |  83 +-
 .../sis/internal/metadata/ServicesForUtility.java  |   6 +-
 .../sis/internal/simple/CitationConstant.java      |   2 +
 .../sis/internal/simple/SimpleAttributeType.java   |  34 +-
 .../apache/sis/internal/simple/SimpleCitation.java |   2 +
 .../apache/sis/internal/simple/SimpleFormat.java   |  22 +
 .../internal/simple/SimpleIdentifiedObject.java    |   2 +-
 .../sis/internal/simple/SimpleIdentifier.java      |   1 +
 .../apache/sis/internal/simple/SimpleMetadata.java | 201 ++++-
 .../main/java/org/apache/sis/io/wkt/Formatter.java |  23 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |  22 +-
 .../org/apache/sis/io/wkt/MathTransformParser.java |   2 +-
 .../org/apache/sis/metadata/MetadataStandard.java  |  14 +-
 .../apache/sis/metadata/PropertyComparator.java    |   9 +-
 .../apache/sis/metadata/PropertyInformation.java   |   1 +
 .../main/java/org/apache/sis/metadata/Pruner.java  |   4 +-
 .../sis/metadata/StandardImplementation.java       |  43 +-
 .../iso/DefaultExtendedElementInformation.java     |  36 +-
 .../apache/sis/metadata/iso/DefaultIdentifier.java |  25 +-
 .../apache/sis/metadata/iso/DefaultMetadata.java   | 127 ++-
 .../sis/metadata/iso/DefaultMetadataScope.java     |  51 +-
 .../sis/metadata/iso/ImmutableIdentifier.java      |  34 +-
 .../sis/metadata/iso/MetadataScopeAdapter.java     |  17 +-
 .../sis/metadata/iso/citation/AbstractParty.java   |  63 +-
 .../sis/metadata/iso/citation/DefaultCitation.java |  16 +-
 .../sis/metadata/iso/citation/DefaultContact.java  |  64 +-
 .../metadata/iso/citation/DefaultIndividual.java   |  48 +-
 .../iso/citation/DefaultOnlineResource.java        |  11 +-
 .../metadata/iso/citation/DefaultOrganisation.java |  78 +-
 .../iso/citation/DefaultResponsibility.java        |  86 +-
 .../iso/citation/DefaultResponsibleParty.java      | 113 ++-
 .../metadata/iso/citation/DefaultTelephone.java    |  83 +-
 .../metadata/iso/citation/LegacyTelephones.java    |  23 +-
 .../iso/constraint/DefaultConstraints.java         |  71 +-
 .../iso/constraint/DefaultReleasability.java       |  76 +-
 .../iso/content/DefaultAttributeGroup.java         |  51 +-
 .../sis/metadata/iso/content/DefaultBand.java      |  53 +-
 .../iso/content/DefaultCoverageDescription.java    |  64 +-
 .../DefaultFeatureCatalogueDescription.java        |  46 +-
 .../iso/content/DefaultFeatureTypeInfo.java        |  51 +-
 .../iso/content/DefaultRangeDimension.java         |  23 +-
 .../iso/content/DefaultSampleDimension.java        | 119 ++-
 .../DefaultDigitalTransferOptions.java             |  22 +-
 .../iso/distribution/DefaultDistribution.java      |  11 +-
 .../metadata/iso/distribution/DefaultFormat.java   |  20 +-
 .../metadata/iso/distribution/DefaultMedium.java   |  50 +-
 .../distribution/DefaultStandardOrderProcess.java  |  15 +-
 .../iso/extent/DefaultSpatialTemporalExtent.java   |  11 +-
 .../metadata/iso/extent/DefaultTemporalExtent.java |   6 +-
 .../metadata/iso/extent/DefaultVerticalExtent.java |   7 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |  34 +-
 .../iso/identification/AbstractIdentification.java | 135 +--
 .../DefaultAggregateInformation.java               |  37 +-
 .../identification/DefaultAssociatedResource.java  |  56 +-
 .../iso/identification/DefaultBrowseGraphic.java   |  15 +-
 .../iso/identification/DefaultCoupledResource.java |  75 +-
 .../iso/identification/DefaultKeywordClass.java    |  43 +-
 .../iso/identification/DefaultKeywords.java        |  23 +-
 .../DefaultOperationChainMetadata.java             |  78 +-
 .../identification/DefaultOperationMetadata.java   | 152 ++--
 .../iso/identification/DefaultResolution.java      |  21 +-
 .../DefaultServiceIdentification.java              | 164 +---
 .../metadata/iso/identification/DefaultUsage.java  |  19 +-
 .../metadata/iso/identification/OperationName.java |  63 +-
 .../metadata/iso/identification/package-info.java  |  13 +-
 .../sis/metadata/iso/lineage/DefaultLineage.java   |  17 +-
 .../metadata/iso/lineage/DefaultProcessStep.java   |  17 +-
 .../sis/metadata/iso/lineage/DefaultSource.java    |  40 +-
 .../maintenance/DefaultMaintenanceInformation.java |  41 +-
 .../sis/metadata/iso/maintenance/DefaultScope.java |  45 +-
 .../org/apache/sis/metadata/iso/package-info.java  |   6 +-
 .../metadata/iso/quality/DefaultDataQuality.java   |   1 -
 .../sis/metadata/iso/quality/DefaultScope.java     |  32 +
 .../sis/metadata/iso/spatial/DefaultDimension.java |  15 +-
 .../java/org/apache/sis/metadata/package-info.java |   2 +-
 .../apache/sis/metadata/sql/MetadataFallback.java  |   4 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |  26 +-
 .../apache/sis/metadata/sql/MetadataWriter.java    |  29 +-
 .../apache/sis/util/iso/DefaultNameFactory.java    |   1 +
 .../apache/sis/util/iso/DefaultRecordSchema.java   |  33 +-
 .../org/apache/sis/util/iso/DefaultRecordType.java |   3 +-
 .../org/apache/sis/util/iso/RecordDefinition.java  |   6 +-
 .../main/java/org/apache/sis/util/iso/Types.java   |  77 +-
 .../java/org/apache/sis/util/iso/package-info.java |   2 +-
 .../main/java/org/apache/sis/xml/LegacyCodes.java  |  24 +-
 .../org/apache/sis/util/iso/class-index.properties | 229 -----
 .../internal/jaxb/cat/CodeListMarshallingTest.java |   6 +-
 .../apache/sis/internal/jaxb/gml/DummyInstant.java |  15 +-
 .../sis/internal/jaxb/lan/LanguageCodeTest.java    |   7 +-
 .../metadata/replace/ServiceParameterTest.java     |   2 +
 .../apache/sis/internal/metadata/MergerTest.java   |   3 +-
 .../sis/internal/metadata/sql/TestDatabase.java    |   2 +-
 .../java/org/apache/sis/io/wkt/FormatterTest.java  |   2 +-
 .../apache/sis/metadata/MetadataStandardTest.java  |  41 +-
 .../apache/sis/metadata/PropertyAccessorTest.java  |  27 +-
 .../sis/metadata/PropertyConsistencyCheck.java     |   7 +-
 .../java/org/apache/sis/metadata/TreeNodeTest.java |  35 +-
 .../apache/sis/metadata/TreeTableFormatTest.java   |  15 +-
 .../org/apache/sis/metadata/TreeTableViewTest.java |  14 +-
 .../apache/sis/metadata/iso/AllMetadataTest.java   |  16 +-
 .../sis/metadata/iso/CustomMetadataTest.java       |   6 +
 .../sis/metadata/iso/DefaultMetadataTest.java      |  11 +-
 .../sis/metadata/iso/ImmutableIdentifierTest.java  |   2 +-
 .../sis/metadata/iso/citation/CitationsTest.java   |   9 +-
 .../metadata/iso/citation/DefaultCitationTest.java |  32 +-
 .../metadata/iso/citation/DefaultContactTest.java  |  20 +-
 .../iso/citation/DefaultResponsibilityTest.java    |   2 +-
 .../constraint/DefaultLegalConstraintsTest.java    |  14 +-
 .../identification/DefaultCoupledResourceTest.java |  10 +-
 .../DefaultServiceIdentificationTest.java          |  29 +-
 .../sis/metadata/sql/MetadataSourceTest.java       |   8 +-
 .../sis/metadata/sql/MetadataWriterTest.java       |  22 +-
 .../java/org/apache/sis/test/MetadataAssert.java   |   7 +-
 .../apache/sis/test/mock/IdentifiedObjectMock.java |  12 +
 .../sis/test/xml/AnnotationConsistencyCheck.java   |  73 +-
 .../java/org/apache/sis/util/iso/TypesTest.java    |  38 +-
 .../sis/xml/CharSequenceSubstitutionTest.java      |   9 +-
 .../java/org/apache/sis/xml/NilReasonTest.java     |   4 +-
 .../apache/sis/metadata/iso/api-changes.properties |  32 -
 .../metadata/xml/2007/ServiceIdentification.xml    |   3 +
 .../metadata/xml/2016/ServiceIdentification.xml    |  12 +-
 core/sis-portrayal/pom.xml                         |   2 +-
 .../java/org/apache/sis/internal/map/MapLayer.java |  24 +-
 core/sis-raster/pom.xml                            |   2 +-
 .../java/org/apache/sis/image/DefaultIterator.java |  13 +
 .../java/org/apache/sis/image/PixelIterator.java   |  62 +-
 .../org/apache/sis/image/DefaultIteratorTest.java  |  17 +
 core/sis-referencing-by-identifiers/pom.xml        |   2 +-
 .../referencing/gazetteer/AbstractLocation.java    |  75 +-
 .../gazetteer/AbstractLocationType.java            |  82 +-
 .../referencing/gazetteer/FinalLocationType.java   |  38 +-
 .../gazetteer/GeohashReferenceSystem.java          |  15 +-
 .../sis/referencing/gazetteer/LocationFormat.java  |  27 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |  28 +-
 .../gazetteer/ModifiableLocationType.java          |  26 +-
 .../gazetteer/ModifiableLocationTypeAdapter.java   | 101 ---
 .../gazetteer/ReferencingByIdentifiers.java        |  90 +-
 .../sis/referencing/gazetteer/SimpleLocation.java  |   5 +-
 .../gazetteer/GeohashReferenceSystemTest.java      |   8 +-
 .../referencing/gazetteer/LocationTypeTest.java    |  13 +-
 .../sis/referencing/gazetteer/LocationViewer.java  |   7 +-
 .../gazetteer/MilitaryGridReferenceSystemTest.java |  12 +-
 core/sis-referencing/pom.xml                       |   2 +-
 .../sis/geometry/AbstractDirectPosition.java       |   1 +
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   4 +-
 .../org/apache/sis/geometry/ArrayEnvelope.java     |   1 +
 .../java/org/apache/sis/geometry/Envelope2D.java   |   1 +
 .../org/apache/sis/geometry/GeneralEnvelope.java   |   1 +
 .../org/apache/sis/geometry/ImmutableEnvelope.java |   1 +
 .../MismatchedReferenceSystemException.java        |  63 --
 .../geometry/UnmodifiableGeometryException.java    |  54 --
 .../referencing/CC_GeneralOperationParameter.java  |   6 +-
 .../jaxb/referencing/CD_ParametricDatum.java       |  13 +-
 .../internal/jaxb/referencing/CS_ParametricCS.java |  13 +-
 .../apache/sis/internal/jaxb/referencing/Code.java |  18 +-
 .../internal/jaxb/referencing/RS_Identifier.java   |   9 +-
 .../internal/referencing/DirectPositionView.java   |   2 +-
 .../sis/internal/referencing/EPSGFactoryProxy.java |   6 +
 .../referencing/GeodeticObjectBuilder.java         |   8 +-
 .../internal/referencing/ReferencingUtilities.java |   6 +-
 .../internal/referencing/ServicesForMetadata.java  | 107 +--
 .../sis/internal/referencing/WKTUtilities.java     |   4 +-
 .../sis/parameter/AbstractParameterDescriptor.java |  24 +-
 .../sis/parameter/DefaultParameterDescriptor.java  |   6 +-
 .../parameter/DefaultParameterDescriptorGroup.java |  32 +-
 .../org/apache/sis/parameter/ParameterFormat.java  |   6 +-
 .../apache/sis/parameter/ParameterTableRow.java    |   7 +-
 .../java/org/apache/sis/parameter/Parameters.java  |   4 +-
 .../org/apache/sis/parameter/TensorParameters.java |   8 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |  13 +-
 .../sis/referencing/AbstractReferenceSystem.java   |   6 +-
 .../java/org/apache/sis/referencing/Builder.java   |  16 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |  47 +-
 .../apache/sis/referencing/IdentifiedObjects.java  |  17 +-
 .../apache/sis/referencing/NamedIdentifier.java    |  12 +-
 .../apache/sis/referencing/crs/AbstractCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |   5 +-
 .../sis/referencing/crs/DefaultDerivedCRS.java     |  25 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |   4 +-
 .../sis/referencing/crs/DefaultGeocentricCRS.java  |   4 +-
 .../sis/referencing/crs/DefaultGeographicCRS.java  |   8 +-
 .../sis/referencing/crs/DefaultImageCRS.java       |   4 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |  69 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |   4 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |   4 +-
 .../apache/sis/referencing/cs/DefaultAffineCS.java |   4 +-
 .../sis/referencing/cs/DefaultCartesianCS.java     |   4 +-
 .../sis/referencing/cs/DefaultCompoundCS.java      |   4 +-
 .../cs/DefaultCoordinateSystemAxis.java            |   4 +-
 .../sis/referencing/cs/DefaultCylindricalCS.java   |   4 +-
 .../sis/referencing/cs/DefaultEllipsoidalCS.java   |   4 +-
 .../apache/sis/referencing/cs/DefaultLinearCS.java |   4 +-
 .../sis/referencing/cs/DefaultParametricCS.java    |  43 +-
 .../apache/sis/referencing/cs/DefaultPolarCS.java  |   4 +-
 .../sis/referencing/cs/DefaultSphericalCS.java     |   4 +-
 .../apache/sis/referencing/cs/DefaultTimeCS.java   |   4 +-
 .../sis/referencing/cs/DefaultUserDefinedCS.java   |   4 +-
 .../sis/referencing/cs/DefaultVerticalCS.java      |   4 +-
 .../sis/referencing/datum/AbstractDatum.java       |   6 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |   6 +-
 .../referencing/datum/DefaultEngineeringDatum.java |   6 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   6 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |   6 +-
 .../referencing/datum/DefaultParametricDatum.java  |  45 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |   6 +-
 .../referencing/datum/DefaultTemporalDatum.java    |   6 +-
 .../referencing/datum/DefaultVerticalDatum.java    |   8 +-
 .../factory/GeodeticAuthorityFactory.java          |  26 +-
 .../referencing/factory/GeodeticObjectFactory.java |  30 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  16 +-
 .../sis/referencing/factory/sql/TableInfo.java     |  11 +-
 .../operation/CoordinateOperationRegistry.java     |   6 +-
 .../DefaultCoordinateOperationFactory.java         |   2 +
 .../operation/MismatchedDatumException.java        |   1 +
 .../referencing/operation/SubOperationInfo.java    |   5 +-
 .../sis/referencing/operation/matrix/Matrices.java |   4 +-
 .../matrix/MismatchedMatrixSizeException.java      |   1 +
 .../operation/projection/NormalizedProjection.java |   4 +-
 .../transform/DefaultMathTransformFactory.java     |   2 +-
 .../operation/transform/MathTransforms.java        |   2 +
 .../transform/SpecializableTransform.java          |   2 +-
 .../apache/sis/geometry/AbstractEnvelopeTest.java  |   2 +-
 .../apache/sis/geometry/GeneralEnvelopeTest.java   |   2 +-
 .../CC_OperationParameterGroupTest.java            |   1 +
 .../sis/internal/jaxb/referencing/CodeTest.java    |  22 +-
 .../referencing/ReferencingUtilitiesTest.java      |   2 +-
 .../sis/internal/referencing/WKTUtilitiesTest.java |   2 +-
 .../provider/CoordinateFrameRotationTest.java      |   1 -
 .../FranceGeocentricInterpolationTest.java         |   4 +
 .../provider/GeocentricTranslationTest.java        |  40 +-
 .../referencing/provider/Geographic3Dto2DTest.java |   2 +-
 .../referencing/provider/MapProjectionTest.java    |   6 +-
 .../internal/referencing/provider/NADCONTest.java  |   2 +-
 .../internal/referencing/provider/NTv2Test.java    |   6 +-
 .../referencing/provider/PoleRotationMock.java     |   2 +-
 .../provider/PositionVector7ParamTest.java         |   1 -
 .../referencing/provider/ProviderMock.java         |   2 +-
 .../referencing/provider/SeismicBinGridMock.java   |   2 +-
 .../provider/TopocentricConversionMock.java        |   2 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       |   3 +-
 .../apache/sis/io/wkt/MathTransformParserTest.java |   2 +-
 .../java/org/apache/sis/io/wkt/WKTParserTest.java  | 700 +++++++++++++++
 .../DefaultParameterDescriptorGroupTest.java       |   2 +
 .../org/apache/sis/parameter/ParametersTest.java   |   3 +
 .../apache/sis/parameter/TensorParametersTest.java |   2 +
 .../org/apache/sis/referencing/BuilderTest.java    |   9 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |   4 +-
 .../sis/referencing/NamedIdentifierTest.java       |   8 +-
 .../sis/referencing/crs/DefaultDerivedCRSTest.java |   2 +-
 .../referencing/crs/DefaultGeographicCRSTest.java  |   4 +-
 .../sis/referencing/crs/HardCodedCRSTest.java      |   4 +-
 .../referencing/cs/DefaultCylindricalCSTest.java   |   2 +-
 .../sis/referencing/cs/DefaultPolarCSTest.java     |   2 +-
 .../sis/referencing/cs/DefaultSphericalCSTest.java |   2 +-
 .../apache/sis/referencing/cs/HardCodedAxes.java   |   4 +-
 .../apache/sis/referencing/factory/GIGS2001.java   |  73 ++
 .../apache/sis/referencing/factory/GIGS2002.java   | 121 +++
 .../apache/sis/referencing/factory/GIGS2003.java   |  77 ++
 .../apache/sis/referencing/factory/GIGS2004.java   | 140 +++
 .../apache/sis/referencing/factory/GIGS2005.java   | 123 +++
 .../apache/sis/referencing/factory/GIGS2006.java   | 118 +++
 .../apache/sis/referencing/factory/GIGS2007.java   |  77 ++
 .../apache/sis/referencing/factory/GIGS2008.java   |  77 ++
 .../apache/sis/referencing/factory/GIGS2009.java   |  77 ++
 .../apache/sis/referencing/factory/GIGS3002.java   |  49 ++
 .../apache/sis/referencing/factory/GIGS3003.java   |  49 ++
 .../apache/sis/referencing/factory/GIGS3004.java   |  56 ++
 .../apache/sis/referencing/factory/GIGS3005.java   |  49 ++
 .../factory/GeodeticObjectFactoryTest.java         | 252 ++++++
 .../referencing/factory/sql/EPSGFactoryTest.java   |   1 +
 .../referencing/factory/sql/EPSGInstallerTest.java |   3 +-
 .../operation/CoordinateOperationFinderTest.java   |   3 +-
 .../operation/CoordinateOperationRegistryTest.java |   8 +-
 .../operation/DefaultOperationMethodTest.java      |   3 +-
 .../operation/DefaultPassThroughOperationTest.java |   2 +-
 .../operation/matrix/NonSquareMatrixTest.java      |   2 +-
 .../operation/projection/AlbersEqualAreaTest.java  |   6 +
 .../projection/ConformalProjectionTest.java        |  14 +
 .../projection/CylindricalEqualAreaTest.java       |   5 +
 .../projection/LambertConicConformalTest.java      |  15 +-
 .../projection/MapProjectionTestCase.java          |   5 +-
 .../operation/projection/MercatorTest.java         |  22 +-
 .../projection/NormalizedProjectionTest.java       |   2 +-
 .../operation/projection/ObliqueMercatorTest.java  |  31 +
 .../projection/ObliqueStereographicTest.java       |  22 +-
 .../projection/ParameterizedTransformTestMock.java |  80 --
 .../projection/PolarStereographicTest.java         |  12 +-
 .../projection/TransverseMercatorTest.java         |   8 +-
 .../transform/AbridgedMolodenskyTransformTest.java |   8 +-
 .../operation/transform/CartesianToPolarTest.java  |   1 +
 .../transform/CartesianToSphericalTest.java        |   1 +
 .../transform/ContextualParametersTest.java        |   2 +-
 .../transform/DefaultMathTransformFactoryTest.java |   2 +-
 .../transform/EllipsoidToCentricTransformTest.java |   8 +-
 .../transform/ExponentialTransform1DTest.java      |  23 +-
 .../InterpolatedMolodenskyTransformTest.java       |   4 +-
 .../transform/LinearInterpolator1DTest.java        |   4 +-
 .../operation/transform/LinearTransformTest.java   |  14 -
 .../transform/LogarithmicTransform1DTest.java      |  10 +-
 .../operation/transform/MathTransformTestCase.java |  65 ++
 .../operation/transform/MathTransformsTest.java    |   2 +-
 .../transform/MolodenskyTransformTest.java         | 167 +++-
 .../transform/PassThroughTransformTest.java        |  21 +-
 .../operation/transform/PolarToCartesianTest.java  |   1 +
 .../transform/ProjectiveTransformTest.java         |  91 +-
 .../operation/transform/ScaleTransformTest.java    |   2 +-
 .../transform/SpecializableTransformTest.java      |   2 +
 .../transform/SphericalToCartesianTest.java        |   1 +
 .../operation/transform/TransferFunctionTest.java  |   2 +-
 .../transform/TransformResultComparator.java       |   2 +-
 .../transform/TransformSeparatorTest.java          |   2 +-
 .../operation/transform/TransformTestCase.java     |  68 --
 .../report/CoordinateOperationMethods.java         |  10 +-
 .../report/CoordinateReferenceSystems.java         | 935 +++++++++++++++++++++
 .../org/apache/sis/test/ReferencingAssert.java     |   5 +-
 .../sis/test/integration/ConsistencyTest.java      |   2 +-
 .../apache/sis/test/integration/MetadataTest.java  |  50 +-
 .../sis/test/suite/ReferencingTestSuite.java       |  17 +
 core/sis-utility/pom.xml                           |   2 +-
 .../sis/internal/geoapi/temporal/Period.java       |  44 -
 .../internal/geoapi/temporal/PeriodDuration.java   |  69 --
 .../internal/geoapi/temporal/TemporalFactory.java  |  39 -
 .../org/apache/sis/internal/system/Supervisor.java |   2 +-
 .../org/apache/sis/internal/util/Citations.java    |  26 +-
 .../org/apache/sis/internal/util/CodeLists.java    |  21 +-
 .../apache/sis/internal/util/MetadataServices.java |   6 +-
 .../sis/internal/util/TemporalUtilities.java       |   8 +-
 .../org/apache/sis/io/IdentifiedObjectFormat.java  |   4 +-
 .../main/java/org/apache/sis/io/LineAppender.java  |   1 +
 .../main/java/org/apache/sis/io/TabularFormat.java |   2 +-
 .../java/org/apache/sis/math/MathFunctions.java    |   2 +-
 .../java/org/apache/sis/measure/NumberRange.java   |   4 +-
 .../main/java/org/apache/sis/measure/Range.java    |   9 +-
 .../sis/util/collection/TreeTableFormat.java       |   6 +-
 .../sis/util/iso/AbstractInternationalString.java  |   3 +-
 .../sis/util/resources/IndexedResourceBundle.java  |   5 +-
 .../org/apache/sis/internal/util/CitationMock.java |   5 +
 .../src/test/java/org/apache/sis/test/Assert.java  |   2 +-
 .../java/org/apache/sis/test/ContentVerifier.java  |  40 -
 .../java/org/apache/sis/test/GeoapiAssert.java     | 196 -----
 .../java/org/apache/sis/test/LoggingWatcher.java   |  12 +-
 .../sis/util/collection/TreeTableFormatTest.java   |   2 +-
 ide-project/NetBeans/README.txt                    |  31 +
 ide-project/NetBeans/build.xml                     |  12 +-
 ide-project/NetBeans/nbproject/build-impl.xml      | 847 +++++++------------
 ide-project/NetBeans/nbproject/genfiles.properties |   6 +-
 ide-project/NetBeans/nbproject/project.properties  |  11 +-
 ide-project/NetBeans/nbproject/project.xml         |   4 +-
 pom.xml                                            |  25 +-
 profiles/pom.xml                                   |   4 +-
 profiles/sis-french-profile/pom.xml                |   2 +-
 storage/pom.xml                                    |   4 +-
 storage/sis-earth-observation/pom.xml              |   2 +-
 .../storage/earthobservation/LandsatReader.java    |   2 +-
 .../earthobservation/LandsatReaderTest.java        | 367 ++++----
 storage/sis-gdal/pom.xml                           |   2 +-
 .../main/java/org/apache/sis/storage/gdal/PJ.java  |   1 +
 .../org/apache/sis/storage/gdal/MTFactory.java     |  11 +
 .../apache/sis/storage/gdal/Proj4FactoryTest.java  |   2 +-
 .../org/apache/sis/storage/gdal/TransformTest.java | 104 +++
 .../org/apache/sis/test/suite/GDALTestSuite.java   |   1 +
 storage/sis-geotiff/pom.xml                        |   2 +-
 .../org/apache/sis/storage/geotiff/CRSBuilder.java |   8 +-
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |   2 +-
 storage/sis-netcdf/pom.xml                         |   3 +-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |  30 +-
 .../sis/internal/netcdf/ucar/FeaturesWrapper.java  |   8 +-
 .../apache/sis/storage/netcdf/AttributeNames.java  |   6 +-
 .../apache/sis/storage/netcdf/MetadataReader.java  |  60 +-
 .../apache/sis/internal/netcdf/DecoderTest.java    |   1 +
 .../sis/internal/netcdf/GridGeometryTest.java      |   1 +
 .../org/apache/sis/internal/netcdf/TestCase.java   |   1 +
 .../org/apache/sis/internal/netcdf/TestData.java   |  48 --
 .../apache/sis/internal/netcdf/VariableTest.java   |   1 +
 .../internal/netcdf/impl/ChannelDecoderTest.java   |   2 +-
 .../internal/netcdf/impl/GridGeometryInfoTest.java |   2 +-
 .../sis/internal/netcdf/impl/VariableInfoTest.java |   2 +-
 .../sis/storage/netcdf/MetadataReaderTest.java     |   4 +-
 .../storage/netcdf/NetcdfStoreProviderTest.java    |   2 +-
 .../apache/sis/storage/netcdf/NetcdfStoreTest.java |   2 +-
 storage/sis-shapefile/pom.xml                      |   2 +-
 .../internal/shapefile/ShapefileByteReader.java    |  11 +-
 .../internal/shapefile/jdbc/Dbase3ByteReader.java  |  10 +-
 .../internal/shapefile/jdbc/MappedByteReader.java  |   4 +-
 .../sis/storage/shapefile/InputFeatureStream.java  |   8 +-
 .../sis/storage/shapefile/ShapeFileTest.java       |  27 +-
 storage/sis-sql/pom.xml                            |   2 +-
 .../java/org/apache/sis/internal/sql/Dialect.java  | 167 ++++
 .../internal/sql/SingleAttributeTypeBuilder.java   | 260 ++++++
 .../org/apache/sis/internal/sql}/package-info.java |  11 +-
 .../sis/internal/sql/postgres/PostgresDialect.java | 107 +++
 .../sis/internal/sql/postgres/PostgresStore.java   |  88 ++
 .../sql/postgres/PostgresStoreProvider.java        |  74 ++
 .../sis/internal/sql/reverse/CachedResultSet.java  |  73 ++
 .../sis/internal/sql/reverse/ColumnMetaModel.java  | 208 +++++
 .../sis/internal/sql/reverse/DataBaseModel.java    | 769 +++++++++++++++++
 .../sis/internal/sql/reverse/InsertRelation.java   |  25 +-
 .../internal/sql/reverse/MetaDataConstants.java    | 515 ++++++++++++
 .../apache/sis/internal/sql/reverse/MetaModel.java |  79 ++
 .../sis/internal/sql/reverse/PrimaryKey.java       | 104 +++
 .../sis/internal/sql/reverse/QueryFeatureSet.java  |  94 +++
 .../internal/sql/reverse/RelationMetaModel.java    |  65 ++
 .../sis/internal/sql/reverse/SchemaMetaModel.java  |  72 ++
 .../sis/internal/sql/reverse/TableMetaModel.java   | 118 +++
 .../sis/internal/sql/reverse/package-info.java     |  19 +-
 storage/sis-storage/pom.xml                        |   2 +-
 .../sis/internal/storage/AbstractFeatureSet.java   |   4 +-
 .../sis/internal/storage/AbstractResource.java     |   7 +-
 .../apache/sis/internal/storage/Capability.java    |  22 +-
 .../internal/storage/FeatureCatalogBuilder.java    |  10 +-
 .../sis/internal/storage/JoinFeatureSet.java       | 563 +++++++++++++
 .../sis/internal/storage/MemoryFeatureSet.java     |  18 +-
 .../sis/internal/storage/MetadataBuilder.java      |  19 +-
 .../sis/internal/storage/StoreUtilities.java       |   4 +-
 .../sis/internal/storage/csv/FeatureIterator.java  |  24 +-
 .../storage/csv/MovingFeatureIterator.java         |  18 +-
 .../org/apache/sis/internal/storage/csv/Store.java |  30 +-
 .../storage/folder/FolderStoreProvider.java        |   5 +-
 .../apache/sis/internal/storage/folder/Store.java  |   2 +-
 .../sis/internal/storage/query/FeatureSubset.java  |  50 +-
 .../sis/internal/storage/query/SimpleQuery.java    | 263 +++++-
 .../internal/storage/query/SortByComparator.java   | 116 +++
 .../java/org/apache/sis/storage/Aggregate.java     |   6 +-
 .../java/org/apache/sis/storage/FeatureNaming.java |   4 +-
 .../java/org/apache/sis/storage/FeatureSet.java    |  10 +-
 .../main/java/org/apache/sis/storage/Query.java    |   8 +-
 .../main/java/org/apache/sis/storage/Resource.java |   2 +-
 .../org/apache/sis/storage/WritableFeatureSet.java |  18 +-
 .../sis/internal/storage/JoinFeatureSetTest.java   | 310 +++++++
 .../sis/internal/storage/MetadataBuilderTest.java  |   7 +-
 .../apache/sis/internal/storage/csv/StoreTest.java |  35 +-
 .../internal/storage/query/SimpleQueryTest.java    |  88 +-
 .../apache/sis/internal/storage/wkt/StoreTest.java |   2 +-
 .../apache/sis/internal/storage/xml/StoreTest.java |  16 +-
 .../apache/sis/test/suite/StorageTestSuite.java    |   1 +
 storage/sis-xmlstore/pom.xml                       |   2 +-
 .../apache/sis/internal/storage/gpx/Copyright.java | 183 ++--
 .../storage/gpx/GroupAsPolylineOperation.java      |  25 +-
 .../org/apache/sis/internal/storage/gpx/Link.java  |  10 +
 .../apache/sis/internal/storage/gpx/Metadata.java  |  28 +-
 .../apache/sis/internal/storage/gpx/Person.java    | 142 +++-
 .../apache/sis/internal/storage/gpx/Reader.java    |  32 +-
 .../org/apache/sis/internal/storage/gpx/Store.java |  16 +-
 .../org/apache/sis/internal/storage/gpx/Types.java |  20 +-
 .../apache/sis/internal/storage/gpx/Writer.java    |  18 +-
 .../storage/xml/stream/StaxStreamReader.java       |   8 +-
 .../storage/xml/stream/StaxStreamWriter.java       |  10 +-
 .../sis/internal/storage/gpx/MetadataTest.java     |   6 -
 .../sis/internal/storage/gpx/ReaderTest.java       |  72 +-
 .../apache/sis/internal/storage/gpx/TypesTest.java |   8 +-
 .../sis/internal/storage/gpx/WriterTest.java       |  24 +-
 598 files changed, 17112 insertions(+), 7195 deletions(-)

diff --git a/application/pom.xml b/application/pom.xml
index 307df00..1862415 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
 
@@ -116,6 +116,7 @@
        =========================================================== -->
   <modules>
     <module>sis-console</module>
+    <module>sis-javafx</module>
     <module>sis-webapp</module>
   </modules>
 
diff --git a/application/sis-console/pom.xml b/application/sis-console/pom.xml
index c15ff2a..19fce93 100644
--- a/application/sis-console/pom.xml
+++ b/application/sis-console/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
 
diff --git a/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java b/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java
index 3a071f3..e80e360 100644
--- a/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java
+++ b/application/sis-console/src/main/java/org/apache/sis/console/IdentifierCommand.java
@@ -36,10 +36,6 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Branch-dependent imports
-import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.metadata.iso.DefaultIdentifier;
-
 
 /**
  * The "identifier" sub-command.
@@ -128,11 +124,11 @@ final class IdentifierCommand extends FormattedOutputCommand {
         }
         if (metadata != null) {
             final List<Row> rows;
-            if (metadata instanceof DefaultMetadata) {
+            if (metadata instanceof Metadata) {
                 rows = new ArrayList<>();
-                final Identifier id = ((DefaultMetadata) metadata).getMetadataIdentifier();
-                if (id instanceof DefaultIdentifier) {
-                    CharSequence desc = ((DefaultIdentifier) id).getDescription();
+                final Identifier id = ((Metadata) metadata).getMetadataIdentifier();
+                if (id != null) {
+                    CharSequence desc = id.getDescription();
                     if (desc != null && !files.isEmpty()) desc = files.get(0);
                     rows.add(new Row(State.VALID, IdentifiedObjects.toString(id), desc));
                 }
diff --git a/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java b/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java
index 8dcd9dd..3b61f36 100644
--- a/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java
+++ b/application/sis-console/src/test/java/org/apache/sis/console/MetadataCommandTest.java
@@ -17,10 +17,10 @@
 package org.apache.sis.console;
 
 import java.net.URL;
+import org.opengis.test.dataset.TestData;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
-import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -42,9 +42,8 @@ public final strictfp class MetadataCommandTest extends TestCase {
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
-    @Ignore("Requires GeoAPI 3.1")
     public void testNetCDF() throws Exception {
-        final URL url = new URL("Cube2D_geographic_packed.nc"); // TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
         final MetadataCommand test = new MetadataCommand(0, CommandRunner.TEST, url.toString());
         test.run();
         verifyNetCDF("Metadata", test.outputBuffer.toString());
@@ -67,10 +66,9 @@ public final strictfp class MetadataCommandTest extends TestCase {
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
-    @Ignore("Requires GeoAPI 3.1")
     @DependsOnMethod("testNetCDF")
     public void testFormatXML() throws Exception {
-        final URL url = new URL("Cube2D_geographic_packed.nc") ; // TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
         final MetadataCommand test = new MetadataCommand(0, CommandRunner.TEST, url.toString(), "--format", "XML");
         test.run();
         verifyNetCDF("<?xml", test.outputBuffer.toString());
diff --git a/core/sis-raster/pom.xml b/application/sis-javafx/pom.xml
similarity index 74%
copy from core/sis-raster/pom.xml
copy to application/sis-javafx/pom.xml
index 42c83f8..4c42ddd 100644
--- a/core/sis-raster/pom.xml
+++ b/application/sis-javafx/pom.xml
@@ -27,19 +27,19 @@
 
   <parent>
     <groupId>org.apache.sis</groupId>
-    <artifactId>core</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <artifactId>application</artifactId>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
 
   <!-- ===========================================================
            Module Description
        =========================================================== -->
-  <groupId>org.apache.sis.core</groupId>
-  <artifactId>sis-raster</artifactId>
-  <name>Apache SIS rasters</name>
+  <groupId>org.apache.sis.application</groupId>
+  <artifactId>sis-javafx</artifactId>
+  <name>Apache SIS application for JavaFX</name>
   <description>
-    Access to raster data.
+    Client application for JavaFX.
   </description>
 
 
@@ -48,9 +48,9 @@
        =========================================================== -->
   <developers>
     <developer>
-      <name>Martin Desruisseaux</name>
-      <id>desruisseaux</id>
-      <email>desruisseaux@apache.org</email>
+      <name>Johann Sorel</name>
+      <id>jsorel</id>
+      <email>johann.sorel@geomatys.com</email>
       <organization>Geomatys</organization>
       <organizationUrl>http://www.geomatys.com</organizationUrl>
       <timezone>+1</timezone>
@@ -58,17 +58,6 @@
         <role>developer</role>
       </roles>
     </developer>
-    <developer>
-      <name>Rémi Maréchal</name>
-      <id>rmarechal</id>
-      <email>rmarechal@apache.org</email>
-      <organization>Geomatys</organization>
-      <organizationUrl>http://www.geomatys.com</organizationUrl>
-      <timezone>+1</timezone>
-      <roles>
-        <role>developer</role>
-      </roles>
-   </developer>
   </developers>
 
 
@@ -85,7 +74,7 @@
           <archive>
             <manifestEntries>
               <Automatic-Module-Name>
-                org.apache.sis.raster
+                org.apache.sis.gui
               </Automatic-Module-Name>
             </manifestEntries>
           </archive>
@@ -101,17 +90,13 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.sis.core</groupId>
-      <artifactId>sis-utility</artifactId>
+      <artifactId>sis-metadata</artifactId>
       <version>${project.version}</version>
     </dependency>
-
-    <!-- Test dependencies -->
     <dependency>
-      <groupId>org.apache.sis.core</groupId>
-      <artifactId>sis-utility</artifactId>
-      <version>${project.version}</version>
-      <type>test-jar</type>
-      <scope>test</scope>
+      <groupId>org.webjars</groupId>
+      <artifactId>material-design-icons</artifactId>
+      <version>3.0.1</version>
     </dependency>
   </dependencies>
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
new file mode 100644
index 0000000..c892e95
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java
@@ -0,0 +1,246 @@
+/*
+ * 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.awt.Color;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableList;
+import javafx.geometry.Pos;
+import javafx.scene.control.ContentDisplay;
+import javafx.scene.control.TreeItem;
+import javafx.scene.control.TreeTableCell;
+import javafx.scene.control.TreeTableColumn;
+import javafx.scene.control.TreeTableRow;
+import javafx.scene.control.TreeTableView;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.text.TextAlignment;
+import javafx.util.Callback;
+import org.apache.sis.internal.gui.FontGlyphs;
+import org.apache.sis.internal.util.Citations;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.storage.Aggregate;
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.storage.Resource;
+import org.opengis.metadata.Metadata;
+import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.identification.Identification;
+import org.opengis.util.InternationalString;
+
+
+/**
+ * Tree viewer displaying a {@link Resource} hierarchy.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+public class ResourceTree extends TreeTableView<Resource> {
+
+    private static final Image ICON_VECTOR = FontGlyphs.createImage("\uE922",18,Color.GRAY);
+    private static final Image ICON_FOLDER = FontGlyphs.createImage("\uE2C8",18,Color.GRAY);
+    private static final Image ICON_STORE = FontGlyphs.createImage("\uE2C7",18,Color.GRAY);
+    private static final Image ICON_OTHER = FontGlyphs.createImage("\uE24D",18,Color.GRAY);
+
+    public ResourceTree() {
+        getColumns().add(new ResourceNameColumn());
+        setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
+    }
+
+    /**
+     * Get root {@link Resource} of this tree.
+     *
+     * @return root {@link Resource}, may be null
+     */
+    public Resource getResource() {
+        final TreeItem<Resource> root = getRoot();
+        return root==null ? null : root.getValue();
+    }
+
+    /**
+     * Set root {@link Resource} of this tree.
+     *
+     * @param resource can be null
+     */
+    public void setResource(Resource resource) {
+        if (resource==null) {
+            setRoot(null);
+        } else {
+            setRoot(new ResourceItem(resource));
+        }
+    }
+
+    /**
+     * Find an appropriate icon for given resource.
+     *
+     * @param resource resource to test
+     * @return Image icon
+     */
+    private static Image getTypeIcon(Resource resource){
+        if (resource instanceof FeatureSet) {
+            return ICON_VECTOR;
+        } else if (resource instanceof DataStore) {
+            return ICON_STORE;
+        } else if (resource instanceof Aggregate) {
+            return ICON_FOLDER;
+        } else {
+            //unspecific resource type
+            return ICON_OTHER;
+        }
+    }
+
+    /**
+     * Returns a label for a resource. Current implementation builds a string containing the resource title
+     * if non-ambiguous, followed by filename in order to resolve ambiguity that may be caused by different
+     * files having the same resource identification in their metadata.
+     *
+     * @param  name      the result of {@link DataStore#getDisplayName()}, or {@code null} if unknown.
+     * @param  metadata  the result of {@link DataStore#getMetadata()} (may be {@code null}).
+     */
+    private static String getTitle(final String name, final Metadata metadata) {
+        if (metadata != null) {
+            String title = null;
+            for (final Identification identification : CollectionsExt.nonNull(metadata.getIdentificationInfo())) {
+                final Citation citation = identification.getCitation();
+                if (citation != null) {
+                    final InternationalString i18n = citation.getTitle();
+                    String id;
+                    if (i18n != null) {
+                        id = i18n.toString();                   // TODO: use display locale.
+                    } else {
+                        id = Citations.getIdentifier(identification.getCitation(), false);
+                    }
+                    if (id != null && !(id = id.trim()).isEmpty()) {
+                        if (title == null) {
+                            title = id;
+                        } else if (!title.equals(id)) {
+                            return name;                        // Ambiguity - will use the filename instead.
+                        }
+                    }
+                }
+            }
+            if (title != null) {
+                if (name != null) {
+                    title += " (" + name + ')';
+                }
+                return title;
+            }
+        }
+        return name;
+    }
+
+
+    private static class ResourceItem extends TreeItem<Resource> {
+
+        private final Resource resource;
+        private boolean isFirstTimeChildren = true;
+
+        public ResourceItem(Resource res) {
+            super(res);
+            this.resource = res;
+        }
+
+        @Override
+        public ObservableList<TreeItem<Resource>> getChildren() {
+            if (isFirstTimeChildren) {
+                isFirstTimeChildren = false;
+                super.getChildren().setAll(buildChildren());
+            }
+            return super.getChildren();
+        }
+
+        @Override
+        public boolean isLeaf() {
+            return !(resource instanceof Aggregate);
+        }
+
+        private List<TreeItem<Resource>> buildChildren() {
+            if (resource instanceof Aggregate) {
+                final List<TreeItem<Resource>> lst = new ArrayList<>();
+                try {
+                    for (Resource res : ((Aggregate) resource).components()) {
+                        lst.add(new ResourceItem(res));
+                    }
+                } catch (DataStoreException ex) {
+                    ex.printStackTrace();
+                }
+                return lst;
+            }
+            return Collections.EMPTY_LIST;
+        }
+
+    }
+
+    private static class ResourceNameColumn extends TreeTableColumn<Resource,String> {
+
+        public ResourceNameColumn() {
+            super("Resource");
+            setCellValueFactory(new Callback<CellDataFeatures<Resource, String>, javafx.beans.value.ObservableValue<java.lang.String>>() {
+                @Override
+                public ObservableValue<String> call(CellDataFeatures<Resource, String> param) {
+                    final Resource res = param.getValue().getValue();
+                    final String name = (res instanceof DataStore) ? ((DataStore) res).getDisplayName() : null;
+                    try {
+                        return new SimpleObjectProperty<>(getTitle(name, res.getMetadata()));
+                    } catch (DataStoreException ex) {
+                       return new SimpleObjectProperty<>(ex.getMessage());
+                    }
+                }
+            });
+            setCellFactory((TreeTableColumn<Resource, String> param) -> new Cell());
+            setEditable(true);
+            setPrefWidth(200);
+            setMinWidth(120);
+            setResizable(true);
+        }
+
+    }
+
+    private static class Cell extends TreeTableCell<Resource, String> {
+
+        @Override
+        public void updateItem(String item, boolean empty) {
+            super.updateItem(item, empty);
+            setText(item);
+            setGraphic(null);
+            setContentDisplay(ContentDisplay.LEFT);
+            setAlignment(Pos.CENTER_LEFT);
+            setTextAlignment(TextAlignment.LEFT);
+            setWrapText(false);
+            if (empty) return;
+
+            final TreeTableRow<Resource> row = getTreeTableRow();
+            if (row == null) {
+                return;
+            }
+            final TreeItem<Resource> ti = row.getTreeItem();
+            if (ti == null) {
+                return;
+            }
+
+            final Resource resource = ti.getValue();
+            setGraphic(new ImageView(getTypeIcon(resource)));
+        }
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSButton.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSButton.java
new file mode 100644
index 0000000..b953613
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSButton.java
@@ -0,0 +1,79 @@
+/*
+ * 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.referencing;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.scene.control.Button;
+import javafx.scene.image.ImageView;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+/**
+ * Widget button used to select a {@link CoordinateReferenceSystem}.
+ *
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+public class CRSButton extends Button{
+
+    private final ObjectProperty<CoordinateReferenceSystem> crsProperty = new SimpleObjectProperty<>();
+
+    /**
+     * Create a new CRSButton with no {@link CoordinateReferenceSystem} defined.
+     */
+    public CRSButton() {
+        setText("-");
+
+        setOnAction(new EventHandler<ActionEvent>() {
+            @Override
+            public void handle(ActionEvent event) {
+                final CoordinateReferenceSystem crs = CRSChooser.showDialog(CRSButton.this, crsProperty.get());
+                crsProperty.set(crs);
+            }
+        });
+
+        //update button text when needed
+        crsProperty.addListener((ObservableValue<? extends CoordinateReferenceSystem> observable,
+                CoordinateReferenceSystem oldValue, CoordinateReferenceSystem newValue) -> {
+            if (newValue!=null) {
+                setText(newValue.getName().toString());
+                setGraphic(new ImageView(CRSTable.getIcon(newValue)));
+            } else {
+                setText(" - ");
+                setGraphic(null);
+            }
+        });
+    }
+
+    /**
+     * Returns the property containing the edited {@link CoordinateReferenceSystem}.
+     * This property can be modified and will send events.
+     * It can be used with JavaFx binding operations.
+     *
+     * @return Property containing the edited {@link CoordinateReferenceSystem}
+     */
+    public ObjectProperty<CoordinateReferenceSystem> crsProperty() {
+        return crsProperty;
+    }
+
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
new file mode 100644
index 0000000..3aeede0
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
@@ -0,0 +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.gui.referencing;
+
+import java.util.Locale;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ChoiceBox;
+import javafx.scene.control.DialogPane;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.BorderPane;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.internal.gui.FXUtilities;
+import org.apache.sis.referencing.crs.DefaultGeographicCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.crs.AbstractCRS;
+
+
+/**
+ * Widget configuration panel used to select a {@link CoordinateReferenceSystem}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public class CRSChooser extends BorderPane {
+
+    @FXML
+    private CheckBox uiLongFirst;
+    @FXML
+    private CheckBox uiAxisConv;
+    @FXML
+    private BorderPane uiPane;
+    @FXML
+    private TextField uiSearch;
+    @FXML
+    private ChoiceBox<AxesConvention> uiChoice;
+
+    private CRSTable uiTable;
+
+    private final ObjectProperty<CoordinateReferenceSystem> crsProperty = new SimpleObjectProperty<>();
+    private boolean updateText;
+
+    /**
+     * Create a new CRSChooser with no {@link CoordinateReferenceSystem} defined.
+     */
+    public CRSChooser() {
+        try {
+            FXUtilities.loadJRXML(this, CRSChooser.class, Locale.getDefault());
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        uiSearch.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
+            if (updateText) return;
+            uiTable.searchCRS(uiSearch.getText());
+        });
+
+        uiTable = new CRSTable();
+        uiPane.setCenter(uiTable);
+
+        uiTable.crsProperty().bindBidirectional(crsProperty);
+
+        crsProperty.addListener((ObservableValue<? extends CoordinateReferenceSystem> observable,
+                              CoordinateReferenceSystem oldValue, CoordinateReferenceSystem newValue) -> {
+            uiTable.crsProperty().set(newValue);
+            if (newValue != null) {
+                updateText = true;
+                uiSearch.setText(newValue.getName().toString());
+                updateText = false;
+            }
+        });
+
+        uiChoice.setItems(FXCollections.observableArrayList(AxesConvention.values()));
+
+    }
+
+    private CoordinateReferenceSystem getCorrectedCRS(){
+        CoordinateReferenceSystem crs = crsProperty.get();
+        if (crs == null) return null;
+
+        try {
+            Integer epsg = IdentifiedObjects.lookupEPSG(crs);
+            if (epsg != null) {
+                crs = CRS.forCode("EPSG:" + epsg);
+                if (uiLongFirst.isSelected()) {
+                    crs = AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
+                }
+            }
+        } catch (FactoryException ex) {
+            // TODO
+        }
+        if (uiAxisConv.isSelected() && crs instanceof DefaultGeographicCRS && uiChoice.getValue() != null) {
+            crs = ((DefaultGeographicCRS) crs).forConvention(uiChoice.getValue());
+        }
+        return crs;
+    }
+
+    /**
+     * Returns the property containing the edited {@link CoordinateReferenceSystem}.
+     * This property can be modified and will send events.
+     * It can be used with JavaFx binding operations.
+     *
+     * @return Property containing the edited {@link CoordinateReferenceSystem}
+     */
+    public ObjectProperty<CoordinateReferenceSystem> crsProperty(){
+        return crsProperty;
+    }
+
+    /**
+     * Show a modal dialog to select a {@link CoordinateReferenceSystem}.
+     *
+     * @param parent parent frame of widget.
+     * @param crs {@link CoordinateReferenceSystem} to edit.
+     * @return modified {@link CoordinateReferenceSystem}.
+     */
+    public static CoordinateReferenceSystem showDialog(Object parent, CoordinateReferenceSystem crs) {
+        final CRSChooser chooser = new CRSChooser();
+        chooser.crsProperty.set(crs);
+        final Alert alert = new Alert(Alert.AlertType.NONE);
+        final DialogPane pane = alert.getDialogPane();
+        pane.setContent(chooser);
+        alert.getButtonTypes().setAll(ButtonType.OK,ButtonType.CANCEL);
+        alert.setResizable(true);
+        final ButtonType res = alert.showAndWait().orElse(ButtonType.CANCEL);
+        return res == ButtonType.CANCEL ? null : chooser.getCorrectedCRS();
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSTable.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSTable.java
new file mode 100644
index 0000000..66448c5
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSTable.java
@@ -0,0 +1,349 @@
+/*
+ * 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.referencing;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.event.EventHandler;
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.control.ProgressIndicator;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SelectionMode;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TablePosition;
+import javafx.scene.control.TableView;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import javafx.util.Callback;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.gui.FontGlyphs;
+import org.apache.sis.internal.gui.FXUtilities;
+import org.apache.sis.io.wkt.FormattableObject;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CRSAuthorityFactory;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.operation.ConicProjection;
+import org.opengis.referencing.operation.CylindricalProjection;
+import org.opengis.referencing.operation.PlanarProjection;
+import org.opengis.referencing.operation.Projection;
+import org.opengis.util.FactoryException;
+
+/**
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class CRSTable extends ScrollPane {
+
+    private static final Color COLOR = new Color(30, 150, 250);
+    private static final Image ICON_GEO, ICON_SQUARE, ICON_STEREO, ICON_UTM, ICON_CONIC;
+    private static final Image ICON_UNKNOWN = FontGlyphs.createImage("\uE22F",16,COLOR);
+    static {
+        final Class<?> c = CRSTable.class;
+        final Dimension dim = new Dimension(16, 16);
+        try {
+            ICON_GEO    = FXUtilities.getImage(c, "proj_geo.png",    dim);
+            ICON_SQUARE = FXUtilities.getImage(c, "proj_square.png", dim);
+            ICON_STEREO = FXUtilities.getImage(c, "proj_stereo.png", dim);
+            ICON_UTM    = FXUtilities.getImage(c, "proj_utm.png",    dim);
+            ICON_CONIC  = FXUtilities.getImage(c, "proj_conic.png",  dim);
+        } catch (IOException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private final ObjectProperty<CoordinateReferenceSystem> crsProperty = new SimpleObjectProperty<>();
+    private final TableView<Code> uiTable = new TableView<>();
+
+    private List<Code> allValues;
+
+    public CRSTable(){
+        uiTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
+        setContent(uiTable);
+        setFitToHeight(true);
+        setFitToWidth(true);
+
+        //add a loader while we load datas
+        final ProgressIndicator loading = new ProgressIndicator();
+        loading.setMaxWidth(60);
+        loading.setMaxHeight(60);
+        loading.setBackground(new Background(new BackgroundFill(new javafx.scene.paint.Color(0, 0, 0, 0), CornerRadii.EMPTY, Insets.EMPTY)));
+        loading.setProgress(-1);
+        uiTable.setPlaceholder(loading);
+        uiTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+        uiTable.setTableMenuButtonVisible(false);
+
+        uiTable.getSelectionModel().getSelectedCells().addListener(new ListChangeListener<TablePosition>() {
+            @Override
+            public void onChanged(ListChangeListener.Change<? extends TablePosition> c) {
+                final ObservableList<TablePosition> cells = uiTable.getSelectionModel().getSelectedCells();
+                if (!cells.isEmpty()) {
+                    final TablePosition cell = cells.get(0);
+                    final Code code = uiTable.getItems().get(cell.getRow());
+                    try {
+                        crsProperty.set((CoordinateReferenceSystem) code.createObject());
+                    } catch (FactoryException ex) {
+                        error(ex);
+                    }
+                }
+            }
+        });
+
+        uiTable.getColumns().add(new TypeColumn());
+        uiTable.getColumns().add(new CodeColumn());
+        uiTable.getColumns().add(new DescColumn());
+        uiTable.getColumns().add(new WKTColumn());
+
+
+        //load list
+        new Thread(){
+            @Override
+            public void run() {
+                try {
+                    allValues = getCodes();
+                    Platform.runLater(() -> {
+                        uiTable.setItems(FXCollections.observableArrayList(allValues));
+                        uiTable.setPlaceholder(new Label(""));
+                    });
+                } catch (FactoryException ex) {
+                    error(ex);
+                }
+            }
+        }.start();
+    }
+
+    public ObjectProperty<CoordinateReferenceSystem> crsProperty(){
+        return crsProperty;
+    }
+
+    public void searchCRS(final String searchword){
+        filter(searchword);
+    }
+
+    private static void error(final Exception e) {
+        // TODO
+    }
+
+    /**
+     * Display only the CRS name that contains the specified keywords. The {@code keywords}
+     * argument is a space-separated list, usually provided by the user after he pressed the
+     * "Search" button.
+     *
+     * @param keywords space-separated list of keywords to look for.
+     */
+    private void filter(String keywords) {
+        List<Code> model = allValues;
+        if (keywords != null) {
+            final Locale locale = Locale.getDefault();
+            keywords = keywords.toLowerCase(locale).trim();
+            final String[] tokens = keywords.split("\\s+");
+            if (tokens.length != 0) {
+                model = new ArrayList<>();
+                scan:
+                for (Code code : allValues) {
+                    final String name = code.toString().toLowerCase(locale);
+                    for (int j=0; j<tokens.length; j++) {
+                        if (!name.contains(tokens[j])) {
+                            continue scan;
+                        }
+                    }
+                    model.add(code);
+                }
+            }
+        }
+        uiTable.getItems().setAll(model);
+    }
+
+    /**
+     * Returns a collection containing only the factories of the specified authority.
+     */
+    private static Collection<CRSAuthorityFactory> filter(
+            final Collection<? extends CRSAuthorityFactory> factories, final String authority){
+        final List<CRSAuthorityFactory> filtered = new ArrayList<>();
+        for (final CRSAuthorityFactory factory : factories) {
+            if (Citations.identifierMatches(factory.getAuthority(), authority)) {
+                filtered.add(factory);
+            }
+        }
+        return filtered;
+    }
+
+    private List<Code> getCodes() throws FactoryException{
+        final CRSAuthorityFactory factory = org.apache.sis.referencing.CRS.getAuthorityFactory(null);
+        final Set<String> strs = factory.getAuthorityCodes(CoordinateReferenceSystem.class);
+        final List<Code> codes = new ArrayList<>();
+        for (String str : strs) {
+            codes.add(new Code(factory, str));
+        }
+        return codes;
+    }
+
+    static Image getIcon(IdentifiedObject obj) {
+        Image icon = ICON_UNKNOWN;
+        if (obj instanceof GeographicCRS) {
+            icon = ICON_GEO;
+        } else if (obj instanceof ProjectedCRS) {
+            final ProjectedCRS pcrs = (ProjectedCRS) obj;
+            final Projection proj = pcrs.getConversionFromBase();
+
+            if (String.valueOf(proj.getName()).toLowerCase().contains("utm")) {
+                icon = ICON_UTM;
+            } else if (proj instanceof ConicProjection) {
+                icon = ICON_CONIC;
+            } else if (proj instanceof CylindricalProjection) {
+                icon = ICON_SQUARE;
+            } else if (proj instanceof PlanarProjection) {
+                icon = ICON_STEREO;
+            } else {
+                icon = ICON_SQUARE;
+            }
+        } else {
+            icon = ICON_SQUARE;
+        }
+        return icon;
+    }
+
+    private static class TypeColumn extends TableColumn<Code, Code> {
+
+        public TypeColumn() {
+            setEditable(false);
+            setPrefWidth(30);
+            setMinWidth(30);
+            setMaxWidth(30);
+            setCellValueFactory((CellDataFeatures<Code, Code> param) -> new SimpleObjectProperty<>(param.getValue()));
+            setCellFactory(new Callback<TableColumn<Code, Code>, TableCell<Code, Code>>() {
+
+                @Override
+                public TableCell<Code, Code> call(TableColumn<Code, Code> param) {
+                    return new TableCell<Code,Code>(){
+                        @Override
+                        protected void updateItem(Code item, boolean empty) {
+                            super.updateItem(item, empty);
+                            setGraphic(null);
+                            if (item!=null){
+                                Image icon = ICON_UNKNOWN;
+                                try {
+                                    final IdentifiedObject obj = item.createObject();
+                                    icon = getIcon(obj);
+                                } catch (FactoryException ex) {
+                                    error(ex);
+                                }
+                                setGraphic(new ImageView(icon));
+                            }
+                        }
+                    };
+                }
+            });
+        }
+
+    }
+
+    private static class CodeColumn extends TableColumn<Code, String> {
+
+        public CodeColumn() {
+            super(Resources.format(Resources.Keys.Code));
+            setEditable(false);
+            setPrefWidth(150);
+            setCellValueFactory((TableColumn.CellDataFeatures<Code, String> param) -> new SimpleObjectProperty<>(param.getValue().code));
+        }
+
+    }
+
+    private static class DescColumn extends TableColumn<Code, String> {
+
+        public DescColumn() {
+            super(Resources.format(Resources.Keys.Description));
+            setEditable(false);
+            setCellValueFactory((TableColumn.CellDataFeatures<Code, String> param) ->
+                    new SimpleObjectProperty<>(param.getValue().getDescription()));
+        }
+
+    }
+
+    private static class WKTColumn extends TableColumn<Code, Code> {
+
+        private static final Image ICON = FontGlyphs.createImage("\uE873",16,COLOR);
+
+        public WKTColumn() {
+            super("");
+            setEditable(false);
+            setPrefWidth(26);
+            setMinWidth(26);
+            setMaxWidth(26);
+            setCellValueFactory((CellDataFeatures<Code, Code> param) -> new SimpleObjectProperty<>(param.getValue()));
+            setCellFactory(new Callback<TableColumn<Code, Code>, TableCell<Code, Code>>() {
+
+                @Override
+                public TableCell<Code, Code> call(TableColumn<Code, Code> param) {
+                    return new TableCell<Code,Code>() {
+                        {
+                            setOnMouseClicked(new EventHandler<MouseEvent>() {
+                                @Override
+                                public void handle(MouseEvent event) {
+                                    final Code item = getItem();
+                                    if (item!=null) {
+                                        try {
+                                            final IdentifiedObject obj = getItem().createObject();
+                                            if (obj instanceof FormattableObject) {
+                                                WKTPane.showDialog(this, (FormattableObject) obj);
+                                            }
+                                        } catch (FactoryException ex) {
+                                            error(ex);
+                                        }
+                                    }
+                                }
+                            });
+                        }
+
+                        @Override
+                        protected void updateItem(Code item, boolean empty) {
+                            super.updateItem(item, empty);
+                            if (item !=null && !empty) {
+                                setGraphic(new ImageView(ICON));
+                            } else {
+                                setGraphic(null);
+                            }
+                        }
+                    };
+                }
+            });
+        }
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
new file mode 100644
index 0000000..59676d3
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
@@ -0,0 +1,89 @@
+/*
+ * 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.referencing;
+
+import org.opengis.referencing.AuthorityFactory;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.util.FactoryException;
+
+
+/**
+ * Stores the code of a coordinate reference system (CRS) together with its description.
+ * The description will be fetched when first needed and returned by {@link #toString()}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class Code {
+    /**
+     * The CRS code. Usually defined by EPSG, but other authorities are allowed.
+     */
+    final String code;
+
+    /**
+     * The CRS object description for the {@linkplain #code}, fetched when first needed.
+     * In Apache SIS implementation of EPSG factory, this is the CRS name.
+     */
+    private String name;
+
+    /**
+     * The authority factory to use for fetching the name. Will be set to {@code null} after
+     * {@linkplain #name} has been made available, in order to allow the garbage collector
+     * to do its work if possible.
+     */
+    private final AuthorityFactory factory;
+
+    /**
+     * Creates a code from the specified value.
+     */
+    Code(final AuthorityFactory factory, final String code) {
+        this.factory = factory;
+        this.code    = code;
+    }
+
+    /**
+     * Create the Object identified by code.
+     */
+    IdentifiedObject createObject() throws FactoryException{
+        return factory.createObject(code);
+    }
+
+    /**
+     * Returns a description of the object.
+     */
+    public String getDescription() {
+        if (name == null) try {
+            name = factory.getDescriptionText(code).toString();
+        } catch (FactoryException e) {
+            name = e.getLocalizedMessage();
+        }
+        return name;
+    }
+
+    /**
+     * Returns the name for this code.
+     *
+     * @todo Maybe we should use the widget Locale when invoking InternationalString.toString(...).
+     */
+    @Override
+    public String toString() {
+        return code + " - " + getDescription();
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
new file mode 100644
index 0000000..b984d4c
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
@@ -0,0 +1,68 @@
+/*
+ * 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.referencing;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.FXCollections;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.ChoiceBox;
+import javafx.scene.control.DialogPane;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.BorderPane;
+import org.apache.sis.io.wkt.Convention;
+import org.apache.sis.io.wkt.FormattableObject;
+
+
+/**
+ * Small panel to display an object as WKT in various conventions.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class WKTPane extends BorderPane {
+
+    private final ChoiceBox<Convention> choice = new ChoiceBox<>(FXCollections.observableArrayList(Convention.values()));
+    private final TextArea text = new TextArea();
+
+    public WKTPane(final FormattableObject obj) {
+        setTop(choice);
+        setCenter(text);
+
+        choice.valueProperty().addListener(new ChangeListener<Convention>() {
+            @Override
+            public void changed(ObservableValue<? extends Convention> observable, Convention oldValue, Convention newValue) {
+                text.setText(obj.toString(newValue));
+            }
+        });
+        choice.getSelectionModel().select(Convention.WKT1);
+    }
+
+    public static void showDialog(Object parent, FormattableObject candidate){
+        final WKTPane chooser = new WKTPane(candidate);
+
+        final Alert alert = new Alert(Alert.AlertType.NONE);
+        final DialogPane pane = alert.getDialogPane();
+        pane.setContent(chooser);
+        alert.getButtonTypes().setAll(ButtonType.OK);
+        alert.setResizable(true);
+        alert.showAndWait();
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FXUtilities.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FXUtilities.java
new file mode 100644
index 0000000..73c8bc8
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FXUtilities.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gui;
+
+import java.util.Locale;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.image.Image;
+import javafx.embed.swing.SwingFXUtils;
+
+
+/**
+ * JavaFX utilities for internal purpose only.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final class FXUtilities {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private FXUtilities() {
+    }
+
+    /**
+     * Loads an image and resizes it to requested size.
+     *
+     * @param  loader  the class to use for loading the image.
+     * @param  path    path to image in the jar, relative to the {@code loader} class.
+     * @param  resize  the desired size, or {@code null} for no resizing.
+     * @return image resized to the given dimension.
+     * @throws IOException if the image can not be loaded.
+     *
+     * @deprecated we need a mechanism without dependency to AWT.
+     */
+    @Deprecated
+    public static Image getImage(final Class<?> loader, final String path, final Dimension resize) throws IOException {
+        BufferedImage img = ImageIO.read(loader.getResourceAsStream(path));
+        if (resize != null) {
+            final BufferedImage resized = new BufferedImage(resize.width, resize.height, BufferedImage.TYPE_INT_ARGB);
+            final Graphics2D g = resized.createGraphics();
+            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+            g.drawImage(img, 0, 0, resize.width, resize.height, null);
+            g.dispose();
+            img = resized;
+        }
+        return SwingFXUtils.toFXImage(img, null);
+    }
+
+    /**
+     * Loads and initializes widget from JRXML definition provided in this module.
+     * The JRXML file shall be in the same package than the given {@code loader} class
+     * and have the same simple name followed by the {@code ".fxml"} extension.
+     *
+     * @param  target  the widget for which to load the JRXML file.
+     * @param  loader  the class to use for loading the file.
+     * @param  locale  the locale for the resources.
+     * @throws IOException if an error occurred while loading the JRXML file.
+     */
+    public static void loadJRXML(final Parent target, final Class<?> loader, final Locale locale) throws IOException {
+        final FXMLLoader fxl = new FXMLLoader(loader.getResource(loader.getSimpleName() + ".fxml"),
+                                              Resources.forLocale(locale));
+        fxl.setRoot(target);
+        fxl.setController(target);
+        /*
+         * In some environements like OSGi, we must use the class loader of the widget
+         * (not the class loader of FXMLLoader).
+         */
+        fxl.setClassLoader(loader.getClassLoader());
+        fxl.load();
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FontGlyphs.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FontGlyphs.java
new file mode 100644
index 0000000..154e572
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/FontGlyphs.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gui;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.LinearGradientPaint;
+import java.awt.RenderingHints;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import javafx.embed.swing.SwingFXUtils;
+import javafx.scene.image.Image;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * Internal image tool to generate icons for javafx widgets.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ *
+ * @deprecated This introduces a dependency to AWT, which we wish to avoid at this early stage.
+ */
+@Deprecated
+public final class FontGlyphs {
+
+    private static Font FONT;
+
+    static {
+        try {
+            InputStream is = FontGlyphs.class.getResourceAsStream("/META-INF/resources/webjars/material-design-icons/3.0.1/MaterialIcons-Regular.ttf");
+            FONT = Font.createFont(Font.TRUETYPE_FONT, is);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            System.err.println("Font not loaded.  Using serif font.");
+            FONT = new Font("serif", Font.PLAIN, 24);
+        }
+    }
+
+    private FontGlyphs() {
+    }
+
+    /**
+     * Create a image icon from glyph code and color.
+     *
+     * @param text glyph codes
+     * @param size output image size
+     * @param textColor glyph color
+     * @return glyph image
+     */
+    public static Image createImage(String text, float size, Color textColor) {
+        return SwingFXUtils.toFXImage(createImage(text, textColor, FONT.deriveFont(size), null, null, true, false),null);
+    }
+
+    private static BufferedImage createImage(String text, Color textColor, Font font, Color bgColor, Insets insets, final boolean squareWanted, final boolean removeLeading) {
+        ArgumentChecks.ensureNonEmpty("Text to draw", text);
+        ArgumentChecks.ensureNonNull("Font to use", text);
+        if (insets == null) {
+            insets = new Insets(0, 0, 0, 0);
+        }
+
+        final int border = 0;
+        BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = img.createGraphics();
+        final FontMetrics fm = g.getFontMetrics(font);
+        final int textSize = fm.stringWidth(text);
+
+        int width = textSize + border * 2 + insets.left + insets.right;
+        int height = fm.getHeight() + border * 2 + insets.top + insets.bottom;
+        if (removeLeading) {
+            height -= fm.getLeading();
+        }
+
+        // We want a square. We compute additional margin to draw icon and text in center of thee square.
+        final int additionalLeftInset;
+        final int additionalTopInset;
+        if (squareWanted) {
+            final int tmpWidth = width;
+            width = Math.max(width, height);
+            additionalLeftInset = (width - tmpWidth) / 2;
+
+            final int tmpHeight = height;
+            height = Math.max(width, height);
+            additionalTopInset = (height - tmpHeight) / 2;
+        } else {
+            additionalLeftInset = 0;
+            additionalTopInset = 0;
+        }
+
+        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        g = img.createGraphics();
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        final RoundRectangle2D rect = new RoundRectangle2D.Double(0, 0, width - 1, img.getHeight() - 1, border, border);
+
+        if (bgColor != null) {
+            final Color brighter = new Color(
+                    Math.min(255, bgColor.getRed() + 100),
+                    Math.min(255, bgColor.getGreen() + 100),
+                    Math.min(255, bgColor.getBlue() + 100));
+
+            final LinearGradientPaint gradiant = new LinearGradientPaint(0, 0, 0, height, new float[]{0, 1}, new Color[]{brighter, bgColor});
+
+            g.setPaint(gradiant);
+            g.fill(rect);
+        }
+        int x = border + insets.left + additionalLeftInset;
+
+        //draw text
+        if (textColor != null) {
+            g.setColor(textColor);
+        }
+        g.setFont(font);
+        g.drawString(text, x, fm.getAscent() + border + insets.top + additionalTopInset);
+
+        if (bgColor != null) {
+            //draw border
+            g.setColor(Color.BLACK);
+            g.draw(rect);
+        }
+        return img;
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
new file mode 100644
index 0000000..8eda591
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gui;
+
+import java.net.URL;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import javax.annotation.Generated;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.resources.KeyConstants;
+import org.apache.sis.util.resources.IndexedResourceBundle;
+import org.apache.sis.util.resources.ResourceInternationalString;
+
+
+/**
+ * Messages that are specific to the {@code sis-javafx} module.
+ * Resources in this file should not be used by any other module. For resources shared by
+ * all modules in the Apache SIS project, see {@link org.apache.sis.util.resources} package.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final class Resources extends IndexedResourceBundle {
+    /**
+     * Resource keys. This class is used when compiling sources, but no dependencies to
+     * {@code Keys} should appear in any resulting class files. Since the Java compiler
+     * inlines final integer values, using long identifiers will not bloat the constant
+     * pools of compiled classes.
+     */
+    @Generated("org.apache.sis.util.resources.IndexedResourceCompiler")
+    public static final class Keys extends KeyConstants {
+        /**
+         * The unique instance of key constants handler.
+         */
+        static final Keys INSTANCE = new Keys();
+
+        /**
+         * For {@link #INSTANCE} creation only.
+         */
+        private Keys() {
+        }
+
+        /**
+         * Axis convention
+         */
+        public static final short AxisConvention = 3;
+
+        /**
+         * CRS
+         */
+        public static final short CRS = 4;
+
+        /**
+         * Code
+         */
+        public static final short Code = 1;
+
+        /**
+         * Description
+         */
+        public static final short Description = 2;
+
+        /**
+         * Longitude first
+         */
+        public static final short LongitudeFirst = 5;
+    }
+
+    /**
+     * Constructs a new resource bundle loading data from the given UTF file.
+     *
+     * @param resources  the path of the binary file containing resources, or {@code null} if
+     *        there is no resources. The resources may be a file or an entry in a JAR file.
+     */
+    public Resources(final URL resources) {
+        super(resources);
+    }
+
+    /**
+     * Returns the handle for the {@code Keys} constants.
+     *
+     * @return a handler for the constants declared in the inner {@code Keys} class.
+     */
+    @Override
+    protected KeyConstants getKeyConstants() {
+        return Keys.INSTANCE;
+    }
+
+    /**
+     * Returns resources in the given locale.
+     *
+     * @param  locale  the locale, or {@code null} for the default locale.
+     * @return resources in the given locale.
+     * @throws MissingResourceException if resources can not be found.
+     */
+    public static Resources forLocale(final Locale locale) throws MissingResourceException {
+        return getBundle(Resources.class, locale);
+    }
+
+    /**
+     * Gets a string for the given key from this resource bundle or one of its parents.
+     *
+     * @param  key  the key for the desired string.
+     * @return the string for the given key.
+     * @throws MissingResourceException if no object for the given key can be found.
+     */
+    public static String format(final short key) throws MissingResourceException {
+        return forLocale(null).getString(key);
+    }
+
+    /**
+     * Gets a string for the given key are replace all occurrence of "{0}"
+     * with values of {@code arg0}.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be found.
+     */
+    public static String format(final short  key,
+                                final Object arg0) throws MissingResourceException
+    {
+        return forLocale(null).getString(key, arg0);
+    }
+
+    /**
+     * Gets a string for the given key are replace all occurrence of "{0}",
+     * "{1}", with values of {@code arg0}, {@code arg1}.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @param  arg1  value to substitute to "{1}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be found.
+     */
+    public static String format(final short  key,
+                                final Object arg0,
+                                final Object arg1) throws MissingResourceException
+    {
+        return forLocale(null).getString(key, arg0, arg1);
+    }
+
+    /**
+     * Gets a string for the given key are replace all occurrence of "{0}",
+     * "{1}", with values of {@code arg0}, {@code arg1}, etc.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @param  arg1  value to substitute to "{1}".
+     * @param  arg2  value to substitute to "{2}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be found.
+     */
+    public static String format(final short  key,
+                                final Object arg0,
+                                final Object arg1,
+                                final Object arg2) throws MissingResourceException
+    {
+        return forLocale(null).getString(key, arg0, arg1, arg2);
+    }
+
+    /**
+     * The international string to be returned by {@link formatInternational}.
+     */
+    private static final class International extends ResourceInternationalString {
+        private static final long serialVersionUID = -7265791441872360274L;
+
+        International(short key)                           {super(key);}
+        International(short key, Object args)              {super(key, args);}
+        @Override protected KeyConstants getKeyConstants() {return Keys.INSTANCE;}
+        @Override protected IndexedResourceBundle getBundle(final Locale locale) {
+            return forLocale(locale);
+        }
+    }
+
+    /**
+     * Gets an international string for the given key. This method does not check for the key
+     * validity. If the key is invalid, then a {@link MissingResourceException} may be thrown
+     * when a {@link InternationalString#toString(Locale)} method is invoked.
+     *
+     * @param  key  the key for the desired string.
+     * @return an international string for the given key.
+     */
+    public static InternationalString formatInternational(final short key) {
+        return new International(key);
+    }
+
+    /**
+     * Gets an international string for the given key. This method does not check for the key
+     * validity. If the key is invalid, then a {@link MissingResourceException} may be thrown
+     * when a {@link InternationalString#toString(Locale)} method is invoked.
+     *
+     * @param  key   the key for the desired string.
+     * @param  args  values to substitute to "{0}", "{1}", <i>etc</i>.
+     * @return an international string for the given key.
+     */
+    public static InternationalString formatInternational(final short key, final Object... args) {
+        return new International(key, args);
+    }
+}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
new file mode 100644
index 0000000..6ce5fb7
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+#
+# Resources in this file are for "sis-javafx" usage only and should not be used by any other module.
+# For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources" package.
+#
+
+Code            = Code
+Description     = Description
+CRS             = CRS
+LongitudeFirst  = Longitude first
+AxisConvention  = Axis convention
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
new file mode 100644
index 0000000..878ee29
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+#
+# Resources in this file are for "sis-javafx" usage only and should not be used by any other module.
+# For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources" package.
+#
+# Punctuation rules in French (source: http://unicode.org/udhr/n/notes_fra.html)
+#
+#   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
+#   U+00A0 NO-BREAK SPACE         before  :
+#
+
+Code            = Code
+Description     = Description
+CRS             = CRS
+LongitudeFirst  = Longitude en premier
+AxisConvention  = Convention
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java
similarity index 84%
copy from core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
copy to application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java
index 61f8d32..673060f 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/package-info.java
@@ -16,16 +16,16 @@
  */
 
 /**
- * Placeholder for GeoAPI interfaces not present in GeoAPI 3.0.
+ * A set of helper classes for the SIS implementation.
  *
  * <STRONG>Do not use!</STRONG>
  *
  * This package is for internal use by SIS only. Classes in this package
  * may change in incompatible ways in any future version without notice.
  *
- * @author  Martin Desruisseaux (Geomatys)
- * @since   0.3
- * @version 0.3
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
  * @module
  */
-package org.apache.sis.internal.geoapi.temporal;
+package org.apache.sis.internal.gui;
diff --git a/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/CRSChooser.fxml b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/CRSChooser.fxml
new file mode 100644
index 0000000..cb22507
--- /dev/null
+++ b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/CRSChooser.fxml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.*?>
+<?import java.lang.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<fx:root maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" type="BorderPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
+   <top>
+      <GridPane hgap="10.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" vgap="10.0" BorderPane.alignment="CENTER">
+        <columnConstraints>
+          <ColumnConstraints hgrow="NEVER" />
+          <ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" />
+            <ColumnConstraints hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="100.0" />
+        </columnConstraints>
+        <rowConstraints>
+            <RowConstraints vgrow="NEVER" />
+            <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="NEVER" />
+            <RowConstraints vgrow="NEVER" />
+        </rowConstraints>
+         <children>
+            <Label styleClass="property-key" text="%CRS" GridPane.rowSpan="2" />
+            <CheckBox fx:id="uiLongFirst" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="property-key" text="%LongitudeFirst" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.halignment="LEFT" />
+            <TextField fx:id="uiSearch" GridPane.columnSpan="3" GridPane.rowIndex="2" />
+            <CheckBox fx:id="uiAxisConv" mnemonicParsing="false" text="%AxisConvention" GridPane.columnIndex="1" GridPane.rowIndex="1" />
+            <ChoiceBox fx:id="uiChoice" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="1" />
+         </children>
+         <BorderPane.margin>
+            <Insets bottom="10.0" left="10.0" right="10.0" />
+         </BorderPane.margin>
+      </GridPane>
+   </top>
+   <center>
+      <BorderPane fx:id="uiPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" BorderPane.alignment="CENTER" />
+   </center>
+</fx:root>
diff --git a/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_conic.png b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_conic.png
new file mode 100644
index 0000000..a062e1f
Binary files /dev/null and b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_conic.png differ
diff --git a/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_geo.png b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_geo.png
new file mode 100644
index 0000000..6f9340c
Binary files /dev/null and b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_geo.png differ
diff --git a/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_square.png b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_square.png
new file mode 100644
index 0000000..6e21c43
Binary files /dev/null and b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_square.png differ
diff --git a/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_stereo.png b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_stereo.png
new file mode 100644
index 0000000..d22f575
Binary files /dev/null and b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_stereo.png differ
diff --git a/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_utm.png b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_utm.png
new file mode 100644
index 0000000..d04a803
Binary files /dev/null and b/application/sis-javafx/src/main/resources/org/apache/sis/gui/referencing/proj_utm.png differ
diff --git a/application/sis-openoffice/pom.xml b/application/sis-openoffice/pom.xml
index fd2bc3d..9fcfdd9 100644
--- a/application/sis-openoffice/pom.xml
+++ b/application/sis-openoffice/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
 
@@ -87,7 +87,7 @@
   <dependencies>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi</artifactId>
+      <artifactId>geoapi-pending</artifactId>
     </dependency>
     <dependency>
       <groupId>org.apache.sis.core</groupId>
diff --git a/application/sis-webapp/pom.xml b/application/sis-webapp/pom.xml
index 324f6bc..f691d0d 100644
--- a/application/sis-webapp/pom.xml
+++ b/application/sis-webapp/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.application</groupId>
diff --git a/core/pom.xml b/core/pom.xml
index 51d8c69..f0de645 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
 
@@ -171,7 +171,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi</artifactId>
+      <artifactId>geoapi-pending</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
diff --git a/core/sis-build-helper/pom.xml b/core/sis-build-helper/pom.xml
index 3307115..f3b6e97 100644
--- a/core/sis-build-helper/pom.xml
+++ b/core/sis-build-helper/pom.xml
@@ -32,7 +32,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
 
diff --git a/core/sis-feature/pom.xml b/core/sis-feature/pom.xml
index b8e852b..88d283b 100644
--- a/core/sis-feature/pom.xml
+++ b/core/sis-feature/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>dev-1.0-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
index 3ae19d4..713bbc0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
@@ -25,6 +25,14 @@ import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.MultiValuedPropertyException;
+
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole feature association role} containing the associated feature.
@@ -48,7 +56,7 @@ import org.apache.sis.internal.feature.Resources;
  * @since 0.5
  * @module
  */
-public abstract class AbstractAssociation extends Field<AbstractFeature> implements Cloneable, Serializable {
+public abstract class AbstractAssociation extends Field<Feature> implements FeatureAssociation, Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -57,16 +65,16 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
     /**
      * Information about the association.
      */
-    final DefaultAssociationRole role;
+    final FeatureAssociationRole role;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      *
-     * @see #create(DefaultAssociationRole)
+     * @see #create(FeatureAssociationRole)
      */
-    protected AbstractAssociation(final DefaultAssociationRole role) {
+    protected AbstractAssociation(final FeatureAssociationRole role) {
         this.role = role;
     }
 
@@ -78,7 +86,7 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
      *
      * @see DefaultAssociationRole#newInstance()
      */
-    public static AbstractAssociation create(final DefaultAssociationRole role) {
+    public static AbstractAssociation create(final FeatureAssociationRole role) {
         ArgumentChecks.ensureNonNull("role", role);
         return isSingleton(role.getMaximumOccurs())
                ? new SingletonAssociation(role)
@@ -92,16 +100,16 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
      * @param  value  the initial value (may be {@code null}).
      * @return the new association.
      */
-    static AbstractAssociation create(final DefaultAssociationRole role, final Object value) {
+    static AbstractAssociation create(final FeatureAssociationRole role, final Object value) {
         ArgumentChecks.ensureNonNull("role", role);
         return isSingleton(role.getMaximumOccurs())
-               ? new SingletonAssociation(role, (AbstractFeature) value)
+               ? new SingletonAssociation(role, (Feature) value)
                : new MultiValuedAssociation(role, value);
     }
 
     /**
      * Returns the name of this association as defined by its {@linkplain #getRole() role}.
-     * This convenience method delegates to {@link DefaultAssociationRole#getName()}.
+     * This convenience method delegates to {@link FeatureAssociationRole#getName()}.
      *
      * @return the association name specified by its role.
      */
@@ -113,12 +121,10 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
     /**
      * Returns information about the association.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.AssociationRole}. This change is pending GeoAPI revision.</div>
-     *
      * @return information about the association.
      */
-    public DefaultAssociationRole getRole() {
+    @Override
+    public FeatureAssociationRole getRole() {
         return role;
     }
 
@@ -127,16 +133,13 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
      * the common case where the {@linkplain DefaultAssociationRole#getMaximumOccurs() maximum number} of
      * features is restricted to 1 or 0.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.Feature}. This change is pending GeoAPI revision.</div>
-     *
      * @return the associated feature (may be {@code null}).
-     * @throws IllegalStateException if this association contains more than one value.
+     * @throws MultiValuedPropertyException if this association contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract AbstractFeature getValue() throws IllegalStateException;
+    public abstract Feature getValue() throws MultiValuedPropertyException;
 
     /**
      * Returns all features, or an empty collection if none.
@@ -149,16 +152,13 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
      * @return the features in a <cite>live</cite> collection.
      */
     @Override
-    public Collection<AbstractFeature> getValues() {
+    public Collection<Feature> getValues() {
         return super.getValues();
     }
 
     /**
      * Sets the associated feature.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument type may be changed
-     * to {@code org.opengis.feature.Feature}. This change is pending GeoAPI revision.</div>
-     *
      * <div class="section">Validation</div>
      * The amount of validation performed by this method is implementation dependent.
      * Usually, only the most basic constraints are verified. This is so for performance reasons
@@ -166,24 +166,24 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws IllegalArgumentException if the given feature is not valid for this association.
+     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final AbstractFeature value) throws IllegalArgumentException;
+    public abstract void setValue(final Feature value) throws InvalidPropertyValueException;
 
     /**
      * Sets the features. All previous values are replaced by the given collection.
      *
      * <p>The default implementation ensures that the given collection contains at most one element,
-     * then delegates to {@link #setValue(AbstractFeature)}.</p>
+     * then delegates to {@link #setValue(Feature)}.</p>
      *
      * @param  values  the new values.
-     * @throws IllegalArgumentException if the given collection contains too many elements.
+     * @throws InvalidPropertyValueException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends AbstractFeature> values) throws IllegalArgumentException {
+    public void setValues(final Collection<? extends Feature> values) throws InvalidPropertyValueException {
         super.setValues(values);
     }
 
@@ -191,9 +191,9 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
      * Ensures that storing a feature of the given type is valid for an association
      * expecting the given base type.
      */
-    final void ensureValid(final DefaultFeatureType base, final DefaultFeatureType type) {
+    final void ensureValid(final FeatureType base, final FeatureType type) {
         if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-            throw new IllegalArgumentException(
+            throw new InvalidPropertyValueException(
                     Resources.format(Resources.Keys.IllegalFeatureType_3, getName(), base.getName(), type.getName()));
         }
     }
@@ -229,7 +229,7 @@ public abstract class AbstractAssociation extends Field<AbstractFeature> impleme
     @Override
     public String toString() {
         final String pt = DefaultAssociationRole.getTitleProperty(role);
-        final Iterator<AbstractFeature> it = getValues().iterator();
+        final Iterator<Feature> it = getValues().iterator();
         return FieldType.toString(isDeprecated(role), "FeatureAssociation", role.getName(),
                 DefaultAssociationRole.getValueTypeName(role), new Iterator<Object>()
         {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
index 8dc4601..20e2303 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
@@ -32,6 +32,12 @@ import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArgumentChecks;
 
+// Branch-dependent imports
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.MultiValuedPropertyException;
+
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing the value of an attribute in a feature.
@@ -74,7 +80,7 @@ import org.apache.sis.util.ArgumentChecks;
  * @module
  */
 @SuppressWarnings("CloneInNonCloneableClass")       // Decision left to subclasses - see javadoc
-public abstract class AbstractAttribute<V> extends Field<V> implements Serializable {
+public abstract class AbstractAttribute<V> extends Field<V> implements Attribute<V>, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -83,7 +89,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
     /**
      * Information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    final DefaultAttributeType<V> type;
+    final AttributeType<V> type;
 
     /**
      * Other attributes that describes this attribute, or {@code null} if not yet created.
@@ -98,16 +104,16 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      *
      * @see #characteristics()
      */
-    private transient Map<String,AbstractAttribute<?>> characteristics;
+    private transient Map<String,Attribute<?>> characteristics;
 
     /**
      * Creates a new attribute of the given type.
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      *
-     * @see #create(DefaultAttributeType)
+     * @see #create(AttributeType)
      */
-    protected AbstractAttribute(final DefaultAttributeType<V> type) {
+    protected AbstractAttribute(final AttributeType<V> type) {
         this.type = type;
     }
 
@@ -121,7 +127,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      *
      * @see DefaultAttributeType#newInstance()
      */
-    public static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type) {
+    public static <V> AbstractAttribute<V> create(final AttributeType<V> type) {
         ArgumentChecks.ensureNonNull("type", type);
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type)
@@ -137,7 +143,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      * @param  value  the initial value (may be {@code null}).
      * @return the new attribute.
      */
-    static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type, final Object value) {
+    static <V> AbstractAttribute<V> create(final AttributeType<V> type, final Object value) {
         ArgumentChecks.ensureNonNull("type", type);
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type, value)
@@ -152,9 +158,9 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
-        final AbstractAttribute<?>[] characterizedBy;
+        final Attribute<?>[] characterizedBy;
         if (characteristics instanceof CharacteristicMap) {
-            characterizedBy = characteristics.values().toArray(new AbstractAttribute<?>[characteristics.size()]);
+            characterizedBy = characteristics.values().toArray(new Attribute<?>[characteristics.size()]);
         } else {
             characterizedBy = null;
         }
@@ -171,7 +177,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final AbstractAttribute<?>[] characterizedBy = (AbstractAttribute<?>[]) in.readObject();
+            final Attribute<?>[] characterizedBy = (Attribute<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = newCharacteristicsMap();
                 characteristics.values().addAll(Arrays.asList(characterizedBy));
@@ -184,7 +190,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
 
     /**
      * Returns the name of this attribute as defined by its {@linkplain #getType() type}.
-     * This convenience method delegates to {@link DefaultAttributeType#getName()}.
+     * This convenience method delegates to {@link AttributeType#getName()}.
      *
      * @return the attribute name specified by its type.
      */
@@ -196,12 +202,10 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
     /**
      * Returns information about the attribute (base Java class, domain of values, <i>etc.</i>).
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.AttributeType}. This change is pending GeoAPI revision.</div>
-     *
      * @return information about the attribute.
      */
-    public DefaultAttributeType<V> getType() {
+    @Override
+    public AttributeType<V> getType() {
         return type;
     }
 
@@ -211,12 +215,12 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      * of attribute values is restricted to 1 or 0.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws IllegalStateException if this attribute contains more than one value.
+     * @throws MultiValuedPropertyException if this attribute contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws IllegalStateException;
+    public abstract V getValue() throws MultiValuedPropertyException;
 
     /**
      * Returns all attribute values, or an empty collection if none.
@@ -243,13 +247,13 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null} for removing all values from this attribute.
-     * @throws IllegalArgumentException if this method verifies argument validity and the given value
+     * @throws InvalidPropertyValueException if this method verifies argument validity and the given value
      *         does not met the attribute constraints.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final V value) throws IllegalArgumentException;
+    public abstract void setValue(final V value) throws InvalidPropertyValueException;
 
     /**
      * Sets the attribute values. All previous values are replaced by the given collection.
@@ -258,10 +262,10 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws IllegalArgumentException if the given collection contains too many elements.
+     * @throws InvalidPropertyValueException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
+    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
         super.setValues(values);
     }
 
@@ -340,8 +344,9 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      *
      * @see DefaultAttributeType#characteristics()
      */
+    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Map<String,AbstractAttribute<?>> characteristics() {
+    public Map<String,Attribute<?>> characteristics() {
         if (characteristics == null) {
             characteristics = newCharacteristicsMap();
         }
@@ -353,13 +358,13 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      * This method does not store the new map in the {@link #characteristics} field;
      * it is caller responsibility to do so if desired.
      */
-    private Map<String,AbstractAttribute<?>> newCharacteristicsMap() {
+    private Map<String,Attribute<?>> newCharacteristicsMap() {
         if (type instanceof DefaultAttributeType<?>) {
-            Map<String, DefaultAttributeType<?>> map = type.characteristics();
+            Map<String, AttributeType<?>> map = ((DefaultAttributeType<?>) type).characteristics();
             if (!map.isEmpty()) {
                 if (!(map instanceof CharacteristicTypeMap)) {
-                    final Collection<DefaultAttributeType<?>> types = map.values();
-                    map = CharacteristicTypeMap.create(type, types.toArray(new DefaultAttributeType<?>[types.size()]));
+                    final Collection<AttributeType<?>> types = map.values();
+                    map = CharacteristicTypeMap.create(type, types.toArray(new AttributeType<?>[types.size()]));
                 }
                 return new CharacteristicMap(this, (CharacteristicTypeMap) map);
             }
@@ -372,7 +377,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
      * Contrarily to {@link #characteristics()}, this method does not create the map. This method
      * is suitable when then caller only wants to read the map and does not plan to write anything.
      */
-    final Map<String,AbstractAttribute<?>> characteristicsReadOnly() {
+    final Map<String,Attribute<?>> characteristicsReadOnly() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -470,7 +475,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
         if (characteristics != null && !characteristics.isEmpty()) {
             buffer.append(System.lineSeparator());
             String separator = "└─ characteristics: ";
-            for (final Map.Entry<String,AbstractAttribute<?>> entry : characteristics.entrySet()) {
+            for (final Map.Entry<String,Attribute<?>> entry : characteristics.entrySet()) {
                 buffer.append(separator).append(entry.getKey()).append('=').append(entry.getValue().getValue());
                 separator = ", ";
             }
@@ -495,7 +500,7 @@ public abstract class AbstractAttribute<V> extends Field<V> implements Serializa
     @SuppressWarnings("unchecked")
     public AbstractAttribute<V> clone() throws CloneNotSupportedException {
         final AbstractAttribute<V> clone = (AbstractAttribute<V>) super.clone();
-        final Map<String,AbstractAttribute<?>> c = clone.characteristics;
+        final Map<String,Attribute<?>> c = clone.characteristics;
         if (c instanceof CharacteristicMap) {
             clone.characteristics = ((CharacteristicMap) c).clone();
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
index 752ee0a..26f0c7a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
@@ -32,6 +32,20 @@ import org.apache.sis.util.CorruptedObjectException;
 import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.PropertyNotFoundException;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.Operation;
+
 
 /**
  * An instance of a {@linkplain DefaultFeatureType feature type} containing values for a real-world phenomena.
@@ -71,7 +85,7 @@ import org.apache.sis.internal.feature.Resources;
  * @since 0.5
  * @module
  */
-public abstract class AbstractFeature implements Serializable {
+public abstract class AbstractFeature implements Feature, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -80,7 +94,7 @@ public abstract class AbstractFeature implements Serializable {
     /**
      * Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    final DefaultFeatureType type;
+    final FeatureType type;
 
     /**
      * Creates a new feature of the given type.
@@ -89,7 +103,7 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @see DefaultFeatureType#newInstance()
      */
-    protected AbstractFeature(final DefaultFeatureType type) {
+    protected AbstractFeature(final FeatureType type) {
         ArgumentChecks.ensureNonNull("type", type);
         this.type = type;
     }
@@ -105,12 +119,10 @@ public abstract class AbstractFeature implements Serializable {
     /**
      * Returns information about the feature (name, characteristics, <i>etc.</i>).
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
-     *
      * @return information about the feature.
      */
-    public DefaultFeatureType getType() {
+    @Override
+    public FeatureType getType() {
         return type;
     }
 
@@ -135,17 +147,15 @@ public abstract class AbstractFeature implements Serializable {
      * Implementors are encouraged to override this method if they can provide a more efficient implementation.
      * Note that this is already the case when using implementations created by {@link DefaultFeatureType#newInstance()}.</div>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
-     *
      * @param  name  the property name.
      * @return the property of the given name (never {@code null}).
-     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
      *
      * @see #getPropertyValue(String)
      * @see DefaultFeatureType#getProperty(String)
      */
-    public Object getProperty(final String name) throws IllegalArgumentException {
+    @Override
+    public Property getProperty(final String name) throws PropertyNotFoundException {
         return PropertyView.create(this, type.getProperty(name));
     }
 
@@ -180,24 +190,22 @@ public abstract class AbstractFeature implements Serializable {
      * Implementors are encouraged to override this method if they can provide a better implementation.
      * Note that this is already the case when using implementations created by {@link DefaultFeatureType#newInstance()}.</div>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument may be changed
-     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
-     *
      * @param  property  the property to set.
-     * @throws IllegalArgumentException if the name of the given property is not a property name of this feature.
-     * @throws IllegalArgumentException if the value of the given property is not valid.
+     * @throws PropertyNotFoundException if the name of the given property is not a property name of this feature.
+     * @throws InvalidPropertyValueException if the value of the given property is not valid.
      * @throws IllegalArgumentException if the property can not be set for another reason.
      *
      * @see #setPropertyValue(String, Object)
      */
-    public void setProperty(final Object property) throws IllegalArgumentException {
+    @Override
+    public void setProperty(final Property property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = ((Property) property).getName().toString();
-        verifyPropertyType(name, (Property) property);
-        if (property instanceof AbstractAttribute<?> && !Containers.isNullOrEmpty(((AbstractAttribute<?>) property).characteristics())) {
+        final String name = property.getName().toString();
+        verifyPropertyType(name, property);
+        if (property instanceof Attribute<?> && !Containers.isNullOrEmpty(((Attribute<?>) property).characteristics())) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.CanNotAssignCharacteristics_1, name));
         }
-        setPropertyValue(name, ((Property) property).getValue());
+        setPropertyValue(name, property.getValue());
     }
 
     /**
@@ -209,11 +217,11 @@ public abstract class AbstractFeature implements Serializable {
      * @return a {@code Property} wrapping the given value.
      */
     final Property createProperty(final String name, final Object value) {
-        final AbstractIdentifiedType pt = type.getProperty(name);
-        if (pt instanceof DefaultAttributeType<?>) {
-            return AbstractAttribute.create((DefaultAttributeType<?>) pt, value);
-        } else if (pt instanceof DefaultAssociationRole) {
-            return AbstractAssociation.create((DefaultAssociationRole) pt, value);
+        final PropertyType pt = type.getProperty(name);
+        if (pt instanceof AttributeType<?>) {
+            return AbstractAttribute.create((AttributeType<?>) pt, value);
+        } else if (pt instanceof FeatureAssociationRole) {
+            return AbstractAssociation.create((FeatureAssociationRole) pt, value);
         } else {
             // Should never happen, unless the user gave us some mutable FeatureType.
             throw new CorruptedObjectException(Errors.format(Errors.Keys.UnknownType_1, pt));
@@ -225,15 +233,15 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  name  the name of the property to create.
      * @return a {@code Property} of the given name.
-     * @throws IllegalArgumentException if the given argument is not the name of an attribute or
+     * @throws PropertyNotFoundException if the given argument is not the name of an attribute or
      *         feature association of this feature.
      */
-    final Property createProperty(final String name) throws IllegalArgumentException {
-        final AbstractIdentifiedType pt = type.getProperty(name);
-        if (pt instanceof DefaultAttributeType<?>) {
-            return ((DefaultAttributeType<?>) pt).newInstance();
-        } else if (pt instanceof DefaultAssociationRole) {
-            return ((DefaultAssociationRole) pt).newInstance();
+    final Property createProperty(final String name) throws PropertyNotFoundException {
+        final PropertyType pt = type.getProperty(name);
+        if (pt instanceof AttributeType<?>) {
+            return ((AttributeType<?>) pt).newInstance();
+        } else if (pt instanceof FeatureAssociationRole) {
+            return ((FeatureAssociationRole) pt).newInstance();
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
         }
@@ -244,14 +252,14 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @see #getOperationValue(String)
      */
-    final Object getOperationResult(final String name) {
+    final Property getOperationResult(final String name) {
         /*
          * The (Operation) cast below should never fail (unless the DefaultFeatureType in not really immutable,
          * which would be a contract violation) because all callers shall ensure that this method is invoked in
          * a context where the following assertion holds.
          */
-        assert DefaultFeatureType.OPERATION_INDEX.equals(type.indices().get(name)) : name;
-        return ((AbstractOperation) type.getProperty(name)).apply(this, null);
+        assert DefaultFeatureType.OPERATION_INDEX.equals(((DefaultFeatureType) type).indices().get(name)) : name;
+        return ((Operation) type.getProperty(name)).apply(this, null);
     }
 
     /**
@@ -260,14 +268,14 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  name  the name of the property for which to get the default value.
      * @return the default value for the {@code Property} of the given name.
-     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
-     */
-    final Object getDefaultValue(final String name) throws IllegalArgumentException {
-        final AbstractIdentifiedType pt = type.getProperty(name);
-        if (pt instanceof DefaultAttributeType<?>) {
-            return getDefaultValue((DefaultAttributeType<?>) pt);
-        } else if (pt instanceof DefaultAssociationRole) {
-            final int maximumOccurs = ((DefaultAssociationRole) pt).getMaximumOccurs();
+     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     */
+    final Object getDefaultValue(final String name) throws PropertyNotFoundException {
+        final PropertyType pt = type.getProperty(name);
+        if (pt instanceof AttributeType<?>) {
+            return getDefaultValue((AttributeType<?>) pt);
+        } else if (pt instanceof FeatureAssociationRole) {
+            final int maximumOccurs = ((FeatureAssociationRole) pt).getMaximumOccurs();
             return maximumOccurs > 1 ? Collections.EMPTY_LIST : null;       // No default value for associations.
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -277,7 +285,7 @@ public abstract class AbstractFeature implements Serializable {
     /**
      * Returns the default value to be returned by {@link #getPropertyValue(String)} for the given attribute type.
      */
-    private static <V> Object getDefaultValue(final DefaultAttributeType<V> attribute) {
+    private static <V> Object getDefaultValue(final AttributeType<V> attribute) {
         final V defaultValue = attribute.getDefaultValue();
         if (Field.isSingleton(attribute.getMaximumOccurs())) {
             return defaultValue;
@@ -295,10 +303,10 @@ public abstract class AbstractFeature implements Serializable {
      * <table class="sis">
      *   <caption>Class of returned value</caption>
      *   <tr><th>Property type</th>                  <th>max. occurs</th> <th>Method invoked</th>                         <th>Return type</th></tr>
-     *   <tr><td>{@code AttributeType}</td>          <td>0 or 1</td>      <td>{@code Attribute.getValue()}</td>           <td>{@link Object}</td></tr>
-     *   <tr><td>{@code AttributeType}</td>          <td>2 or more</td>   <td>{@code Attribute.getValues()}</td>          <td>{@code Collection<?>}</td></tr>
-     *   <tr><td>{@code FeatureAssociationRole}</td> <td>0 or 1</td>      <td>{@code FeatureAssociation.getValue()}</td>  <td>{@code Feature}</td></tr>
-     *   <tr><td>{@code FeatureAssociationRole}</td> <td>2 or more</td>   <td>{@code FeatureAssociation.getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
+     *   <tr><td>{@link AttributeType}</td>          <td>0 or 1</td>      <td>{@link Attribute#getValue()}</td>           <td>{@link Object}</td></tr>
+     *   <tr><td>{@code AttributeType}</td>          <td>2 or more</td>   <td>{@link Attribute#getValues()}</td>          <td>{@code Collection<?>}</td></tr>
+     *   <tr><td>{@link FeatureAssociationRole}</td> <td>0 or 1</td>      <td>{@link FeatureAssociation#getValue()}</td>  <td>{@link Feature}</td></tr>
+     *   <tr><td>{@code FeatureAssociationRole}</td> <td>2 or more</td>   <td>{@link FeatureAssociation#getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
      * </table>
      *
      * <div class="note"><b>Note:</b> “max. occurs” is the {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
@@ -319,11 +327,12 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
      *
      * @see AbstractAttribute#getValue()
      */
-    public abstract Object getPropertyValue(final String name) throws IllegalArgumentException;
+    @Override
+    public abstract Object getPropertyValue(final String name) throws PropertyNotFoundException;
 
     /**
      * Sets the value for the property of the given name.
@@ -336,12 +345,13 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  name   the attribute name.
      * @param  value  the new value for the given attribute (may be {@code null}).
-     * @throws IllegalArgumentException if the given name is not an attribute or association name of this feature.
+     * @throws PropertyNotFoundException if the given name is not an attribute or association name of this feature.
      * @throws ClassCastException if the value is not assignable to the expected value class.
-     * @throws IllegalArgumentException if the given value is not valid for a reason other than its type.
+     * @throws InvalidPropertyValueException if the given value is not valid for a reason other than its type.
      *
      * @see AbstractAttribute#setValue(Object)
      */
+    @Override
     public abstract void setPropertyValue(final String name, final Object value) throws IllegalArgumentException;
 
     /**
@@ -364,21 +374,21 @@ public abstract class AbstractFeature implements Serializable {
      * }
      *
      * @param  name  the name of the operation to execute. The caller is responsible to ensure that the
-     *               property type for that name is an instance of {@link AbstractOperation}.
+     *               property type for that name is an instance of {@link Operation}.
      * @return the result value of the given operation, or {@code null} if none.
      *
      * @since 0.8
      */
     protected Object getOperationValue(final String name) {
-        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
+        final Operation operation = (Operation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             return getPropertyValue(((LinkOperation) operation).referentName);
         }
-        final Object result = operation.apply(this, null);
-        if (result instanceof AbstractAttribute<?>) {
-            return getAttributeValue((AbstractAttribute<?>) result);
-        } else if (result instanceof AbstractAssociation) {
-            return getAssociationValue((AbstractAssociation) result);
+        final Property result = operation.apply(this, null);
+        if (result instanceof Attribute<?>) {
+            return getAttributeValue((Attribute<?>) result);
+        } else if (result instanceof FeatureAssociation) {
+            return getAssociationValue((FeatureAssociation) result);
         } else {
             return null;
         }
@@ -391,20 +401,20 @@ public abstract class AbstractFeature implements Serializable {
      * but the {@linkplain FeatureOperations#link link} operation for instance does.
      *
      * @param  name   the name of the operation to execute. The caller is responsible to ensure that the
-     *                property type for that name is an instance of {@link AbstractOperation}.
+     *                property type for that name is an instance of {@link Operation}.
      * @param  value  the value to assign to the result of the named operation.
      * @throws IllegalStateException if the operation of the given name does not accept assignment.
      *
      * @since 0.8
      */
     protected void setOperationValue(final String name, final Object value) {
-        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
+        final Operation operation = (Operation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             setPropertyValue(((LinkOperation) operation).referentName, value);
         } else {
-            final Object result = operation.apply(this, null);
-            if (result instanceof Property) {
-                setPropertyValue((Property) result, value);
+            final Property result = operation.apply(this, null);
+            if (result != null) {
+                setPropertyValue(result, value);
             } else {
                 throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetPropertyValue_1, name));
             }
@@ -415,7 +425,7 @@ public abstract class AbstractFeature implements Serializable {
      * Returns the value of the given attribute, as a singleton or as a collection depending
      * on the maximum number of occurrences.
      */
-    static Object getAttributeValue(final AbstractAttribute<?> property) {
+    static Object getAttributeValue(final Attribute<?> property) {
         return Field.isSingleton(property.getType().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -423,7 +433,7 @@ public abstract class AbstractFeature implements Serializable {
      * Returns the value of the given association, as a singleton or as a collection depending
      * on the maximum number of occurrences.
      */
-    static Object getAssociationValue(final AbstractAssociation property) {
+    static Object getAssociationValue(final FeatureAssociation property) {
         return Field.isSingleton(property.getRole().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -431,10 +441,10 @@ public abstract class AbstractFeature implements Serializable {
      * Sets the value of the given property, with some minimal checks.
      */
     static void setPropertyValue(final Property property, final Object value) {
-        if (property instanceof AbstractAttribute<?>) {
-            setAttributeValue((AbstractAttribute<?>) property, value);
-        } else if (property instanceof AbstractAssociation) {
-            setAssociationValue((AbstractAssociation) property, value);
+        if (property instanceof Attribute<?>) {
+            setAttributeValue((Attribute<?>) property, value);
+        } else if (property instanceof FeatureAssociation) {
+            setAssociationValue((FeatureAssociation) property, value);
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
         }
@@ -446,9 +456,9 @@ public abstract class AbstractFeature implements Serializable {
      * use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static <V> void setAttributeValue(final AbstractAttribute<V> attribute, final Object value) {
+    private static <V> void setAttributeValue(final Attribute<V> attribute, final Object value) {
         if (value != null) {
-            final DefaultAttributeType<V> pt = attribute.getType();
+            final AttributeType<V> pt = attribute.getType();
             final Class<?> base = pt.getValueClass();
             if (!base.isInstance(value)) {
                 Object element = value;
@@ -459,7 +469,7 @@ public abstract class AbstractFeature implements Serializable {
                      */
                     final Iterator<?> it = ((Collection<?>) value).iterator();
                     do if (!it.hasNext()) {
-                        ((AbstractAttribute) attribute).setValues((Collection) value);
+                        ((Attribute) attribute).setValues((Collection) value);
                         return;
                     } while ((element = it.next()) == null || base.isInstance(element));
                     // Found an illegal value. Exeption is thrown below.
@@ -467,7 +477,7 @@ public abstract class AbstractFeature implements Serializable {
                 throw new ClassCastException(illegalValueClass(pt, base, element));         // 'element' can not be null here.
             }
         }
-        ((AbstractAttribute) attribute).setValue(value);
+        ((Attribute) attribute).setValue(value);
     }
 
     /**
@@ -475,24 +485,24 @@ public abstract class AbstractFeature implements Serializable {
      * For a more exhaustive validation, use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static void setAssociationValue(final AbstractAssociation association, final Object value) {
+    private static void setAssociationValue(final FeatureAssociation association, final Object value) {
         if (value != null) {
-            final DefaultAssociationRole role = association.getRole();
-            final DefaultFeatureType base = role.getValueType();
-            if (value instanceof AbstractFeature) {
-                final DefaultFeatureType actual = ((AbstractFeature) value).getType();
+            final FeatureAssociationRole role = association.getRole();
+            final FeatureType base = role.getValueType();
+            if (value instanceof Feature) {
+                final FeatureType actual = ((Feature) value).getType();
                 if (base != actual && !DefaultFeatureType.maybeAssignableFrom(base, actual)) {
-                    throw new IllegalArgumentException(illegalFeatureType(role, base, actual));
+                    throw new InvalidPropertyValueException(illegalFeatureType(role, base, actual));
                 }
             } else if (value instanceof Collection<?>) {
                 verifyAssociationValues(role, (Collection<?>) value);
-                association.setValues((Collection<? extends AbstractFeature>) value);
+                association.setValues((Collection<? extends Feature>) value);
                 return;                                 // Skip the setter at the end of this method.
             } else {
-                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
+                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
             }
         }
-        association.setValue((AbstractFeature) value);
+        association.setValue((Feature) value);
     }
 
     /**
@@ -510,7 +520,7 @@ public abstract class AbstractFeature implements Serializable {
             if (value == null) {
                 return true;
             }
-            if (previous.getClass() == value.getClass() && !(value instanceof AbstractFeature)) {
+            if (previous.getClass() == value.getClass() && !(value instanceof Feature)) {
                 return true;
             }
         }
@@ -524,19 +534,19 @@ public abstract class AbstractFeature implements Serializable {
      * @param  property  the property to verify.
      */
     final void verifyPropertyType(final String name, final Property property) {
-        final AbstractIdentifiedType pt, base = type.getProperty(name);
-        if (property instanceof AbstractAttribute<?>) {
-            pt = ((AbstractAttribute<?>) property).getType();
-        } else if (property instanceof AbstractAssociation) {
-            pt = ((AbstractAssociation) property).getRole();
+        final PropertyType pt, base = type.getProperty(name);
+        if (property instanceof Attribute<?>) {
+            pt = ((Attribute<?>) property).getType();
+        } else if (property instanceof FeatureAssociation) {
+            pt = ((FeatureAssociation) property).getRole();
         } else {
-            throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
+            throw new InvalidPropertyValueException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
         }
         if (pt != base) {
             if (base == null) {
-                throw new IllegalArgumentException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
+                throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
             } else {
-                throw new IllegalArgumentException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
+                throw new InvalidPropertyValueException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
             }
         }
     }
@@ -546,14 +556,14 @@ public abstract class AbstractFeature implements Serializable {
      * The returned value is usually the same than the given one, except in the case of collections.
      */
     final Object verifyPropertyValue(final String name, final Object value) {
-        final AbstractIdentifiedType pt = type.getProperty(name);
-        if (pt instanceof DefaultAttributeType<?>) {
+        final PropertyType pt = type.getProperty(name);
+        if (pt instanceof AttributeType<?>) {
             if (value != null) {
-                return verifyAttributeValue((DefaultAttributeType<?>) pt, value);
+                return verifyAttributeValue((AttributeType<?>) pt, value);
             }
-        } else if (pt instanceof DefaultAssociationRole) {
+        } else if (pt instanceof FeatureAssociationRole) {
             if (value != null) {
-                return verifyAssociationValue((DefaultAssociationRole) pt, value);
+                return verifyAssociationValue((FeatureAssociationRole) pt, value);
             }
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -571,7 +581,7 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) {
+    private static <T> Object verifyAttributeValue(final AttributeType<T> type, final Object value) {
         final Class<T> valueClass = type.getValueClass();
         final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs());
         if (valueClass.isInstance(value)) {
@@ -593,42 +603,42 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static Object verifyAssociationValue(final DefaultAssociationRole role, final Object value) {
+    private static Object verifyAssociationValue(final FeatureAssociationRole role, final Object value) {
         final boolean isSingleton = Field.isSingleton(role.getMaximumOccurs());
-        if (value instanceof AbstractFeature) {
+        if (value instanceof Feature) {
             /*
              * If the user gave us a single value, first verify its validity.
              * Then wrap it in a list of 1 element if this property is multi-valued.
              */
-            final DefaultFeatureType valueType = ((AbstractFeature) value).getType();
-            final DefaultFeatureType base = role.getValueType();
+            final FeatureType valueType = ((Feature) value).getType();
+            final FeatureType base = role.getValueType();
             if (base == valueType || DefaultFeatureType.maybeAssignableFrom(base, valueType)) {
-                return isSingleton ? value : singletonList(AbstractFeature.class, role.getMinimumOccurs(), value);
+                return isSingleton ? value : singletonList(Feature.class, role.getMinimumOccurs(), value);
             } else {
-                throw new IllegalArgumentException(illegalFeatureType(role, base, valueType));
+                throw new InvalidPropertyValueException(illegalFeatureType(role, base, valueType));
             }
         } else if (!isSingleton && value instanceof Collection<?>) {
             verifyAssociationValues(role, (Collection<?>) value);
-            return CheckedArrayList.castOrCopy((Collection<?>) value, AbstractFeature.class);
+            return CheckedArrayList.castOrCopy((Collection<?>) value, Feature.class);
         } else {
-            throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
+            throw new ClassCastException(illegalValueClass(role, Feature.class, value));
         }
     }
 
     /**
      * Verifies if all values in the given collection are valid instances of feature for the given association role.
      */
-    private static void verifyAssociationValues(final DefaultAssociationRole role, final Collection<?> values) {
-        final DefaultFeatureType base = role.getValueType();
+    private static void verifyAssociationValues(final FeatureAssociationRole role, final Collection<?> values) {
+        final FeatureType base = role.getValueType();
         int index = 0;
         for (final Object value : values) {
             ArgumentChecks.ensureNonNullElement("values", index, value);
-            if (!(value instanceof AbstractFeature)) {
-                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
+            if (!(value instanceof Feature)) {
+                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
             }
-            final DefaultFeatureType type = ((AbstractFeature) value).getType();
+            final FeatureType type = ((Feature) value).getType();
             if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-                throw new IllegalArgumentException(illegalFeatureType(role, base, type));
+                throw new InvalidPropertyValueException(illegalFeatureType(role, base, type));
             }
             index++;
         }
@@ -654,7 +664,7 @@ public abstract class AbstractFeature implements Serializable {
      */
     static String propertyNotFound(final FeatureType type, final Object feature, final String property) {
         GenericName ambiguous = null;
-        for (final AbstractIdentifiedType p : type.getProperties(true)) {
+        for (final IdentifiedType p : type.getProperties(true)) {
             final GenericName next = p.getName();
             GenericName name = next;
             do {
@@ -672,7 +682,7 @@ public abstract class AbstractFeature implements Serializable {
 
     /**
      * Returns the exception message for a property type which is neither an attribute or an association.
-     * This method is invoked after a {@code PropertyType} has been found for the user-supplied name,
+     * This method is invoked after a {@link PropertyType} has been found for the user-supplied name,
      * but that property can not be stored in or extracted from a {@link Property} instance.
      */
     static String unsupportedPropertyType(final GenericName name) {
@@ -684,7 +694,7 @@ public abstract class AbstractFeature implements Serializable {
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static String illegalValueClass(final AbstractIdentifiedType property, final Class<?> expected, final Object value) {
+    private static String illegalValueClass(final IdentifiedType property, final Class<?> expected, final Object value) {
         return Resources.format(Resources.Keys.IllegalPropertyValueClass_3,
                                 property.getName(), expected, value.getClass());
     }
@@ -693,7 +703,7 @@ public abstract class AbstractFeature implements Serializable {
      * Returns the exception message for an association value of wrong type.
      */
     private static String illegalFeatureType(
-            final DefaultAssociationRole association, final FeatureType expected, final FeatureType actual)
+            final FeatureAssociationRole association, final FeatureType expected, final FeatureType actual)
     {
         return Resources.format(Resources.Keys.IllegalFeatureType_3,
                                 association.getName(), expected.getName(), actual.getName());
@@ -789,7 +799,7 @@ public abstract class AbstractFeature implements Serializable {
     @Override
     public int hashCode() {
         int code = type.hashCode() * 37;
-        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+        for (final PropertyType pt : type.getProperties(true)) {
             final String name = pt.getName().toString();
             if (name != null) {                                             // Paranoiac check.
                 final Object value = getPropertyValue(name);
@@ -833,7 +843,7 @@ public abstract class AbstractFeature implements Serializable {
             if (!type.equals(that.type)) {
                 return false;
             }
-            for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+            for (final PropertyType pt : type.getProperties(true)) {
                 final String name = pt.getName().toString();
                 if (!Objects.equals(getPropertyValue(name), that.getPropertyValue(name))) {
                     return false;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
index 6e8798f..35581f1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
@@ -30,21 +30,19 @@ import org.apache.sis.util.iso.Types;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
+// Branch-dependent imports
+import org.opengis.feature.IdentifiedType;
+
 
 /**
  * Identification and description information inherited by property types and feature types.
  *
- * <div class="warning"><b>Warning:</b>
- * This class is expected to implement a GeoAPI {@code IdentifiedType} interface in a future version.
- * When such interface will be available, most references to {@code AbstractIdentifiedType} in the API
- * will be replaced by references to the {@code IdentifiedType} interface.</div>
- *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  * @since   0.5
  * @module
  */
-public class AbstractIdentifiedType implements Deprecable, Serializable {
+public class AbstractIdentifiedType implements IdentifiedType, Deprecable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -256,6 +254,7 @@ public class AbstractIdentifiedType implements Deprecable, Serializable {
      *
      * @return the type name.
      */
+    @Override
     public final GenericName getName() {
         return name;
     }
@@ -265,6 +264,7 @@ public class AbstractIdentifiedType implements Deprecable, Serializable {
      *
      * @return concise definition of the element.
      */
+    @Override
     public InternationalString getDefinition() {
         return definition;
     }
@@ -275,6 +275,7 @@ public class AbstractIdentifiedType implements Deprecable, Serializable {
      *
      * @return natural language designator for the element, or {@code null} if none.
      */
+    @Override
     public InternationalString getDesignation() {
         return designation;
     }
@@ -288,6 +289,7 @@ public class AbstractIdentifiedType implements Deprecable, Serializable {
      *
      * @return information beyond that required for concise definition of the element, or {@code null} if none.
      */
+    @Override
     public InternationalString getDescription() {
         return description;
     }
@@ -377,9 +379,7 @@ public class AbstractIdentifiedType implements Deprecable, Serializable {
      * @param  index      index of the characteristics having the given name.
      * @throws IllegalArgumentException if the given name is null or have an empty string representation.
      */
-    static String toString(final GenericName name, final AbstractIdentifiedType container,
-            final String argument, final int index)
-    {
+    static String toString(final GenericName name, final IdentifiedType container, final String argument, final int index) {
         short key = Errors.Keys.MissingValueForProperty_1;
         if (name != null) {
             final String s = name.toString();
diff --git 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
index a5959f1..8c2dcd0 100644
--- 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
@@ -33,6 +33,14 @@ 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;
 
 
 /**
@@ -47,8 +55,8 @@ import org.apache.sis.util.Debug;
  * <div class="note"><b>Example:</b> a mutator operation may raise the height of a dam. This changes
  * may affect other properties like the watercourse and the reservoir associated with the dam.</div>
  *
- * The value is computed, or the operation is executed, by {@code apply(Feature, ParameterValueGroup)}.
- * If the value is modifiable, new value can be set by call to {@code Attribute.setValue(Object)}.
+ * The value is computed, or the operation is executed, by {@link #apply(Feature, ParameterValueGroup)}.
+ * If the value is modifiable, new value can be set by call to {@link Attribute#setValue(Object)}.
  *
  * <div class="warning"><b>Warning:</b> this class is experimental and may change after we gained more
  * experience on this aspect of ISO 19109.</div>
@@ -61,8 +69,8 @@ import org.apache.sis.util.Debug;
  * @since 0.6
  * @module
  */
-public abstract class AbstractOperation extends AbstractIdentifiedType
-        implements BiFunction<AbstractFeature, ParameterValueGroup, Object>
+public abstract class AbstractOperation extends AbstractIdentifiedType implements Operation,
+        BiFunction<Feature, ParameterValueGroup, Property>
 {
     /**
      * For cross-version compatibility.
@@ -150,17 +158,16 @@ public abstract class AbstractOperation extends AbstractIdentifiedType
      *
      * @return description of the input parameters.
      */
+    @Override
     public abstract ParameterDescriptorGroup getParameters();
 
     /**
      * Returns the expected result type, or {@code null} if none.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.IdentifiedType}. This change is pending GeoAPI revision.</div>
-     *
      * @return the type of the result, or {@code null} if none.
      */
-    public abstract AbstractIdentifiedType getResult();
+    @Override
+    public abstract IdentifiedType getResult();
 
     /**
      * Executes the operation on the specified feature with the specified parameters.
@@ -169,11 +176,11 @@ public abstract class AbstractOperation extends AbstractIdentifiedType
      * <ul>
      *   <li>If {@code getResult()} returns {@code null},
      *       then this method should return {@code null}.</li>
-     *   <li>If {@code getResult()} returns an instance of {@code AttributeType},
-     *       then this method shall return an instance of {@code Attribute}
+     *   <li>If {@code getResult()} returns an instance of {@link AttributeType},
+     *       then this method shall return an instance of {@link Attribute}
      *       and the {@code Attribute.getType() == getResult()} relation should hold.</li>
-     *   <li>If {@code getResult()} returns an instance of {@code FeatureAssociationRole},
-     *       then this method shall return an instance of {@code FeatureAssociation}
+     *   <li>If {@code getResult()} returns an instance of {@link org.opengis.feature.FeatureAssociationRole},
+     *       then this method shall return an instance of {@link FeatureAssociation}
      *       and the {@code FeatureAssociation.getRole() == getResult()} relation should hold.</li>
      * </ul>
      *
@@ -183,18 +190,15 @@ public abstract class AbstractOperation extends AbstractIdentifiedType
      * in the Java language, and may be {@code null} if the operation does not need a feature instance
      * (like static methods in the Java language).</div>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the parameter type and return value may
-     * be changed to {@code org.opengis.feature.Feature} and {@code org.opengis.feature.Property} respectively.
-     * This change is pending GeoAPI revision.</div>
-     *
      * @param  feature     the feature on which to execute the operation.
      *                     Can be {@code null} if the operation does not need feature instance.
      * @param  parameters  the parameters to use for executing the operation.
      *                     Can be {@code null} if the operation does not take any parameters.
      * @return the operation result, or {@code null} if this operation does not produce any result.
+     * @throws FeatureOperationException if the operation execution can not complete.
      */
     @Override
-    public abstract Object apply(AbstractFeature feature, ParameterValueGroup parameters);
+    public abstract Property apply(Feature feature, ParameterValueGroup parameters) throws FeatureOperationException;
 
     /**
      * Returns the names of feature properties that this operation needs for performing its task.
@@ -266,11 +270,11 @@ public abstract class AbstractOperation extends AbstractIdentifiedType
         if (name != null) {
             buffer.append('”');
         }
-        final AbstractIdentifiedType result = getResult();
+        final IdentifiedType result = getResult();
         if (result != null) {
             final Object type;
-            if (result instanceof DefaultAttributeType<?>) {
-                type = Classes.getShortName(((DefaultAttributeType<?>) result).getValueClass());
+            if (result instanceof AttributeType<?>) {
+                type = Classes.getShortName(((AttributeType<?>) result).getValueClass());
             } else {
                 type = result.getName();
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java
index a97ac0e..529bed1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AssociationView.java
@@ -20,7 +20,9 @@ import java.util.Collection;
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
-import java.util.Objects;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.FeatureAssociationRole;
 
 
 /**
@@ -37,30 +39,23 @@ import java.util.Objects;
  * @since   0.8
  * @module
  */
-class AssociationView extends AbstractAssociation {
+class AssociationView extends PropertyView<Feature> implements FeatureAssociation {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = -148100967531766909L;
 
     /**
-     * The feature from which to read and where to write the attribute or association value.
+     * The role of this association. Must be one of the properties listed in the {@link #feature}.
      */
-    final AbstractFeature feature;
-
-    /**
-     * The string representation of the property name. This is the value to be given in calls to
-     * {@code Feature.getPropertyValue(String)} and {@code Feature.setPropertyValue(String, Object)}.
-     */
-    final String name;
+    private final FeatureAssociationRole role;
 
     /**
      * Creates a new association which will delegate its work to the given feature.
      */
-    private AssociationView(final AbstractFeature feature, final DefaultAssociationRole role) {
-        super(role);
-        this.feature = feature;
-        this.name = role.getName().toString();
+    private AssociationView(final Feature feature, final FeatureAssociationRole role) {
+        super(feature, role.getName().toString());
+        this.role = role;
     }
 
     /**
@@ -70,7 +65,7 @@ class AssociationView extends AbstractAssociation {
      * @param role     the role of this association. Must be one of the properties listed in the
      *                 {@link #feature} (this is not verified by this constructor).
      */
-    static AbstractAssociation create(final AbstractFeature feature, final DefaultAssociationRole role) {
+    static FeatureAssociation create(final Feature feature, final FeatureAssociationRole role) {
         if (isSingleton(role.getMaximumOccurs())) {
             return new Singleton(feature, role);
         } else {
@@ -86,24 +81,20 @@ class AssociationView extends AbstractAssociation {
         return role.getName();
     }
 
+    /**
+     * Returns the role specified at construction time.
+     */
     @Override
-    public AbstractFeature getValue() {
-        return (AbstractFeature) PropertyView.getValue(feature, name);
-    }
-
-    @Override
-    public void setValue(final AbstractFeature value) {
-        PropertyView.setValue(feature, name, value);
-    }
-
-    @Override
-    public Collection<AbstractFeature> getValues() {
-        return PropertyView.getValues(feature, name, AbstractFeature.class);
+    public final FeatureAssociationRole getRole() {
+        return role;
     }
 
+    /**
+     * Returns the class of values.
+     */
     @Override
-    public final void setValues(final Collection<? extends AbstractFeature> values) {
-        PropertyView.setValues(feature, name, values);
+    final Class<Feature> getValueClass() {
+        return Feature.class;
     }
 
     /**
@@ -120,7 +111,7 @@ class AssociationView extends AbstractAssociation {
         /**
          * Creates a new association which will delegate its work to the given feature.
          */
-        Singleton(final AbstractFeature feature, final DefaultAssociationRole role) {
+        Singleton(final Feature feature, final FeatureAssociationRole role) {
             super(feature, role);
         }
 
@@ -128,17 +119,17 @@ class AssociationView extends AbstractAssociation {
          * Returns the single value, or {@code null} if none.
          */
         @Override
-        public AbstractFeature getValue() {
-            return (AbstractFeature) this.feature.getPropertyValue(this.name);
+        public Feature getValue() {
+            return (Feature) this.feature.getPropertyValue(this.name);
         }
 
         /**
          * Sets the value of this association. This method assumes that the
-         * {@code Feature.setPropertyValue(String, Object)} implementation
+         * {@link Feature#setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
-        public void setValue(final AbstractFeature value) {
+        public void setValue(final Feature value) {
             this.feature.setPropertyValue(this.name, value);
         }
 
@@ -146,30 +137,8 @@ class AssociationView extends AbstractAssociation {
          * Wraps the property value in a set.
          */
         @Override
-        public Collection<AbstractFeature> getValues() {
-            return PropertyView.singletonOrEmpty(getValue());
-        }
-    }
-
-    @Override
-    public final int hashCode() {
-        return PropertyView.hashCode(feature, name);
-    }
-
-    @Override
-    public final boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj != null && obj.getClass() == getClass()) {
-            final AssociationView that = (AssociationView) obj;
-            return feature == that.feature && Objects.equals(name, that.name);
+        public Collection<Feature> getValues() {
+            return singletonOrEmpty(getValue());
         }
-        return false;
-    }
-
-    @Override
-    public final String toString() {
-        return PropertyView.toString(getClass(), AbstractFeature.class, getName(), getValues());
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java b/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java
index c93e657..613c30e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/AttributeView.java
@@ -22,7 +22,9 @@ import java.util.Collections;
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
-import java.util.Objects;
+import org.opengis.feature.Feature;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
 
 
 /**
@@ -39,30 +41,23 @@ import java.util.Objects;
  * @since   0.8
  * @module
  */
-class AttributeView<V> extends AbstractAttribute<V> {
+class AttributeView<V> extends PropertyView<V> implements Attribute<V> {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 3617999929561826634L;
 
     /**
-     * The feature from which to read and where to write the attribute or association value.
+     * The type of this attribute. Must be one of the properties listed in the {@link #feature}.
      */
-    final AbstractFeature feature;
-
-    /**
-     * The string representation of the property name. This is the value to be given in calls to
-     * {@code Feature.getPropertyValue(String)} and {@code Feature.setPropertyValue(String, Object)}.
-     */
-    final String name;
+    final AttributeType<V> type;
 
     /**
      * Creates a new attribute which will delegate its work to the given feature.
      */
-    private AttributeView(final AbstractFeature feature, final DefaultAttributeType<V> type) {
-        super(type);
-        this.feature = feature;
-        this.name = type.getName().toString();
+    private AttributeView(final Feature feature, final AttributeType<V> type) {
+        super(feature, type.getName().toString());
+        this.type = type;
     }
 
     /**
@@ -72,7 +67,7 @@ class AttributeView<V> extends AbstractAttribute<V> {
      * @param type     the type of this attribute. Must be one of the properties listed in the
      *                 {@link #feature} (this is not verified by this constructor).
      */
-    static <V> AbstractAttribute<V> create(final AbstractFeature feature, final DefaultAttributeType<V> type) {
+    static <V> Attribute<V> create(final Feature feature, final AttributeType<V> type) {
         if (isSingleton(type.getMaximumOccurs())) {
             return new Singleton<>(feature, type);
         } else {
@@ -88,31 +83,27 @@ class AttributeView<V> extends AbstractAttribute<V> {
         return type.getName();
     }
 
+    /**
+     * Returns the type specified at construction time.
+     */
     @Override
-    public V getValue() {
-        return type.getValueClass().cast(PropertyView.getValue(feature, name));
-    }
-
-    @Override
-    public void setValue(final V value) {
-        PropertyView.setValue(feature, name, value);
-    }
-
-    @Override
-    public Collection<V> getValues() {
-        return PropertyView.getValues(feature, name, type.getValueClass());
+    public final AttributeType<V> getType() {
+        return type;
     }
 
+    /**
+     * Returns the class of values.
+     */
     @Override
-    public final void setValues(final Collection<? extends V> values) {
-        PropertyView.setValues(feature, name, values);
+    final Class<V> getValueClass() {
+        return type.getValueClass();
     }
 
     /**
      * Returns an empty map since this simple view does not support characteristics.
      */
     @Override
-    public final Map<String,AbstractAttribute<?>> characteristics() {
+    public final Map<String,Attribute<?>> characteristics() {
         return Collections.emptyMap();
     }
 
@@ -130,7 +121,7 @@ class AttributeView<V> extends AbstractAttribute<V> {
         /**
          * Creates a new attribute which will delegate its work to the given feature.
          */
-        Singleton(final AbstractFeature feature, final DefaultAttributeType<V> type) {
+        Singleton(final Feature feature, final AttributeType<V> type) {
             super(feature, type);
         }
 
@@ -144,7 +135,7 @@ class AttributeView<V> extends AbstractAttribute<V> {
 
         /**
          * Sets the value of this attribute. This method assumes that the
-         * {@code Feature.setPropertyValue(String, Object)} implementation
+         * {@link Feature#setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
@@ -157,29 +148,7 @@ class AttributeView<V> extends AbstractAttribute<V> {
          */
         @Override
         public Collection<V> getValues() {
-            return PropertyView.singletonOrEmpty(getValue());
-        }
-    }
-
-    @Override
-    public final int hashCode() {
-        return PropertyView.hashCode(feature, name);
-    }
-
-    @Override
-    public final boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj != null && obj.getClass() == getClass()) {
-            final AttributeView<?> that = (AttributeView<?>) obj;
-            return feature == that.feature && Objects.equals(name, that.name);
+            return singletonOrEmpty(getValue());
         }
-        return false;
-    }
-
-    @Override
-    public final String toString() {
-        return PropertyView.toString(getClass(), type.getValueClass(), getName(), getValues());
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
index bae01db..adddf98 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
@@ -24,6 +24,12 @@ import org.apache.sis.internal.util.AbstractMap;
 import org.apache.sis.internal.util.AbstractMapEntry;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.PropertyNotFoundException;
+
 
 /**
  * Implementation of {@link AbstractAttribute#characteristics()} map.
@@ -34,16 +40,16 @@ import org.apache.sis.internal.feature.Resources;
  * @since   0.5
  * @module
  */
-final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> implements Cloneable {
+final class CharacteristicMap extends AbstractMap<String,Attribute<?>> implements Cloneable {
     /**
      * The attribute source for which to provide characteristics.
      */
-    private final AbstractAttribute<?> source;
+    private final Attribute<?> source;
 
     /**
      * Characteristics of the {@code source} attribute, created when first needed.
      */
-    AbstractAttribute<?>[] characterizedBy;
+    Attribute<?>[] characterizedBy;
 
     /**
      * Description of the attribute characteristics.
@@ -56,7 +62,7 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * @param  source  the attribute which is characterized by {@code characterizedBy}.
      * @param  types   description of the characteristics of {@code source}.
      */
-    CharacteristicMap(final AbstractAttribute<?> source, final CharacteristicTypeMap types) {
+    CharacteristicMap(final Attribute<?> source, final CharacteristicTypeMap types) {
         this.source = source;
         this.types  = types;
     }
@@ -69,14 +75,14 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
     @Override
     public CharacteristicMap clone() throws CloneNotSupportedException {
         final CharacteristicMap clone = (CharacteristicMap) super.clone();
-        AbstractAttribute<?>[] c = clone.characterizedBy;
+        Attribute<?>[] c = clone.characterizedBy;
         if (c != null) {
             clone.characterizedBy = c = c.clone();
             final Cloner cloner = new Cloner();
             for (int i=0; i<c.length; i++) {
-                final AbstractAttribute<?> attribute = c[i];
+                final Attribute<?> attribute = c[i];
                 if (attribute instanceof Cloneable) {
-                    c[i] = (AbstractAttribute<?>) cloner.clone(attribute);
+                    c[i] = (Attribute<?>) cloner.clone(attribute);
                 }
             }
         }
@@ -102,7 +108,7 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
     @Override
     public boolean isEmpty() {
         if (characterizedBy != null) {
-            for (final AbstractAttribute<?> attribute : characterizedBy) {
+            for (final Attribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     return false;
                 }
@@ -118,7 +124,7 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
     public int size() {
         int n = 0;
         if (characterizedBy != null) {
-            for (final AbstractAttribute<?> attribute : characterizedBy) {
+            for (final Attribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     n++;
                 }
@@ -131,7 +137,7 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public AbstractAttribute<?> get(final Object key) {
+    public Attribute<?> get(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
@@ -145,11 +151,11 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * Removes the attribute characteristic for the given name.
      */
     @Override
-    public AbstractAttribute<?> remove(final Object key) {
+    public Attribute<?> remove(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
-                final AbstractAttribute<?> previous = characterizedBy[index];
+                final Attribute<?> previous = characterizedBy[index];
                 characterizedBy[index] = null;
                 return previous;
             }
@@ -162,13 +168,13 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      *
      * @param  key  the name for which to get the characteristic index.
      * @return the index for the characteristic of the given name.
-     * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
+     * @throws PropertyNotFoundException if the given key is not the name of a characteristic in this map.
      */
     private int indexOf(final String key) {
         ArgumentChecks.ensureNonNull("key", key);
         final Integer index = types.indices.get(key);
         if (index == null) {
-            throw new IllegalArgumentException(Resources.format(
+            throw new PropertyNotFoundException(Resources.format(
                     Resources.Keys.CharacteristicsNotFound_2, source.getName(), key));
         }
         return index;
@@ -182,12 +188,12 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * @param  index  index of the expected attribute type.
      * @param  type   the actual attribute type.
      */
-    final void verifyAttributeType(final int index, final DefaultAttributeType<?> type) {
-        final DefaultAttributeType<?> expected = types.characterizedBy[index];
+    final void verifyAttributeType(final int index, final AttributeType<?> type) {
+        final AttributeType<?> expected = types.characterizedBy[index];
         if (!expected.equals(type)) {
             final GenericName en = expected.getName();
             final GenericName an = type.getName();
-            throw new IllegalArgumentException(String.valueOf(en).equals(String.valueOf(an))
+            throw new InvalidPropertyValueException(String.valueOf(en).equals(String.valueOf(an))
                     ? Resources.format(Resources.Keys.MismatchedPropertyType_1, en)
                     : Resources.format(Resources.Keys.CanNotSetCharacteristics_2, en.push(source.getName()), an));
         }
@@ -200,14 +206,14 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
      */
     @Override
-    public AbstractAttribute<?> put(final String key, final AbstractAttribute<?> value) {
+    public Attribute<?> put(final String key, final Attribute<?> value) {
         final int index = indexOf(key);
         ArgumentChecks.ensureNonNull("value", value);
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
+            characterizedBy = new Attribute<?>[types.characterizedBy.length];
         }
-        final AbstractAttribute<?> previous = characterizedBy[index];
+        final Attribute<?> previous = characterizedBy[index];
         characterizedBy[index] = value;
         return previous;
     }
@@ -224,7 +230,7 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
     protected boolean addKey(final String name) {
         final int index = indexOf(name);
         if (characterizedBy == null) {
-            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
+            characterizedBy = new Attribute<?>[types.characterizedBy.length];
         }
         if (characterizedBy[index] == null) {
             characterizedBy[index] = types.characterizedBy[index].newInstance();
@@ -242,14 +248,14 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * @throws IllegalStateException if another characteristic already exists for the characteristic name.
      */
     @Override
-    protected boolean addValue(final AbstractAttribute<?> value) {
+    protected boolean addValue(final Attribute<?> value) {
         ArgumentChecks.ensureNonNull("value", value);
         final int index = indexOf(value.getName().toString());
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
+            characterizedBy = new Attribute<?>[types.characterizedBy.length];
         }
-        final AbstractAttribute<?> previous = characterizedBy[index];
+        final Attribute<?> previous = characterizedBy[index];
         if (previous == null) {
             characterizedBy[index] = value;
             return true;
@@ -265,16 +271,16 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
      * Returns an iterator over the entries.
      */
     @Override
-    protected EntryIterator<String, AbstractAttribute<?>> entryIterator() {
+    protected EntryIterator<String, Attribute<?>> entryIterator() {
         if (characterizedBy == null) {
             return null;
         }
-        return new EntryIterator<String, AbstractAttribute<?>>() {
+        return new EntryIterator<String, Attribute<?>>() {
             /** Index of the current element to return in the iteration. */
             private int index = -1;
 
             /** The element to return, or {@code null} if we reached the end of iteration. */
-            private AbstractAttribute<?> value;
+            private Attribute<?> value;
 
             /** Returns {@code true} if there is more entries in the iteration. */
             @Override protected boolean next() {
@@ -292,12 +298,12 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
             }
 
             /** Returns the attribute characteristic (never {@code null}). */
-            @Override protected AbstractAttribute<?> getValue() {
+            @Override protected Attribute<?> getValue() {
                 return value;
             }
 
             /** Creates and return the next entry. */
-            @Override protected Map.Entry<String, AbstractAttribute<?>> getEntry() {
+            @Override protected Map.Entry<String, Attribute<?>> getEntry() {
                 return new Entry(index, value);
             }
 
@@ -311,17 +317,17 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
     /**
      * An entry returned by the {@link CharacteristicMap#entrySet()} iterator.
      * The key and value are never null, even in case of concurrent modification.
-     * This entry supports the {@code setValue(Attribute)} operation.
+     * This entry supports the {@link #setValue(Attribute)} operation.
      */
-    private final class Entry extends AbstractMapEntry<String, AbstractAttribute<?>> {
+    private final class Entry extends AbstractMapEntry<String, Attribute<?>> {
         /** Index of the attribute characteristics represented by this entry. */
         private final int index;
 
         /** The current attribute value, which is guaranteed to be non-null. */
-        private AbstractAttribute<?> value;
+        private Attribute<?> value;
 
         /** Creates a new entry for the characteristic at the given index. */
-        Entry(final int index, final AbstractAttribute<?> value) {
+        Entry(final int index, final Attribute<?> value) {
             this.index = index;
             this.value = value;
         }
@@ -332,15 +338,15 @@ final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> i
         }
 
         /** Returns the attribute characteristic (never {@code null}). */
-        @Override public AbstractAttribute<?> getValue() {
+        @Override public Attribute<?> getValue() {
             return value;
         }
 
         /** Sets the attribute characteristic. */
-        @Override public AbstractAttribute<?> setValue(final AbstractAttribute<?> value) {
+        @Override public Attribute<?> setValue(final Attribute<?> value) {
             ArgumentChecks.ensureNonNull("value", value);
             verifyAttributeType(index, value.getType());
-            final AbstractAttribute<?> previous = this.value;
+            final Attribute<?> previous = this.value;
             characterizedBy[index] = value;
             this.value = value;
             return previous;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
index 4e8f895..fda0a6d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicTypeMap.java
@@ -28,6 +28,9 @@ import org.apache.sis.util.resources.Errors;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
 
+// Branch-dependent imports
+import org.opengis.feature.AttributeType;
+
 
 /**
  * Implementation of the map returned by {@link DefaultAttributeType#characteristics()}.
@@ -37,7 +40,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
  * The straightforward approach would be to store the attributes directly as values in a standard {@code HashMap}.
  * But instead of that, we store attributes in an array and the array indices in a {@code HashMap}. This level of
  * indirection is useless if we consider only the {@link DefaultAttributeType#characteristics()} method, since a
- * standard {@code HashMap<String,DefaultAttributeType>} would work as well or better. However this level of indirection
+ * standard {@code HashMap<String,AttributeType>} would work as well or better. However this level of indirection
  * become useful for {@link CharacteristicMap} (the map returned by {@link AbstractAttribute#characteristics()}),
  * since it allows a more efficient storage. We do this effort because some applications may create a very large
  * amount of attribute instances.
@@ -47,17 +50,17 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
  * @since   0.5
  * @module
  */
-final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeType<?>> {
+final class CharacteristicTypeMap extends AbstractMap<String,AttributeType<?>> {
     /**
      * For sharing the same {@code CharacteristicTypeMap} instances among the attribute types
      * having the same characteristics.
      */
     @SuppressWarnings("unchecked")
-    private static final WeakValueHashMap<DefaultAttributeType<?>[],CharacteristicTypeMap> SHARED =
-            new WeakValueHashMap<>((Class) DefaultAttributeType[].class);
+    private static final WeakValueHashMap<AttributeType<?>[],CharacteristicTypeMap> SHARED =
+            new WeakValueHashMap<>((Class) AttributeType[].class);
 
     /*
-     * This class has intentionally no reference to the DefaultAttributeType for which we are providing characteristics.
+     * This class has intentionally no reference to the AttributeType for which we are providing characteristics.
      * This allows us to use the same CharacteristicTypeMap instance for various attribute types having the same
      * characteristic (e.g. many measurements may have an "accuracy" characteristic).
      */
@@ -66,7 +69,7 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
      * Characteristics of an other attribute type (the {@code source} attribute given to the constructor).
      * This array shall not be modified.
      */
-    final DefaultAttributeType<?>[] characterizedBy;
+    final AttributeType<?>[] characterizedBy;
 
     /**
      * The names of attribute types listed in the {@link #characterizedBy} array,
@@ -85,7 +88,7 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
      * @return a map for this given characteristics.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    static CharacteristicTypeMap create(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
+    static CharacteristicTypeMap create(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
         CharacteristicTypeMap map;
         synchronized (SHARED) {
             map = SHARED.get(characterizedBy);
@@ -107,13 +110,13 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
      * @param  characterizedBy  characteristics of {@code source}. Should not be empty.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    private CharacteristicTypeMap(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
+    private CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
         this.characterizedBy = characterizedBy;
         int index = 0;
         final Map<String,Integer> indices = new HashMap<>(Containers.hashMapCapacity(characterizedBy.length));
         final Map<String,Integer> aliases = new HashMap<>();
         for (int i=0; i<characterizedBy.length; i++) {
-            final DefaultAttributeType<?> attribute = characterizedBy[i];
+            final AttributeType<?> attribute = characterizedBy[i];
             ensureNonNullElement("characterizedBy", i, attribute);
             GenericName name = attribute.getName();
             String key = AbstractIdentifiedType.toString(name, source, "characterizedBy", i);
@@ -178,7 +181,7 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
      */
     @Override
     public boolean containsValue(final Object key) {
-        for (final DefaultAttributeType<?> type : characterizedBy) {
+        for (final AttributeType<?> type : characterizedBy) {
             if (type.equals(key)) {
                 return true;
             }
@@ -190,7 +193,7 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public DefaultAttributeType<?> get(final Object key) {
+    public AttributeType<?> get(final Object key) {
         final Integer index = indices.get(key);
         return (index != null) ? characterizedBy[index] : null;
     }
@@ -200,13 +203,13 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
      * This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
      */
     @Override
-    protected EntryIterator<String, DefaultAttributeType<?>> entryIterator() {
-        return new EntryIterator<String, DefaultAttributeType<?>>() {
+    protected EntryIterator<String, AttributeType<?>> entryIterator() {
+        return new EntryIterator<String, AttributeType<?>>() {
             /** Index of the next element to return in the iteration. */
             private int index;
 
             /** Value of current entry. */
-            private DefaultAttributeType<?> value;
+            private AttributeType<?> value;
 
             /**
              * Returns {@code true} if there is more entries in the iteration.
@@ -232,7 +235,7 @@ final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeTyp
              * Returns the attribute characteristic contained in this entry.
              */
             @Override
-            protected DefaultAttributeType<?> getValue() {
+            protected AttributeType<?> getValue() {
                 return value;
             }
         };
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
index 1d033c0..01932ab 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
@@ -30,6 +30,14 @@ import org.apache.sis.util.Debug;
 
 import static org.apache.sis.util.ArgumentChecks.*;
 
+// Branch-dependent imports
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.PropertyNotFoundException;
+
 
 /**
  * Indicates the role played by the association between two features.
@@ -57,7 +65,7 @@ import static org.apache.sis.util.ArgumentChecks.*;
  * @since 0.5
  * @module
  */
-public class DefaultAssociationRole extends FieldType {
+public class DefaultAssociationRole extends FieldType implements FeatureAssociationRole {
     /**
      * For cross-version compatibility.
      */
@@ -74,6 +82,8 @@ public class DefaultAssociationRole extends FieldType {
      * The name of the property to use as a title for the associated feature, or an empty string if none.
      * This field is initially null, then computed when first needed.
      * This information is used only by {@link AbstractAssociation#toString()} implementation.
+     *
+     * @see #getTitleProperty(FeatureAssociationRole)
      */
     private transient volatile String titleProperty;
 
@@ -124,7 +134,7 @@ public class DefaultAssociationRole extends FieldType {
      *
      * @see org.apache.sis.feature.builder.AssociationRoleBuilder
      */
-    public DefaultAssociationRole(final Map<String,?> identification, final DefaultFeatureType valueType,
+    public DefaultAssociationRole(final Map<String,?> identification, final FeatureType valueType,
             final int minimumOccurs, final int maximumOccurs)
     {
         super(identification, minimumOccurs, maximumOccurs);
@@ -215,7 +225,7 @@ public class DefaultAssociationRole extends FieldType {
      *         invoking that method on {@code creating} may cause a failure with user code.
      * @return {@code true} if this association references a resolved feature type after this method call.
      */
-    final boolean resolve(final DefaultFeatureType creating, final Collection<AbstractIdentifiedType> properties) {
+    final boolean resolve(final DefaultFeatureType creating, final Collection<PropertyType> properties) {
         final FeatureType type = valueType;
         if (type instanceof NamedFeatureType) {
             FeatureType resolved = ((NamedFeatureType) type).resolved;
@@ -229,7 +239,7 @@ public class DefaultAssociationRole extends FieldType {
                      * this desired feature in an association of the 'creating' feature, instead than beeing
                      * the 'creating' feature itself. This is a little bit unusual, but not illegal.
                      */
-                    final List<DefaultFeatureType> deferred = new ArrayList<>();
+                    final List<FeatureType> deferred = new ArrayList<>();
                     resolved = search(creating, properties, name, deferred);
                     if (resolved == null) {
                         /*
@@ -264,8 +274,8 @@ public class DefaultAssociationRole extends FieldType {
      * @return the feature of the given name, or {@code null} if none.
      */
     @SuppressWarnings("null")
-    private static DefaultFeatureType search(final DefaultFeatureType feature, Collection<? extends AbstractIdentifiedType> properties,
-            final GenericName name, final List<DefaultFeatureType> deferred)
+    private static FeatureType search(final FeatureType feature, Collection<? extends PropertyType> properties,
+            final GenericName name, final List<FeatureType> deferred)
     {
         /*
          * Search only in associations declared in the given feature, not in inherited associations.
@@ -275,17 +285,21 @@ public class DefaultAssociationRole extends FieldType {
         if (properties == null) {
             properties = feature.getProperties(false);
         }
-        for (final AbstractIdentifiedType property : properties) {
-            if (property instanceof DefaultAssociationRole) {
+        for (final PropertyType property : properties) {
+            if (property instanceof FeatureAssociationRole) {
                 final FeatureType valueType;
-                valueType = ((DefaultAssociationRole) property).valueType;
-                if (valueType instanceof NamedFeatureType) {
-                    continue;                                       // Skip unresolved feature types.
+                if (property instanceof DefaultAssociationRole) {
+                    valueType = ((DefaultAssociationRole) property).valueType;
+                    if (valueType instanceof NamedFeatureType) {
+                        continue;                                   // Skip unresolved feature types.
+                    }
+                } else {
+                    valueType = ((FeatureAssociationRole) property).getValueType();
                 }
                 if (name.equals(valueType.getName())) {
-                    return (DefaultFeatureType) valueType;
+                    return valueType;
                 }
-                deferred.add((DefaultFeatureType) valueType);
+                deferred.add(valueType);
             }
         }
         /*
@@ -294,7 +308,7 @@ public class DefaultAssociationRole extends FieldType {
          * but not necessarily the same feature type (may be a subtype). This is equivalent to
          * "covariant return type" in the Java language.
          */
-        for (DefaultFeatureType type : feature.getSuperTypes()) {
+        for (FeatureType type : feature.getSuperTypes()) {
             if (name.equals(type.getName())) {
                 return type;
             }
@@ -307,7 +321,7 @@ public class DefaultAssociationRole extends FieldType {
     }
 
     /**
-     * Potentially invoked after {@code search(FeatureType, Collection, GenericName, List)} for searching
+     * Potentially invoked after {@link #search(FeatureType, Collection, GenericName, List)} for searching
      * in associations of associations.
      *
      * <p>Current implementation does not check that there is no duplicated names. Even if we did so,
@@ -315,14 +329,14 @@ public class DefaultAssociationRole extends FieldType {
      * later. We rather put a warning in {@link #DefaultAssociationRole(Map, GenericName, int, int)}
      * javadoc.</p>
      *
-     * @param  deferred  the feature types collected by {@code search(FeatureType, Collection, GenericName, List)}.
+     * @param  deferred  the feature types collected by {@link #search(FeatureType, Collection, GenericName, List)}.
      * @param  name      the name of the feature to search.
      * @return the feature of the given name, or {@code null} if none.
      */
-    private static DefaultFeatureType deepSearch(final List<DefaultFeatureType> deferred, final GenericName name) {
+    private static FeatureType deepSearch(final List<FeatureType> deferred, final GenericName name) {
         final Map<FeatureType,Boolean> done = new IdentityHashMap<>(8);
         for (int i=0; i<deferred.size();) {
-            DefaultFeatureType valueType = deferred.get(i++);
+            FeatureType valueType = deferred.get(i++);
             if (done.put(valueType, Boolean.TRUE) == null) {
                 deferred.subList(0, i).clear();                 // Discard previous value for making more room.
                 valueType = search(valueType, null, name, deferred);
@@ -339,20 +353,19 @@ public class DefaultAssociationRole extends FieldType {
      * Returns the type of feature values.
      *
      * <p>This method can not be invoked if {@link #isResolved()} returns {@code false}.
-     * However it is still possible to {@linkplain Features#getValueTypeName
+     * However it is still possible to {@linkplain Features#getValueTypeName(PropertyType)
      * get the associated feature type name}.</p>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
-     *
      * @return the type of feature values.
      * @throws IllegalStateException if the feature type has been specified
      *         {@linkplain #DefaultAssociationRole(Map, GenericName, int, int) only by its name}
      *         and not yet resolved.
      *
      * @see #isResolved()
+     * @see Features#getValueTypeName(PropertyType)
      */
-    public final DefaultFeatureType getValueType() {
+    @Override
+    public final FeatureType getValueType() {
         /*
          * This method shall be final for consistency with other methods in this classes
          * which use the 'valueType' field directly. Furthermore, this method is invoked
@@ -366,16 +379,15 @@ public class DefaultAssociationRole extends FieldType {
             }
             valueType = type;
         }
-        return (DefaultFeatureType) type;
+        return type;
     }
 
     /**
      * Returns the name of the feature type. This information is always available
      * even when the name has not yet been {@linkplain #resolve resolved}.
      */
-    static GenericName getValueTypeName(final DefaultAssociationRole role) {
-        // Method is static for compatibility with branches on GeoAPI snapshots.
-        return role.valueType.getName();
+    static GenericName getValueTypeName(final FeatureAssociationRole role) {
+        return (role instanceof DefaultAssociationRole ? ((DefaultAssociationRole) role).valueType : role.getValueType()).getName();
     }
 
     /**
@@ -393,35 +405,34 @@ public class DefaultAssociationRole extends FieldType {
      *
      * This method should be used only for display purpose, not as a reliable or stable way to get the identifier.
      * The heuristic rules implemented in this method may change in any future Apache SIS version.
-     *
-     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
-     * However this method needs to be static in other SIS branches, because they work with interfaces
-     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    static String getTitleProperty(final DefaultAssociationRole role) {
-        String p = role.titleProperty;       // No synchronization - not a big deal if computed twice.
-        if (p != null) {
-            return p.isEmpty() ? null : p;
+    static String getTitleProperty(final FeatureAssociationRole role) {
+        if (role instanceof DefaultAssociationRole) {
+            String p = ((DefaultAssociationRole) role).titleProperty;       // No synchronization - not a big deal if computed twice.
+            if (p != null) {
+                return p.isEmpty() ? null : p;
+            }
+            p = searchTitleProperty(role.getValueType());
+            ((DefaultAssociationRole) role).titleProperty = (p != null) ? p : "";
+            return p;
         }
-        p = searchTitleProperty(role.getValueType());
-        role.titleProperty = (p != null) ? p : "";
-        return p;
+        return searchTitleProperty(role.getValueType());
     }
 
     /**
-     * Implementation of {@link #getTitleProperty(DefaultAssociationRole)} for first search,
+     * Implementation of {@link #getTitleProperty(FeatureAssociationRole)} for first search,
      * or for non-SIS {@code FeatureAssociationRole} implementations.
      */
-    private static String searchTitleProperty(final DefaultFeatureType ft) {
+    private static String searchTitleProperty(final FeatureType ft) {
         String fallback = null;
         try {
             return ft.getProperty(AttributeConvention.IDENTIFIER).getName().toString();
-        } catch (IllegalArgumentException e) {
+        } catch (PropertyNotFoundException e) {
             // Ignore.
         }
-        for (final AbstractIdentifiedType type : ft.getProperties(true)) {
-            if (type instanceof DefaultAttributeType<?>) {
-                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) type;
+        for (final PropertyType type : ft.getProperties(true)) {
+            if (type instanceof AttributeType<?>) {
+                final AttributeType<?> pt = (AttributeType<?>) type;
                 final Class<?> valueClass = pt.getValueClass();
                 if (CharSequence.class.isAssignableFrom(valueClass) ||
                     GenericName .class.isAssignableFrom(valueClass) ||
@@ -468,9 +479,10 @@ public class DefaultAssociationRole extends FieldType {
      *
      * @return a new association instance.
      *
-     * @see AbstractAssociation#create(DefaultAssociationRole)
+     * @see AbstractAssociation#create(FeatureAssociationRole)
      */
-    public AbstractAssociation newInstance() {
+    @Override
+    public FeatureAssociation newInstance() {
         return AbstractAssociation.create(this);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
index 4a191b2..d369031 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
@@ -31,6 +31,10 @@ import org.apache.sis.internal.util.Numerics;
 
 import static org.apache.sis.util.ArgumentChecks.*;
 
+// Branch-dependent imports
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+
 
 /**
  * Definition of an attribute in a feature type.
@@ -44,11 +48,6 @@ import static org.apache.sis.util.ArgumentChecks.*;
  * Attribute characterization (discussed below) is similar to {@link java.lang.annotation.Annotation}.
  * </div>
  *
- * <div class="warning"><b>Warning:</b>
- * This class is expected to implement a GeoAPI {@code AttributeType} interface in a future version.
- * When such interface will be available, most references to {@code DefaultAttributeType} in current
- * API will be replaced by references to the {@code AttributeType} interface.</div>
- *
  * <div class="section">Value type</div>
  * Attributes can be used for both spatial and non-spatial properties.
  * Some examples are:
@@ -107,7 +106,7 @@ import static org.apache.sis.util.ArgumentChecks.*;
  * @since 0.5
  * @module
  */
-public class DefaultAttributeType<V> extends FieldType {
+public class DefaultAttributeType<V> extends FieldType implements AttributeType<V> {
     /**
      * For cross-version compatibility.
      */
@@ -190,7 +189,7 @@ public class DefaultAttributeType<V> extends FieldType {
     @SuppressWarnings("ThisEscapedInObjectConstruction")    // Okay because used only in package-private class.
     public DefaultAttributeType(final Map<String,?> identification, final Class<V> valueClass,
             final int minimumOccurs, final int maximumOccurs, final V defaultValue,
-            final DefaultAttributeType<?>... characterizedBy)
+            final AttributeType<?>... characterizedBy)
     {
         super(identification, minimumOccurs, maximumOccurs);
         ensureNonNull("valueClass",   valueClass);
@@ -223,7 +222,7 @@ public class DefaultAttributeType<V> extends FieldType {
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final DefaultAttributeType<?>[] characterizedBy = (DefaultAttributeType<?>[]) in.readObject();
+            final AttributeType<?>[] characterizedBy = (AttributeType<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = CharacteristicTypeMap.create(this, characterizedBy);
             }
@@ -238,6 +237,7 @@ public class DefaultAttributeType<V> extends FieldType {
      *
      * @return the type of attribute values.
      */
+    @Override
     public final Class<V> getValueClass() {
         return valueClass;
     }
@@ -289,6 +289,7 @@ public class DefaultAttributeType<V> extends FieldType {
      *
      * @return the default value for the attribute, or {@code null} if none.
      */
+    @Override
     public V getDefaultValue() {
         return defaultValue;
     }
@@ -312,7 +313,8 @@ public class DefaultAttributeType<V> extends FieldType {
      *
      * @see AbstractAttribute#characteristics()
      */
-    public Map<String,DefaultAttributeType<?>> characteristics() {
+    @Override
+    public Map<String,AttributeType<?>> characteristics() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -321,9 +323,10 @@ public class DefaultAttributeType<V> extends FieldType {
      *
      * @return a new attribute instance.
      *
-     * @see AbstractAttribute#create(DefaultAttributeType)
+     * @see AbstractAttribute#create(AttributeType)
      */
-    public AbstractAttribute<V> newInstance() {
+    @Override
+    public Attribute<V> newInstance() {
         return AbstractAttribute.create(this);
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
index d6150da..17cb879 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
@@ -40,6 +40,17 @@ import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.Operation;
+import org.opengis.feature.FeatureInstantiationException;
+import org.opengis.feature.PropertyNotFoundException;
+
 
 /**
  * Abstraction of a real-world phenomena. A {@code FeatureType} instance describes the class of all
@@ -49,11 +60,6 @@ import org.apache.sis.internal.feature.Resources;
  * compared to the Java language, {@code FeatureType} is equivalent to {@link Class} while
  * {@code Feature} instances are equivalent to {@link Object} instances of that class.</div>
  *
- * <div class="warning"><b>Warning:</b>
- * This class is expected to implement a GeoAPI {@code FeatureType} interface in a future version.
- * When such interface will be available, most references to {@code DefaultFeatureType} in the API
- * will be replaced by references to the {@code FeatureType} interface.</div>
- *
  * <div class="section">Naming</div>
  * The feature type {@linkplain #getName() name} is mandatory and should be unique. Those names are the main
  * criterion used for deciding if a feature type {@linkplain #isAssignableFrom is assignable from} another type.
@@ -86,7 +92,7 @@ import org.apache.sis.internal.feature.Resources;
  *
  * <div class="section">Immutability and thread safety</div>
  * Instances of this class are immutable if all properties ({@link GenericName} and {@link InternationalString}
- * instances) and all arguments ({@code AttributeType} instances) given to the constructor are also immutable.
+ * instances) and all arguments ({@link AttributeType} instances) given to the constructor are also immutable.
  * Such immutable instances can be shared by many objects and passed between threads without synchronization.
  *
  * @author  Johann Sorel (Geomatys)
@@ -134,7 +140,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * any unresolved name (i.e. a {@link DefaultAssociationRole#valueType} specified only be the
      * feature type name instead than its actual instance). A value of {@code true} means that all
      * names have been resolved. However a value of {@code false} only means that we are not sure,
-     * and that {@code resolve(FeatureType, Map)} should check again.
+     * and that {@link #resolve(FeatureType, Map)} should check again.
      *
      * <div class="note"><b>Note:</b>
      * Strictly speaking, this field should be declared {@code volatile} since the names could
@@ -151,13 +157,13 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @see #getSuperTypes()
      */
-    private final Set<DefaultFeatureType> superTypes;
+    private final Set<FeatureType> superTypes;
 
     /**
-     * The names of all parents of this feature type, including parents of parents. This is used
-     * for a more efficient implementation of {@link #isAssignableFrom(DefaultFeatureType)}.
+     * The names of all parents of this feature type, including parents of parents.
+     * This is used for a more efficient implementation of {@link #isAssignableFrom(FeatureType)}.
      *
-     * @see #isAssignableFrom(DefaultFeatureType)
+     * @see #isAssignableFrom(FeatureType)
      */
     private transient Set<GenericName> assignableTo;
 
@@ -168,7 +174,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @see #getProperties(boolean)
      */
-    private final List<AbstractIdentifiedType> properties;
+    private final List<PropertyType> properties;
 
     /**
      * All properties, including the ones declared in the super-types.
@@ -176,7 +182,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @see #getProperties(boolean)
      */
-    private transient Collection<AbstractIdentifiedType> allProperties;
+    private transient Collection<PropertyType> allProperties;
 
     /**
      * A lookup table for fetching properties by name, including the properties from super-types.
@@ -184,7 +190,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @see #getProperty(String)
      */
-    private transient Map<String, AbstractIdentifiedType> byName;
+    private transient Map<String, PropertyType> byName;
 
     /**
      * Indices of properties in an array of properties similar to {@link #properties},
@@ -241,12 +247,6 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *   </tr>
      * </table>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the type of array elements may be
-     * changed to {@code org.opengis.feature.FeatureType} and {@code org.opengis.feature.PropertyType}.
-     * This change is pending GeoAPI revision. In the meantime, make sure that the {@code properties}
-     * array contains only attribute types, association roles or operations, <strong>not</strong> other
-     * feature types since the later are not properties in the ISO sense.</div>
-     *
      * @param identification  the name and other information to be given to this feature type.
      * @param isAbstract      if {@code true}, the feature type acts as an abstract super-type.
      * @param superTypes      the parents of this feature type, or {@code null} or empty if none.
@@ -257,7 +257,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
     public DefaultFeatureType(final Map<String,?> identification, final boolean isAbstract,
-            final DefaultFeatureType[] superTypes, final AbstractIdentifiedType... properties)
+            final FeatureType[] superTypes, final PropertyType... properties)
     {
         super(identification);
         ArgumentChecks.ensureNonNull("properties", properties);
@@ -278,9 +278,9 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
          * in case of duplicated values. Opportunistically verify for null values. The same verification could
          * be done in the scanPropertiesFrom(…) method, but doing it here produces a less confusing stacktrace.
          */
-        final List<AbstractIdentifiedType> sourceProperties = new ArrayList<>(properties.length);
+        final List<PropertyType> sourceProperties = new ArrayList<>(properties.length);
         for (int i=0; i<properties.length; i++) {
-            final AbstractIdentifiedType property = properties[i];
+            final PropertyType property = properties[i];
             ArgumentChecks.ensureNonNullElement("properties", i, property);
             sourceProperties.add(property);
         }
@@ -289,13 +289,13 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
         switch (size) {
             case 0:  this.properties = Collections.emptyList(); break;
             case 1:  this.properties = Collections.singletonList(sourceProperties.get(0)); break;
-            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new AbstractIdentifiedType[size])); break;
+            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new PropertyType[size])); break;
         }
         /*
          * Before to resolve cyclic associations, verify that operations depend only on existing properties.
          * Note: the 'allProperties' collection has been created by computeTransientFields(…) above.
          */
-        for (final AbstractIdentifiedType property : allProperties) {
+        for (final PropertyType property : allProperties) {
             if (property instanceof AbstractOperation) {
                 for (final String dependency : ((AbstractOperation) property).getDependencies()) {
                     if (!byName.containsKey(dependency)) {
@@ -343,17 +343,17 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @param  properties  same content as {@link #properties} (may be the reference to the same list), but
      *         optionally in a temporarily modifiable list if we want to allow removal of duplicated values.
-     *         See {@code scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
+     *         See {@link #scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void computeTransientFields(final List<AbstractIdentifiedType> properties) {
+    private void computeTransientFields(final List<PropertyType> properties) {
         final int capacity = Containers.hashMapCapacity(properties.size());
         byName       = new LinkedHashMap<>(capacity);
         indices      = new LinkedHashMap<>(capacity);
         assignableTo = new HashSet<>(4);
         assignableTo.add(super.getName());
         scanPropertiesFrom(this, properties);
-        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(new AbstractIdentifiedType[byName.size()]));
+        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(new PropertyType[byName.size()]));
         /*
          * Now check if the feature is simple/complex or dense/sparse. We perform this check after we finished
          * to create the list of all properties, because some properties may be overridden and we want to take
@@ -362,16 +362,16 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
         isSimple = true;
         int index = 0;
         int mandatory = 0;                                                  // Count of mandatory properties.
-        for (final Map.Entry<String,AbstractIdentifiedType> entry : byName.entrySet()) {
+        for (final Map.Entry<String,PropertyType> entry : byName.entrySet()) {
             final int minimumOccurs, maximumOccurs;
-            final AbstractIdentifiedType property = entry.getValue();
-            if (property instanceof DefaultAttributeType<?>) { // Other SIS branches check for AttributeType instead.
-                minimumOccurs = ((DefaultAttributeType<?>) property).getMinimumOccurs();
-                maximumOccurs = ((DefaultAttributeType<?>) property).getMaximumOccurs();
+            final PropertyType property = entry.getValue();
+            if (property instanceof AttributeType<?>) {
+                minimumOccurs = ((AttributeType<?>) property).getMinimumOccurs();
+                maximumOccurs = ((AttributeType<?>) property).getMaximumOccurs();
                 isSimple &= (minimumOccurs == maximumOccurs);
-            } else if (property instanceof FieldType) { // TODO: check for AssociationRole instead (after GeoAPI upgrade).
-                minimumOccurs = ((FieldType) property).getMinimumOccurs();
-                maximumOccurs = ((FieldType) property).getMaximumOccurs();
+            } else if (property instanceof FeatureAssociationRole) {
+                minimumOccurs = ((FeatureAssociationRole) property).getMinimumOccurs();
+                maximumOccurs = ((FeatureAssociationRole) property).getMaximumOccurs();
                 isSimple = false;
             } else {
                 if (isParameterlessOperation(property)) {
@@ -394,8 +394,8 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
          *
          * In the 'aliases' map below, null values will be assigned to ambiguous short names.
          */
-        final Map<String, AbstractIdentifiedType> aliases = new LinkedHashMap<>();
-        for (final AbstractIdentifiedType property : allProperties) {
+        final Map<String, PropertyType> aliases = new LinkedHashMap<>();
+        for (final PropertyType property : allProperties) {
             GenericName name = property.getName();
             while (name instanceof ScopedName) {
                 if (name == (name = ((ScopedName) name).tail())) break;   // Safety against broken implementations.
@@ -404,8 +404,8 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
                 aliases.put(key, aliases.containsKey(key) ? null : property);
             }
         }
-        for (final Map.Entry<String,AbstractIdentifiedType> entry : aliases.entrySet()) {
-            final AbstractIdentifiedType property = entry.getValue();
+        for (final Map.Entry<String,PropertyType> entry : aliases.entrySet()) {
+            final PropertyType property = entry.getValue();
             if (property != null) {
                 final String tip = entry.getKey();
                 if (byName.putIfAbsent(tip, property) == null) {
@@ -451,7 +451,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * <ul>
      *   <li>Avoid a call to the user-overrideable {@link #getProperties(boolean)} method
      *       while this {@code DefaultFeatureType} instance is still under constructor.</li>
-     *   <li>Allow the {@code DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
+     *   <li>Allow the {@link #DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
      *       to pass a temporary modifiable list that allow element removal.</li>
      * </ul>
      *
@@ -459,20 +459,18 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * @param  sourceProperties  {@code source.getProperties(false)} (see above method javadoc).
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void scanPropertiesFrom(final DefaultFeatureType source,
-            final Collection<? extends AbstractIdentifiedType> sourceProperties)
-    {
-        for (final DefaultFeatureType parent : source.getSuperTypes()) {
+    private void scanPropertiesFrom(final FeatureType source, final Collection<? extends PropertyType> sourceProperties) {
+        for (final FeatureType parent : source.getSuperTypes()) {
             if (assignableTo.add(parent.getName())) {
                 scanPropertiesFrom(parent, parent.getProperties(false));
             }
         }
         int index = -1;
-        final Iterator<? extends AbstractIdentifiedType> it = sourceProperties.iterator();
+        final Iterator<? extends PropertyType> it = sourceProperties.iterator();
         while (it.hasNext()) {
-            final AbstractIdentifiedType property = it.next();
+            final PropertyType property = it.next();
             final String name = toString(property.getName(), source, "properties", ++index);
-            final AbstractIdentifiedType previous = byName.put(name, property);
+            final PropertyType previous = byName.put(name, property);
             if (previous != null) {
                 if (previous.equals(property)) {
                     byName.put(name, previous);         // Keep the instance declared in super-type.
@@ -492,18 +490,14 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * Returns the name of the feature which defines the given property, or {@code null} if not found.
      * This method is for information purpose when producing an error message - its implementation does
      * not need to be efficient.
-     *
-     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
-     * However this method needs to be static in other SIS branches, because they work with interfaces
-     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    private static GenericName ownerOf(final DefaultFeatureType type, final Collection<? extends AbstractIdentifiedType> properties,
-            final AbstractIdentifiedType toSearch)
+    private static GenericName ownerOf(final FeatureType type, final Collection<? extends PropertyType> properties,
+            final PropertyType toSearch)
     {
         if (properties.contains(toSearch)) {
             return type.getName();
         }
-        for (final DefaultFeatureType superType : type.getSuperTypes()) {
+        for (final FeatureType superType : type.getSuperTypes()) {
             final GenericName owner = ownerOf(superType, superType.getProperties(false), toSearch);
             if (owner != null) {
                 return owner;
@@ -527,16 +521,23 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * @param  previous  previous results, for avoiding never ending loop.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final DefaultFeatureType feature, final Map<FeatureType,Boolean> previous) {
+    private boolean resolve(final FeatureType feature, final Map<FeatureType,Boolean> previous) {
         /*
          * The isResolved field is used only as a cache for skipping completely the DefaultFeatureType instance if
-         * we have determined that there is no unresolved name.
+         * we have determined that there is no unresolved name.  If the given argument is not a DefaultFeatureType
+         * instance, conservatively assumes 'isSimple'. It may cause more calculation than needed, but should not
+         * change the result.
          */
-        return feature.isResolved = resolve(feature, feature.properties, previous, feature.isResolved);
+        if (feature instanceof DefaultFeatureType) {
+            final DefaultFeatureType dt = (DefaultFeatureType) feature;
+            return dt.isResolved = resolve(dt, dt.properties, previous, dt.isResolved);
+        } else {
+            return resolve(feature, feature.getProperties(false), previous, feature.isSimple());
+        }
     }
 
     /**
-     * Implementation of {@code resolve(FeatureType, Map)}, also to be invoked from the constructor.
+     * Implementation of {@link #resolve(FeatureType, Map)}, also to be invoked from the constructor.
      *
      * <p>{@code this} shall be the instance in process of being created, not other instance
      * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
@@ -547,19 +548,21 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * @param  resolved    {@code true} if we already know that all names are resolved.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final DefaultFeatureType feature, final Collection<? extends AbstractIdentifiedType> toUpdate,
+    private boolean resolve(final FeatureType feature, final Collection<? extends PropertyType> toUpdate,
             Map<FeatureType,Boolean> previous, boolean resolved)
     {
         if (!resolved) {
             resolved = true;
-            for (final DefaultFeatureType type : feature.getSuperTypes()) {
+            for (final FeatureType type : feature.getSuperTypes()) {
                 resolved &= resolve(type, previous);
             }
-            for (final AbstractIdentifiedType property : toUpdate) {
-                if (property instanceof DefaultAssociationRole) {
-                    if (!((DefaultAssociationRole) property).resolve(this, properties)) {
-                        resolved = false;
-                        continue;
+            for (final PropertyType property : toUpdate) {
+                if (property instanceof FeatureAssociationRole) {
+                    if (property instanceof DefaultAssociationRole) {
+                        if (!((DefaultAssociationRole) property).resolve(this, properties)) {
+                            resolved = false;
+                            continue;
+                        }
                     }
                     /*
                      * Resolve recursively the associated features, with a check against infinite recursivity.
@@ -567,7 +570,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
                      * may not be the most accurate answer, but will not cause any more hurt than checking more
                      * often than necessary.
                      */
-                    final DefaultFeatureType valueType = ((DefaultAssociationRole) property).getValueType();
+                    final FeatureType valueType = ((FeatureAssociationRole) property).getValueType();
                     if (valueType != this) {
                         if (previous == null) {
                             previous = new IdentityHashMap<>(8);
@@ -590,11 +593,11 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @see #OPERATION_INDEX
      */
-    static boolean isParameterlessOperation(final AbstractIdentifiedType type) {
-        if (type instanceof AbstractOperation) {
-            final ParameterDescriptorGroup parameters = ((AbstractOperation) type).getParameters();
+    static boolean isParameterlessOperation(final PropertyType type) {
+        if (type instanceof Operation) {
+            final ParameterDescriptorGroup parameters = ((Operation) type).getParameters();
             return ((parameters == null) || parameters.descriptors().isEmpty())
-                   && ((AbstractOperation) type).getResult() != null;
+                   && ((Operation) type).getResult() != null;
         }
         return false;
     }
@@ -609,6 +612,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @return {@code true} if the feature type acts as an abstract super-type.
      */
+    @Override
     public final boolean isAbstract() {
         return isAbstract;
     }
@@ -628,22 +632,31 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @return {@code true} if this feature type contains only simple attributes or operations.
      */
+    @Override
     public boolean isSimple() {
         return isSimple;
     }
 
     /**
      * Returns {@code true} if the given base type may be the same or a super-type of the given type, using only
-     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(DefaultFeatureType)}.
+     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(FeatureType)}.
      *
      * <p>Performance note: callers should verify that {@code base != type} before to invoke this method.</p>
-     *
-     * <p><b>API note:</b> a non-static method would be more elegant in this "SIS for GeoAPI 3.0" branch.
-     * However this method needs to be static in other SIS branches, because they work with interfaces
-     * rather than SIS implementation. We keep the method static in this branch too for easier merges.</p>
      */
-    static boolean maybeAssignableFrom(final DefaultFeatureType base, final DefaultFeatureType type) {
-        return type.assignableTo.contains(base.getName());
+    static boolean maybeAssignableFrom(final FeatureType base, final FeatureType type) {
+        if (type instanceof DefaultFeatureType) {
+            return ((DefaultFeatureType) type).assignableTo.contains(base.getName());
+        }
+        // Slower path for non-SIS implementations.
+        if (Objects.equals(base.getName(), type.getName())) {
+            return true;
+        }
+        for (final FeatureType superType : type.getSuperTypes()) {
+            if (base == superType || maybeAssignableFrom(base, superType)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -660,7 +673,7 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * @return {@code true} if instances of the given type can be assigned to association of this type.
      */
     @Override
-    public boolean isAssignableFrom(final DefaultFeatureType type) {
+    public boolean isAssignableFrom(final FeatureType type) {
         if (type == this) {
             return true;                            // Optimization for a common case.
         }
@@ -672,11 +685,11 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
          * Ensures that all properties defined in this feature type is also defined
          * in the given property, and that the former is assignable from the later.
          */
-        for (final Map.Entry<String, AbstractIdentifiedType> entry : byName.entrySet()) {
-            final AbstractIdentifiedType other;
+        for (final Map.Entry<String, PropertyType> entry : byName.entrySet()) {
+            final PropertyType other;
             try {
                 other = type.getProperty(entry.getKey());
-            } catch (IllegalArgumentException e) {
+            } catch (PropertyNotFoundException e) {
                 /*
                  * A property in this FeatureType does not exist in the given FeatureType.
                  * Catching exceptions is not an efficient way to perform this check, but
@@ -697,25 +710,22 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * Returns {@code true} if instances of the {@code other} type are assignable to the given {@code base} type.
      * This method does not compare the names — this verification is presumed already done by the caller.
      */
-    private static boolean isAssignableIgnoreName(final AbstractIdentifiedType base, final AbstractIdentifiedType other) {
+    private static boolean isAssignableIgnoreName(final PropertyType base, final PropertyType other) {
         if (base != other) {
             /*
              * If the base property is an attribute, then the overriding property shall be either an attribute
              * or a parameterless operation producing an attribute.  The parameterless operation is considered
              * has having a [1…1] cardinality.
-             *
-             * Note: other SIS branches use AttributeType and FeatureAssociationRole
-             *       instead than DefaultAttributeType and DefaultAssociationRole.
              */
-            if (base instanceof DefaultAttributeType<?>) {
-                final DefaultAttributeType<?> p0 = (DefaultAttributeType<?>) base;
-                final DefaultAttributeType<?> p1;
-                if (other instanceof DefaultAttributeType<?>) {
-                    p1 = (DefaultAttributeType<?>) other;
+            if (base instanceof AttributeType<?>) {
+                final AttributeType<?> p0 = (AttributeType<?>) base;
+                final AttributeType<?> p1;
+                if (other instanceof AttributeType<?>) {
+                    p1 = (AttributeType<?>) other;
                 } else if (isParameterlessOperation(other)) {
-                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
-                    if (result instanceof DefaultAttributeType<?>) {
-                        p1 = (DefaultAttributeType<?>) result;
+                    final IdentifiedType result = ((Operation) other).getResult();
+                    if (result instanceof AttributeType<?>) {
+                        p1 = (AttributeType<?>) result;
                     } else {
                         return false;
                     }
@@ -736,15 +746,15 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
              * because an implementation could implement both AttributeType and AssociationRole interfaces.
              * This is not recommended, but if it happen we want a behavior as consistent as possible.
              */
-            if (base instanceof DefaultAssociationRole) {
-                final DefaultAssociationRole p0 = (DefaultAssociationRole) base;
-                final DefaultAssociationRole p1;
-                if (other instanceof DefaultAssociationRole) {
-                    p1 = (DefaultAssociationRole) other;
+            if (base instanceof FeatureAssociationRole) {
+                final FeatureAssociationRole p0 = (FeatureAssociationRole) base;
+                final FeatureAssociationRole p1;
+                if (other instanceof FeatureAssociationRole) {
+                    p1 = (FeatureAssociationRole) other;
                 } else if (isParameterlessOperation(other)) {
-                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
-                    if (result instanceof DefaultAssociationRole) {
-                        p1 = (DefaultAssociationRole) result;
+                    final IdentifiedType result = ((Operation) other).getResult();
+                    if (result instanceof FeatureAssociationRole) {
+                        p1 = (FeatureAssociationRole) result;
                     } else {
                         return false;
                     }
@@ -758,8 +768,8 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
                 {
                     return false;
                 }
-                final DefaultFeatureType f0 = p0.getValueType();
-                final DefaultFeatureType f1 = p1.getValueType();
+                final FeatureType f0 = p0.getValueType();
+                final FeatureType f1 = p1.getValueType();
                 if (f0 != f1 && !f0.isAssignableFrom(f1)) {
                     return false;
                 }
@@ -769,11 +779,11 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
              * In the special case of parameterless operations, can also be overridden by
              * AttributeType or FeatureAssociationRole.
              */
-            if (base instanceof AbstractOperation) {
-                final AbstractOperation p0 = (AbstractOperation) base;
-                final AbstractIdentifiedType r1;
-                if (other instanceof AbstractOperation) {
-                    final AbstractOperation p1 = (AbstractOperation) other;
+            if (base instanceof Operation) {
+                final Operation p0 = (Operation) base;
+                final IdentifiedType r1;
+                if (other instanceof Operation) {
+                    final Operation p1 = (Operation) other;
                     if (!Objects.equals(p0.getParameters(), p1.getParameters())) {
                         return false;
                     }
@@ -783,14 +793,15 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
                 } else {
                     return false;
                 }
-                final AbstractIdentifiedType r0 = p0.getResult();
+                final IdentifiedType r0 = p0.getResult();
                 if (r0 != r1) {
-                    if (r0 instanceof DefaultFeatureType) {
-                        if (!(r1 instanceof DefaultFeatureType) || !((FeatureType) r0).isAssignableFrom((DefaultFeatureType) r1)) {
+                    if (r0 instanceof FeatureType) {
+                        if (!(r1 instanceof FeatureType) || !((FeatureType) r0).isAssignableFrom((FeatureType) r1)) {
                             return false;
                         }
-                    } else if (r0 != null) {
-                        if (r1 == null || !isAssignableIgnoreName(r0, r1)) {
+                    }
+                    if (r0 instanceof PropertyType) {
+                        if (!(r1 instanceof PropertyType) || !isAssignableIgnoreName((PropertyType) r0, (PropertyType) r1)) {
                             return false;
                         }
                     }
@@ -808,10 +819,6 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * if we compare {@code FeatureType} to {@link Class} in the Java language, then this method is equivalent
      * to {@link Class#getSuperclass()} except that feature types allow multi-inheritance.</div>
      *
-     * <div class="warning"><b>Warning:</b>
-     * The type of list elements will be changed to {@code FeatureType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * <div class="note"><b>Note for subclasses:</b>
      * this method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
      * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
@@ -820,8 +827,9 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      *
      * @return  the parents of this feature type, or an empty set if none.
      */
+    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public final Set<DefaultFeatureType> getSuperTypes() {
+    public final Set<FeatureType> getSuperTypes() {
         return superTypes;      // Immutable
     }
 
@@ -831,39 +839,32 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * inherited from the {@linkplain #getSuperTypes() super-types} only if {@code includeSuperTypes}
      * is {@code true}.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The type of list elements will be changed to {@code PropertyType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  includeSuperTypes  {@code true} for including the properties inherited from the super-types,
      *         or {@code false} for returning only the properties defined explicitely in this type.
      * @return feature operation, attribute type and association role that carries characteristics of this
      *         feature type (not including parent types).
      */
     @Override
-    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
+    public Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
         return includeSuperTypes ? allProperties : properties;
     }
 
     /**
      * Returns the attribute, operation or association role for the given name.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The type of returned element will be changed to {@code PropertyType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  name  the name of the property to search.
      * @return the property for the given name, or {@code null} if none.
-     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
      *
      * @see AbstractFeature#getProperty(String)
      */
-    public AbstractIdentifiedType getProperty(final String name) throws IllegalArgumentException {
-        final AbstractIdentifiedType pt = byName.get(name);
+    @Override
+    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
+        final PropertyType pt = byName.get(name);
         if (pt != null) {
             return pt;
         }
-        throw new IllegalArgumentException(AbstractFeature.propertyNotFound(this, getName(), name));
+        throw new PropertyNotFoundException(AbstractFeature.propertyNotFound(this, getName(), name));
     }
 
     /**
@@ -883,11 +884,12 @@ public class DefaultFeatureType extends AbstractIdentifiedType implements Featur
      * then this method is equivalent to {@link Class#newInstance()}.</div>
      *
      * @return a new feature instance.
-     * @throws IllegalStateException if this feature type {@linkplain #isAbstract() is abstract}.
+     * @throws FeatureInstantiationException if this feature type {@linkplain #isAbstract() is abstract}.
      */
-    public AbstractFeature newInstance() throws IllegalStateException {
+    @Override
+    public Feature newInstance() throws FeatureInstantiationException {
         if (isAbstract) {
-            throw new IllegalStateException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
+            throw new FeatureInstantiationException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
         }
         return isSparse ? new SparseFeature(this) : new DenseFeature(this);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
index 73e471b..79b8204 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
@@ -23,6 +23,12 @@ import org.opengis.metadata.quality.DataQuality;
 import org.apache.sis.internal.util.Cloner;
 import org.apache.sis.util.ArgumentChecks;
 
+// Branch-dependent imports
+import org.opengis.feature.Property;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.PropertyNotFoundException;
+
 
 /**
  * A feature in which most properties are expected to be provided. This implementation uses a plain array for
@@ -79,14 +85,14 @@ final class DenseFeature extends AbstractFeature implements Cloneable {
      * @param  name  the property name.
      * @return the index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
      */
-    private int getIndex(final String name) throws IllegalArgumentException {
+    private int getIndex(final String name) throws PropertyNotFoundException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
+        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -94,10 +100,10 @@ final class DenseFeature extends AbstractFeature implements Cloneable {
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
      */
     @Override
-    public Object getProperty(final String name) throws IllegalArgumentException {
+    public Property getProperty(final String name) throws PropertyNotFoundException {
         ArgumentChecks.ensureNonNull("name", name);
         final int index = getIndex(name);
         if (index < 0) {
@@ -130,10 +136,10 @@ final class DenseFeature extends AbstractFeature implements Cloneable {
      *         known to this feature, or if the property can not be set or another reason.
      */
     @Override
-    public void setProperty(final Object property) throws IllegalArgumentException {
+    public void setProperty(final Property property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = ((Property) property).getName().toString();
-        verifyPropertyType(name, (Property) property);
+        final String name = property.getName().toString();
+        verifyPropertyType(name, property);
         if (!(properties instanceof Property[])) {
             wrapValuesInProperties();
         }
@@ -170,10 +176,10 @@ final class DenseFeature extends AbstractFeature implements Cloneable {
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws IllegalArgumentException {
+    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
         ArgumentChecks.ensureNonNull("name", name);
         final int index = getIndex(name);
         if (index < 0) {
@@ -184,10 +190,10 @@ final class DenseFeature extends AbstractFeature implements Cloneable {
             if (element != null) {
                 if (!(properties instanceof Property[])) {
                     return element; // Most common case.
-                } else if (element instanceof AbstractAttribute<?>) {
-                    return getAttributeValue((AbstractAttribute<?>) element);
-                } else if (element instanceof AbstractAssociation) {
-                    return getAssociationValue((AbstractAssociation) element);
+                } else if (element instanceof Attribute<?>) {
+                    return getAttributeValue((Attribute<?>) element);
+                } else if (element instanceof FeatureAssociation) {
+                    return getAssociationValue((FeatureAssociation) element);
                 } else {
                     throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
                 }
@@ -299,10 +305,10 @@ final class DenseFeature extends AbstractFeature implements Cloneable {
                 for (final Property p : (Property[]) properties) {
                     code = 31 * code;
                     final Object value;
-                    if (p instanceof AbstractAttribute<?>) {
-                        value = getAttributeValue((AbstractAttribute<?>) p);
-                    } else if (p instanceof AbstractAssociation) {
-                        value = getAssociationValue((AbstractAssociation) p);
+                    if (p instanceof Attribute<?>) {
+                        value = getAttributeValue((Attribute<?>) p);
+                    } else if (p instanceof FeatureAssociation) {
+                        value = getAssociationValue((FeatureAssociation) p);
                     } else {
                         continue;
                     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
index 7526e50..aaecc9a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
@@ -38,6 +38,15 @@ import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.Operation;
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+
 
 /**
  * An operation computing the envelope that encompass all geometries found in a list of attributes.
@@ -104,7 +113,7 @@ final class EnvelopeOperation extends AbstractOperation {
     /**
      * The type of the result returned by the envelope operation.
      */
-    private final DefaultAttributeType<Envelope> resultType;
+    private final AttributeType<Envelope> resultType;
 
     /**
      * Creates a new operation computing the envelope of features of the given type.
@@ -114,7 +123,7 @@ final class EnvelopeOperation extends AbstractOperation {
      * @param geometryAttributes  the operation or attribute type from which to get geometry values.
      */
     EnvelopeOperation(final Map<String,?> identification, CoordinateReferenceSystem crs,
-            final AbstractIdentifiedType[] geometryAttributes) throws FactoryException
+            final PropertyType[] geometryAttributes) throws FactoryException
     {
         super(identification);
         String defaultGeometry = null;
@@ -129,7 +138,7 @@ final class EnvelopeOperation extends AbstractOperation {
          */
         boolean characterizedByCRS = false;
         final Map<String,CoordinateReferenceSystem> names = new LinkedHashMap<>(4);
-        for (AbstractIdentifiedType property : geometryAttributes) {
+        for (IdentifiedType property : geometryAttributes) {
             if (AttributeConvention.isGeometryAttribute(property)) {
                 final GenericName name = property.getName();
                 final String attributeName = (property instanceof LinkOperation)
@@ -139,8 +148,8 @@ final class EnvelopeOperation extends AbstractOperation {
                     defaultGeometry = attributeName;
                 }
                 CoordinateReferenceSystem attributeCRS = null;
-                while (property instanceof AbstractOperation) {
-                    property = ((AbstractOperation) property).getResult();
+                while (property instanceof Operation) {
+                    property = ((Operation) property).getResult();
                 }
                 /*
                  * At this point 'property' is an attribute, otherwise isGeometryAttribute(property) would have
@@ -148,7 +157,7 @@ final class EnvelopeOperation extends AbstractOperation {
                  * have the "CRS" characteristic. Note that we can not rely on 'attributeCRS' being non-null
                  * because an attribute may be characterized by a CRS without providing default CRS.
                  */
-                final DefaultAttributeType<?> at = ((DefaultAttributeType<?>) property).characteristics().get(characteristicName);
+                final AttributeType<?> at = ((AttributeType<?>) property).characteristics().get(characteristicName);
                 if (at != null && CoordinateReferenceSystem.class.isAssignableFrom(at.getValueClass())) {
                     attributeCRS = (CoordinateReferenceSystem) at.getDefaultValue();              // May still null.
                     if (crs == null && isDefault) {
@@ -214,7 +223,7 @@ final class EnvelopeOperation extends AbstractOperation {
      * @return an {@code AttributeType<Envelope>}.
      */
     @Override
-    public AbstractIdentifiedType getResult() {
+    public IdentifiedType getResult() {
         return resultType;
     }
 
@@ -239,7 +248,7 @@ final class EnvelopeOperation extends AbstractOperation {
      * @return the envelope of geometries in feature property values.
      */
     @Override
-    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
+    public Property apply(Feature feature, ParameterValueGroup parameters) {
         return new Result(feature);
     }
 
@@ -257,14 +266,14 @@ final class EnvelopeOperation extends AbstractOperation {
         private static final long serialVersionUID = 926172863066901618L;
 
         /**
-         * The feature specified to the {@code StringJoinOperation.apply(Feature, ParameterValueGroup)} method.
+         * The feature specified to the {@link StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
          */
-        private final AbstractFeature feature;
+        private final Feature feature;
 
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final AbstractFeature feature) {
+        Result(final Feature feature) {
             super(resultType);
             this.feature = feature;
         }
@@ -299,7 +308,7 @@ final class EnvelopeOperation extends AbstractOperation {
                      * We do not distinguish which particular property may have a CRS characteristic because SIS 0.7
                      * implementations of DenseFeature and SparseFeature have a "all of nothing" behavior anyway.
                      */
-                    final Property property = (Property) feature.getProperty(name);
+                    final Property property = feature.getProperty(name);
                     genv = Geometries.getEnvelope(property.getValue());
                     if (genv == null) continue;
                     /*
@@ -308,7 +317,7 @@ final class EnvelopeOperation extends AbstractOperation {
                      * cases where a CRS characteristic is associated to a particular feature, we will let
                      * Envelopes.transform(…) searches a coordinate operation.
                      */
-                    final AbstractAttribute<?> at = ((AbstractAttribute<?>) property).characteristics()
+                    final Attribute<?> at = ((Attribute<?>) property).characteristics()
                                     .get(AttributeConvention.CRS_CHARACTERISTIC.toString());
                     try {
                         if (at == null) {
@@ -342,7 +351,7 @@ final class EnvelopeOperation extends AbstractOperation {
          */
         @Override
         public void setValue(Envelope value) {
-            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.class));
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
index 052767f..17c9df6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
@@ -50,6 +50,16 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.math.MathFunctions;
 
 // Branch-dependent imports
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.Operation;
 
 
 /**
@@ -148,7 +158,7 @@ public class FeatureFormat extends TabularFormat<Object> {
 
     /**
      * Returns the type of objects formatted by this class. This method has to return {@code Object.class}
-     * since it is the only common parent to {@code Feature} and {@link FeatureType}.
+     * since it is the only common parent to {@link Feature} and {@link FeatureType}.
      *
      * @return {@code Object.class}
      */
@@ -215,39 +225,39 @@ public class FeatureFormat extends TabularFormat<Object> {
     public enum Column {
         /**
          * Natural language designator for the property.
-         * This is the character sequence returned by {@link AbstractIdentifiedType#getDesignation()}.
+         * This is the character sequence returned by {@link PropertyType#getDesignation()}.
          * This column is omitted if no property has a designation.
          */
         DESIGNATION(Vocabulary.Keys.Designation),
 
         /**
          * Name of the property.
-         * This is the character sequence returned by {@link AbstractIdentifiedType#getName()}.
+         * This is the character sequence returned by {@link PropertyType#getName()}.
          */
         NAME(Vocabulary.Keys.Name),
 
         /**
-         * Type of property values. This is the type returned by {@link DefaultAttributeType#getValueClass()} or
-         * {@link DefaultAssociationRole#getValueType()}.
+         * Type of property values. This is the type returned by {@link AttributeType#getValueClass()} or
+         * {@link FeatureAssociationRole#getValueType()}.
          */
         TYPE(Vocabulary.Keys.Type),
 
         /**
          * The minimum and maximum occurrences of attribute values. This is made from the numbers returned
-         * by {@link DefaultAttributeType#getMinimumOccurs()} and {@link DefaultAttributeType#getMaximumOccurs()}.
+         * by {@link AttributeType#getMinimumOccurs()} and {@link AttributeType#getMaximumOccurs()}.
          */
         CARDINALITY(Vocabulary.Keys.Cardinality),
 
         /**
          * Property value (for properties) or default value (for property types).
-         * This is the value returned by {@link AbstractAttribute#getValue()}, {@link AbstractAssociation#getValue()}
-         * or {@link DefaultAttributeType#getDefaultValue()}.
+         * This is the value returned by {@link Attribute#getValue()}, {@link FeatureAssociation#getValue()}
+         * or {@link AttributeType#getDefaultValue()}.
          */
         VALUE(Vocabulary.Keys.Value),
 
         /**
          * Other attributes that describes the attribute.
-         * This is made from the map returned by {@link AbstractAttribute#characteristics()}.
+         * This is made from the map returned by {@link Attribute#characteristics()}.
          * This column is omitted if no property has characteristics.
          */
         CHARACTERISTICS(Vocabulary.Keys.Characteristics),
@@ -284,8 +294,8 @@ public class FeatureFormat extends TabularFormat<Object> {
      * The object may be an instance of any of the following types:
      *
      * <ul>
-     *   <li>{@code Feature}</li>
-     *   <li>{@code FeatureType}</li>
+     *   <li>{@link Feature}</li>
+     *   <li>{@link FeatureType}</li>
      * </ul>
      *
      * @throws IOException if an error occurred while writing to the given appendable.
@@ -297,13 +307,13 @@ public class FeatureFormat extends TabularFormat<Object> {
         /*
          * Separate the Feature (optional) and the FeatureType (mandatory) instances.
          */
-        final DefaultFeatureType featureType;
-        final AbstractFeature feature;
-        if (object instanceof AbstractFeature) {
-            feature     = (AbstractFeature) object;
+        final FeatureType featureType;
+        final Feature feature;
+        if (object instanceof Feature) {
+            feature     = (Feature) object;
             featureType = feature.getType();
-        } else if (object instanceof DefaultFeatureType) {
-            featureType = (DefaultFeatureType) object;
+        } else if (object instanceof FeatureType) {
+            featureType = (FeatureType) object;
             feature     = null;
         } else {
             throw new IllegalArgumentException(Errors.getResources(displayLocale)
@@ -320,12 +330,12 @@ public class FeatureFormat extends TabularFormat<Object> {
             boolean hasDesignation     = false;
             boolean hasCharacteristics = false;
             boolean hasDeprecatedTypes = false;
-            for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
+            for (final PropertyType propertyType : featureType.getProperties(true)) {
                 if (!hasDesignation) {
                     hasDesignation = propertyType.getDesignation() != null;
                 }
-                if (!hasCharacteristics && propertyType instanceof DefaultAttributeType<?>) {
-                    hasCharacteristics = !((DefaultAttributeType<?>) propertyType).characteristics().isEmpty();
+                if (!hasCharacteristics && propertyType instanceof AttributeType<?>) {
+                    hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty();
                 }
                 if (!hasDeprecatedTypes && propertyType instanceof Deprecable) {
                     hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated();
@@ -378,19 +388,26 @@ public class FeatureFormat extends TabularFormat<Object> {
         final StringBuffer  buffer  = new StringBuffer();
         final FieldPosition dummyFP = new FieldPosition(-1);
         final List<String>  remarks = new ArrayList<>();
-        for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
+        for (final PropertyType propertyType : featureType.getProperties(true)) {
             Object value = null;
             int cardinality = -1;
             if (feature != null) {
-                if (!(propertyType instanceof DefaultAttributeType<?>) &&
-                    !(propertyType instanceof DefaultAssociationRole) &&
+                if (!(propertyType instanceof AttributeType<?>) &&
+                    !(propertyType instanceof FeatureAssociationRole) &&
                     !DefaultFeatureType.isParameterlessOperation(propertyType))
                 {
                     continue;
                 }
                 value = feature.getPropertyValue(propertyType.getName().toString());
                 if (value == null) {
-                    if (propertyType instanceof FieldType && ((FieldType) propertyType).getMinimumOccurs() == 0) {
+                    if (propertyType instanceof AttributeType<?>
+                            && ((AttributeType<?>) propertyType).getMinimumOccurs() == 0)
+                    {
+                        continue;                           // If optional and no value, skip the full row.
+                    }
+                    if (propertyType instanceof FeatureAssociationRole
+                            && ((FeatureAssociationRole) propertyType).getMinimumOccurs() == 0)
+                    {
                         continue;                           // If optional and no value, skip the full row.
                     }
                     cardinality = 0;
@@ -399,12 +416,16 @@ public class FeatureFormat extends TabularFormat<Object> {
                 } else {
                     cardinality = 1;
                 }
-            } else if (propertyType instanceof DefaultAttributeType<?>) {
-                value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
-            } else if (propertyType instanceof AbstractOperation) {
+            } else if (propertyType instanceof AttributeType<?>) {
+                value = ((AttributeType<?>) propertyType).getDefaultValue();
+            } else if (propertyType instanceof Operation) {
                 buffer.append(" = ");
                 try {
-                    ((AbstractOperation) propertyType).formatResultFormula(buffer);
+                    if (propertyType instanceof AbstractOperation) {
+                        ((AbstractOperation) propertyType).formatResultFormula(buffer);
+                    } else {
+                        AbstractOperation.defaultFormula(((Operation) propertyType).getParameters(), buffer);
+                    }
                 } catch (IOException e) {
                     throw new UncheckedIOException(e);      // Should never happen since we write in a StringBuffer.
                 }
@@ -414,25 +435,25 @@ public class FeatureFormat extends TabularFormat<Object> {
             final String   valueType;                       // The value to write in the type column.
             final Class<?> valueClass;                      // AttributeType.getValueClass() if applicable.
             final int minimumOccurs, maximumOccurs;         // Negative values mean no cardinality.
-            final AbstractIdentifiedType resultType;        // Result of operation if applicable.
-            if (propertyType instanceof AbstractOperation) {
-                resultType = ((AbstractOperation) propertyType).getResult();        // May be null
+            final IdentifiedType resultType;                // Result of operation if applicable.
+            if (propertyType instanceof Operation) {
+                resultType = ((Operation) propertyType).getResult();                // May be null
             } else {
                 resultType = propertyType;
             }
-            if (resultType instanceof DefaultAttributeType<?>) {
-                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) resultType;
+            if (resultType instanceof AttributeType<?>) {
+                final AttributeType<?> pt = (AttributeType<?>) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueClass    = pt.getValueClass();
                 valueType     = getFormat(Class.class).format(valueClass, buffer, dummyFP).toString();
                 buffer.setLength(0);
-            } else if (resultType instanceof DefaultAssociationRole) {
-                final DefaultAssociationRole pt = (DefaultAssociationRole) resultType;
+            } else if (resultType instanceof FeatureAssociationRole) {
+                final FeatureAssociationRole pt = (FeatureAssociationRole) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueType     = toString(DefaultAssociationRole.getValueTypeName(pt));
-                valueClass    = AbstractFeature.class;
+                valueClass    = Feature.class;
             } else {
                 valueType  = (resultType != null) ? toString(resultType.getName()) : "";
                 valueClass = null;
@@ -519,10 +540,10 @@ public class FeatureFormat extends TabularFormat<Object> {
                         while (it.hasNext()) {
                             value = it.next();
                             if (value != null) {
-                                if (propertyType instanceof DefaultAssociationRole) {
-                                    final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
+                                if (propertyType instanceof FeatureAssociationRole) {
+                                    final String p = DefaultAssociationRole.getTitleProperty((FeatureAssociationRole) propertyType);
                                     if (p != null) {
-                                        value = ((AbstractFeature) value).getPropertyValue(p);
+                                        value = ((Feature) value).getPropertyValue(p);
                                         if (value == null) continue;
                                     }
                                 } else if (format != null && valueClass.isInstance(value)) {    // Null safe because of getFormat(valueClass) contract.
@@ -576,10 +597,10 @@ public class FeatureFormat extends TabularFormat<Object> {
                      * Characteristics are handled as "attributes of attributes".
                      */
                     case CHARACTERISTICS: {
-                        if (propertyType instanceof DefaultAttributeType<?>) {
+                        if (propertyType instanceof AttributeType<?>) {
                             int length = 0;
                             String separator = "";
-format:                     for (final DefaultAttributeType<?> ct : ((DefaultAttributeType<?>) propertyType).characteristics().values()) {
+format:                     for (final AttributeType<?> ct : ((AttributeType<?>) propertyType).characteristics().values()) {
                                 /*
                                  * Format the characteristic name. We will append the value(s) later.
                                  * We keep trace of the text length in order to stop formatting if the
@@ -597,9 +618,9 @@ format:                     for (final DefaultAttributeType<?> ct : ((DefaultAtt
                                      * given by the default value 'cv'.  Nevertheless we have to check if current
                                      * feature overrides this characteristic.
                                      */
-                                    final Object cp = feature.getProperty(propertyType.getName().toString());
-                                    if (cp instanceof AbstractAttribute<?>) {            // Should always be true, but we are paranoiac.
-                                        AbstractAttribute<?> ca = ((AbstractAttribute<?>) cp).characteristics().get(cn.toString());
+                                    final Property cp = feature.getProperty(propertyType.getName().toString());
+                                    if (cp instanceof Attribute<?>) {            // Should always be true, but we are paranoiac.
+                                        Attribute<?> ca = ((Attribute<?>) cp).characteristics().get(cn.toString());
                                         if (ca != null) cv = ca.getValues();
                                     }
                                 }
@@ -683,8 +704,8 @@ format:                     for (final DefaultAttributeType<?> ct : ((DefaultAtt
             text = ((InternationalString) value).toString(displayLocale);
         } else if (value instanceof GenericName) {
             text = toString((GenericName) value);
-        } else if (value instanceof AbstractIdentifiedType) {
-            text = toString(((AbstractIdentifiedType) value).getName());
+        } else if (value instanceof IdentifiedType) {
+            text = toString(((IdentifiedType) value).getName());
         } else if (value instanceof IdentifiedObject) {
             text = IdentifiedObjects.getIdentifierOrName((IdentifiedObject) value);
         } else if ((text = Geometries.toString(value)) == null) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
index 613c0e0..77b6f6d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
@@ -27,6 +27,11 @@ import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.FeatureAssociationRole;
+
 
 /**
  * A set of pre-defined operations expecting a {@code Feature} as input and producing an {@code Attribute} as output.
@@ -110,7 +115,7 @@ public final class FeatureOperations extends Static {
     /**
      * The pool of operations or operation dependencies created so far, for sharing exiting instances.
      */
-    static final WeakHashSet<AbstractIdentifiedType> POOL = new WeakHashSet<>(AbstractIdentifiedType.class);
+    static final WeakHashSet<PropertyType> POOL = new WeakHashSet<>(PropertyType.class);
 
     /**
      * Do not allow instantiation of this class.
@@ -147,15 +152,11 @@ public final class FeatureOperations extends Static {
      * identified by the {@code referent} argument, the returned property is writable if the referenced
      * property is also writable.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The type of {@code referent} parameter will be changed to {@code PropertyType}
-     * if and when such interface will be defined in GeoAPI.</div>
-     *
      * @param  identification  the name and other information to be given to the operation.
      * @param  referent        the referenced attribute or feature association.
      * @return an operation which is an alias for the {@code referent} property.
      */
-    public static AbstractOperation link(final Map<String,?> identification, final AbstractIdentifiedType referent) {
+    public static Operation link(final Map<String,?> identification, final PropertyType referent) {
         ArgumentChecks.ensureNonNull("referent", referent);
         return POOL.unique(new LinkOperation(identification, referent));
     }
@@ -186,10 +187,6 @@ public final class FeatureOperations extends Static {
      * operation, the given string value will be split around the {@code delimiter} and each substring will be
      * forwarded to the corresponding single property.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The type of {@code singleAttributes} elements will be changed to {@code PropertyType}
-     * if and when such interface will be defined in GeoAPI.</div>
-     *
      * @param  identification    the name and other information to be given to the operation.
      * @param  delimiter         the characters to use as delimiter between each single property value.
      * @param  prefix            characters to use at the beginning of the concatenated string, or {@code null} if none.
@@ -204,8 +201,8 @@ public final class FeatureOperations extends Static {
      *
      * @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key on Wikipedia</a>
      */
-    public static AbstractOperation compound(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final AbstractIdentifiedType... singleAttributes)
+    public static Operation compound(final Map<String,?> identification, final String delimiter,
+            final String prefix, final String suffix, final PropertyType... singleAttributes)
             throws UnconvertibleObjectException
     {
         ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
@@ -221,8 +218,8 @@ public final class FeatureOperations extends Static {
             }
             case 1: {
                 if ((prefix == null || prefix.isEmpty()) && (suffix == null || suffix.isEmpty())) {
-                    final AbstractIdentifiedType at = singleAttributes[0];
-                    if (!(at instanceof DefaultAssociationRole)) {
+                    final PropertyType at = singleAttributes[0];
+                    if (!(at instanceof FeatureAssociationRole)) {
                         return link(identification, at);
                     }
                 }
@@ -257,10 +254,6 @@ public final class FeatureOperations extends Static {
      * This operation is read-only. Calls to {@code Attribute.setValue(Envelope)} will result in an
      * {@link IllegalStateException} to be thrown.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The type of {@code geometryAttributes} elements will be changed to {@code PropertyType}
-     * if and when such interface will be defined in GeoAPI.</div>
-     *
      * @param  identification      the name and other information to be given to the operation.
      * @param  crs                 the Coordinate Reference System in which to express the envelope, or {@code null}.
      * @param  geometryAttributes  the operation or attribute type from which to get geometry values.
@@ -268,8 +261,8 @@ public final class FeatureOperations extends Static {
      * @return an operation which will compute the envelope encompassing all geometries in the given attributes.
      * @throws FactoryException if a coordinate operation to the target CRS can not be created.
      */
-    public static AbstractOperation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
-            final AbstractIdentifiedType... geometryAttributes) throws FactoryException
+    public static Operation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
+            final PropertyType... geometryAttributes) throws FactoryException
     {
         ArgumentChecks.ensureNonNull("geometryAttributes", geometryAttributes);
         return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes));
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java
deleted file mode 100644
index 5f5e078..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.feature;
-
-import java.util.Collection;
-import org.opengis.util.GenericName;
-
-
-/**
- * Place-holder for an interface not available in GeoAPI 3.0.
- * This place-holder will be removed after we upgrade to a later GeoAPI version.
- *
- * <p><strong>Do not put this type in public API</strong>. We need to prevent users from using
- * this type in order to reduce compatibility breaks when we will upgrade the GeoAPI version.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @since   0.5
- * @version 0.5
- * @module
- */
-interface FeatureType {
-    GenericName getName();
-
-    Collection<AbstractIdentifiedType> getProperties(boolean includeSuperTypes);
-
-    boolean isAssignableFrom(DefaultFeatureType type);
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
index 11b189d..8551212 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
@@ -19,6 +19,7 @@ package org.apache.sis.feature;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
+import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.quality.ConformanceResult;
 import org.opengis.metadata.quality.DataQuality;
 import org.opengis.metadata.quality.Element;
@@ -28,6 +29,16 @@ import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+
 
 /**
  * Static methods working on features or attributes.
@@ -59,7 +70,7 @@ public final class Features extends Static {
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> DefaultAttributeType<V> cast(final DefaultAttributeType<?> type, final Class<V> valueClass)
+    public static <V> AttributeType<V> cast(final AttributeType<?> type, final Class<V> valueClass)
             throws ClassCastException
     {
         if (type != null) {
@@ -73,7 +84,7 @@ public final class Features extends Static {
                         type.getName(), valueClass, actual));
             }
         }
-        return (DefaultAttributeType<V>) type;
+        return (AttributeType<V>) type;
     }
 
     /**
@@ -90,7 +101,7 @@ public final class Features extends Static {
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> AbstractAttribute<V> cast(final AbstractAttribute<?> attribute, final Class<V> valueClass)
+    public static <V> Attribute<V> cast(final Attribute<?> attribute, final Class<V> valueClass)
             throws ClassCastException
     {
         if (attribute != null) {
@@ -104,22 +115,22 @@ public final class Features extends Static {
                         attribute.getName(), valueClass, actual));
             }
         }
-        return (AbstractAttribute<V>) attribute;
+        return (Attribute<V>) attribute;
     }
 
     /**
      * Returns the name of the type of values that the given property can take.
-     * The type of value can be a {@link Class}, a {@code FeatureType}
+     * The type of value can be a {@link Class}, a {@link org.opengis.feature.FeatureType}
      * or another {@code PropertyType} depending on given argument:
      *
      * <ul>
-     *   <li>If {@code property} is an {@code AttributeType}, then this method gets the
+     *   <li>If {@code property} is an {@link AttributeType}, then this method gets the
      *       {@linkplain DefaultAttributeType#getValueClass() value class} and
      *       {@linkplain DefaultNameFactory#toTypeName(Class) maps that class to a name}.</li>
-     *   <li>If {@code property} is a {@code FeatureAssociationRole}, then this method gets
+     *   <li>If {@code property} is a {@link FeatureAssociationRole}, then this method gets
      *       the name of the {@linkplain DefaultAssociationRole#getValueType() value type}.
      *       This methods can work even if the associated {@code FeatureType} is not yet resolved.</li>
-     *   <li>If {@code property} is an {@code Operation}, then this method returns the name of the
+     *   <li>If {@code property} is an {@link Operation}, then this method returns the name of the
      *       {@linkplain AbstractOperation#getResult() result type}.</li>
      * </ul>
      *
@@ -128,15 +139,15 @@ public final class Features extends Static {
      *
      * @since 0.8
      */
-    public static GenericName getValueTypeName(final AbstractIdentifiedType property) {
-        if (property instanceof DefaultAssociationRole) {
+    public static GenericName getValueTypeName(final PropertyType property) {
+        if (property instanceof FeatureAssociationRole) {
             // Tested first because this is the main interest for this method.
-            return DefaultAssociationRole.getValueTypeName((DefaultAssociationRole) property);
-        } else if (property instanceof DefaultAttributeType<?>) {
+            return DefaultAssociationRole.getValueTypeName((FeatureAssociationRole) property);
+        } else if (property instanceof AttributeType<?>) {
             final DefaultNameFactory factory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
-            return factory.toTypeName(((DefaultAttributeType<?>) property).getValueClass());
-        } else if (property instanceof AbstractOperation) {
-            final AbstractIdentifiedType result = ((AbstractOperation) property).getResult();
+            return factory.toTypeName(((AttributeType<?>) property).getValueClass());
+        } else if (property instanceof Operation) {
+            final IdentifiedType result = ((Operation) property).getResult();
             if (result != null) {
                 return result.getName();
             }
@@ -158,13 +169,24 @@ public final class Features extends Static {
      * {@code InvalidPropertyValueException} is thrown. Otherwise this method returns doing nothing.
      *
      * @param  feature  the feature to validate, or {@code null}.
-     * @throws IllegalArgumentException if the given feature is non-null and does not pass validation.
+     * @throws InvalidPropertyValueException if the given feature is non-null and does not pass validation.
      *
      * @since 0.7
      */
-    public static void validate(final AbstractFeature feature) throws IllegalArgumentException {
+    public static void validate(final Feature feature) throws InvalidPropertyValueException {
         if (feature != null) {
-            final DataQuality quality = feature.quality();
+            /*
+             * Delegate to AbstractFeature.quality() if possible because the user may have overridden the method.
+             * Otherwise fallback on the same code than AbstractFeature.quality() default implementation.
+             */
+            final DataQuality quality;
+            if (feature instanceof AbstractFeature) {
+                quality = ((AbstractFeature) feature).quality();
+            } else {
+                final Validator v = new Validator(ScopeCode.FEATURE);
+                v.validate(feature.getType(), feature);
+                quality = v.quality;
+            }
             /*
              * Loop on quality elements and check conformance results.
              * NOTE: other types of result are ignored for now, since those other
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
index df704ee..9a6e94f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
@@ -20,6 +20,12 @@ import java.util.Collection;
 import java.util.Iterator;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.MultiValuedPropertyException;
+import org.opengis.feature.InvalidPropertyValueException;
 import org.apache.sis.util.Deprecable;
 
 
@@ -32,7 +38,7 @@ import org.apache.sis.util.Deprecable;
  * @since   0.5
  * @module
  */
-abstract class Field<V> extends Property {
+abstract class Field<V> implements Property {
     /**
      * For subclass constructors.
      */
@@ -54,12 +60,12 @@ abstract class Field<V> extends Property {
      * Returns the field feature or attribute value, or {@code null} if none.
      *
      * @return the feature or attribute value (may be {@code null}).
-     * @throws IllegalStateException if this field contains more than one value.
+     * @throws MultiValuedPropertyException if this field contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws IllegalStateException;
+    public abstract V getValue() throws MultiValuedPropertyException;
 
     /**
      * Returns all features or attribute values, or an empty collection if none.
@@ -88,16 +94,16 @@ abstract class Field<V> extends Property {
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws IllegalArgumentException if the given collection contains too many elements.
+     * @throws InvalidPropertyValueException if the given collection contains too many elements.
      */
-    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
+    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
         V value = null;
         ArgumentChecks.ensureNonNull("values", values);
         final Iterator<? extends V> it = values.iterator();
         if (it.hasNext()) {
             value = it.next();
             if (it.hasNext()) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
+                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
             }
         }
         setValue(value);
@@ -106,7 +112,7 @@ abstract class Field<V> extends Property {
     /**
      * Returns whether the given property is deprecated.
      */
-    static boolean isDeprecated(final AbstractIdentifiedType type) {
+    static boolean isDeprecated(final PropertyType type) {
         return (type instanceof Deprecable) && ((Deprecable) type).isDeprecated();
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
index ca89ef2..450cfd6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
@@ -21,6 +21,9 @@ import java.util.Iterator;
 import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.opengis.feature.PropertyType;
+
 
 /**
  * Base class of property types having a value and a cardinality.
@@ -36,7 +39,7 @@ import org.apache.sis.util.resources.Errors;
  * @since   0.5
  * @module
  */
-abstract class FieldType extends AbstractIdentifiedType {
+abstract class FieldType extends AbstractIdentifiedType implements PropertyType {
     /**
      * For cross-version compatibility.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java b/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java
index 6a56bdb..6f2b9e8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/InvalidFeatureException.java
@@ -19,19 +19,27 @@ package org.apache.sis.feature;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.LocalizedException;
 
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.InvalidPropertyValueException;
+
 
 /**
  * Thrown when a feature fails at least one conformance test.
  *
+ * <div class="note"><b>Note:</b>
+ * this exception extends {@link InvalidPropertyValueException} because an Apache SIS feature
+ * can be invalid only if a property is invalid.</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
- * @see Features#validate(AbstractFeature)
+ * @see Features#validate(Feature)
  *
  * @since 0.7
  * @module
  */
-final class InvalidFeatureException extends IllegalArgumentException implements LocalizedException {
+final class InvalidFeatureException extends InvalidPropertyValueException implements LocalizedException {
     /**
      * For cross-version compatibility.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
index 4153fa4..7e9e7fb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java
@@ -26,6 +26,12 @@ import org.apache.sis.internal.feature.FeatureUtilities;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+
 
 /**
  * A link operation, which is like a redirection or an alias.
@@ -45,7 +51,7 @@ final class LinkOperation extends AbstractOperation {
     /**
      * The type of the result.
      */
-    private final AbstractIdentifiedType result;
+    private final PropertyType result;
 
     /**
      * The name of the referenced attribute or feature association.
@@ -57,8 +63,10 @@ final class LinkOperation extends AbstractOperation {
      *
      * @param identification  the name of the link, together with optional information.
      * @param referent        the referenced attribute or feature association.
+     *
+     * @see FeatureOperations#link(Map, PropertyType)
      */
-    LinkOperation(final Map<String,?> identification, AbstractIdentifiedType referent) {
+    LinkOperation(final Map<String,?> identification, PropertyType referent) {
         super(identification);
         if (referent instanceof LinkOperation) {
             referent = ((LinkOperation) referent).result;
@@ -83,7 +91,7 @@ final class LinkOperation extends AbstractOperation {
      * Returns the expected result type.
      */
     @Override
-    public AbstractIdentifiedType getResult() {
+    public IdentifiedType getResult() {
         return result;
     }
 
@@ -103,7 +111,7 @@ final class LinkOperation extends AbstractOperation {
      * @return the linked property from the given feature.
      */
     @Override
-    public Object apply(final AbstractFeature feature, final ParameterValueGroup parameters) {
+    public Property apply(final Feature feature, final ParameterValueGroup parameters) {
         ArgumentChecks.ensureNonNull("feature", feature);
         return feature.getProperty(referentName);
     }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
index 19f5ff8..00ff6b9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
@@ -21,6 +21,12 @@ import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.MultiValuedPropertyException;
+
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole association role} containing an arbitrary amount of values.
@@ -54,16 +60,16 @@ final class MultiValuedAssociation extends AbstractAssociation {
     /**
      * The association values.
      */
-    private CheckedArrayList<AbstractFeature> values;
+    private CheckedArrayList<Feature> values;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role Information about the association.
      */
-    public MultiValuedAssociation(final DefaultAssociationRole role) {
+    public MultiValuedAssociation(final FeatureAssociationRole role) {
         super(role);
-        values = new CheckedArrayList<>(AbstractFeature.class);
+        values = new CheckedArrayList<>(Feature.class);
     }
 
     /**
@@ -72,12 +78,12 @@ final class MultiValuedAssociation extends AbstractAssociation {
      * @param role   Information about the association.
      * @param values The initial values, or {@code null} for initializing to an empty list.
      */
-    MultiValuedAssociation(final DefaultAssociationRole role, final Object values) {
+    MultiValuedAssociation(final FeatureAssociationRole role, final Object values) {
         super(role);
         if (values == null) {
-            this.values = new CheckedArrayList<>(AbstractFeature.class);
+            this.values = new CheckedArrayList<>(Feature.class);
         } else {
-            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, AbstractFeature.class);
+            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, Feature.class);
         }
     }
 
@@ -85,14 +91,14 @@ final class MultiValuedAssociation extends AbstractAssociation {
      * Returns the feature, or {@code null} if none.
      *
      * @return the feature (may be {@code null}).
-     * @throws IllegalStateException if this association contains more than one value.
+     * @throws MultiValuedPropertyException if this association contains more than one value.
      */
     @Override
-    public AbstractFeature getValue() {
+    public Feature getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
@@ -105,7 +111,7 @@ final class MultiValuedAssociation extends AbstractAssociation {
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<AbstractFeature> getValues() {
+    public Collection<Feature> getValues() {
         return values;
     }
 
@@ -115,7 +121,7 @@ final class MultiValuedAssociation extends AbstractAssociation {
      * @param  value  the new value, or {@code null} for removing all values from this association.
      */
     @Override
-    public void setValue(final AbstractFeature value) {
+    public void setValue(final Feature value) {
         values.clear();
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
@@ -129,12 +135,12 @@ final class MultiValuedAssociation extends AbstractAssociation {
      * @param  newValues  the new values.
      */
     @Override
-    public void setValues(final Collection<? extends AbstractFeature> newValues) {
+    public void setValues(final Collection<? extends Feature> newValues) {
         if (newValues != values) {
             ArgumentChecks.ensureNonNull("values", newValues);  // The parameter name in public API is "values".
-            final DefaultFeatureType base = role.getValueType();
+            final FeatureType base = role.getValueType();
             values.clear();
-            for (final AbstractFeature value : newValues) {
+            for (final Feature value : newValues) {
                 ensureValid(base, value.getType());
                 values.add(value);
             }
@@ -153,7 +159,7 @@ final class MultiValuedAssociation extends AbstractAssociation {
     @SuppressWarnings("unchecked")
     public MultiValuedAssociation clone() throws CloneNotSupportedException {
         final MultiValuedAssociation clone = (MultiValuedAssociation) super.clone();
-        clone.values = (CheckedArrayList<AbstractFeature>) clone.values.clone();
+        clone.values = (CheckedArrayList<Feature>) clone.values.clone();
         return clone;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
index 5363bab..572eb0b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
@@ -23,6 +23,10 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.feature.Resources;
 
+// Branch-dependent imports
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.MultiValuedPropertyException;
+
 
 /**
  * An instance of an {@linkplain DefaultAttributeType attribute type} containing an arbitrary amount of values.
@@ -69,7 +73,7 @@ final class MultiValuedAttribute<V> extends AbstractAttribute<V> implements Clon
      *
      * @param  type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public MultiValuedAttribute(final DefaultAttributeType<V> type) {
+    public MultiValuedAttribute(final AttributeType<V> type) {
         super(type);
         values = new CheckedArrayList<>(type.getValueClass());
         final V value = type.getDefaultValue();
@@ -86,7 +90,7 @@ final class MultiValuedAttribute<V> extends AbstractAttribute<V> implements Clon
      * @param  values  the initial values, or {@code null} for initializing to an empty list.
      */
     @SuppressWarnings("unchecked")
-    MultiValuedAttribute(final DefaultAttributeType<V> type, final Object values) {
+    MultiValuedAttribute(final AttributeType<V> type, final Object values) {
         super(type);
         final Class<V> valueClass = type.getValueClass();
         if (values == null) {
@@ -105,14 +109,14 @@ final class MultiValuedAttribute<V> extends AbstractAttribute<V> implements Clon
      * Returns the attribute value, or {@code null} if none.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws IllegalStateException if this attribute contains more than one value.
+     * @throws MultiValuedPropertyException if this attribute contains more than one value.
      */
     @Override
     public V getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java b/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
index 5cb7456..0caa7cc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
@@ -16,10 +16,18 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Set;
 import java.util.Collection;
 import java.util.Collections;
 import java.io.Serializable;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.PropertyNotFoundException;
+import org.opengis.feature.FeatureInstantiationException;
 import org.opengis.util.GenericName;
+import org.opengis.util.InternationalString;
+import org.apache.sis.internal.feature.Resources;
 
 
 /**
@@ -66,11 +74,48 @@ final class NamedFeatureType implements FeatureType, Serializable {
         return name;
     }
 
+    /** Undefined. */ @Override public InternationalString getDefinition()  {return null;}
+    /** Undefined. */ @Override public InternationalString getDesignation() {return null;}
+    /** Undefined. */ @Override public InternationalString getDescription() {return null;}
+
+    /**
+     * Declares that this feature shall not be instantiated.
+     */
+    @Override
+    public boolean isAbstract() {
+        return true;
+    }
+
+    /**
+     * Conservatively assumes that the feature is not simple,
+     * since we do not know what the actual feature will be.
+     */
+    @Override
+    public boolean isSimple() {
+        return false;
+    }
+
+    /**
+     * Always throws {@link PropertyNotFoundException} since this feature type has no declared property yet.
+     */
+    @Override
+    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
+        throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
+    }
+
     /**
      * Returns an empty set since this feature has no declared property yet.
      */
     @Override
-    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
+    public Collection<? extends PropertyType> getProperties(final boolean includeSuperTypes) {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Returns an empty set since this feature has no declared parent yet.
+     */
+    @Override
+    public Set<? extends FeatureType> getSuperTypes() {
         return Collections.emptySet();
     }
 
@@ -78,8 +123,26 @@ final class NamedFeatureType implements FeatureType, Serializable {
      * This feature type is considered independent of all other feature types except itself.
      */
     @Override
-    public boolean isAssignableFrom(final DefaultFeatureType type) {
-        return false;
+    public boolean isAssignableFrom(FeatureType type) {
+        if (type == this) {
+            return true;
+        }
+        if (type instanceof NamedFeatureType) {
+            type = ((NamedFeatureType) type).resolved;
+        }
+        if (type == null) {
+            return false;
+        }
+        final FeatureType resolved = this.resolved;
+        return (resolved != null) && resolved.isAssignableFrom(type);
+    }
+
+    /**
+     * Unsupported operation, since the feature has not yet been resolved.
+     */
+    @Override
+    public Feature newInstance() throws FeatureInstantiationException {
+        throw new FeatureInstantiationException(Resources.format(Resources.Keys.UnresolvedFeatureName_1, getName()));
     }
 
     /**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java
deleted file mode 100644
index 00b4ff3..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.feature;
-
-import org.opengis.util.GenericName;
-
-
-/**
- * Place-holder for an interface not available in GeoAPI 3.0.
- * This place-holder will be removed after we upgrade to a later GeoAPI version.
- *
- * <p><strong>Do not put this type in public API</strong>. We need to prevent users from using
- * this type in order to reduce compatibility breaks when we will upgrade the GeoAPI version.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @since   0.5
- * @version 0.5
- * @module
- */
-abstract class Property {
-    public abstract GenericName getName();
-
-    public abstract Object getValue();
-}
diff --git 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
index 975ccf6..0c2ab30 100644
--- 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
@@ -21,13 +21,22 @@ import java.util.Objects;
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Collections;
-import org.opengis.util.GenericName;
+import java.io.Serializable;
 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.
@@ -43,11 +52,32 @@ import org.apache.sis.internal.feature.Resources;
  * @since   0.8
  * @module
  */
-final class PropertyView {
+abstract class PropertyView<V> extends Field<V> implements Property, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -5605415150581699255L;
+
     /**
-     * Do not allow instantiation of this class.
+     * The feature from which to read and where to write the attribute or association value.
+     */
+    final Feature feature;
+
+    /**
+     * The string representation of the property name. This is the value to be given in calls to
+     * {@link Feature#getPropertyValue(String)} and {@link Feature#setPropertyValue(String, Object)}.
+     */
+    final String name;
+
+    /**
+     * Creates a new property which will delegate its work to the given feature.
+     *
+     * @param feature  the feature from which to read and where to write the property value.
+     * @param name     the string representation of the property name.
      */
-    private PropertyView() {
+    PropertyView(final Feature feature, final String name) {
+        this.feature = feature;
+        this.name = name;
     }
 
     /**
@@ -57,26 +87,32 @@ final class PropertyView {
      * @param type     the type of the property. Must be one of the properties listed in the
      *                 {@code feature} (this is not verified by this constructor).
      */
-    static Property create(final AbstractFeature feature, final AbstractIdentifiedType type) {
-        if (type instanceof DefaultAttributeType<?>) {
-            return AttributeView.create(feature, (DefaultAttributeType<?>) type);
-        } else if (type instanceof DefaultAssociationRole) {
-            return AssociationView.create(feature, (DefaultAssociationRole) type);
-        } else if (type instanceof AbstractOperation) {
-            return (Property) ((AbstractOperation) type).apply(feature, null);
+    static Property create(final Feature feature, final PropertyType type) {
+        if (type instanceof AttributeType<?>) {
+            return AttributeView.create(feature, (AttributeType<?>) type);
+        } else if (type instanceof FeatureAssociationRole) {
+            return AssociationView.create(feature, (FeatureAssociationRole) type);
+        } else if (type instanceof Operation) {
+            return ((Operation) type).apply(feature, null);
         } else {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, Classes.getClass(type)));
         }
     }
 
     /**
+     * Returns the class of values.
+     */
+    abstract Class<V> getValueClass();
+
+    /**
      * Returns the singleton value. This default implementation assumes that the property is multi-valued
      * (single-valued properties shall override this method), but we nevertheless provide a fallback for
      * non-{@code Iterable} values as a safety against implementations that are not strictly compliant
-     * to our {@code Feature.getPropertyValue(String)} method contract. Then this method verifies that
+     * to our {@link Feature#getPropertyValue(String)} method contract. Then this method verifies that
      * the value is a collection containing zero or one element and returns that element or {@code null}.
      */
-    static Object getValue(final AbstractFeature feature, final String name) {
+    @Override
+    public V getValue() throws MultiValuedPropertyException {
         Object value = feature.getPropertyValue(name);
         if (value instanceof Iterable<?>) {
             final Iterator<?> it = ((Iterable<?>) value).iterator();
@@ -85,18 +121,19 @@ final class PropertyView {
             }
             value = it.next();
             if (it.hasNext()) {
-                throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, name));
+                throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, name));
             }
         }
-        return value;
+        return getValueClass().cast(value);
     }
 
     /**
      * Sets the values of the given attribute. This default implementation assumes that the property
      * is multi-valued (single-valued properties shall override this method) and that the
-     * {@code Feature.setPropertyValue(String, Object)} implementation will verify the argument type.
+     * {@link Feature#setPropertyValue(String, Object)} implementation will verify the argument type.
      */
-    static void setValue(final AbstractFeature feature, final String name, final Object value) {
+    @Override
+    public void setValue(final V value) {
         feature.setPropertyValue(name, singletonOrEmpty(value));
     }
 
@@ -116,11 +153,13 @@ final class PropertyView {
      * contains elements of the expected type, but this verification is not always possible.
      * Consequently this method may, sometime, be actually unsafe.
      */
+    @Override
     @SuppressWarnings("unchecked")              // Actually not 100% safe, but we have done our best.
-    static <V> Collection<V> getValues(final AbstractFeature feature, final String name, final Class<V> expected) {
+    public Collection<V> getValues() {
         final Object values = feature.getPropertyValue(name);
         if (values instanceof Collection<?>) {
             if (values instanceof CheckedContainer<?>) {
+                final Class<?> expected = getValueClass();
                 final Class<?> actual = ((CheckedContainer<?>) values).getElementType();
                 if (expected != actual) {       // Really exact match, not Class.isAssignableFrom(Class).
                     throw new ClassCastException(Errors.format(Errors.Keys.UnexpectedTypeForReference_3, name, expected, actual));
@@ -128,32 +167,50 @@ final class PropertyView {
             }
             return (Collection<V>) values;
         } else {
-            return singletonOrEmpty(expected.cast(values));
+            return singletonOrEmpty(getValueClass().cast(values));
         }
     }
 
     /**
      * Sets the values of the given attribute. This method assumes that the
-     * {@code Feature.setPropertyValue(String, Object)} implementation will
+     * {@link Feature#setPropertyValue(String, Object)} implementation will
      * verify the argument type.
      */
-    static void setValues(final AbstractFeature feature, final String name, final Collection<?> values) {
+    @Override
+    public final void setValues(final Collection<? extends V> values) {
         feature.setPropertyValue(name, values);
     }
 
     /**
      * Returns a hash code value for this property.
      */
-    static int hashCode(final AbstractFeature feature, final String name) {
+    @Override
+    public final int hashCode() {
         return Objects.hashCode(name) ^ System.identityHashCode(feature);
     }
 
     /**
+     * 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
-    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();
+    @Override
+    public final String toString() {
+        return FieldType.toString(false, getClass().getSimpleName(), getName(),
+                Classes.getShortName(getValueClass()), getValues().iterator()).toString();
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
index 27eb607..4601cc7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
@@ -18,6 +18,11 @@ package org.apache.sis.feature;
 
 import java.util.Objects;
 
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.InvalidPropertyValueException;
+
 
 /**
  * An instance of an {@linkplain DefaultAssociationRole association role} containing at most one value.
@@ -49,14 +54,14 @@ final class SingletonAssociation extends AbstractAssociation {
     /**
      * The associated feature.
      */
-    private AbstractFeature value;
+    private Feature value;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      */
-    public SingletonAssociation(final DefaultAssociationRole role) {
+    public SingletonAssociation(final FeatureAssociationRole role) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
     }
@@ -67,7 +72,7 @@ final class SingletonAssociation extends AbstractAssociation {
      * @param role   information about the association.
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAssociation(final DefaultAssociationRole role, final AbstractFeature value) {
+    SingletonAssociation(final FeatureAssociationRole role, final Feature value) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
         this.value = value;
@@ -82,7 +87,7 @@ final class SingletonAssociation extends AbstractAssociation {
      * @return the associated feature (may be {@code null}).
      */
     @Override
-    public AbstractFeature getValue() {
+    public Feature getValue() {
         return value;
     }
 
@@ -90,10 +95,10 @@ final class SingletonAssociation extends AbstractAssociation {
      * Sets the associated feature.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws IllegalArgumentException if the given feature is not valid for this association.
+     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
      */
     @Override
-    public void setValue(final AbstractFeature value) throws IllegalArgumentException {
+    public void setValue(final Feature value) throws InvalidPropertyValueException {
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
index ba3b60f..235f75d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
@@ -19,6 +19,7 @@ package org.apache.sis.feature;
 import java.util.Objects;
 
 // Branch-dependent imports
+import org.opengis.feature.AttributeType;
 
 
 /**
@@ -64,7 +65,7 @@ final class SingletonAttribute<V> extends AbstractAttribute<V> implements Clonea
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public SingletonAttribute(final DefaultAttributeType<V> type) {
+    public SingletonAttribute(final AttributeType<V> type) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         value = type.getDefaultValue();
@@ -77,7 +78,7 @@ final class SingletonAttribute<V> extends AbstractAttribute<V> implements Clonea
      * @param type   information about the attribute (base Java class, domain of values, <i>etc.</i>).
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAttribute(final DefaultAttributeType<V> type, final Object value) {
+    SingletonAttribute(final AttributeType<V> type, final Object value) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         this.value = type.getValueClass().cast(value);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
index 056a64a..f2a46a5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
@@ -26,6 +26,12 @@ import org.apache.sis.internal.util.Cloner;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CorruptedObjectException;
 
+// Branch-dependent imports
+import org.opengis.feature.Property;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.PropertyNotFoundException;
+
 
 /**
  * A feature in which only a small fraction of properties are expected to be provided. This implementation uses
@@ -114,14 +120,14 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
      * @param  name  the property name.
      * @return the index for the property of the given name,
      *         or a negative value if the property is a parameterless operation.
-     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
      */
-    private int getIndex(final String name) throws IllegalArgumentException {
+    private int getIndex(final String name) throws PropertyNotFoundException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
+        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -166,10 +172,10 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
      */
     @Override
-    public Object getProperty(final String name) throws IllegalArgumentException {
+    public Property getProperty(final String name) throws PropertyNotFoundException {
         ArgumentChecks.ensureNonNull("name", name);
         requireMapOfProperties();
         return getPropertyInstance(name);
@@ -179,11 +185,11 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
      * Implementation of {@link #getProperty(String)} invoked when we know that the {@link #properties}
      * map contains {@code Property} instances (as opposed to their value).
      */
-    private Property getPropertyInstance(final String name) throws IllegalArgumentException {
+    private Property getPropertyInstance(final String name) throws PropertyNotFoundException {
         assert valuesKind == PROPERTIES : valuesKind;
         final Integer index = getIndex(name);
         if (index < 0) {
-            return (Property) getOperationResult(name);
+            return getOperationResult(name);
         }
         Property property = (Property) properties.get(index);
         if (property == null) {
@@ -201,10 +207,10 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
      *         known to this feature, or if the property can not be set for another reason.
      */
     @Override
-    public void setProperty(final Object property) throws IllegalArgumentException {
+    public void setProperty(final Property property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = ((Property) property).getName().toString();
-        verifyPropertyType(name, (Property) property);
+        final String name = property.getName().toString();
+        verifyPropertyType(name, property);
         requireMapOfProperties();
         /*
          * Following index should never be OPERATION_INDEX (a negative value) because the call
@@ -218,10 +224,10 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
+     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws IllegalArgumentException {
+    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
         ArgumentChecks.ensureNonNull("name", name);
         final Integer index = getIndex(name);
         if (index < 0) {
@@ -231,10 +237,10 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
         if (element != null) {
             if (valuesKind == VALUES) {
                 return element; // Most common case.
-            } else if (element instanceof AbstractAttribute<?>) {
-                return getAttributeValue((AbstractAttribute<?>) element);
-            } else if (element instanceof AbstractAssociation) {
-                return getAssociationValue((AbstractAssociation) element);
+            } else if (element instanceof Attribute<?>) {
+                return getAttributeValue((Attribute<?>) element);
+            } else if (element instanceof FeatureAssociation) {
+                return getAssociationValue((FeatureAssociation) element);
             } else if (valuesKind == PROPERTIES) {
                 throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
             } else {
@@ -367,10 +373,10 @@ final class SparseFeature extends AbstractFeature implements Cloneable {
             for (final Map.Entry<Integer,Object> entry : properties.entrySet()) {
                 final Object p = entry.getValue();
                 final Object value;
-                if (p instanceof AbstractAttribute<?>) {
-                    value = getAttributeValue((AbstractAttribute<?>) p);
-                } else if (p instanceof AbstractAssociation) {
-                    value = getAssociationValue((AbstractAssociation) p);
+                if (p instanceof Attribute<?>) {
+                    value = getAttributeValue((Attribute<?>) p);
+                } else if (p instanceof FeatureAssociation) {
+                    value = getAssociationValue((FeatureAssociation) p);
                 } else {
                     value = null;
                 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
index 355a92f..6a4fa83 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/StringJoinOperation.java
@@ -38,6 +38,18 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Classes;
 
+// Branch-dependent imports
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.InvalidPropertyValueException;
+import org.opengis.feature.Operation;
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.PropertyNotFoundException;
+
 
 /**
  * An operation concatenating the string representations of the values of multiple properties.
@@ -114,7 +126,7 @@ final class StringJoinOperation extends AbstractOperation {
         @Override public Class<Object>                  getTargetClass() {return Object.class;}
         @Override public Object apply(final Object f) {
             return (f != null) ? format(converter.inverse(),
-                    ((AbstractFeature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
+                    ((Feature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
         }
     }
 
@@ -143,7 +155,7 @@ final class StringJoinOperation extends AbstractOperation {
     /**
      * The type of the result returned by the string concatenation operation.
      */
-    private final DefaultAttributeType<String> resultType;
+    private final AttributeType<String> resultType;
 
     /**
      * The characters to use at the beginning of the concatenated string, or an empty string if none.
@@ -165,11 +177,11 @@ final class StringJoinOperation extends AbstractOperation {
      * It is caller's responsibility to ensure that {@code delimiter} and {@code singleAttributes} are not null.
      * This private constructor does not verify that condition on the assumption that the public API did.
      *
-     * @see FeatureOperations#compound(Map, String, String, String, AbstractIdentifiedType...)
+     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
      */
     @SuppressWarnings({"rawtypes", "unchecked"})                                        // Generic array creation.
     StringJoinOperation(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes)
+            final String prefix, final String suffix, final PropertyType[] singleAttributes)
             throws UnconvertibleObjectException
     {
         super(identification);
@@ -188,29 +200,29 @@ final class StringJoinOperation extends AbstractOperation {
              * which may in turn produce an AttributeType. We do not accept more complex
              * combinations (e.g. operation producing an association).
              */
-            AbstractIdentifiedType propertyType = singleAttributes[i];
+            IdentifiedType propertyType = singleAttributes[i];
             ArgumentChecks.ensureNonNullElement("singleAttributes", i, propertyType);
             final GenericName name = propertyType.getName();
             int maximumOccurs = 0;                              // May be a bitwise combination; need only to know if > 1.
-            IllegalArgumentException cause = null;              // In case of failure to find "sis:identifier" property.
-            final boolean isAssociation = (propertyType instanceof DefaultAssociationRole);
+            PropertyNotFoundException cause = null;             // In case of failure to find "sis:identifier" property.
+            final boolean isAssociation = (propertyType instanceof FeatureAssociationRole);
             if (isAssociation) {
-                final DefaultAssociationRole role = (DefaultAssociationRole) propertyType;
-                final DefaultFeatureType ft = role.getValueType();
+                final FeatureAssociationRole role = (FeatureAssociationRole) propertyType;
+                final FeatureType ft = role.getValueType();
                 maximumOccurs = role.getMaximumOccurs();
                 try {
                     propertyType = ft.getProperty(AttributeConvention.IDENTIFIER);
-                } catch (IllegalArgumentException e) {
+                } catch (PropertyNotFoundException e) {
                     cause = e;
                 }
             }
-            if (propertyType instanceof AbstractOperation) {
-                propertyType = ((AbstractOperation) propertyType).getResult();
+            if (propertyType instanceof Operation) {
+                propertyType = ((Operation) propertyType).getResult();
             }
-            if (propertyType instanceof DefaultAttributeType) {
-                maximumOccurs |= ((DefaultAttributeType<?>) propertyType).getMaximumOccurs();
+            if (propertyType instanceof AttributeType) {
+                maximumOccurs |= ((AttributeType<?>) propertyType).getMaximumOccurs();
             } else {
-                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), AbstractIdentifiedType.class);
+                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), PropertyType.class);
                 throw new IllegalArgumentException(Resources.forProperties(identification)
                         .getString(Resources.Keys.IllegalPropertyType_2, name, (inf.length != 0) ? inf[0] : null), cause);
             }
@@ -224,7 +236,7 @@ final class StringJoinOperation extends AbstractOperation {
              */
             attributeNames[i] = name.toString();
             ObjectConverter<? super String, ?> converter = ObjectConverters.find(
-                    String.class, ((DefaultAttributeType<?>) propertyType).getValueClass());
+                    String.class, ((AttributeType<?>) propertyType).getValueClass());
             if (isAssociation) {
                 converter = new ForFeature(converter);
             }
@@ -255,7 +267,7 @@ final class StringJoinOperation extends AbstractOperation {
      * @return an {@code AttributeType<String>}.
      */
     @Override
-    public AbstractIdentifiedType getResult() {
+    public IdentifiedType getResult() {
         return resultType;
     }
 
@@ -291,7 +303,7 @@ final class StringJoinOperation extends AbstractOperation {
      * @return the concatenation of feature property values.
      */
     @Override
-    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
+    public Property apply(Feature feature, ParameterValueGroup parameters) {
         ArgumentChecks.ensureNonNull("feature", feature);
         return new Result(feature);
     }
@@ -310,14 +322,14 @@ final class StringJoinOperation extends AbstractOperation {
         private static final long serialVersionUID = -8435975199763452547L;
 
         /**
-         * The feature specified to the {@link StringJoinOperation#apply(AbstractFeature, ParameterValueGroup)} method.
+         * The feature specified to the {@link StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
          */
-        private final AbstractFeature feature;
+        private final Feature feature;
 
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final AbstractFeature feature) {
+        Result(final Feature feature) {
             super(resultType);
             this.feature = feature;
         }
@@ -377,14 +389,14 @@ final class StringJoinOperation extends AbstractOperation {
          * parsed, then this method does not store any property value ("all or nothing" behavior).
          *
          * @param  value  the concatenated string.
-         * @throws IllegalArgumentException if one of the attribute values can not be parsed to the expected type.
+         * @throws InvalidPropertyValueException if one of the attribute values can not be parsed to the expected type.
          */
         @Override
-        public void setValue(final String value) throws IllegalArgumentException {
+        public void setValue(final String value) throws InvalidPropertyValueException {
             final int endAt = value.length() - suffix.length();
             final boolean prefixMatches = value.startsWith(prefix);
             if (!prefixMatches || !value.endsWith(suffix)) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
+                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
                         getName(),
                         prefixMatches ? 1 : 0,              // For "{1,choice,0#begin|1#end}" in message format.
                         prefixMatches ? suffix : prefix,
@@ -451,7 +463,7 @@ final class StringJoinOperation extends AbstractOperation {
                     try {
                         values[count] = converter.apply(element);
                     } catch (UnconvertibleObjectException e) {
-                        throw new IllegalArgumentException(Errors.format(
+                        throw new InvalidPropertyValueException(Errors.format(
                                 Errors.Keys.CanNotAssign_2, attributeNames[count], element), e);
                     }
                 }
@@ -465,14 +477,14 @@ final class StringJoinOperation extends AbstractOperation {
              * below do not fail).
              */
             if (values.length != count) {
-                throw new IllegalArgumentException(Resources.format(
+                throw new InvalidPropertyValueException(Resources.format(
                         Resources.Keys.UnexpectedNumberOfComponents_4, getName(), value, values.length, count));
             }
             for (int i=0; i < values.length; i++) {
-                AbstractFeature f = feature;
+                Feature f   = feature;
                 String name = attributeNames[i];
                 if (converters[i] instanceof ForFeature) {
-                    f = (AbstractFeature) f.getPropertyValue(name);
+                    f = (Feature) f.getPropertyValue(name);
                     name = AttributeConvention.IDENTIFIER;
                 }
                 f.setPropertyValue(name, values[i]);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java b/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
index f8b7774..6b759f3 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
@@ -28,10 +28,20 @@ import org.apache.sis.metadata.iso.quality.AbstractElement;
 import org.apache.sis.metadata.iso.quality.DefaultDataQuality;
 import org.apache.sis.metadata.iso.quality.DefaultDomainConsistency;
 import org.apache.sis.metadata.iso.quality.DefaultConformanceResult;
-import org.apache.sis.metadata.iso.maintenance.DefaultScope;
+import org.apache.sis.metadata.iso.quality.DefaultScope;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.Attribute;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociation;
+import org.opengis.feature.FeatureAssociationRole;
+
 
 /**
  * Provides validation methods to be shared by different implementations.
@@ -78,7 +88,7 @@ final class Validator {
      * @return the {@code report}, or a new report if {@code report} was null.
      */
     private AbstractElement addViolationReport(AbstractElement report,
-            final AbstractIdentifiedType type, final InternationalString explanation)
+            final PropertyType type, final InternationalString explanation)
     {
         if (report == null) {
             final GenericName name = type.getName();
@@ -109,19 +119,19 @@ final class Validator {
      * @param type     the type of the {@code feature} argument, provided explicitely for protecting from user overriding.
      * @param feature  the feature to validate.
      */
-    void validate(final FeatureType type, final AbstractFeature feature) {
-        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
-            final Object property = feature.getProperty(pt.getName().toString());
+    void validate(final FeatureType type, final Feature feature) {
+        for (final PropertyType pt : type.getProperties(true)) {
+            final Property property = feature.getProperty(pt.getName().toString());
             final DataQuality pq;
             if (property instanceof AbstractAttribute<?>) {
                 pq = ((AbstractAttribute<?>) property).quality();
             } else if (property instanceof AbstractAssociation) {
                 pq = ((AbstractAssociation) property).quality();
-            } else if (property instanceof AbstractAttribute<?>) {
-                validate(((AbstractAttribute<?>) property).getType(), ((AbstractAttribute<?>) property).getValues());
+            } else if (property instanceof Attribute<?>) {
+                validate(((Attribute<?>) property).getType(), ((Attribute<?>) property).getValues());
                 continue;
-            } else if (property instanceof AbstractAssociation) {
-                validate(((AbstractAssociation) property).getRole(), ((AbstractAssociation) property).getValues());
+            } else if (property instanceof FeatureAssociation) {
+                validate(((FeatureAssociation) property).getRole(), ((FeatureAssociation) property).getValues());
                 continue;
             } else {
                 continue;
@@ -136,21 +146,21 @@ final class Validator {
      * Verifies if the given value is valid for the given attribute type.
      * This method delegates to one of the {@code validate(…)} methods depending of the value type.
      */
-    void validateAny(final AbstractIdentifiedType type, final Object value) {
-        if (type instanceof DefaultAttributeType<?>) {
-            validate((DefaultAttributeType<?>) type, asList(value,
-                    ((DefaultAttributeType<?>) type).getMaximumOccurs()));
+    void validateAny(final PropertyType type, final Object value) {
+        if (type instanceof AttributeType<?>) {
+            validate((AttributeType<?>) type, asList(value,
+                    ((AttributeType<?>) type).getMaximumOccurs()));
         }
-        if (type instanceof DefaultAssociationRole) {
-            validate((DefaultAssociationRole) type, asList(value,
-                    ((DefaultAssociationRole) type).getMaximumOccurs()));
+        if (type instanceof FeatureAssociationRole) {
+            validate((FeatureAssociationRole) type, asList(value,
+                    ((FeatureAssociationRole) type).getMaximumOccurs()));
         }
     }
 
     /**
      * Verifies if the given values are valid for the given attribute type.
      */
-    void validate(final DefaultAttributeType<?> type, final Collection<?> values) {
+    void validate(final AttributeType<?> type, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
             /*
@@ -173,10 +183,10 @@ final class Validator {
     /**
      * Verifies if the given value is valid for the given association role.
      */
-    void validate(final DefaultAssociationRole role, final Collection<?> values) {
+    void validate(final FeatureAssociationRole role, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
-            final DefaultFeatureType type = ((AbstractFeature) value).getType();
+            final FeatureType type = ((Feature) value).getType();
             final FeatureType valueType = role.getValueType();
             if (!valueType.isAssignableFrom(type)) {
                 report = addViolationReport(report, role, Errors.formatInternational(
@@ -194,7 +204,7 @@ final class Validator {
      *
      * @param report  where to add the result, or {@code null} if not yet created.
      */
-    private void verifyCardinality(final AbstractElement report, final AbstractIdentifiedType type,
+    private void verifyCardinality(final AbstractElement report, final PropertyType type,
             final int minimumOccurs, final int maximumOccurs, final int count)
     {
         if (count < minimumOccurs) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
index bf9c347..164c31b 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java
@@ -21,21 +21,22 @@ import org.apache.sis.feature.Features;
 import org.apache.sis.feature.DefaultAssociationRole;
 
 // Branch-dependent imports
-import org.apache.sis.feature.DefaultFeatureType;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.FeatureAssociationRole;
 
 
 /**
  * Describes one association from the {@code FeatureType} to be built by an {@code FeatureTypeBuilder} to another
  * {@code FeatureType}. A different instance of {@code AssociationRoleBuilder} exists for each feature association
- * to describe. Those instances are created preferably by {@code FeatureTypeBuilder.addAssociation(FeatureType)},
- * or in case of cyclic reference by {@code FeatureTypeBuilder.addAssociation(GenericName)}.
+ * to describe. Those instances are created preferably by {@link FeatureTypeBuilder#addAssociation(FeatureType)},
+ * or in case of cyclic reference by {@link FeatureTypeBuilder#addAssociation(GenericName)}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
  * @see org.apache.sis.feature.DefaultAssociationRole
- * @see FeatureTypeBuilder#addAssociation(DefaultFeatureType)
+ * @see FeatureTypeBuilder#addAssociation(FeatureType)
  * @see FeatureTypeBuilder#addAssociation(GenericName)
  *
  * @since 0.8
@@ -45,7 +46,7 @@ public final class AssociationRoleBuilder extends PropertyTypeBuilder {
     /**
      * The target feature type, or {@code null} if unknown.
      */
-    private final DefaultFeatureType type;
+    private final FeatureType type;
 
     /**
      * Name of the target feature type (never null).
@@ -56,7 +57,7 @@ public final class AssociationRoleBuilder extends PropertyTypeBuilder {
      * The association created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient DefaultAssociationRole property;
+    private transient FeatureAssociationRole property;
 
     /**
      * Creates a new {@code AssociationRole} builder for values of the given type.
@@ -64,7 +65,7 @@ public final class AssociationRoleBuilder extends PropertyTypeBuilder {
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultFeatureType type, final GenericName typeName) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureType type, final GenericName typeName) {
         super(owner);
         this.type     = type;
         this.typeName = typeName;
@@ -75,12 +76,12 @@ public final class AssociationRoleBuilder extends PropertyTypeBuilder {
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultAssociationRole template) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureAssociationRole template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
-        if (!template.isResolved()) {
+        if (template instanceof DefaultAssociationRole && !((DefaultAssociationRole) template).isResolved()) {
             type     = null;
             typeName = Features.getValueTypeName(template);
         } else {
@@ -248,13 +249,10 @@ public final class AssociationRoleBuilder extends PropertyTypeBuilder {
      * If a role has already been built and this builder state has not changed since the role creation,
      * then the previously created {@code FeatureAssociationRole} instance is returned.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
-     * {@code org.opengis.feature.FeatureAssociationRole} interface. This change is pending GeoAPI revision.</div>
-     *
      * @return the association role.
      */
     @Override
-    public DefaultAssociationRole build() {
+    public FeatureAssociationRole build() {
         if (property == null) {
             if (type != null) {
                 property = new DefaultAssociationRole(identification(), type, minimumOccurs, maximumOccurs);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
index 6ec2587..2b9eab2 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java
@@ -40,6 +40,7 @@ import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.UnconvertibleObjectException;
 
 // Branch-dependent imports
+import org.opengis.feature.AttributeType;
 
 
 /**
@@ -103,7 +104,7 @@ public final class AttributeTypeBuilder<V> extends PropertyTypeBuilder {
      * The attribute type created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient DefaultAttributeType<V> property;
+    private transient AttributeType<V> property;
 
     /**
      * Creates a new builder initialized to the values of the given builder.
@@ -138,16 +139,16 @@ public final class AttributeTypeBuilder<V> extends PropertyTypeBuilder {
      *
      * @param owner  the builder of the {@code FeatureType} for which to add the attribute.
      */
-    AttributeTypeBuilder(final FeatureTypeBuilder owner, final DefaultAttributeType<V> template) {
+    AttributeTypeBuilder(final FeatureTypeBuilder owner, final AttributeType<V> template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
         valueClass    = template.getValueClass();
         defaultValue  = template.getDefaultValue();
-        final Map<String, DefaultAttributeType<?>> tc = template.characteristics();
+        final Map<String, AttributeType<?>> tc = template.characteristics();
         characteristics = new ArrayList<>(tc.size());
-        for (final DefaultAttributeType<?> c : tc.values()) {
+        for (final AttributeType<?> c : tc.values()) {
             characteristics.add(new CharacteristicTypeBuilder<>(this, c));
         }
         initialize(template);
@@ -493,17 +494,13 @@ public final class AttributeTypeBuilder<V> extends PropertyTypeBuilder {
      * Adds another attribute type that describes this attribute type, using an existing one as a template.
      * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  <C>       the compile-time type of values in the {@code template} argument.
      * @param  template  an existing attribute type to use as a template.
      * @return a builder for a characteristic of this attribute, initialized with the values of the given template.
      *
      * @see #characteristics()
      */
-    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final DefaultAttributeType<C> template) {
+    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final AttributeType<C> template) {
         ensureNonNull("template", template);
         final CharacteristicTypeBuilder<C> characteristic = new CharacteristicTypeBuilder<>(this, template);
         characteristics.add(characteristic);
@@ -521,7 +518,7 @@ public final class AttributeTypeBuilder<V> extends PropertyTypeBuilder {
      *
      * @see #getCharacteristic(String)
      * @see #addCharacteristic(Class)
-     * @see #addCharacteristic(DefaultAttributeType)
+     * @see #addCharacteristic(AttributeType)
      * @see #setValidValues(Object...)
      * @see #setCRS(CoordinateReferenceSystem)
      */
@@ -724,15 +721,12 @@ public final class AttributeTypeBuilder<V> extends PropertyTypeBuilder {
      * By comparison, this {@code build()} method has a more accurate return type.
      * </div>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
-     * {@code org.opengis.feature.AttributeType} interface. This change is pending GeoAPI revision.</div>
-     *
      * @return the attribute type.
      */
     @Override
-    public DefaultAttributeType<V> build() {
+    public AttributeType<V> build() {
         if (property == null) {
-            final DefaultAttributeType<?>[] chrts = new DefaultAttributeType<?>[characteristics.size()];
+            final AttributeType<?>[] chrts = new AttributeType<?>[characteristics.size()];
             for (int i=0; i<chrts.length; i++) {
                 chrts[i] = characteristics.get(i).build();
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
index 35d380c..45c316f 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java
@@ -24,6 +24,7 @@ import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.UnconvertibleObjectException;
 
 // Branch-dependent imports
+import org.opengis.feature.AttributeType;
 
 
 /**
@@ -69,7 +70,7 @@ public final class CharacteristicTypeBuilder<V> extends TypeBuilder {
      * The characteristic created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient DefaultAttributeType<V> characteristic;
+    private transient AttributeType<V> characteristic;
 
     /**
      * Creates a new builder initialized to the values of the given builder but a different type.
@@ -104,7 +105,7 @@ public final class CharacteristicTypeBuilder<V> extends TypeBuilder {
      *
      * @param owner  the builder of the {@code AttributeType} for which to add this property.
      */
-    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final DefaultAttributeType<V> template) {
+    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final AttributeType<V> template) {
         super(owner.getLocale());
         this.owner     = owner;
         characteristic = template;
@@ -320,13 +321,10 @@ public final class CharacteristicTypeBuilder<V> extends TypeBuilder {
      * If a type has already been built and this builder state has not changed since the type creation,
      * then the previously created {@code AttributeType} instance is returned.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
-     * {@code org.opengis.feature.AttributeType} interface. This change is pending GeoAPI revision.</div>
-     *
      * @return the characteristic type.
      */
     @Override
-    public DefaultAttributeType<V> build() {
+    public AttributeType<V> build() {
         if (characteristic == null) {
             characteristic = new DefaultAttributeType<>(identification(), valueClass, 0, 1, defaultValue);
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
index ba883a2..4c13a5d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
@@ -43,14 +43,16 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArraysExt;
 
 // Branch-dependent imports
-import org.apache.sis.feature.AbstractFeature;
-import org.apache.sis.feature.AbstractIdentifiedType;
-import org.apache.sis.feature.DefaultAssociationRole;
-import org.apache.sis.feature.DefaultAttributeType;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.Operation;
 
 
 /**
- * Helper class for the creation of {@code FeatureType} instances.
+ * Helper class for the creation of {@link FeatureType} instances.
  * This builder can create the arguments to be given to the
  * {@linkplain DefaultFeatureType#DefaultFeatureType feature type constructor}
  * from simpler parameters given to this builder.
@@ -64,7 +66,7 @@ import org.apache.sis.feature.DefaultAttributeType;
  *       and whether the feature type is {@linkplain #setAbstract abstract}.</li>
  *   <li>Convenience methods for setting the {@linkplain #setNameSpace name space} and the
  *       {@linkplain #setDefaultCardinality default cardinality} of properties to be added to the feature type.</li>
- *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(DefaultFeatureType)
+ *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(FeatureType)
  *       an association} or {@linkplain #addProperty an operation}.</li>
  *   <li>Method for listing the previously added {@linkplain #properties() properties}.</li>
  *   <li>A {@link #build()} method for creating the {@code FeatureType} instance from all previous information.</li>
@@ -120,7 +122,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
     /**
      * The parent of the feature to create. By default, new features have no parent.
      */
-    private final List<DefaultFeatureType> superTypes;
+    private final List<FeatureType> superTypes;
 
     /**
      * Whether the feature type is abstract. The default value is {@code false}.
@@ -189,7 +191,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * The object created by this builder, or {@code null} if not yet created.
      * This field must be cleared every time that a setter method is invoked on this builder.
      */
-    private transient DefaultFeatureType feature;
+    private transient FeatureType feature;
 
     /**
      * Creates a new builder instance using the default name factory.
@@ -205,13 +207,11 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * to values inferred from the given template. The properties list will contain properties
      * declared explicitely in the given template, not including properties inherited from super types.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param template  an existing feature type to use as a template, or {@code null} if none.
+     *
+     * @see #setAll(FeatureType)
      */
-    public FeatureTypeBuilder(final DefaultFeatureType template) {
+    public FeatureTypeBuilder(final FeatureType template) {
         this(null, null, null);
         if (template != null) {
             initialize(template);
@@ -267,16 +267,14 @@ public class FeatureTypeBuilder extends TypeBuilder {
     /**
      * Sets all properties of this builder to the values of the given feature type.
      * This builder is {@linkplain #clear() cleared} before the properties of the given type are copied.
-     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(DefaultFeatureType) constructor}.
-     *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
-     * will be defined in GeoAPI.</div>
+     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(FeatureType) constructor}.
      *
      * @param  template  an existing feature type to use as a template, or {@code null} if none.
      * @return {@code this} for allowing method calls chaining.
+     *
+     * @see #FeatureTypeBuilder(FeatureType)
      */
-    public FeatureTypeBuilder setAll(final DefaultFeatureType template) {
+    public FeatureTypeBuilder setAll(final FeatureType template) {
         clear();
         if (template != null) {
             initialize(template);
@@ -287,8 +285,10 @@ public class FeatureTypeBuilder extends TypeBuilder {
     /**
      * Initializes this builder to the value of the given type.
      * The caller is responsible to invoke {@link #clear()} (if needed) before this method.
+     *
+     * @see #setAll(FeatureType)
      */
-    private void initialize(final DefaultFeatureType template) {
+    private void initialize(final FeatureType template) {
         super.initialize(template);
         feature    = template;
         isAbstract = template.isAbstract();
@@ -299,12 +299,12 @@ public class FeatureTypeBuilder extends TypeBuilder {
          * is not one of the operations automatically generated by this builder.
          */
         final Map<String,Set<AttributeRole>> propertyRoles = new HashMap<>();
-        for (final AbstractIdentifiedType property : template.getProperties(false)) {
+        for (final PropertyType property : template.getProperties(false)) {
             PropertyTypeBuilder builder;
-            if (property instanceof DefaultAttributeType<?>) {
-                builder = new AttributeTypeBuilder<>(this, (DefaultAttributeType<?>) property);
-            } else if (property instanceof DefaultAssociationRole) {
-                builder = new AssociationRoleBuilder(this, (DefaultAssociationRole) property);
+            if (property instanceof AttributeType<?>) {
+                builder = new AttributeTypeBuilder<>(this, (AttributeType<?>) property);
+            } else if (property instanceof FeatureAssociationRole) {
+                builder = new AssociationRoleBuilder(this, (FeatureAssociationRole) property);
             } else {
                 builder = null;                             // Do not create OperationWrapper now - see below.
             }
@@ -402,33 +402,25 @@ public class FeatureTypeBuilder extends TypeBuilder {
     /**
      * Returns the direct parents of the feature type to create.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The return type will be changed to {@code FeatureType[]} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @return the parents of the feature type to create, or an empty array if none.
      *
      * @see DefaultFeatureType#getSuperTypes()
      */
-    public DefaultFeatureType[] getSuperTypes() {
-        return superTypes.toArray(new DefaultFeatureType[superTypes.size()]);
+    public FeatureType[] getSuperTypes() {
+        return superTypes.toArray(new FeatureType[superTypes.size()]);
     }
 
     /**
      * Sets the parent types (or super-type) from which to inherit properties.
      * If this method is not invoked, then the default value is no parent.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code parents} argument type will be changed to {@code FeatureType...} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  parents  the parent types from which to inherit properties, or an empty array if none.
      *                  Null elements are ignored.
      * @return {@code this} for allowing method calls chaining.
      */
-    public FeatureTypeBuilder setSuperTypes(final DefaultFeatureType... parents) {
+    public FeatureTypeBuilder setSuperTypes(final FeatureType... parents) {
         ensureNonNull("parents", parents);
-        final List<DefaultFeatureType> asList = Arrays.asList(parents);
+        final List<FeatureType> asList = Arrays.asList(parents);
         if (!superTypes.equals(asList)) {
             superTypes.clear();
             superTypes.addAll(asList);
@@ -599,6 +591,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * @return {@code this} for allowing method calls chaining.
      *
      * @see AttributeRole#IDENTIFIER_COMPONENT
+     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
      */
     public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
         ensureNonEmpty("delimiter", delimiter);
@@ -624,10 +617,10 @@ public class FeatureTypeBuilder extends TypeBuilder {
      *
      * @see #getProperty(String)
      * @see #addAttribute(Class)
-     * @see #addAttribute(DefaultAttributeType)
-     * @see #addAssociation(DefaultFeatureType)
+     * @see #addAttribute(AttributeType)
+     * @see #addAssociation(FeatureType)
      * @see #addAssociation(GenericName)
-     * @see #addAssociation(DefaultAssociationRole)
+     * @see #addAssociation(FeatureAssociationRole)
      */
     public List<PropertyTypeBuilder> properties() {
         return new RemoveOnlyList<>(properties);
@@ -641,6 +634,8 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * @param  name   name of the property to search.
      * @return property of the given name, or {@code null} if none.
      * @throws IllegalArgumentException if the given name is ambiguous.
+     *
+     * @see #addProperty(PropertyType)
      */
     public PropertyTypeBuilder getProperty(final String name) {
         return forName(properties, name);
@@ -657,7 +652,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * }
      *
      * The value class can not be {@code Feature.class} since features shall be handled
-     * as {@linkplain #addAssociation(DefaultFeatureType) associations} instead than attributes.
+     * as {@linkplain #addAssociation(FeatureType) associations} instead than attributes.
      *
      * @param  <V>         the compile-time value of {@code valueClass} argument.
      * @param  valueClass  the class of attribute values (can not be {@code Feature.class}).
@@ -667,7 +662,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
      */
     public <V> AttributeTypeBuilder<V> addAttribute(final Class<V> valueClass) {
         ensureNonNull("valueClass", valueClass);
-        if (AbstractFeature.class.isAssignableFrom(valueClass)) {
+        if (Feature.class.isAssignableFrom(valueClass)) {
             // We disallow Feature.class because that type shall be handled as association instead than attribute.
             throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
         }
@@ -680,17 +675,13 @@ public class FeatureTypeBuilder extends TypeBuilder {
     /**
      * Creates a new {@code AttributeType} builder initialized to the same characteristics than the given template.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code template} argument type will be changed to {@code AttributeType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  <V>       the compile-time type of values in the {@code template} argument.
      * @param  template  an existing attribute type to use as a template.
      * @return a builder for an {@code AttributeType}, initialized with the values of the given template.
      *
      * @see #properties()
      */
-    public <V> AttributeTypeBuilder<V> addAttribute(final DefaultAttributeType<V> template) {
+    public <V> AttributeTypeBuilder<V> addAttribute(final AttributeType<V> template) {
         ensureNonNull("template", template);
         final AttributeTypeBuilder<V> property = new AttributeTypeBuilder<>(this, template);
         properties.add(property);
@@ -753,16 +744,12 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * The default association name is the name of the given type, but callers should invoke one
      * of the {@code AssociationRoleBuilder.setName(…)} methods on the returned instance with a better name.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code type} argument type will be changed to {@code FeatureType} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  type  the type of feature values.
      * @return a builder for a {@code FeatureAssociationRole}.
      *
      * @see #properties()
      */
-    public AssociationRoleBuilder addAssociation(final DefaultFeatureType type) {
+    public AssociationRoleBuilder addAssociation(final FeatureType type) {
         ensureNonNull("type", type);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, type, type.getName());
         properties.add(property);
@@ -772,7 +759,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
 
     /**
      * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
-     * This method can be invoked as an alternative to {@code addAssociation(FeatureType)} when the
+     * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
      * {@code FeatureType} instance is not yet available because of cyclic dependency.
      *
      * @param  type  the name of the type of feature values.
@@ -792,16 +779,12 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * Creates a new {@code FeatureAssociationRole} builder initialized to the same characteristics
      * than the given template.
      *
-     * <div class="warning"><b>Warning:</b>
-     * The {@code template} argument type will be changed to {@code FeatureAssociationRole} if and when such interface
-     * will be defined in GeoAPI.</div>
-     *
      * @param  template  an existing feature association to use as a template.
      * @return a builder for an {@code FeatureAssociationRole}, initialized with the values of the given template.
      *
      * @see #properties()
      */
-    public AssociationRoleBuilder addAssociation(final DefaultAssociationRole template) {
+    public AssociationRoleBuilder addAssociation(final FeatureAssociationRole template) {
         ensureNonNull("template", template);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, template);
         properties.add(property);
@@ -813,15 +796,12 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * Adds the given property in the feature type properties.
      * The given property shall be an instance of one of the following types:
      * <ul>
-     *   <li>{@code AttributeType}, in which case this method delegate to {@code addAttribute(AttributeType)}.</li>
-     *   <li>{@code FeatureAssociationRole}, in which case this method delegate to {@code addAssociation(FeatureAssociationRole)}.</li>
-     *   <li>{@code Operation}, in which case the given operation object will be added verbatim in the {@code FeatureType};
+     *   <li>{@link AttributeType}, in which case this method delegate to {@link #addAttribute(AttributeType)}.</li>
+     *   <li>{@link FeatureAssociationRole}, in which case this method delegate to {@link #addAssociation(FeatureAssociationRole)}.</li>
+     *   <li>{@link Operation}, in which case the given operation object will be added verbatim in the {@code FeatureType};
      *       this builder does not create new operations.</li>
      * </ul>
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the argument type may be changed to the
-     * {@code org.opengis.feature.PropertyType} interface. This change is pending GeoAPI revision.</div>
-     *
      * @param  template  the property to add to the feature type.
      * @return a builder initialized to the given builder.
      *         In the {@code Operation} case, the builder is a read-only accessor on the operation properties.
@@ -829,12 +809,12 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * @see #properties()
      * @see #getProperty(String)
      */
-    public PropertyTypeBuilder addProperty(final AbstractIdentifiedType template) {
+    public PropertyTypeBuilder addProperty(final PropertyType template) {
         ensureNonNull("template", template);
-        if (template instanceof DefaultAttributeType<?>) {
-            return addAttribute((DefaultAttributeType<?>) template);
-        } else if (template instanceof DefaultAssociationRole) {
-            return addAssociation((DefaultAssociationRole) template);
+        if (template instanceof AttributeType<?>) {
+            return addAttribute((AttributeType<?>) template);
+        } else if (template instanceof FeatureAssociationRole) {
+            return addAssociation((FeatureAssociationRole) template);
         } else {
             final PropertyTypeBuilder property = new OperationWrapper(this, template);
             properties.add(property);
@@ -884,9 +864,6 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * One of the {@code setName(…)} methods must have been invoked before this {@code build()} method (mandatory).
      * All other methods are optional, but some calls to a {@code add} method are usually needed.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
-     * {@code org.opengis.feature.FeatureType} interface. This change is pending GeoAPI revision.</div>
-     *
      * <p>If a feature type has already been built and this builder state has not changed since the
      * feature type creation, then the previously created {@code FeatureType} instance is returned.</p>
      *
@@ -896,7 +873,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
      * @see #clear()
      */
     @Override
-    public DefaultFeatureType build() throws IllegalStateException {
+    public FeatureType build() throws IllegalStateException {
         if (feature == null) {
             /*
              * Creates an initial array of property types with up to 3 slots reserved for sis:identifier, sis:geometry
@@ -907,13 +884,13 @@ public class FeatureTypeBuilder extends TypeBuilder {
             int numSynthetic;                               // Number of synthetic properties that may be generated.
             int envelopeIndex = -1;
             int geometryIndex = -1;
-            final AbstractIdentifiedType[] identifierTypes;
+            final PropertyType[] identifierTypes;
             if (identifierCount == 0) {
                 numSynthetic    = 0;
                 identifierTypes = null;
             } else {
                 numSynthetic    = 1;
-                identifierTypes = new AbstractIdentifiedType[identifierCount];
+                identifierTypes = new PropertyType[identifierCount];
             }
             if (defaultGeometry != null) {
                 envelopeIndex = numSynthetic++;
@@ -921,12 +898,12 @@ public class FeatureTypeBuilder extends TypeBuilder {
                     geometryIndex = numSynthetic++;
                 }
             }
-            final AbstractIdentifiedType[] propertyTypes = new AbstractIdentifiedType[numSynthetic + numSpecified];
+            final PropertyType[] propertyTypes = new PropertyType[numSynthetic + numSpecified];
             int propertyCursor = numSynthetic;
             int identifierCursor = 0;
             for (int i=0; i<numSpecified; i++) {
                 final PropertyTypeBuilder builder = properties.get(i);
-                final AbstractIdentifiedType instance = builder.build();
+                final PropertyType instance = builder.build();
                 propertyTypes[propertyCursor] = instance;
                 /*
                  * Collect the attributes to use as identifier components while we loop over all properties.
@@ -988,7 +965,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
                 }
             }
             feature = new DefaultFeatureType(identification(), isAbstract(),
-                    superTypes.toArray(new DefaultFeatureType[superTypes.size()]),
+                    superTypes.toArray(new FeatureType[superTypes.size()]),
                     ArraysExt.resize(propertyTypes, propertyCursor));
         }
         return feature;
@@ -1032,7 +1009,7 @@ public class FeatureTypeBuilder extends TypeBuilder {
             buffer.insert(buffer.indexOf("[") + 1, "abstract ");
         }
         String separator = " : ";
-        for (final DefaultFeatureType parent : superTypes) {
+        for (final FeatureType parent : superTypes) {
             buffer.append(separator).append('“').append(parent.getName()).append('”');
             separator = ", ";
         }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
index f68b6ae..5758e8e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
@@ -20,7 +20,7 @@ import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.apache.sis.feature.AbstractIdentifiedType;
+import org.opengis.feature.PropertyType;
 
 
 /**
@@ -37,12 +37,12 @@ final class OperationWrapper extends PropertyTypeBuilder {
     /**
      * The wrapped operation.
      */
-    private final AbstractIdentifiedType operation;
+    private final PropertyType operation;
 
     /**
      * Creates a new wrapper for the given operation.
      */
-    OperationWrapper(final FeatureTypeBuilder owner, final AbstractIdentifiedType operation) {
+    OperationWrapper(final FeatureTypeBuilder owner, final PropertyType operation) {
         super(owner);
         this.operation = operation;
         minimumOccurs = 1;
@@ -54,7 +54,7 @@ final class OperationWrapper extends PropertyTypeBuilder {
      * Returns the wrapped operation.
      */
     @Override
-    public AbstractIdentifiedType build() {
+    public PropertyType build() {
         return operation;
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
index 63a8eb9..9517cbd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
@@ -20,7 +20,10 @@ import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.apache.sis.feature.AbstractIdentifiedType;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.FeatureAssociationRole;
 
 
 /**
@@ -30,10 +33,10 @@ import org.apache.sis.feature.AbstractIdentifiedType;
  *
  * <ul>
  *   <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
- *   <li>{@link FeatureTypeBuilder#addAttribute(DefaultAttributeType)} for using an existing attribute as a template</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultFeatureType)}</li>
+ *   <li>{@link FeatureTypeBuilder#addAttribute(AttributeType)} for using an existing attribute as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
  *   <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultAssociationRole)} for using an existing association as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureAssociationRole)} for using an existing association as a template</li>
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
@@ -287,14 +290,11 @@ public abstract class PropertyTypeBuilder extends TypeBuilder {
      * then the previously created {@code PropertyType} instance is returned
      * (see {@link AttributeTypeBuilder#build()} for more information).
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.PropertyType}. This change is pending GeoAPI revision.</div>
-     *
      * @return the property type.
      * @throws IllegalStateException if the builder contains inconsistent information.
      */
     @Override
-    public abstract AbstractIdentifiedType build() throws IllegalStateException;
+    public abstract PropertyType build() throws IllegalStateException;
 
     /**
      * Flags this builder as a disposed one. The builder should not be used anymore after this method call.
diff --git 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
index 90e2c18..51089f6 100644
--- 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
@@ -34,6 +34,10 @@ 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).
@@ -117,7 +121,7 @@ public abstract class TypeBuilder implements Localized {
      * Initializes this builder to the value of the given type.
      * The caller is responsible to invoke {@link #reset()} (if needed) before this method.
      */
-    final void initialize(final AbstractIdentifiedType template) {
+    final void initialize(final IdentifiedType template) {
         putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
         putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
         putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
@@ -455,7 +459,7 @@ public abstract class TypeBuilder implements Localized {
             }
         }
         if (ambiguity != null) {
-            throw new IllegalArgumentException(errors().getString(
+            throw new PropertyNotFoundException(errors().getString(
                     Errors.Keys.AmbiguousName_3, best.getName(), ambiguity.getName(), name));
         }
         return best;
@@ -568,11 +572,8 @@ public abstract class TypeBuilder implements Localized {
      * If a type has already been built and this builder state has not changed since the type creation,
      * then the previously created {@code IdentifiedType} instance is returned.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed to the
-     * {@code org.opengis.feature.IdentifiedType} interface. This change is pending GeoAPI revision.</div>
-     *
      * @return the feature or property type.
      * @throws IllegalStateException if the builder contains inconsistent information.
      */
-    public abstract AbstractIdentifiedType build() throws IllegalStateException;
+    public abstract IdentifiedType build() throws IllegalStateException;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java
new file mode 100644
index 0000000..f7a2dab
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.filter;
+
+import java.io.Serializable;
+import org.opengis.filter.Filter;
+import org.opengis.filter.expression.Expression;
+
+
+/**
+ * Base class for filters performing operations on two values.
+ * The nature of the operation is dependent on the subclass.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+abstract class AbstractBinaryOperator implements Filter, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -5079157703540568112L;
+
+    /**
+     * The first of the two expressions to be used by this operator.
+     *
+     * @see #getExpression1()
+     */
+    protected final Expression expression1;
+
+    /**
+     * The second of the two expressions to be used by this operator.
+     *
+     * @see #getExpression2()
+     */
+    protected final Expression expression2;
+
+    /**
+     * Creates a new binary operator.
+     * It is caller responsibility to ensure that no argument is null.
+     */
+    AbstractBinaryOperator(final Expression expression1, final Expression expression2) {
+        this.expression1 = expression1;
+        this.expression2 = expression2;
+    }
+
+    /**
+     * Returns the mathematical symbol for this binary operator.
+     * For comparison operators, the symbol should be one of the following:
+     * {@literal < > ≤ ≥ = ≠}.
+     */
+    protected abstract char symbol();
+
+    /**
+     * Returns the first of the two expressions to be used by this operator.
+     */
+    public final Expression getExpression1() {
+        return expression1;
+    }
+
+    /**
+     * Returns the second of the two expressions to be used by this operator.
+     */
+    public final Expression getExpression2() {
+        return expression2;
+    }
+
+    /**
+     * Returns a hash code value for this operator.
+     */
+    @Override
+    public int hashCode() {
+        // We use the symbol as a way to differentiate the subclasses.
+        return (31 * expression1.hashCode() + expression2.hashCode()) ^ symbol();
+    }
+
+    /**
+     * Compares this operator with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj != null && obj.getClass() == getClass()) {
+            final AbstractBinaryOperator other = (AbstractBinaryOperator) obj;
+            return expression1.equals(other.expression1) &&
+                   expression2.equals(other.expression2);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation of this operator.
+     */
+    @Override
+    public String toString() {
+        return new StringBuilder(30).append(expression1).append(' ').append(symbol()).append(' ')
+                                    .append(expression2).toString();
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
new file mode 100644
index 0000000..cd99903
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.filter;
+
+import java.io.Serializable;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.MatchAction;
+import org.opengis.filter.expression.Expression;
+
+
+/**
+ * Base class for filters that compare exactly two values against each other.
+ * The nature of the comparison is dependent on the subclass.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+abstract class AbstractComparisonOperator extends AbstractBinaryOperator implements BinaryComparisonOperator, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -4709016194087609721L;
+
+    /**
+     * Whether comparisons are case sensitive.
+     *
+     * @see #isMatchingCase()
+     */
+    protected final boolean matchCase;
+
+    /**
+     * Specifies how the comparison predicate shall be evaluated for a collection of values.
+     *
+     * @see #getMatchAction()
+     */
+    protected final MatchAction matchAction;
+
+    /**
+     * Creates a new binary comparison operator.
+     * It is caller responsibility to ensure that no argument is null.
+     */
+    AbstractComparisonOperator(final Expression expression1, final Expression expression2,
+                               final boolean matchCase, final MatchAction matchAction)
+    {
+        super(expression1, expression2);
+        this.matchCase   = matchCase;
+        this.matchAction = matchAction;
+    }
+
+    /**
+     * Specifies whether comparisons are case sensitive.
+     *
+     * @return {@code true} if the comparisons are case sensitive, otherwise {@code false}.
+     */
+    @Override
+    public final boolean isMatchingCase() {
+        return matchCase;
+    }
+
+    /**
+     * Specifies how the comparison predicate shall be evaluated for a collection of values.
+     * Values can be {@link MatchAction#ALL ALL} if all values in the collection shall satisfy the predicate,
+     * {@link MatchAction#ANY ANY} if any of the value in the collection can satisfy the predicate, or
+     * {@link MatchAction#ONE ONE} if only one of the values in the collection shall satisfy the predicate.
+     *
+     * @return how the comparison predicate shall be evaluated for a collection of values.
+     */
+    @Override
+    public final MatchAction getMatchAction() {
+        return matchAction;
+    }
+
+    /**
+     * Returns a hash code value for this comparison operator.
+     */
+    @Override
+    public final int hashCode() {
+        int hash = super.hashCode() * 37 + matchAction.hashCode();
+        if (matchCase) hash = ~hash;
+        return hash;
+    }
+
+    /**
+     * Compares this operator with the given object for equality.
+     */
+    @Override
+    public final boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (super.equals(obj)) {
+            final AbstractComparisonOperator other = (AbstractComparisonOperator) obj;
+            return matchCase == other.matchCase && matchAction.equals(other.matchAction);
+        }
+        return false;
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractExpression.java b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractExpression.java
new file mode 100644
index 0000000..3404456
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractExpression.java
@@ -0,0 +1,65 @@
+/*
+ * 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.filter;
+
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ObjectConverters;
+import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.internal.feature.FeatureExpression;
+
+// Branch-dependent imports
+import org.opengis.feature.FeatureType;
+import org.opengis.filter.expression.Expression;
+
+
+/**
+ * Base class of Apache SIS implementation of OGC expressions operating on feature instances.
+ * This base class adds an additional method, {@link #expectedType(FeatureType)}, for fetching
+ * in advance the expected type of expression results.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+abstract class AbstractExpression implements Expression, FeatureExpression {
+    /**
+     * Creates a new expression.
+     */
+    protected AbstractExpression() {
+    }
+
+    /**
+     * Evaluates the expression for producing a result of the given type.
+     * The default implementation evaluate the expression in the default
+     * way and attempt to convert the result.
+     *
+     * @param  feature  to feature to evaluate with this expression.
+     * @param  target   the desired type for the expression result.
+     */
+    @Override
+    public <T> T evaluate(final Object feature, final Class<T> target) {
+        ArgumentChecks.ensureNonNull("target", target);
+        final Object value = evaluate(feature);
+        try {
+            return ObjectConverters.convert(value, target);
+        } catch (UnconvertibleObjectException ex) {
+            // TODO: should report the exception somewhere.
+            return null;
+        }
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java
new file mode 100644
index 0000000..ea0bddc
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.filter;
+
+import java.io.Serializable;
+import org.opengis.filter.Filter;
+import org.opengis.filter.expression.Expression;
+
+
+/**
+ * Base class for filters performing operations on one value.
+ * The nature of the operation is dependent on the subclass.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+abstract class AbstractUnaryOperator implements Filter, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -8183962550739028650L;
+
+    /**
+     * The expression to be used by this operator.
+     *
+     * @see #getExpression()
+     */
+    protected final Expression expression;
+
+    /**
+     * Creates a new unary operator.
+     * It is caller responsibility to ensure that no argument is null.
+     */
+    AbstractUnaryOperator(final Expression expression) {
+        this.expression = expression;
+    }
+
+    /**
+     * Returns the mathematical symbol for this operator.
+     */
+    protected abstract char symbol();
+
+    /**
+     * Returns the expressions to be used by this operator.
+     */
+    public final Expression getExpression() {
+        return expression;
+    }
+
+    /**
+     * Returns a hash code value for this operator.
+     */
+    @Override
+    public int hashCode() {
+        // We use the symbol as a way to differentiate the subclasses.
+        return expression.hashCode() ^ symbol();
+    }
+
+    /**
+     * Compares this operator with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj != null && obj.getClass() == getClass()) {
+            return expression.equals(((AbstractUnaryOperator) obj).expression);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation of this operator.
+     */
+    @Override
+    public String toString() {
+        return new StringBuilder(30).append(expression).append(':').append(symbol()).toString();
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
new file mode 100644
index 0000000..0fc07c3
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
@@ -0,0 +1,883 @@
+/*
+ * 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.filter;
+
+import java.util.List;
+import java.util.Set;
+import org.opengis.filter.*;
+import org.opengis.filter.capability.*;
+import org.opengis.filter.expression.*;
+import org.opengis.filter.identity.*;
+import org.opengis.filter.sort.*;
+import org.opengis.filter.spatial.*;
+import org.opengis.filter.temporal.*;
+import org.opengis.filter.capability.SpatialOperator;       // Resolve ambiguity with org.opengis.filter.spatial.
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
+import org.opengis.util.GenericName;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * Default implementation of GeoAPI filter factory for creation of {@link Filter} and {@link Expression} instances.
+ *
+ * <div class="warning"><b>Warning:</b> most methods in this class are still unimplemented.
+ * This is a very early draft subject to changes.
+ * <b>TODO: the API of this class needs severe revision! DO NOT RELEASE.</b>
+ * See <a href="https://github.com/opengeospatial/geoapi/issues/32">GeoAPI issue #32</a>.</div>
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public class DefaultFilterFactory implements FilterFactory2 {
+    /**
+     * Creates a new factory.
+     */
+    public DefaultFilterFactory() {
+    }
+
+    // SPATIAL FILTERS /////////////////////////////////////////////////////////
+
+    /**
+     * Creates an operator that evaluates to {@code true} when the bounding box of the feature's geometry overlaps
+     * the given bounding box.
+     *
+     * @param  propertyName  name of geometry property (for a {@link PropertyName} to access a feature's Geometry)
+     * @param  minx          minimum "x" value (for a literal envelope).
+     * @param  miny          minimum "y" value (for a literal envelope).
+     * @param  maxx          maximum "x" value (for a literal envelope).
+     * @param  maxy          maximum "y" value (for a literal envelope).
+     * @param  srs           identifier of the Coordinate Reference System to use for a literal envelope.
+     * @return operator that evaluates to {@code true} when the bounding box of the feature's geometry overlaps
+     *         the bounding box provided in arguments to this method.
+     *
+     * @see #bbox(Expression, Envelope)
+     */
+    @Override
+    public BBOX bbox(final String propertyName, final double minx,
+            final double miny, final double maxx, final double maxy, final String srs)
+    {
+        return bbox(property(propertyName), minx, miny, maxx, maxy, srs);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public BBOX bbox(final Expression e, final double minx, final double miny,
+            final double maxx, final double maxy, final String srs)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public BBOX bbox(final Expression e, final Envelope bounds)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * Creates an operator that checks if all of a feature's geometry is more distant than the given distance
+     * from the given geometry.
+     *
+     * @param  propertyName  name of geometry property (for a {@link PropertyName} to access a feature's Geometry).
+     * @param  geometry      the geometry from which to evaluate the distance.
+     * @param  distance      minimal distance for evaluating the expression as {@code true}.
+     * @param  units         units of the given {@code distance}.
+     * @return operator that evaluates to {@code true} when all of a feature's geometry is more distant than
+     *         the given distance from the given geometry.
+     */
+    @Override
+    public Beyond beyond(final String propertyName, final Geometry geometry,
+            final double distance, final String units)
+    {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return beyond(name, geom, distance, units);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Beyond beyond(final Expression left, final Expression right,
+            final double distance, final String units)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Contains contains(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return contains(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Contains contains(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Crosses crosses(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return crosses(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Crosses crosses(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Disjoint disjoint(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return disjoint(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Disjoint disjoint(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public DWithin dwithin(final String propertyName, final Geometry geometry,
+            final double distance, final String units) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return dwithin(name, geom, distance, units);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public DWithin dwithin(final Expression left, final Expression right,
+            final double distance, final String units) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Equals equals(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return equal(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Equals equal(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Intersects intersects(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return intersects(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Intersects intersects(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Overlaps overlaps(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return overlaps(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Overlaps overlaps(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Touches touches(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return touches(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Touches touches(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Within within(final String propertyName, final Geometry geometry) {
+        final PropertyName name = property(propertyName);
+        final Literal geom = literal(geometry);
+        return within(name, geom);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Within within(final Expression left, final Expression right) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    // IDENTIFIERS /////////////////////////////////////////////////////////////
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public FeatureId featureId(final String id) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public GmlObjectId gmlObjectId(final String id) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    // FILTERS /////////////////////////////////////////////////////////////////
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public And and(final Filter filter1, final Filter filter2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public And and(final List<Filter> filters) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Or or(final Filter filter1, final Filter filter2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Or or(final List<Filter> filters) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Not not(final Filter filter) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Id id(final Set<? extends Identifier> ids) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyName property(final GenericName name) {
+        return property(name.toString());
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyName property(final String name) {
+        ArgumentChecks.ensureNonNull("name", name);
+        return new DefaultPropertyName(name);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsBetween between(final Expression expr,
+            final Expression lower, final Expression upper) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsEqualTo equals(final Expression expr1, final Expression expr2) {
+        return equal(expr1, expr2, true, MatchAction.ANY);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsEqualTo equal(final Expression expression1, final Expression expression2,
+                                   final boolean matchCase, final MatchAction matchAction)
+    {
+        ArgumentChecks.ensureNonNull("expression1", expression1);
+        ArgumentChecks.ensureNonNull("expression2", expression2);
+        ArgumentChecks.ensureNonNull("matchAction", matchAction);
+        return new DefaultPropertyIsEqualTo(expression1, expression2, matchCase, matchAction);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsNotEqualTo notEqual(final Expression expr1, final Expression expr2) {
+        return notEqual(expr1, expr2,false, MatchAction.ANY);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsNotEqualTo notEqual(final Expression expr1,
+            final Expression expr2, final boolean matchCase, final MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsGreaterThan greater(final Expression expr1,
+            final Expression expr2) {
+        return greater(expr1,expr2,false, MatchAction.ANY);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsGreaterThan greater(final Expression expr1,
+            final Expression expr2, final boolean matchCase, final MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsGreaterThanOrEqualTo greaterOrEqual(
+            final Expression expr1, final Expression expr2) {
+        return greaterOrEqual(expr1, expr2,false, MatchAction.ANY);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsGreaterThanOrEqualTo greaterOrEqual(
+            final Expression expr1, final Expression expr2, final boolean matchCase, final MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLessThan less(final Expression expr1, final Expression expr2) {
+        return less(expr1, expr2, false, MatchAction.ANY);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLessThan less(final Expression expr1,
+            final Expression expr2, final boolean matchCase, MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLessThanOrEqualTo lessOrEqual(
+            final Expression expr1, final Expression expr2) {
+        return lessOrEqual(expr1, expr2, false, MatchAction.ANY);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression expr1,
+            final Expression expr2, final boolean matchCase, final MatchAction matchAction) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLike like(final Expression expr, final String pattern) {
+        return like(expr, pattern, "*", "?", "\\");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLike like(final Expression expr, final String pattern,
+            final String wildcard, final String singleChar, final String escape) {
+        return like(expr,pattern,wildcard,singleChar,escape,false);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsLike like(final Expression expr, final String pattern,
+            final String wildcard, final String singleChar,
+            final String escape, final boolean matchCase) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsNull isNull(final Expression expression) {
+        ArgumentChecks.ensureNonNull("expression", expression);
+        return new DefaultPropertyIsNull(expression);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public PropertyIsNil isNil(Expression expr) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    // TEMPORAL FILTER /////////////////////////////////////////////////////////
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public After after(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public AnyInteracts anyInteracts(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Before before(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Begins begins(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public BegunBy begunBy(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public During during(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Ends ends(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public EndedBy endedBy(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Meets meets(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public MetBy metBy(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public OverlappedBy overlappedBy(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public TContains tcontains(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public TEquals tequals(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public TOverlaps toverlaps(Expression expr1, Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    // EXPRESSIONS /////////////////////////////////////////////////////////////
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Add add(final Expression expr1, final Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Divide divide(final Expression expr1, final Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Multiply multiply(final Expression expr1, final Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Subtract subtract(final Expression expr1, final Expression expr2) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Function function(final String name, final Expression... parameters) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final Object value) {
+        ArgumentChecks.ensureNonNull("value", value);
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final byte value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final short value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final int value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final long value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final float value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final double value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final char value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Literal literal(final boolean value) {
+        return new DefaultLiteral<>(value);
+    }
+
+    // SORT BY /////////////////////////////////////////////////////////////////
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public SortBy sort(final String propertyName, final SortOrder order) {
+        return new DefaultSortBy(property(propertyName), order);
+    }
+
+    // CAPABILITIES ////////////////////////////////////////////////////////////
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Operator operator(final String name) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public SpatialOperator spatialOperator(final String name,
+            final GeometryOperand[] geometryOperands) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public FunctionName functionName(final String name, final int nargs) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Functions functions(final FunctionName[] functionNames) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public SpatialOperators spatialOperators(final SpatialOperator[] spatialOperators) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public ComparisonOperators comparisonOperators(final Operator[] comparisonOperators) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public ArithmeticOperators arithmeticOperators(final boolean simple, final Functions functions) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public ScalarCapabilities scalarCapabilities(final ComparisonOperators comparison,
+            final ArithmeticOperators arithmetic, final boolean logical)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public SpatialCapabilities spatialCapabilities(
+            final GeometryOperand[] geometryOperands, final SpatialOperators spatial)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public IdCapabilities idCapabilities(final boolean eid, final boolean fid) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public FilterCapabilities capabilities(final String version,
+            final ScalarCapabilities scalar, final SpatialCapabilities spatial,
+            final TemporalCapabilities temporal, final IdCapabilities id)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public TemporalCapabilities temporalCapabilities(TemporalOperand[] temporalOperands, TemporalOperators temporal) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLiteral.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLiteral.java
new file mode 100644
index 0000000..33a10ea
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLiteral.java
@@ -0,0 +1,148 @@
+/*
+ * 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.filter;
+
+import java.util.Collections;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.io.Serializable;
+import org.opengis.util.LocalName;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.AttributeType;
+import org.opengis.filter.expression.Literal;
+import org.opengis.filter.expression.ExpressionVisitor;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.iso.Names;
+
+
+/**
+ * A constant, literal value that can be used in expressions.
+ * The {@link #evaluate(Object)} method ignore the argument and always returns {@link #getValue()}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ *
+ * @param <T>  the literal value type.
+ *
+ * @since 1.0
+ * @module
+ */
+final class DefaultLiteral<T> extends AbstractExpression implements Literal, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 3240145927452086297L;
+
+    /**
+     * The name of attribute types associated with literals.
+     */
+    private static final LocalName NAME = Names.createLocalName(null, null, "Literal");
+
+    /**
+     * A cache of {@link AttributeType} instances for literal classes. Used for avoiding to create many duplicated
+     * instances for the common case where the literal is a very common type like {@link String} or {@link Integer}.
+     */
+    private static final ConcurrentMap<Class<?>, AttributeType<?>> TYPES = new ConcurrentHashMap<>();
+
+    /**
+     * The constant value to be returned by {@link #getValue()}.
+     */
+    private final T value;
+
+    /**
+     * Creates a new literal holding the given constant value.
+     * It is caller responsibility to ensure that the given argument is non-null.
+     */
+    DefaultLiteral(final T value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the constant value held by this object.
+     */
+    @Override
+    public T getValue() {
+        return value;
+    }
+
+    /**
+     * Returns the constant value held by this object.
+     */
+    @Override
+    public T evaluate(Object ignored) {
+        return value;
+    }
+
+    /**
+     * Returns the type of values produced by this expression.
+     */
+    @Override
+    public AttributeType<?> expectedType(FeatureType ignored) {
+        final Class<?> valueType = value.getClass();
+        AttributeType<?> type = TYPES.get(valueType);
+        if (type == null) {
+            final Class<?> standardType = Classes.getStandardType(valueType);
+            type = TYPES.computeIfAbsent(standardType, DefaultLiteral::newType);
+            if (valueType != standardType) {
+                TYPES.put(valueType, type);
+            }
+        }
+        return type;
+    }
+
+    /**
+     * Invoked when a new attribute type need to be created for the given standard type.
+     */
+    private static <T> AttributeType<T> newType(final Class<T> standardType) {
+        return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, NAME),
+                                          standardType, 1, 1, null, (AttributeType<?>[]) null);
+    }
+
+    /**
+     * Accepts a visitor.
+     */
+    @Override
+    public Object accept(final ExpressionVisitor visitor, final Object extraData) {
+        return visitor.visit(this, extraData);
+    }
+
+    /**
+     * Compares this literal with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        return (obj instanceof Literal) && value.equals(((DefaultLiteral) obj).value);
+    }
+
+    /**
+     * Returns a hash-code value for this literal.
+     */
+    @Override
+    public int hashCode() {
+        return value.hashCode() ^ (int) serialVersionUID;
+    }
+
+    /**
+     * Returns a string representation of this literal.
+     */
+    @Override
+    public String toString() {
+        return value.toString();
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
new file mode 100644
index 0000000..2a4e78e
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
@@ -0,0 +1,96 @@
+/*
+ * 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.filter;
+
+import java.util.Objects;
+import org.apache.sis.util.Numbers;
+import org.opengis.filter.FilterVisitor;
+import org.opengis.filter.MatchAction;
+import org.opengis.filter.PropertyIsEqualTo;
+import org.opengis.filter.expression.Expression;
+
+
+/**
+ * Filter operator that compares that its two sub-expressions are equal to each other.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class DefaultPropertyIsEqualTo extends AbstractComparisonOperator implements PropertyIsEqualTo {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -5783347523815670017L;
+
+    /**
+     * Creates a new comparison operator.
+     * It is caller responsibility to ensure that no argument is null.
+     */
+    DefaultPropertyIsEqualTo(Expression expression1, Expression expression2, boolean matchCase, MatchAction matchAction) {
+        super(expression1, expression2, matchCase, matchAction);
+    }
+
+    /**
+     * Returns the mathematical symbol for this comparison operator.
+     */
+    @Override
+    protected char symbol() {
+        return '=';
+    }
+
+    /**
+     * Determines if the test represented by this filter passed.
+     *
+     * @todo Use locale-sensitive {@link java.text.Collator} for string comparisons.
+     */
+    @Override
+    public boolean evaluate(Object object) {
+        final Object r1 = expression1.evaluate(object);
+        final Object r2 = expression2.evaluate(object);
+        if (Objects.equals(r1, r2)) {
+            return true;
+        } else if (r1 instanceof Number && r2 instanceof Number) {
+            @SuppressWarnings("unchecked") final Class<? extends Number> c1 = (Class<? extends Number>) r1.getClass();
+            @SuppressWarnings("unchecked") final Class<? extends Number> c2 = (Class<? extends Number>) r2.getClass();
+            if (c1 != c2) {
+                final Class<? extends Number> c = Numbers.widestClass(c1, c2);
+                return Numbers.cast((Number) r1, c).equals(
+                       Numbers.cast((Number) r2, c));
+            }
+        } else if (r1 instanceof CharSequence && r2 instanceof CharSequence) {
+            final String s1 = r1.toString();
+            final String s2 = r2.toString();
+            if (!matchCase) {
+                return s1.equalsIgnoreCase(s2);
+            } else if (r1 != s1 || r2 != s2) {
+                return s1.equals(s2);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Accepts a visitor.
+     */
+    @Override
+    public Object accept(FilterVisitor visitor, Object extraData) {
+        return visitor.visit(this, extraData);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java
new file mode 100644
index 0000000..9c2a238
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java
@@ -0,0 +1,73 @@
+/*
+ * 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.filter;
+
+import java.io.Serializable;
+import org.opengis.filter.FilterVisitor;
+import org.opengis.filter.PropertyIsNull;
+import org.opengis.filter.expression.Expression;
+
+
+/**
+ * Filter operator that checks if an expression's value is {@code null}.  A {@code null}
+ * is equivalent to no value present. The value 0 is a valid value and is not considered
+ * {@code null}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class DefaultPropertyIsNull extends AbstractUnaryOperator implements PropertyIsNull, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 3942075458551232678L;
+
+    /**
+     * Creates a new operator.
+     * It is caller responsibility to ensure that no argument is null.
+     */
+    DefaultPropertyIsNull(final Expression expression) {
+        super(expression);
+    }
+
+    /**
+     * Returns the null symbol, to be used in string representation.
+     */
+    @Override
+    protected char symbol() {
+        return '∅';
+    }
+
+    /**
+     * Returns {@code true} if the given value evaluates to {@code null}.
+     */
+    @Override
+    public boolean evaluate(final Object object) {
+        return expression.evaluate(object) == null;
+    }
+
+    /**
+     * Accepts a visitor.
+     */
+    @Override
+    public Object accept(final FilterVisitor visitor, final Object extraData) {
+        return visitor.visit(this, extraData);
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyName.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyName.java
new file mode 100644
index 0000000..1d1626a
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyName.java
@@ -0,0 +1,156 @@
+/*
+ * 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.filter;
+
+import java.util.Map;
+import java.io.Serializable;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.feature.DefaultAssociationRole;
+
+import static java.util.Collections.singletonMap;
+
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.PropertyNotFoundException;
+import org.opengis.filter.expression.ExpressionVisitor;
+import org.opengis.filter.expression.PropertyName;
+
+
+/**
+ * Expression whose value is computed by retrieving the value indicated by the provided name.
+ * A property name does not store any value; it acts as an indirection to a property value of
+ * the evaluated feature.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class DefaultPropertyName extends AbstractExpression implements PropertyName, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -8474562134021521300L;
+
+    /**
+     * Name of the property from which to retrieve the value.
+     */
+    private final String name;
+
+    /**
+     * Creates a new expression retrieving values from a property of the given name.
+     * It is caller responsibility to ensure that the given name is non-null.
+     *
+     * @param  name  name of the property (usually a feature attribute).
+     */
+    DefaultPropertyName(final String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the name of the property whose value will be returned by the {@link #evaluate evaluate} method.
+     */
+    @Override
+    public String getPropertyName() {
+        return name;
+    }
+
+    /**
+     * Returns the value of the property of the given name.
+     * The {@code candidate} object can be any of the following type:
+     *
+     * <ul>
+     *   <li>A {@link Feature}, in which case {@link Feature#getPropertyValue(String)} will be invoked.</li>
+     *   <li>A {@link Map}, in which case {@link Map#get(Object)} will be invoked.</li>
+     * </ul>
+     *
+     * If no value is found for the given property, then this method returns {@code null}.
+     */
+    @Override
+    public Object evaluate(final Object candidate) {
+        if (candidate instanceof Feature) {
+            try {
+                return ((Feature) candidate).getPropertyValue(name);
+            } catch (PropertyNotFoundException ex) {
+                // Null will be returned below.
+                // TODO: report a warning somewhere?
+            }
+        } else if (candidate instanceof Map<?,?>) {
+            return ((Map<?,?>) candidate).get(name);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the expected type of values produced by this expression when a feature of the given type is evaluated.
+     *
+     * @throws IllegalArgumentException if this method can not determine the property type for the given feature type.
+     */
+    @Override
+    public PropertyType expectedType(final FeatureType type) {
+        PropertyType propertyType = type.getProperty(name);         // May throw IllegalArgumentException.
+        while (propertyType instanceof Operation) {
+            final IdentifiedType it = ((Operation) propertyType).getResult();
+            if (it instanceof PropertyType) {
+                propertyType = (PropertyType) it;
+            } else if (it instanceof FeatureType) {
+                propertyType = new DefaultAssociationRole(singletonMap(DefaultAssociationRole.NAME_KEY, name), type, 1, 1);
+            } else {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyValueClass_3,
+                            name, PropertyType.class, Classes.getStandardType(Classes.getClass(it))));
+            }
+        }
+        return propertyType;
+    }
+
+    /**
+     * Accepts a visitor.
+     */
+    @Override
+    public Object accept(final ExpressionVisitor visitor, final Object extraData) {
+        return visitor.visit(this, extraData);
+    }
+
+    /**
+     * Returns a hash-code value for this expression.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        return (obj instanceof DefaultPropertyName) && name.equals(((DefaultPropertyName) obj).name);
+    }
+
+    /**
+     * Returns a hash-code value for this expression.
+     */
+    @Override
+    public int hashCode() {
+        return name.hashCode() ^ (int) serialVersionUID;
+    }
+
+    /**
+     * Returns a string representation of this expression.
+     */
+    @Override
+    public String toString() {
+        return '{' + name + '}';
+    }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java
new file mode 100644
index 0000000..4495a1b
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java
@@ -0,0 +1,102 @@
+/*
+ * 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.filter;
+
+import java.io.Serializable;
+
+// Branch-dependent imports
+import org.opengis.filter.sort.SortBy;
+import org.opengis.filter.sort.SortOrder;
+import org.opengis.filter.expression.PropertyName;
+
+
+/**
+ * Defines a sort order based on a property and ascending/descending order.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class DefaultSortBy implements SortBy, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 5434026034835575812L;
+
+    /**
+     * The property on which to apply sorting.
+     */
+    private final PropertyName property;
+
+    /**
+     * The desired order: {@code ASCENDING} or {@code DESCENDING}.
+     */
+    private final SortOrder order;
+
+    /**
+     * Creates a new {@code SortBy} filter.
+     * It is caller responsibility to ensure that no argument is null.
+     *
+     * @param property  property on which to apply sorting.
+     * @param order     the desired order: {@code ASCENDING} or {@code DESCENDING}.
+     */
+    DefaultSortBy(final PropertyName property, final SortOrder order) {
+        this.property = property;
+        this.order    = order;
+    }
+
+    /**
+     * Returns the property to sort by.
+     */
+    @Override
+    public PropertyName getPropertyName() {
+        return property;
+    }
+
+    /**
+     * Returns the sort order: {@code ASCENDING} or {@code DESCENDING}.
+     */
+    @Override
+    public SortOrder getSortOrder() {
+        return order;
+    }
+
+    /**
+     * Computes a hash code value for this filter.
+     */
+    @Override
+    public int hashCode() {
+        return property.hashCode() + 41 * order.hashCode();
+    }
+
+    /**
+     * Compares this filter with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultSortBy) {
+            final DefaultSortBy other = (DefaultSortBy) obj;
+            return property.equals(other.property)
+                   && order.equals(other.order);
+        }
+        return false;
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java b/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
similarity index 70%
rename from core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
rename to core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
index a6bb000..7291456 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
@@ -14,24 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.geoapi.temporal;
-
-import java.util.Date;
-
 
 /**
... 35932 lines suppressed ...

-- 
To stop receiving notification emails like this one, please contact
desruisseaux@apache.org.

Mime
View raw message