Merge HTML changes in javadoc from branch 'geoapi-3.1'.
diff --git a/application/pom.xml b/application/pom.xml
index 3991a25..611bbe0 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/application/sis-console/pom.xml b/application/sis-console/pom.xml
index 2cef7f9..0296b0d 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.x-SNAPSHOT</version>
+    <version>1.1-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 9171156..2d7979c 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,6 +36,10 @@
 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.
@@ -124,11 +128,11 @@
         }
         if (metadata != null) {
             final List<Row> rows;
-            if (metadata instanceof Metadata) {
+            if (metadata instanceof DefaultMetadata) {
                 rows = new ArrayList<>();
-                final Identifier id = ((Metadata) metadata).getMetadataIdentifier();
-                if (id != null) {
-                    CharSequence desc = id.getDescription();
+                final Identifier id = ((DefaultMetadata) metadata).getMetadataIdentifier();
+                if (id instanceof DefaultIdentifier) {
+                    CharSequence desc = ((DefaultIdentifier) 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 3b61f36..8dcd9dd 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,8 +42,9 @@
      * @throws Exception if an error occurred while creating the command.
      */
     @Test
+    @Ignore("Requires GeoAPI 3.1")
     public void testNetCDF() throws Exception {
-        final URL url = TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = new URL("Cube2D_geographic_packed.nc"); // TestData.NETCDF_2D_GEOGRAPHIC.location();
         final MetadataCommand test = new MetadataCommand(0, CommandRunner.TEST, url.toString());
         test.run();
         verifyNetCDF("Metadata", test.outputBuffer.toString());
@@ -66,9 +67,10 @@
      * @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 = TestData.NETCDF_2D_GEOGRAPHIC.location();
+        final URL url = new URL("Cube2D_geographic_packed.nc") ; // 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/application/sis-javafx/pom.xml b/application/sis-javafx/pom.xml
index fa7f327..390c87d 100644
--- a/application/sis-javafx/pom.xml
+++ b/application/sis-javafx/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>application</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
index fa5338a..dd5ec20 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
@@ -33,8 +33,8 @@
 import javafx.scene.control.TreeView;
 import javafx.scene.layout.BorderPane;
 import javafx.beans.property.SimpleObjectProperty;
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
 import org.opengis.geometry.Geometry;
 import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.storage.DataStoreException;
@@ -58,7 +58,7 @@
 
     private String bundlePrefix;
 
-    private String generateFinalColumnName(final PropertyType prop) {
+    private String generateFinalColumnName(final AbstractIdentifiedType prop) {
         Map<String, Map.Entry<String, String>> labelInfo = (Map) prop.getDesignation();
         final String labelName = prop.getName().toString();
         String columnName = labelName;
@@ -123,7 +123,7 @@
     }
 
     public FeatureTable(Resource res, int i) throws DataStoreException {
-        TableView<Feature> ttv = new TableView<>();
+        TableView<AbstractFeature> ttv = new TableView<>();
         final ScrollPane scroll = new ScrollPane(ttv);
         scroll.setFitToHeight(true);
         scroll.setFitToWidth(true);
@@ -133,14 +133,14 @@
         scroll.setPrefSize(600, 400);
         scroll.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
         setCenter(scroll);
-        final List<Feature> list;
+        final List<AbstractFeature> list;
         if (res instanceof FeatureSet) {
-            try (Stream<Feature> stream = ((FeatureSet) res).features(false)) {
+            try (Stream<AbstractFeature> stream = ((FeatureSet) res).features(false)) {
                 list = stream.collect(Collectors.toList());
                 ttv.setItems(FXCollections.observableArrayList(list));
-                for (PropertyType pt : list.get(0).getType().getProperties(false)) {
-                    final TableColumn<Feature, BorderPane> column = new TableColumn<>(generateFinalColumnName(pt));
-                    column.setCellValueFactory((TableColumn.CellDataFeatures<Feature, BorderPane> param) -> {
+                for (AbstractIdentifiedType pt : list.get(0).getType().getProperties(false)) {
+                    final TableColumn<AbstractFeature, BorderPane> column = new TableColumn<>(generateFinalColumnName(pt));
+                    column.setCellValueFactory((TableColumn.CellDataFeatures<AbstractFeature, BorderPane> param) -> {
                         final Object val = param.getValue().getPropertyValue(pt.getName().toString());
                         if (val instanceof Geometry) {
                             return new SimpleObjectProperty<>(new BorderPane(new Label("{geometry}")));
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java
index 77df9a5..b556230 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/MetadataOverview.java
@@ -48,8 +48,6 @@
 import javafx.scene.paint.Color;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.citation.CitationDate;
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.GeographicDescription;
@@ -63,8 +61,6 @@
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.metadata.iso.citation.DefaultIndividual;
-import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
 import org.apache.sis.metadata.iso.spatial.DefaultGridSpatialRepresentation;
 import org.apache.sis.util.iso.Types;
 
@@ -159,35 +155,12 @@
             gp.add(comboBox, j, k++, 2, 1);
         }
 
-        // Show author information.
-        Collection<? extends Responsibility> contacts = this.metadata.getContacts();
-        if (!contacts.isEmpty()) {
-            Responsibility contact = contacts.iterator().next();
-            Collection<? extends Party> parties = contact.getParties();
-            if (!parties.isEmpty()) {
-                Party party = parties.iterator().next();
-                if (party.getName() != null) {
-                    Label partyType = new Label("Party");
-                    Label partyValue = new Label(party.getName().toString());
-                    partyValue.setWrapText(true);
-                    if (party instanceof DefaultOrganisation) {
-                        partyType.setText("Organisation");
-                    } else if (party instanceof DefaultIndividual) {
-                        partyType.setText("Author");
-                    }
-                    gp.add(partyType, j, k);
-                    gp.add(partyValue, ++j, k++);
-                    j = 0;
-                }
-            }
-        }
-
         GridPane gpi = new GridPane();
         gpi.setHgap(10.00);
 
         comboBox.setOnAction(e -> {
             gpi.getChildren().clear();
-            Identification id = m.get(comboBox.getValue());
+            DataIdentification id = (DataIdentification) m.get(comboBox.getValue());
             if (comboBox.getValue().equals("No data to show")) {
                 return;
             }
diff --git a/application/sis-openoffice/pom.xml b/application/sis-openoffice/pom.xml
index 5ad6d84..71a7aa0 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.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
@@ -87,7 +87,7 @@
   <dependencies>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
     <dependency>
       <groupId>org.apache.sis.core</groupId>
diff --git a/application/sis-openoffice/src/main/unopkg/build-instruction.html b/application/sis-openoffice/src/main/unopkg/build-instruction.html
index 27fffa1..a26b547 100644
--- a/application/sis-openoffice/src/main/unopkg/build-instruction.html
+++ b/application/sis-openoffice/src/main/unopkg/build-instruction.html
@@ -102,7 +102,7 @@
 
 <h2>Test in Apache OpenOffice:</h2>
 <blockquote><pre>cd target
-unopkg add apache-sis-1.0-SNAPSHOT.oxt --log-file log.txt
+unopkg add apache-sis-1.1-SNAPSHOT.oxt --log-file log.txt
 scalc -Xdebug -env:RTL_LOGFILE=log.txt</pre></blockquote>
 
   </body>
diff --git a/application/sis-webapp/pom.xml b/application/sis-webapp/pom.xml
index 3253e8b..5c0be18 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.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.application</groupId>
diff --git a/core/pom.xml b/core/pom.xml
index 6318b08..5702b9c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
@@ -171,7 +171,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
@@ -192,7 +192,6 @@
     <module>sis-referencing</module>
     <module>sis-referencing-by-identifiers</module>
     <module>sis-feature</module>
-    <module>sis-cql</module>
     <module>sis-portrayal</module>
   </modules>
 
diff --git a/core/sis-build-helper/pom.xml b/core/sis-build-helper/pom.xml
index 48071a3..cc0c7e4 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.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
 
diff --git a/core/sis-cql/pom.xml b/core/sis-cql/pom.xml
deleted file mode 100644
index 5aa10c4..0000000
--- a/core/sis-cql/pom.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-  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.
--->
-
-<project xmlns              = "http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi          = "http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
-                               http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>org.apache.sis</groupId>
-    <artifactId>core</artifactId>
-    <version>2.0-SNAPSHOT</version>
-  </parent>
-
-
-  <!-- ===========================================================
-           Module Description
-       =========================================================== -->
-  <groupId>org.apache.sis.core</groupId>
-  <artifactId>sis-cql</artifactId>
-  <name>Apache SIS CQL</name>
-  <description>
-    CQL parser.
-  </description>
-
-
-  <!-- ===========================================================
-           Developers and Contributors
-       =========================================================== -->
-  <developers>
-    <developer>
-      <name>Johann Sorel</name>
-      <id>jsorel</id>
-      <organization>Geomatys</organization>
-      <organizationUrl>http://www.geomatys.com</organizationUrl>
-      <timezone>+1</timezone>
-      <roles>
-        <role>developer</role>
-      </roles>
-    </developer>
-  </developers>
-
-
-  <!-- ===========================================================
-           Build configuration
-       =========================================================== -->
-  <build>
-    <plugins>
-      <!-- Anticipation for Java 9 -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <configuration>
-          <archive>
-            <manifestEntries>
-              <Automatic-Module-Name>
-                org.apache.sis.cql
-              </Automatic-Module-Name>
-            </manifestEntries>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.antlr</groupId>
-        <artifactId>antlr4-maven-plugin</artifactId>
-        <version>4.7</version>
-        <executions>
-          <execution>
-            <id>run antlr</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>antlr4</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-        <executions>
-          <execution>
-            <goals>
-              <goal>check</goal>
-            </goals>
-            <configuration>
-              <skip>true</skip>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-
-  <!-- ===========================================================
-           Dependencies
-       =========================================================== -->
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.sis.core</groupId>
-      <artifactId>sis-feature</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.locationtech.jts</groupId>
-      <artifactId>jts-core</artifactId>
-      <optional>true</optional>
-    </dependency>
-    <dependency>
-      <groupId>org.antlr</groupId>
-      <artifactId>antlr4-runtime</artifactId>
-      <version>4.7</version>
-      <scope>compile</scope>
-    </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>
-    </dependency>
-  </dependencies>
-
-</project>
diff --git a/core/sis-cql/src/main/antlr4/org/apache/sis/internal/cql/CQL.g4 b/core/sis-cql/src/main/antlr4/org/apache/sis/internal/cql/CQL.g4
deleted file mode 100644
index 3578ce8..0000000
--- a/core/sis-cql/src/main/antlr4/org/apache/sis/internal/cql/CQL.g4
+++ /dev/null
@@ -1,280 +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.
- */
-grammar CQL;
-
-options {
-    language = Java;
-}
-
-//-----------------------------------------------------------------//
-// LEXER
-//-----------------------------------------------------------------//
-
-
-// GLOBAL STUFF ---------------------------------------
-
-COMMA 	: ',' ;
-WS  :   ( ' ' | '\t' | '\r'| '\n' ) -> skip;
-UNARY : '+' | '-' ;
-MULT : '*' | '/' ;
-fragment DIGIT : '0'..'9' ;
-
-// caseinsensitive , possible alternative solution ?
-fragment A: ('a'|'A');
-fragment B: ('b'|'B');
-fragment C: ('c'|'C');
-fragment D: ('d'|'D');
-fragment E: ('e'|'E');
-fragment F: ('f'|'F');
-fragment G: ('g'|'G');
-fragment H: ('h'|'H');
-fragment I: ('i'|'I');
-fragment J: ('j'|'J');
-fragment K: ('k'|'K');
-fragment L: ('l'|'L');
-fragment M: ('m'|'M');
-fragment N: ('n'|'N');
-fragment O: ('o'|'O');
-fragment P: ('p'|'P');
-fragment Q: ('q'|'Q');
-fragment R: ('r'|'R');
-fragment S: ('s'|'S');
-fragment T: ('t'|'T');
-fragment U: ('u'|'U');
-fragment V: ('v'|'V');
-fragment W: ('w'|'W');
-fragment X: ('x'|'X');
-fragment Y: ('y'|'Y');
-fragment Z: ('z'|'Z');
-fragment LETTER : ~('0'..'9' | ' ' | '\t' | '\r'| '\n' | ',' | '-' | '+' | '*' | '/' | '(' | ')' | '=' | '>' | '<');
-
-LPAREN : '(';
-RPAREN : ')';
-
-
-//LITERALS  ----------------------------------------------
-
-TEXT :   '\'' ( ESC_SEQ | ~('\'') )* '\'' ;
-INT : DIGIT+ ;
-
-FLOAT
-    :   ('0'..'9')+ '.' ('0'..'9')* EXPONENT?
-    |   '.' ('0'..'9')+ EXPONENT?
-    |   ('0'..'9')+ EXPONENT
-    ;
-
-
-// FILTERING OPERAND -----------------------------------
-COMPARE
-	: EQUALABOVE
-	| EQUALUNDER
-	| NOTEQUAL
-	| EQUAL
-	| ABOVE
-	| UNDER
-	;
-fragment EQUALABOVE : '>=' ;
-fragment EQUALUNDER : '<=' ;
-fragment NOTEQUAL   : '<>' ;
-fragment EQUAL      : '=' ;
-fragment ABOVE      : '>' ;
-fragment UNDER      : '<' ;
-LIKE    : L I K E;
-ILIKE   : I L I K E;
-
-IS      : I S ;
-NULL    : N U L L ;
-BETWEEN : B E T W E E N;
-IN      : I N;
-
-
-
-// LOGIC ----------------------------------------------
-AND : A N D;
-OR  : O R ;
-NOT : N O T ;
-
-// GEOMETRIC TYPES AND FILTERS ------------------------
-POINT               : P O I N T ;
-LINESTRING          : L I N E S T R I N G ;
-POLYGON             : P O L Y G O N ;
-MPOINT              : M U L T I P O I N T ;
-MLINESTRING         : M U L T I L I N E S T R I N G ;
-MPOLYGON            : M U L T I P O L Y G O N ;
-GEOMETRYCOLLECTION  : G E O M E T R Y C O L L E C T I O N ;
-ENVELOPE            : E N V E L O P E ;
-EMPTY               : E M P T Y ;
-
-BBOX        : B B O X ;
-BEYOND      : B E Y O N D ;
-CONTAINS    : C O N T A I N S ;
-CROSSES     : C R O S S E S;
-DISJOINT    : D I S J O I N T ;
-DWITHIN     : D W I T H I N ;
-EQUALS      : E Q U A L S ;
-INTERSECTS  : I N T E R S E C T S;
-OVERLAPS    : O V E R L A P S;
-TOUCHES     : T O U C H E S;
-WITHIN      : W I T H I N ;
-
-// TEMPORAL TYPES AND FILTERS
-
-DATE : DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ('.' DIGIT+)? 'Z';
-DURATION_P : P (INT 'Y')? (INT 'M')? (INT 'D')? (INT 'H')? (INT 'M')? (INT 'S')?;
-DURATION_T : T (INT 'H')? (INT 'M')? (INT 'S')?;
-
-AFTER		: A F T E R ;
-ANYINTERACTS	: A N Y I N T E R A C T S ;
-BEFORE		: B E F O R E ;
-BEGINS		: B E G I N S ;
-BEGUNBY		: B E G U N B Y ;
-DURING		: D U R I N G ;
-ENDEDBY		: E N D E D B Y ;
-ENDS		: E N D S ;
-MEETS		: M E E T S ;
-METBY		: M E T B Y ;
-OVERLAPPEDBY	: O V E R L A P P E D B Y ;
-TCONTAINS	: T C O N T A I N S ;
-TEQUALS		: T E Q U A L S ;
-TOVERLAPS	: T O V E R L A P S ;
-
-
-// PROPERTY NAME -------------------------------------
-PROPERTY_NAME    	:  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'    ;
-NAME   	: LETTER (DIGIT|LETTER)* ;
-
-
-// FRAGMENT -------------------------------------------
-
-fragment EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
-fragment HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
-
-fragment
-ESC_SEQ
-    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
-    |   UNICODE_ESC
-    |   OCTAL_ESC
-    ;
-
-fragment
-OCTAL_ESC
-    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
-    |   '\\' ('0'..'7') ('0'..'7')
-    |   '\\' ('0'..'7')
-    ;
-
-fragment
-UNICODE_ESC
-    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
-    ;
-
-
-
-
-//-----------------------------------------------------------------//
-// PARSER
-//-----------------------------------------------------------------//
-
-expressionNum : INT | FLOAT ;
-expressionUnary : UNARY? expressionNum ;
-
-coordinate          : expressionUnary expressionUnary ;
-coordinateSerie    : LPAREN coordinate (COMMA coordinate)*  RPAREN ;
-coordinateSeries   : LPAREN coordinateSerie (COMMA coordinateSerie)* RPAREN;
-
-expressionGeometry
-	: POINT ( EMPTY | coordinateSerie )
-	| LINESTRING ( EMPTY | coordinateSerie )
-	| POLYGON ( EMPTY | coordinateSeries )
-	| MPOINT ( EMPTY | coordinateSerie )
-	| MLINESTRING  ( EMPTY | coordinateSeries )
-	| MPOLYGON ( EMPTY | LPAREN coordinateSeries (COMMA coordinateSeries)* RPAREN )
-        | GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
-        | ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
-	;
-
-expressionFctParam
-        : expression (COMMA expression)*
-        ;
-
-expressionTerm
-	: TEXT
-	| expressionUnary
-	| PROPERTY_NAME
-	| DATE
-	| DURATION_P
-	| DURATION_T
-	| NAME (LPAREN expressionFctParam? RPAREN)?
-	| expressionGeometry
-	| LPAREN expression RPAREN
-	;
-
-expression : expression MULT expression
-           | expression UNARY expression
-           | expressionTerm
-           ;
-
-filterGeometry
-        : BBOX LPAREN expression COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
-        | BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-        | CONTAINS LPAREN expression COMMA expression RPAREN
-        | CROSSES LPAREN expression COMMA expression RPAREN
-        | DISJOINT LPAREN expression COMMA expression RPAREN
-        | DWITHIN LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-        | EQUALS LPAREN expression COMMA expression RPAREN
-        | INTERSECTS LPAREN expression COMMA expression RPAREN
-        | OVERLAPS LPAREN expression COMMA expression RPAREN
-        | TOUCHES LPAREN expression COMMA expression RPAREN
-        | WITHIN LPAREN expression COMMA expression RPAREN
-        ;
-
-filterTerm 	: expression
-                    (
-                              COMPARE  expression
-                            | NOT? IN LPAREN (expressionFctParam )?  RPAREN
-                            | BETWEEN expression AND expression
-                            | NOT? LIKE expression
-                            | NOT? ILIKE expression
-                            | IS NOT? NULL
-                            | AFTER  expression
-                            | ANYINTERACTS expression
-                            | BEFORE expression
-                            | BEGINS expression
-                            | BEGUNBY expression
-                            | DURING expression
-                            | ENDEDBY expression
-                            | ENDS expression
-                            | MEETS expression
-                            | METBY expression
-                            | OVERLAPPEDBY expression
-                            | TCONTAINS expression
-                            | TEQUALS expression
-                            | TOVERLAPS expression
-                    )
-                | filterGeometry
-                ;
-
-filter : filter (AND filter)+
-       | filter (OR filter )+
-       | LPAREN filter RPAREN
-       | NOT (filterTerm | (LPAREN filter RPAREN) )
-       | filterTerm
-       ;
-
-filterOrExpression : filter | expression ;
-
-
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java b/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
deleted file mode 100644
index 2c543db..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
+++ /dev/null
@@ -1,752 +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.cql;
-
-import java.time.temporal.TemporalAccessor;
-import java.util.ArrayList;
-import java.util.List;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.TerminalNode;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.internal.cql.AntlrCQL;
-import org.apache.sis.internal.cql.CQLParser.CoordinateContext;
-import org.apache.sis.internal.cql.CQLParser.CoordinateSerieContext;
-import org.apache.sis.internal.cql.CQLParser.CoordinateSeriesContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionFctParamContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionGeometryContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionNumContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionTermContext;
-import org.apache.sis.internal.cql.CQLParser.ExpressionUnaryContext;
-import org.apache.sis.internal.cql.CQLParser.FilterContext;
-import org.apache.sis.internal.cql.CQLParser.FilterGeometryContext;
-import org.apache.sis.internal.cql.CQLParser.FilterTermContext;
-import org.apache.sis.internal.util.StandardDateFormat;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.CoordinateSequence;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.And;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.Or;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.PropertyName;
-
-import static org.apache.sis.internal.cql.CQLParser.*;
-
-
-/**
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final class CQL {
-
-    private static final GeometryFactory GF = new GeometryFactory();
-
-    private CQL() {
-    }
-
-    public static Expression parseExpression(String cql) throws CQLException {
-        return parseExpression(cql, null);
-    }
-
-    public static Expression parseExpression(String cql, FilterFactory2 factory) throws CQLException {
-        final Object obj = AntlrCQL.compileExpression(cql);
-        ParseTree tree = null;
-        Expression result = null;
-        if (obj instanceof ExpressionContext) {
-            tree = (ParseTree) obj;
-            if (factory == null) {
-                factory = new DefaultFilterFactory();
-            }
-            result = convertExpression(tree, factory);
-        }
-        return result;
-    }
-
-    public static Filter parseFilter(String cql) throws CQLException {
-        return parseFilter(cql, null);
-    }
-
-    public static Filter parseFilter(String cql, FilterFactory2 factory) throws CQLException {
-        cql = cql.trim();
-
-        // Bypass parsing for inclusive filter.
-        if (cql.isEmpty() || "*".equals(cql)) {
-            return Filter.INCLUDE;
-        }
-
-        final Object obj = AntlrCQL.compileFilter(cql);
-
-        ParseTree tree = null;
-        Filter result = null;
-        if (obj instanceof FilterContext) {
-            tree = (FilterContext) obj;
-            if (factory == null) {
-                factory = new DefaultFilterFactory();
-            }
-            result = convertFilter(tree, factory);
-        }
-        return result;
-    }
-
-    public static String write(Filter filter) {
-        if (filter == null) {
-            return "";
-        }
-        final StringBuilder sb = new StringBuilder();
-        filter.accept(FilterToCQLVisitor.INSTANCE, sb);
-        return sb.toString();
-    }
-
-    public static String write(Expression exp) {
-        if (exp == null) {
-            return "";
-        }
-        final StringBuilder sb = new StringBuilder();
-        exp.accept(FilterToCQLVisitor.INSTANCE, sb);
-        return sb.toString();
-    }
-
-    /**
-     * Convert the given tree in an Expression.
-     */
-    private static Expression convertExpression(ParseTree tree, FilterFactory2 ff) throws CQLException {
-        if (tree instanceof ExpressionContext) {
-            //: expression MULT expression
-            //| expression UNARY expression
-            //| expressionTerm
-            if (tree.getChildCount() == 3) {
-                final String operand = tree.getChild(1).getText();
-                final Expression left = convertExpression((ParseTree) tree.getChild(0), ff);
-                final Expression right = convertExpression((ParseTree) tree.getChild(2), ff);
-                if ("*".equals(operand)) {
-                    return ff.multiply(left, right);
-                } else if ("/".equals(operand)) {
-                    return ff.divide(left, right);
-                } else if ("+".equals(operand)) {
-                    return ff.add(left, right);
-                } else if ("-".equals(operand)) {
-                    return ff.subtract(left, right);
-                }
-            } else {
-                return convertExpression(tree.getChild(0), ff);
-            }
-        } //        else if(tree instanceof ExpressionStringContext){
-        //            //strip start and end '
-        //            final String text = tree.getText();
-        //            return ff.literal(text.substring(1, text.length()-1));
-        //        }
-        else if (tree instanceof ExpressionTermContext) {
-            //: expressionString
-            //| expressionUnary
-            //| PROPERTY_NAME
-            //| DATE
-            //| DURATION_P
-            //| DURATION_T
-            //| NAME (LPAREN expressionFctParam? RPAREN)?
-            //| expressionGeometry
-            //| LPAREN expression RPAREN
-
-            //: TEXT
-            //| expressionUnary
-            //| PROPERTY_NAME
-            //| DATE
-            //| DURATION_P
-            //| DURATION_T
-            //| expressionGeometry
-            final ExpressionTermContext exp = (ExpressionTermContext) tree;
-            if (exp.getChildCount() == 1) {
-                return convertExpression(tree.getChild(0), ff);
-            }
-
-            // LPAREN expression RPAREN
-            if (exp.expression() != null) {
-                return convertExpression(exp.expression(), ff);
-            }
-
-            // NAME (LPAREN expressionFctParam? RPAREN)?
-            if (exp.NAME() != null) {
-                final String name = exp.NAME().getText();
-                final ExpressionFctParamContext prm = exp.expressionFctParam();
-                if (prm == null) {
-                    //handle as property name
-                    return ff.property(name);
-                }
-
-                // Handle as a function.
-                final List<ExpressionContext> params = prm.expression();
-                final List<Expression> exps = new ArrayList<Expression>();
-                for (int i = 0, n = params.size(); i < n; i++) {
-                    exps.add(convertExpression(params.get(i), ff));
-                }
-                return ff.function(name, exps.toArray(new Expression[exps.size()]));
-            }
-        } else if (tree instanceof ExpressionUnaryContext) {
-            //: UNARY? expressionNum ;
-            final ExpressionUnaryContext exp = (ExpressionUnaryContext) tree;
-            return ff.literal(unaryAsNumber(exp));
-
-        } else if (tree instanceof ExpressionNumContext) {
-            //: INT | FLOAT ;
-            return convertExpression(tree.getChild(0), ff);
-        } else if (tree instanceof TerminalNode) {
-            final TerminalNode exp = (TerminalNode) tree;
-            final int type = exp.getSymbol().getType();
-            if (PROPERTY_NAME == type) {
-                //strip start and end "
-                final String text = tree.getText();
-                return ff.property(text.substring(1, text.length() - 1));
-            } else if (NAME == type) {
-                return ff.property(tree.getText());
-            } else if (INT == type) {
-                return ff.literal(Integer.valueOf(tree.getText()));
-            } else if (FLOAT == type) {
-                return ff.literal(Double.valueOf(tree.getText()));
-            } else if (DATE == type) {
-                TemporalAccessor ta = StandardDateFormat.FORMAT.parse(tree.getText());
-                return ff.literal(ta);
-            } else if (DURATION_P == type || DURATION_T == type) {
-                // TODO
-                //return ff.literal(TemporalUtilities.getTimeInMillis(tree.getText()));
-            } else if (TEXT == type) {
-                //strip start and end '
-                String text = tree.getText();
-                text = text.replaceAll("\\\\'", "'");
-                return ff.literal(text.substring(1, text.length() - 1));
-            }
-        } else if (tree instanceof ExpressionGeometryContext) {
-            //: POINT ( EMPTY | coordinateSerie )
-            //| LINESTRING ( EMPTY | coordinateSerie )
-            //| POLYGON ( EMPTY | coordinateSeries )
-            //| MPOINT ( EMPTY | coordinateSerie )
-            //| MLINESTRING  ( EMPTY | coordinateSeries )
-            //| MPOLYGON ( EMPTY | LPAREN coordinateSeries (COMMA coordinateSeries)* RPAREN )
-            //| GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
-            //| ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
-            final ExpressionGeometryContext exp = (ExpressionGeometryContext) tree;
-            final int type = ((TerminalNode) exp.getChild(0)).getSymbol().getType();
-
-            if (POINT == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final CoordinateSequence cs;
-                if (isEmptyToken(st)) {
-                    cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                } else {
-                    cs = parseSequence(st);
-                }
-                final Geometry geom = GF.createPoint(cs);
-                return ff.literal(geom);
-            } else if (LINESTRING == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final CoordinateSequence cs;
-                if (isEmptyToken(st)) {
-                    cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                } else {
-                    cs = parseSequence(st);
-                }
-                final Geometry geom = GF.createLineString(cs);
-                return ff.literal(geom);
-            } else if (POLYGON == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final Geometry geom;
-                if (isEmptyToken(st)) {
-                    geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
-                } else {
-                    final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
-                    final List<CoordinateSerieContext> subs = series.coordinateSerie();
-                    final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
-                    final int n = subs.size();
-                    final LinearRing[] holes = new LinearRing[n - 1];
-                    for (int i = 1; i < n; i++) {
-                        holes[i - 1] = GF.createLinearRing(parseSequence(subs.get(i)));
-                    }
-                    geom = GF.createPolygon(contour, holes);
-                }
-                return ff.literal(geom);
-            } else if (MPOINT == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final CoordinateSequence cs;
-                if (isEmptyToken(st)) {
-                    cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
-                } else {
-                    cs = parseSequence(st);
-                }
-                final Geometry geom = GF.createMultiPoint(cs);
-                return ff.literal(geom);
-            } else if (MLINESTRING == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final Geometry geom;
-                if (isEmptyToken(st)) {
-                    geom = GF.createMultiLineString(new LineString[0]);
-                } else {
-                    final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
-                    final List<CoordinateSerieContext> subs = series.coordinateSerie();
-                    final int n = subs.size();
-                    final LineString[] strings = new LineString[n];
-                    for (int i = 0; i < n; i++) {
-                        strings[i] = GF.createLineString(parseSequence(subs.get(i)));
-                    }
-                    geom = GF.createMultiLineString(strings);
-                }
-                return ff.literal(geom);
-            } else if (MPOLYGON == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final Geometry geom;
-                if (isEmptyToken(st)) {
-                    geom = GF.createMultiPolygon(new Polygon[0]);
-                } else {
-                    final List<CoordinateSeriesContext> eles = exp.coordinateSeries();
-                    final int n = eles.size();
-                    final Polygon[] polygons = new Polygon[n];
-                    for (int i = 0; i < n; i++) {
-                        final CoordinateSeriesContext polyTree = eles.get(i);
-                        final List<CoordinateSerieContext> subs = polyTree.coordinateSerie();
-                        final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
-                        final int hn = subs.size();
-                        final LinearRing[] holes = new LinearRing[hn - 1];
-                        for (int j = 1; j < hn; j++) {
-                            holes[j - 1] = GF.createLinearRing(parseSequence(subs.get(j)));
-                        }
-                        final Polygon poly = GF.createPolygon(contour, holes);
-                        polygons[i] = poly;
-                    }
-                    geom = GF.createMultiPolygon(polygons);
-                }
-                return ff.literal(geom);
-            } else if (GEOMETRYCOLLECTION == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final Geometry geom;
-                if (isEmptyToken(st)) {
-                    geom = GF.createGeometryCollection(new Geometry[0]);
-                } else {
-                    final List<ExpressionGeometryContext> eles = exp.expressionGeometry();
-                    final int n = eles.size();
-                    final Geometry[] subs = new Geometry[n];
-                    for (int i = 0; i < n; i++) {
-                        final ParseTree subTree = eles.get(i);
-                        final Geometry sub = (Geometry) convertExpression(subTree, ff).evaluate(null);
-                        subs[i] = sub;
-                    }
-                    geom = GF.createGeometryCollection(subs);
-                }
-                return ff.literal(geom);
-            } else if (ENVELOPE == type) {
-                final ParseTree st = (ParseTree) tree.getChild(1);
-                final Geometry geom;
-                if (isEmptyToken(st)) {
-                    geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
-                } else {
-                    final List<ExpressionUnaryContext> unaries = exp.expressionUnary();
-                    final double west = unaryAsNumber(unaries.get(0)).doubleValue();
-                    final double east = unaryAsNumber(unaries.get(1)).doubleValue();
-                    final double north = unaryAsNumber(unaries.get(2)).doubleValue();
-                    final double south = unaryAsNumber(unaries.get(3)).doubleValue();
-                    final LinearRing contour = GF.createLinearRing(new Coordinate[]{
-                        new Coordinate(west, north),
-                        new Coordinate(east, north),
-                        new Coordinate(east, south),
-                        new Coordinate(west, south),
-                        new Coordinate(west, north)
-                    });
-                    geom = GF.createPolygon(contour, new LinearRing[0]);
-                }
-                return ff.literal(geom);
-            }
-            return convertExpression(tree.getChild(0), ff);
-        }
-        throw new CQLException("Unreconized expression : type=" + tree.getText());
-    }
-
-    private static boolean isEmptyToken(ParseTree tree) {
-        return tree instanceof TerminalNode && ((TerminalNode) tree).getSymbol().getType() == EMPTY;
-    }
-
-    private static final Number unaryAsNumber(ExpressionUnaryContext tree) {
-        //: UNARY? expressionNum ;
-        final ExpressionUnaryContext exp = (ExpressionUnaryContext) tree;
-        final boolean negate = (exp.UNARY() != null && exp.UNARY().getSymbol().getText().equals("-"));
-        final ExpressionNumContext num = exp.expressionNum();
-        if (num.INT() != null) {
-            int val = Integer.valueOf(num.INT().getText());
-            return negate ? -val : val;
-        } else {
-            double val = Double.valueOf(num.FLOAT().getText());
-            return negate ? -val : val;
-        }
-    }
-
-    private static CoordinateSequence parseSequence(ParseTree tree) {
-        final CoordinateSerieContext exp = (CoordinateSerieContext) tree;
-        final List<CoordinateContext> lst = exp.coordinate();
-        final int size = lst.size();
-        final Coordinate[] coords = new Coordinate[size];
-        for (int i = 0; i < size; i++) {
-            final CoordinateContext cc = lst.get(i);
-            coords[i] = new Coordinate(
-                    unaryAsNumber(cc.expressionUnary(0)).doubleValue(),
-                    unaryAsNumber(cc.expressionUnary(1)).doubleValue());
-        }
-        return GF.getCoordinateSequenceFactory().create(coords);
-    }
-
-    /**
-     * Convert the given tree in a Filter.
-     */
-    private static Filter convertFilter(ParseTree tree, FilterFactory2 ff) throws CQLException {
-        if (tree instanceof FilterContext) {
-            //: filter (AND filter)+
-            //| filter (OR filter)+
-            //| LPAREN filter RPAREN
-            //| NOT filterTerm
-            //| filterTerm
-
-            final FilterContext exp = (FilterContext) tree;
-
-            //| filterTerm
-            if (exp.getChildCount() == 1) {
-                return convertFilter(tree.getChild(0), ff);
-            } else if (exp.NOT() != null) {
-                //| NOT (filterTerm | ( LPAREN filter RPAREN ))
-                if (exp.filterTerm() != null) {
-                    return ff.not(convertFilter(exp.filterTerm(), ff));
-                } else {
-                    return ff.not(convertFilter(exp.filter(0), ff));
-                }
-
-            } else if (!exp.AND().isEmpty()) {
-                //: filter (AND filter)+
-                final List<Filter> subs = new ArrayList<Filter>();
-                for (FilterContext f : exp.filter()) {
-                    final Filter sub = convertFilter(f, ff);
-                    if (sub instanceof And) {
-                        subs.addAll(((And) sub).getChildren());
-                    } else {
-                        subs.add(sub);
-                    }
-                }
-                return ff.and(subs);
-            } else if (!exp.OR().isEmpty()) {
-                //| filter (OR filter)+
-                final List<Filter> subs = new ArrayList<Filter>();
-                for (FilterContext f : exp.filter()) {
-                    final Filter sub = convertFilter(f, ff);
-                    if (sub instanceof Or) {
-                        subs.addAll(((Or) sub).getChildren());
-                    } else {
-                        subs.add(sub);
-                    }
-                }
-                return ff.or(subs);
-            } else if (exp.LPAREN() != null) {
-                //| LPAREN filter RPAREN
-                return convertFilter(exp.filter(0), ff);
-            }
-
-        } else if (tree instanceof FilterTermContext) {
-            //: expression
-            //    (
-            //              COMPARE  expression
-            //            | NOT? IN LPAREN (expressionFctParam )?  RPAREN
-            //            | BETWEEN expression AND expression
-            //            | NOT? LIKE expression
-            //            | NOT? ILIKE expression
-            //            | IS NOT? NULL
-            //            | AFTER expression
-            //            | ANYINTERACTS expression
-            //            | BEFORE expression
-            //            | BEGINS expression
-            //            | BEGUNBY expression
-            //            | DURING expression
-            //            | ENDEDBY expression
-            //            | ENDS expression
-            //            | MEETS expression
-            //            | METBY expression
-            //            | OVERLAPPEDBY expression
-            //            | TCONTAINS expression
-            //            | TEQUALS expression
-            //            | TOVERLAPS expression
-            //    )
-            //| filterGeometry
-
-            final FilterTermContext exp = (FilterTermContext) tree;
-            final List<ExpressionContext> exps = exp.expression();
-
-            if (exp.COMPARE() != null) {
-                // expression COMPARE expression
-                final String text = exp.COMPARE().getText();
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-
-                if ("=".equals(text)) {
-                    return ff.equals(left, right);
-                } else if ("<>".equals(text)) {
-                    return ff.notEqual(left, right);
-                } else if (">".equals(text)) {
-                    return ff.greater(left, right);
-                } else if ("<".equals(text)) {
-                    return ff.less(left, right);
-                } else if (">=".equals(text)) {
-                    return ff.greaterOrEqual(left, right);
-                } else if ("<=".equals(text)) {
-                    return ff.lessOrEqual(left, right);
-                } else if ("<=".equals(text)) {
-                    return ff.lessOrEqual(left, right);
-                }
-            } else if (exp.IN() != null) {
-                // expression NOT? IN LPAREN (expressionFctParam )?  RPAREN
-                final Expression val = convertExpression(exps.get(0), ff);
-                final ExpressionFctParamContext prm = exp.expressionFctParam();
-                final List<ExpressionContext> params = prm.expression();
-                final List<Expression> subexps = new ArrayList<Expression>();
-                for (int i = 0, n = params.size(); i < n; i++) {
-                    subexps.add(convertExpression(params.get(i), ff));
-                }
-
-                final int size = subexps.size();
-                final Filter selection;
-                if (size == 0) {
-                    selection = Filter.EXCLUDE;
-                } else if (size == 1) {
-                    selection = ff.equals(val, subexps.get(0));
-                } else {
-                    final List<Filter> filters = new ArrayList<Filter>();
-                    for (Expression e : subexps) {
-                        filters.add(ff.equals(val, e));
-                    }
-                    selection = ff.or(filters);
-                }
-
-                if (exp.NOT() != null) {
-                    return ff.not(selection);
-                } else {
-                    return selection;
-                }
-            } else if (exp.BETWEEN() != null) {
-                // expression BETWEEN expression AND expression
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                final Expression exp3 = convertExpression(exps.get(2), ff);
-                return ff.between(exp1, exp2, exp3);
-
-            } else if (exp.LIKE() != null) {
-                // expression NOT? LIKE expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", true));
-                } else {
-                    return ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", true);
-                }
-
-            } else if (exp.ILIKE() != null) {
-                // expression NOT? LIKE expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", false));
-                } else {
-                    return ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", false);
-                }
-
-            } else if (exp.IS() != null) {
-                // expression IS NOT? NULL
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                if (exp.NOT() != null) {
-                    return ff.not(ff.isNull(exp1));
-                } else {
-                    return ff.isNull(exp1);
-                }
-
-            } else if (exp.AFTER() != null) {
-                // expression AFTER expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.after(left, right);
-
-            } else if (exp.ANYINTERACTS() != null) {
-                // expression ANYINTERACTS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.anyInteracts(left, right);
-
-            } else if (exp.BEFORE() != null) {
-                // expression BEFORE expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.before(left, right);
-
-            } else if (exp.BEGINS() != null) {
-                // expression BEGINS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.begins(left, right);
-
-            } else if (exp.BEGUNBY() != null) {
-                // expression BEGUNBY expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.begunBy(left, right);
-
-            } else if (exp.DURING() != null) {
-                // expression DURING expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.during(left, right);
-
-            } else if (exp.ENDEDBY() != null) {
-                // expression ENDEDBY expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.endedBy(left, right);
-
-            } else if (exp.ENDS() != null) {
-                // expression ENDS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.ends(left, right);
-
-            } else if (exp.MEETS() != null) {
-                // expression MEETS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.meets(left, right);
-
-            } else if (exp.METBY() != null) {
-                // expression METBY expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.metBy(left, right);
-
-            } else if (exp.OVERLAPPEDBY() != null) {
-                // expression OVERLAPPEDBY expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.overlappedBy(left, right);
-
-            } else if (exp.TCONTAINS() != null) {
-                // expression TCONTAINS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.tcontains(left, right);
-
-            } else if (exp.TEQUALS() != null) {
-                // expression TEQUALS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.tequals(left, right);
-
-            } else if (exp.TOVERLAPS() != null) {
-                // expression TOVERLAPS expression
-                final Expression left = convertExpression(exps.get(0), ff);
-                final Expression right = convertExpression(exps.get(1), ff);
-                return ff.toverlaps(left, right);
-
-            } else if (exp.filterGeometry() != null) {
-                // expression filterGeometry
-                return convertFilter(exp.filterGeometry(), ff);
-            }
-
-        } else if (tree instanceof FilterGeometryContext) {
-            //: BBOX LPAREN (PROPERTY_NAME|NAME) COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
-            //| BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-            //| CONTAINS LPAREN expression COMMA expression RPAREN
-            //| CROSSES LPAREN expression COMMA expression RPAREN
-            //| DISJOINT LPAREN expression COMMA expression RPAREN
-            //| DWITHIN LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
-            //| EQUALS LPAREN expression COMMA expression RPAREN
-            //| INTERSECTS LPAREN expression COMMA expression RPAREN
-            //| OVERLAPS LPAREN expression COMMA expression RPAREN
-            //| TOUCHES LPAREN expression COMMA expression RPAREN
-            //| WITHIN LPAREN expression COMMA expression RPAREN
-
-            final FilterGeometryContext exp = (FilterGeometryContext) tree;
-            final List<ExpressionContext> exps = exp.expression();
-
-            if (exp.BBOX() != null) {
-                final Expression prop = convertExpression(exps.get(0), ff);
-                final double v1 = unaryAsNumber(exp.expressionUnary(0)).doubleValue();
-                final double v2 = unaryAsNumber(exp.expressionUnary(1)).doubleValue();
-                final double v3 = unaryAsNumber(exp.expressionUnary(2)).doubleValue();
-                final double v4 = unaryAsNumber(exp.expressionUnary(3)).doubleValue();
-                String crs = null;
-                if (exp.TEXT() != null) {
-                    crs = convertExpression(exp.TEXT(), ff).evaluate(null, String.class);
-                }
-                return ff.bbox(prop, v1, v2, v3, v4, crs);
-            } else if (exp.BEYOND() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                final double distance = convertExpression(exps.get(2), ff).evaluate(null, Double.class);
-                final Expression unitExp = convertExpression(exps.get(3), ff);
-                final String unit = (unitExp instanceof PropertyName)
-                        ? ((PropertyName) unitExp).getPropertyName()
-                        : unitExp.evaluate(null, String.class);
-                return ff.beyond(exp1, exp2, distance, unit);
-            } else if (exp.CONTAINS() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.contains(exp1, exp2);
-            } else if (exp.CROSSES() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.crosses(exp1, exp2);
-            } else if (exp.DISJOINT() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.disjoint(exp1, exp2);
-            } else if (exp.DWITHIN() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                final double distance = convertExpression(exps.get(2), ff).evaluate(null, Double.class);
-                final Expression unitExp = convertExpression(exps.get(3), ff);
-                final String unit = (unitExp instanceof PropertyName)
-                        ? ((PropertyName) unitExp).getPropertyName()
-                        : unitExp.evaluate(null, String.class);
-                return ff.dwithin(exp1, exp2, distance, unit);
-            } else if (exp.EQUALS() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.equal(exp1, exp2);
-            } else if (exp.INTERSECTS() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.intersects(exp1, exp2);
-            } else if (exp.OVERLAPS() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.overlaps(exp1, exp2);
-            } else if (exp.TOUCHES() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.touches(exp1, exp2);
-            } else if (exp.WITHIN() != null) {
-                final Expression exp1 = convertExpression(exps.get(0), ff);
-                final Expression exp2 = convertExpression(exps.get(1), ff);
-                return ff.within(exp1, exp2);
-            }
-        }
-        throw new CQLException("Unreconized filter : type=" + tree.getText());
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/CQLException.java b/core/sis-cql/src/main/java/org/apache/sis/cql/CQLException.java
deleted file mode 100644
index 88fc422..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/CQLException.java
+++ /dev/null
@@ -1,52 +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.cql;
-
-
-/**
- * Thrown when a CQL statement can not be parsed.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final class CQLException extends Exception {
-    /**
-     * Serial number for inter-operability with different versions.
-     */
-    private static final long serialVersionUID = -1494977914551289727L;
-
-    /**
-     * Constructs an exception with the specified detail message.
-     *
-     * @param  message  the detail message.
-     */
-    public CQLException(String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs an exception with the specified detail message and cause.
-     *
-     * @param  message  the detail message.
-     * @param  cause    the cause for this exception.
-     */
-    public CQLException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java b/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
deleted file mode 100644
index dd72df6..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
+++ /dev/null
@@ -1,656 +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.cql;
-
-import java.util.Date;
-import java.util.List;
-import java.util.regex.Pattern;
-import org.apache.sis.internal.util.StandardDateFormat;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.io.WKTWriter;
-import org.opengis.filter.And;
-import org.opengis.filter.ExcludeFilter;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.Id;
-import org.opengis.filter.IncludeFilter;
-import org.opengis.filter.Not;
-import org.opengis.filter.Or;
-import org.opengis.filter.PropertyIsBetween;
-import org.opengis.filter.PropertyIsEqualTo;
-import org.opengis.filter.PropertyIsGreaterThan;
-import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
-import org.opengis.filter.PropertyIsLessThan;
-import org.opengis.filter.PropertyIsLessThanOrEqualTo;
-import org.opengis.filter.PropertyIsLike;
-import org.opengis.filter.PropertyIsNil;
-import org.opengis.filter.PropertyIsNotEqualTo;
-import org.opengis.filter.PropertyIsNull;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.Divide;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.NilExpression;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.Contains;
-import org.opengis.filter.spatial.Crosses;
-import org.opengis.filter.spatial.DWithin;
-import org.opengis.filter.spatial.Disjoint;
-import org.opengis.filter.spatial.Equals;
-import org.opengis.filter.spatial.Intersects;
-import org.opengis.filter.spatial.Overlaps;
-import org.opengis.filter.spatial.Touches;
-import org.opengis.filter.spatial.Within;
-import org.opengis.filter.temporal.After;
-import org.opengis.filter.temporal.AnyInteracts;
-import org.opengis.filter.temporal.Before;
-import org.opengis.filter.temporal.Begins;
-import org.opengis.filter.temporal.BegunBy;
-import org.opengis.filter.temporal.During;
-import org.opengis.filter.temporal.EndedBy;
-import org.opengis.filter.temporal.Ends;
-import org.opengis.filter.temporal.Meets;
-import org.opengis.filter.temporal.MetBy;
-import org.opengis.filter.temporal.OverlappedBy;
-import org.opengis.filter.temporal.TContains;
-import org.opengis.filter.temporal.TEquals;
-import org.opengis.filter.temporal.TOverlaps;
-
-
-/**
- * Visitor to convert a Filter in CQL.
- * Returned object is a StringBuilder containing the CQL text.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-final class FilterToCQLVisitor implements FilterVisitor, ExpressionVisitor {
-
-    static final FilterToCQLVisitor INSTANCE = new FilterToCQLVisitor();
-
-    /**
-     * Pattern to check for property name to escape against regExp
-     */
-    private final Pattern patternPropertyName = Pattern.compile("[,+\\-/*\\t\\n\\r\\d\\s]");
-
-    private FilterToCQLVisitor() {
-    }
-
-    private static StringBuilder toStringBuilder(final Object o) {
-        if (o instanceof StringBuilder) {
-            return (StringBuilder) o;
-        }
-        return new StringBuilder();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // FILTER //////////////////////////////////////////////////////////////////
-    ////////////////////////////////////////////////////////////////////////////
-    @Override
-    public Object visitNullFilter(final Object o) {
-        throw new UnsupportedOperationException("Null filter not supported in CQL.");
-    }
-
-    @Override
-    public Object visit(final ExcludeFilter filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("1=0");
-        return sb;
-    }
-
-    @Override
-    public Object visit(final IncludeFilter filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("1=1");
-        return sb;
-    }
-
-    @Override
-    public Object visit(final And filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        final List<Filter> filters = filter.getChildren();
-        if (filters != null && !filters.isEmpty()) {
-            final int size = filters.size();
-            sb.append('(');
-            for (int i = 0, n = size - 1; i < n; i++) {
-                filters.get(i).accept(this, sb);
-                sb.append(" AND ");
-            }
-            filters.get(size - 1).accept(this, sb);
-            sb.append(')');
-        }
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Or filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        final List<Filter> filters = filter.getChildren();
-        if (filters != null && !filters.isEmpty()) {
-            final int size = filters.size();
-            sb.append('(');
-            for (int i = 0, n = size - 1; i < n; i++) {
-                filters.get(i).accept(this, sb);
-                sb.append(" OR ");
-            }
-            filters.get(size - 1).accept(this, sb);
-            sb.append(')');
-        }
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Id filter, final Object o) {
-        throw new UnsupportedOperationException("ID filter not supported in CQL.");
-    }
-
-    @Override
-    public Object visit(final Not filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("NOT ");
-        filter.getFilter().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsBetween filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression().accept(this, sb);
-        sb.append(" BETWEEN ");
-        filter.getLowerBoundary().accept(this, sb);
-        sb.append(" AND ");
-        filter.getUpperBoundary().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsEqualTo filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" = ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsNotEqualTo filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" <> ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsGreaterThan filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" > ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsGreaterThanOrEqualTo filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" >= ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsLessThan filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" < ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsLessThanOrEqualTo filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" <= ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsLike filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        final char escape = filter.getEscape().charAt(0);
-        final char wildCard = filter.getWildCard().charAt(0);
-        final char singleChar = filter.getSingleChar().charAt(0);
-        final boolean matchingCase = filter.isMatchingCase();
-        final String literal = filter.getLiteral();
-        //TODO wild card and escape encoded to sql 92
-        final String pattern = literal;
-
-        filter.getExpression().accept(this, sb);
-
-        if (matchingCase) {
-            sb.append(" LIKE ");
-        } else {
-            sb.append(" ILIKE ");
-        }
-        sb.append('\'');
-        sb.append(pattern);
-        sb.append('\'');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyIsNull filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression().accept(this, sb);
-        sb.append(" IS NULL");
-        return sb;
-    }
-
-    @Override
-    public Object visit(PropertyIsNil filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression().accept(this, sb);
-        sb.append(" IS NIL");
-        return sb;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // GEOMETRY FILTER /////////////////////////////////////////////////////////
-    ////////////////////////////////////////////////////////////////////////////
-    @Override
-    public Object visit(final BBOX filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-
-        if (filter.getExpression1() instanceof PropertyName
-                && filter.getExpression2() instanceof Literal) {
-            //use writing : BBOX(att,v1,v2,v3,v4)
-            sb.append("BBOX(");
-            sb.append(filter.getPropertyName());
-            sb.append(',');
-            sb.append(filter.getMinX());
-            sb.append(',');
-            sb.append(filter.getMaxX());
-            sb.append(',');
-            sb.append(filter.getMinY());
-            sb.append(',');
-            sb.append(filter.getMaxY());
-            sb.append(')');
-
-        } else {
-            //use writing BBOX(exp1,exp2)
-            sb.append("BBOX(");
-            filter.getExpression1().accept(this, sb);
-            sb.append(',');
-            filter.getExpression2().accept(this, sb);
-            sb.append(')');
-        }
-
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Beyond filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("BEYOND(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Contains filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("CONTAINS(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Crosses filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("CROSSES(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Disjoint filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("DISJOINT(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final DWithin filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("DWITHIN(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Equals filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("EQUALS(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Intersects filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("INTERSECTS(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Overlaps filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("OVERLAPS(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Touches filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("TOUCHES(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Within filter, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append("WITHIN(");
-        filter.getExpression1().accept(this, sb);
-        sb.append(',');
-        filter.getExpression2().accept(this, sb);
-        sb.append(')');
-        return sb;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // TEMPORAL FILTER /////////////////////////////////////////////////////////
-    ////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public Object visit(After filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" AFTER ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(AnyInteracts filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" ANYINTERACTS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(Before filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" BEFORE ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(Begins filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" BEGINS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(BegunBy filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" BEGUNBY ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(During filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" DURING ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(EndedBy filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" ENDEDBY ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(Ends filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" ENDS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(Meets filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" MEETS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(MetBy filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" METBY ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(OverlappedBy filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" OVERLAPPEDBY ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(TContains filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" TCONTAINS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(TEquals filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" TEQUALS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(TOverlaps filter, Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        filter.getExpression1().accept(this, sb);
-        sb.append(" TOVERLAPS ");
-        filter.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // EXPRESSIONS /////////////////////////////////////////////////////////////
-    ////////////////////////////////////////////////////////////////////////////
-    @Override
-    public Object visit(final Literal exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-
-        final Object value = exp.getValue();
-        if (value instanceof Number) {
-            final Number num = (Number) value;
-            sb.append(num.toString());
-        } else if (value instanceof Date) {
-            final Date date = (Date) value;
-            sb.append(StandardDateFormat.FORMAT.format(date.toInstant()));
-        } else if (value instanceof Geometry) {
-            final Geometry geometry = (Geometry) value;
-            final WKTWriter writer = new WKTWriter();
-            final String wkt = writer.write(geometry);
-            sb.append(wkt);
-        } else {
-            sb.append('\'').append(value != null ? value.toString() : null).append('\'');
-        }
-        return sb;
-    }
-
-    @Override
-    public Object visit(final PropertyName exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        final String name = exp.getPropertyName();
-        if (patternPropertyName.matcher(name).find()) {
-            //escape for special chars
-            sb.append('"').append(name).append('"');
-        } else {
-            sb.append(name);
-        }
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Add exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        exp.getExpression1().accept(this, sb);
-        sb.append(" + ");
-        exp.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Divide exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        exp.getExpression1().accept(this, sb);
-        sb.append(" / ");
-        exp.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Multiply exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        exp.getExpression1().accept(this, sb);
-        sb.append(" * ");
-        exp.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Subtract exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        exp.getExpression1().accept(this, sb);
-        sb.append(" - ");
-        exp.getExpression2().accept(this, sb);
-        return sb;
-    }
-
-    @Override
-    public Object visit(final Function exp, final Object o) {
-        final StringBuilder sb = toStringBuilder(o);
-        sb.append(exp.getName());
-        sb.append('(');
-        final List<Expression> exps = exp.getParameters();
-        if (exps != null) {
-            final int size = exps.size();
-            if (size == 1) {
-                exps.get(0).accept(this, sb);
-            } else if (size > 1) {
-                for (int i = 0, n = size - 1; i < n; i++) {
-                    exps.get(i).accept(this, sb);
-                    sb.append(" , ");
-                }
-                exps.get(size - 1).accept(this, sb);
-            }
-        }
-        sb.append(')');
-        return sb;
-    }
-
-    @Override
-    public Object visit(final NilExpression exp, final Object o) {
-        throw new UnsupportedOperationException("NilExpression not supported in CQL.");
-    }
-}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/internal/cql/AntlrCQL.java b/core/sis-cql/src/main/java/org/apache/sis/internal/cql/AntlrCQL.java
deleted file mode 100644
index 1f3649a..0000000
--- a/core/sis-cql/src/main/java/org/apache/sis/internal/cql/AntlrCQL.java
+++ /dev/null
@@ -1,98 +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.internal.cql;
-
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CodePointCharStream;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.RecognitionException;
-import org.antlr.v4.runtime.TokenStream;
-import org.antlr.v4.runtime.tree.ParseTree;
-
-
-/**
- * ANTLR CQL parser methods.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final class AntlrCQL {
-
-    private AntlrCQL() {
-    }
-
-    public static ParseTree compile(String cql) {
-        final Object obj = compileFilterOrExpression(cql);
-        ParseTree tree = null;
-        if (obj instanceof ParseTree) {
-            tree = (ParseTree) obj;
-        }
-        return tree;
-    }
-
-    public static Object compileExpression(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.ExpressionContext ctx = parser.expression();
-            return ctx;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileFilter(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.FilterContext retfilter = parser.filter();
-
-            return retfilter;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-
-    public static Object compileFilterOrExpression(String cql) {
-        try {
-            // Lexer splits input into tokens.
-            final CodePointCharStream input = CharStreams.fromString(cql);
-            final TokenStream tokens = new CommonTokenStream(new CQLLexer(input));
-
-            // Parser generates abstract syntax tree.
-            final CQLParser parser = new CQLParser(tokens);
-            final CQLParser.FilterOrExpressionContext retfilter = parser.filterOrExpression();
-
-            return retfilter;
-
-        } catch (RecognitionException e) {
-            throw new IllegalStateException("Recognition exception is never thrown, only declared.");
-        }
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
deleted file mode 100644
index a6c9c26..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
+++ /dev/null
@@ -1,60 +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.cql;
-
-import java.time.Instant;
-import java.util.Date;
-import org.opengis.filter.FilterFactory2;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.test.TestCase;
-
-
-/**
- * Base class of all CQL tests.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-strictfp class CQLTestCase extends TestCase {
-    /**
-     * The factory to use for creating filter and expressions.
-     */
-    final FilterFactory2 FF;
-
-    /**
-     * The factory to use for creating Java Topology Suite (JTS) objects.
-     */
-    final GeometryFactory GF;
-
-    /**
-     * Creates a new test case.
-     */
-    CQLTestCase() {
-        FF = new DefaultFilterFactory();
-        GF = new GeometryFactory();
-    }
-
-    /**
-     * Returns a date from the given string.
-     */
-    static Date parseDate(final String date) {
-        return Date.from(Instant.parse(date));
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
deleted file mode 100644
index aa8762b..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
+++ /dev/null
@@ -1,48 +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.cql;
-
-import org.apache.sis.test.TestSuite;
-import org.junit.BeforeClass;
-import org.junit.runners.Suite;
-
-
-/**
- * All tests from the {@code sis-cql} module.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-@Suite.SuiteClasses({
-    org.apache.sis.cql.ExpressionReadingTest.class,
-    org.apache.sis.cql.ExpressionWritingTest.class,
-    org.apache.sis.cql.FilterReadingTest.class,
-    org.apache.sis.cql.FilterWritingTest.class,
-})
-public final strictfp class CQLTestSuite extends TestSuite {
-    /**
-     * Verifies the list of tests before to run the suite.
-     * See {@link #verifyTestList(Class, Class[])} for more information.
-     */
-    @BeforeClass
-    public static void verifyTestList() {
-        assertNoMissingTest(CQLTestSuite.class);
-        verifyTestList(CQLTestSuite.class);
-    }
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
deleted file mode 100644
index 795b34e..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
+++ /dev/null
@@ -1,613 +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.cql;
-
-import java.text.ParseException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryCollection;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.MultiLineString;
-import org.locationtech.jts.geom.MultiPoint;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.Divide;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.Multiply;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.expression.Subtract;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Test reading CQL expressions.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class ExpressionReadingTest extends CQLTestCase {
-    @Test
-    public void testPropertyName1() throws CQLException {
-        final String cql = "geom";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof PropertyName);
-        final PropertyName expression = (PropertyName) obj;
-        assertEquals("geom", expression.getPropertyName());
-    }
-
-    @Test
-    public void testPropertyName2() throws CQLException {
-        final String cql = "\"geom\"";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof PropertyName);
-        final PropertyName expression = (PropertyName) obj;
-        assertEquals("geom", expression.getPropertyName());
-    }
-
-    @Test
-    public void testPropertyName3() throws CQLException {
-        final String cql = "ùth{e_$uglY^_pr@perté";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof PropertyName);
-        final PropertyName expression = (PropertyName) obj;
-        assertEquals("ùth{e_$uglY^_pr@perté", expression.getPropertyName());
-    }
-
-    @Test
-    public void testInteger() throws CQLException {
-        final String cql = "15";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Integer.valueOf(15), expression.getValue());
-    }
-
-    @Test
-    public void testNegativeInteger() throws CQLException {
-        final String cql = "-15";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Integer.valueOf(-15), expression.getValue());
-    }
-
-    @Test
-    public void testDecimal1() throws CQLException {
-        final String cql = "3.14";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(3.14), expression.getValue());
-    }
-
-    @Test
-    public void testDecimal2() throws CQLException {
-        final String cql = "9e-1";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(9e-1), expression.getValue());
-    }
-
-    @Test
-    public void testNegativeDecimal() throws CQLException {
-        final String cql = "-3.14";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(Double.valueOf(-3.14), expression.getValue());
-    }
-
-    @Test
-    public void testText() throws CQLException {
-        final String cql = "'hello world'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("hello world", expression.getValue());
-    }
-
-    @Test
-    public void testText2() throws CQLException {
-        final String cql = "'Valle d\\'Aosta'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("Valle d'Aosta", expression.getValue());
-    }
-
-    @Test
-    public void testText3() throws CQLException {
-        final String cql = "'Valle d\\'Aosta/Vallée d\\'Aoste'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals("Valle d'Aosta/Vallée d'Aoste", expression.getValue());
-    }
-
-    @Ignore
-    @Test
-    public void testDate() throws CQLException, ParseException{
-        //dates are expected to be formated in ISO 8601 : yyyy-MM-dd'T'HH:mm:ss'Z'
-        final String cql = "2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), expression.getValue());
-    }
-
-    @Ignore
-    @Test
-    public void testDuration() throws CQLException, ParseException{
-        final String cql = "P7Y6M5D4H3M2S";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final long duration = (Long) expression.getValue();
-
-        assertEquals(236966582000l, duration);
-    }
-
-    @Ignore
-    @Test
-    public void testDuration2() throws CQLException, ParseException{
-        final String cql = "T4H3M2S";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final long duration = (Long) expression.getValue();
-
-        assertEquals(14582000,duration);
-    }
-
-    @Test
-    public void testAddition() throws CQLException {
-        final String cql = "3 + 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Add);
-        final Add expression = (Add) obj;
-        assertEquals(FF.add(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Ignore
-    @Test
-    public void testAddition2() throws CQLException {
-        final String cql = "'test' + '23'";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Add);
-        final Add expression = (Add) obj;
-
-        Object res = expression.evaluate(null);
-        assertEquals("test23", res);
-    }
-
-    @Test
-    public void testSubstract() throws CQLException {
-        final String cql = "3 - 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Subtract);
-        final Subtract expression = (Subtract) obj;
-        assertEquals(FF.subtract(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    public void testMultiply() throws CQLException {
-        final String cql = "3 * 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Multiply);
-        final Multiply expression = (Multiply) obj;
-        assertEquals(FF.multiply(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Test
-    public void testDivide() throws CQLException {
-        final String cql = "3 / 2";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Divide);
-        final Divide expression = (Divide) obj;
-        assertEquals(FF.divide(FF.literal(3), FF.literal(2)), expression);
-    }
-
-    @Ignore
-    @Test
-    public void testFunction1() throws CQLException {
-        final String cql = "max(\"att\",15)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Function);
-        final Function expression = (Function) obj;
-        assertEquals(FF.function("max",FF.property("att"), FF.literal(15)), expression);
-    }
-
-    @Ignore
-    @Test
-    public void testFunction2() throws CQLException {
-        final String cql = "min(\"att\",cos(3.14))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Function);
-        final Function expression = (Function) obj;
-        assertEquals(FF.function("min",FF.property("att"), FF.function("cos",FF.literal(3.14d))), expression);
-    }
-
-    @Test
-    public void testGeometryPoint() throws CQLException {
-        final String cql = "POINT(15 30)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPoint(new Coordinate(15, 30));
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryPointEmpty() throws CQLException {
-        final String cql = "POINT EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Point);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMPoint() throws CQLException {
-        final String cql = "MULTIPOINT(15 30, 45 60)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createMultiPoint(
-                new Coordinate[]{
-                    new Coordinate(15, 30),
-                    new Coordinate(45, 60)
-                });
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMPointEmpty() throws CQLException {
-        final String cql = "MULTIPOINT EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiPoint);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryLineString() throws CQLException {
-        final String cql = "LINESTRING(10 20, 30 40, 50 60)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryLineStringEmpty() throws CQLException {
-        final String cql = "LINESTRING EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof LineString);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMLineString() throws CQLException {
-        final String cql = "MULTILINESTRING((10 20, 30 40, 50 60),(70 80, 90 100, 110 120))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createMultiLineString(
-                new LineString[]{
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(10, 20),
-                            new Coordinate(30, 40),
-                            new Coordinate(50, 60)
-                        }),
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120)
-                        })
-                    }
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMLineStringEmpty() throws CQLException {
-        final String cql = "MULTILINESTRING EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiLineString);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryPolygon() throws CQLException {
-        final String cql = "POLYGON((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryPolygonEmpty() throws CQLException {
-        final String cql = "POLYGON EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Polygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryMPolygon() throws CQLException {
-        final String cql = "MULTIPOLYGON("
-                + "((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)),"
-                + "((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81))"
-                + ")";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Polygon geom1 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Polygon geom2 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(11, 21),
-                        new Coordinate(31, 41),
-                        new Coordinate(51, 61),
-                        new Coordinate(11, 21)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(71, 81),
-                            new Coordinate(91, 101),
-                            new Coordinate(111, 121),
-                            new Coordinate(71, 81)
-                        })
-                    }
-                );
-        final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryMPolygonEmpty() throws CQLException {
-        final String cql = "MULTIPOLYGON EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof MultiPolygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryCollection() throws CQLException {
-        final String cql = "GEOMETRYCOLLECTION( POINT(15 30), LINESTRING(10 20, 30 40, 50 60) )";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom1 =  GF.createPoint(new Coordinate(15, 30));
-        final Geometry geom2 =  GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        final GeometryCollection geom =  GF.createGeometryCollection(new Geometry[]{geom1,geom2});
-        final GeometryCollection returned = (GeometryCollection)expression.getValue();
-        assertEquals(geom.getNumGeometries(), returned.getNumGeometries());
-        assertEquals(geom.getGeometryN(0), returned.getGeometryN(0));
-        assertEquals(geom.getGeometryN(1), returned.getGeometryN(1));
-    }
-
-    @Test
-    public void testGeometryCollectionEmpty() throws CQLException {
-        final String cql = "GEOMETRYCOLLECTION EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof GeometryCollection);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testGeometryEnvelope() throws CQLException {
-        final String cql = "ENVELOPE(10, 20, 40, 30)";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 40),
-                        new Coordinate(20, 40),
-                        new Coordinate(20, 30),
-                        new Coordinate(10, 30),
-                        new Coordinate(10, 40)
-                    }),
-                new LinearRing[0]
-                );
-        assertTrue(geom.equals((Geometry)expression.getValue()));
-    }
-
-    @Test
-    public void testGeometryEnvelopeEmpty() throws CQLException {
-        final String cql = "ENVELOPE EMPTY";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Literal);
-        final Literal expression = (Literal) obj;
-        final Geometry geom = (Geometry)expression.getValue();
-        assertTrue(geom instanceof Polygon);
-        assertTrue(geom.isEmpty());
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        final String cql = "((3*1)+(2-6))/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Divide);
-        final Divide expression = (Divide) obj;
-        assertEquals(
-                FF.divide(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.subtract(FF.literal(2), FF.literal(6))
-                        ),
-                    FF.literal(4))
-                , expression);
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        final String cql = "3*1+2/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Add);
-        final Add rootAdd = (Add) obj;
-
-        assertEquals(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-
-    }
-
-    @Ignore
-    @Test
-    public void testCombine3() throws CQLException {
-        final String cql = "3*max(val,15)+2/4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Add);
-        final Add rootAdd = (Add) obj;
-
-        assertEquals(
-                    FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"),FF.literal(15))
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-
-    }
-
-    @Ignore
-    @Test
-    public void testCombine4() throws CQLException {
-        final String cql = "3 * max ( val , 15 ) + 2 / 4";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Add);
-        final Add rootAdd = (Add) obj;
-
-        assertEquals(
-                    FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"),FF.literal(15))
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        )
-                , rootAdd);
-
-    }
-
-    @Test
-    public void testCombine5() throws CQLException {
-        final String cql = "(\"NB-Curistes\"*50)/12000";
-        final Object obj = CQL.parseExpression(cql);
-        assertTrue(obj instanceof Divide);
-        final Divide result = (Divide) obj;
-
-        assertEquals(
-                FF.divide(
-                        FF.multiply(
-                                FF.property("NB-Curistes"),
-                                FF.literal(50)
-                        ),
-                        FF.literal(12000)
-                )
-                , result);
-
-    }
-
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
deleted file mode 100644
index f48fa76..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
+++ /dev/null
@@ -1,339 +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.cql;
-
-import java.text.ParseException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.expression.Expression;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Test writing in CQL expressions.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class ExpressionWritingTest extends CQLTestCase {
-    @Test
-    public void testPropertyName1() throws CQLException {
-        final Expression exp = FF.property("geom");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("geom", cql);
-    }
-
-    @Test
-    public void testPropertyName2() throws CQLException {
-        final Expression exp = FF.property("the geom");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("\"the geom\"", cql);
-    }
-
-    @Test
-    public void testInteger() throws CQLException {
-        final Expression exp = FF.literal(15);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("15", cql);
-    }
-
-    @Test
-    public void testNegativeInteger() throws CQLException {
-        final Expression exp = FF.literal(-15);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("-15", cql);
-    }
-
-    @Test
-    public void testDecimal1() throws CQLException {
-        final Expression exp = FF.literal(3.14);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3.14", cql);
-    }
-
-    @Test
-    public void testDecimal2() throws CQLException {
-        final Expression exp = FF.literal(9.0E-21);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("9.0E-21", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testDate() throws CQLException, ParseException{
-        final Expression exp = FF.literal(parseDate("2012-03-21T05:42:36Z"));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("2012-03-21T05:42:36Z", cql);
-    }
-
-
-    @Test
-    public void testNegativeDecimal() throws CQLException {
-        final Expression exp = FF.literal(-3.14);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("-3.14", cql);
-    }
-
-    @Test
-    public void testText() throws CQLException {
-        final Expression exp = FF.literal("hello world");
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("'hello world'", cql);
-    }
-
-    @Test
-    public void testAdd() throws CQLException {
-        final Expression exp = FF.add(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 + 2", cql);
-    }
-
-    @Test
-    public void testSubtract() throws CQLException {
-        final Expression exp = FF.subtract(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 - 2", cql);
-    }
-
-    @Test
-    public void testMultiply() throws CQLException {
-        final Expression exp = FF.multiply(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 2", cql);
-    }
-
-    @Test
-    public void testDivide() throws CQLException {
-        final Expression exp = FF.divide(FF.literal(3),FF.literal(2));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 / 2", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testFunction1() throws CQLException {
-        final Expression exp = FF.function("max",FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("max(att , 15)", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testFunction2() throws CQLException {
-        final Expression exp = FF.function("min",FF.property("att"), FF.function("cos",FF.literal(3.14d)));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("min(att , cos(3.14))", cql);
-    }
-
-    @Test
-    public void testCombine1() throws CQLException {
-        final Expression exp =
-                FF.divide(
-                    FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.subtract(FF.literal(2), FF.literal(6))
-                        ),
-                    FF.literal(4));
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 1 + 2 - 6 / 4", cql);
-    }
-
-    @Test
-    public void testCombine2() throws CQLException {
-        final Expression exp =
-                FF.add(
-                        FF.multiply(FF.literal(3), FF.literal(1)),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        );
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * 1 + 2 / 4", cql);
-
-    }
-
-    @Ignore
-    @Test
-    public void testCombine3() throws CQLException {
-        final Expression exp =
-                FF.add(
-                        FF.multiply(
-                            FF.literal(3),
-                            FF.function("max", FF.property("val"),FF.literal(15))
-                        ),
-                        FF.divide(FF.literal(2), FF.literal(4))
-                        );
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("3 * max(val , 15) + 2 / 4", cql);
-    }
-
-    @Test
-    public void testPoint() throws CQLException {
-        final Geometry geom = GF.createPoint(new Coordinate(15, 30));
-        final Expression exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("POINT (15 30)", cql);
-    }
-
-    @Test
-    public void testMPoint() throws CQLException {
-        final Geometry geom = GF.createMultiPoint(
-                new Coordinate[]{
-                    new Coordinate(15, 30),
-                    new Coordinate(45, 60)
-                });
-        final Expression exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTIPOINT ((15 30), (45 60))", cql);
-    }
-
-    @Test
-    public void testLineString() throws CQLException {
-        final Geometry geom = GF.createLineString(
-                new Coordinate[]{
-                    new Coordinate(10, 20),
-                    new Coordinate(30, 40),
-                    new Coordinate(50, 60)
-                });
-        final Expression exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("LINESTRING (10 20, 30 40, 50 60)", cql);
-    }
-
-    @Test
-    public void testMLineString() throws CQLException {
-        final Geometry geom = GF.createMultiLineString(
-                new LineString[]{
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(10, 20),
-                            new Coordinate(30, 40),
-                            new Coordinate(50, 60)
-                        }),
-                    GF.createLineString(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120)
-                        })
-                    }
-                );
-        final Expression exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTILINESTRING ((10 20, 30 40, 50 60), (70 80, 90 100, 110 120))", cql);
-    }
-
-    @Test
-    public void testPolygon() throws CQLException {
-        final Geometry geom = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Expression exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("POLYGON ((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))", cql);
-    }
-
-    @Test
-    public void testMPolygon() throws CQLException {
-        final Polygon geom1 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(70, 80),
-                            new Coordinate(90, 100),
-                            new Coordinate(110, 120),
-                            new Coordinate(70, 80)
-                        })
-                    }
-                );
-        final Polygon geom2 = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(11, 21),
-                        new Coordinate(31, 41),
-                        new Coordinate(51, 61),
-                        new Coordinate(11, 21)
-                    }),
-                new LinearRing[]{
-                    GF.createLinearRing(
-                        new Coordinate[]{
-                            new Coordinate(71, 81),
-                            new Coordinate(91, 101),
-                            new Coordinate(111, 121),
-                            new Coordinate(71, 81)
-                        })
-                    }
-                );
-        final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
-        final Expression exp = FF.literal(geom);
-        final String cql = CQL.write(exp);
-        assertNotNull(cql);
-        assertEquals("MULTIPOLYGON (((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)), ((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81)))", cql);
-    }
-
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
deleted file mode 100644
index ab2f673..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
+++ /dev/null
@@ -1,846 +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.cql;
-
-import java.text.ParseException;
-import java.util.Date;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-import org.opengis.filter.And;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Not;
-import org.opengis.filter.Or;
-import org.opengis.filter.PropertyIsBetween;
-import org.opengis.filter.PropertyIsEqualTo;
-import org.opengis.filter.PropertyIsGreaterThan;
-import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
-import org.opengis.filter.PropertyIsLessThan;
-import org.opengis.filter.PropertyIsLessThanOrEqualTo;
-import org.opengis.filter.PropertyIsLike;
-import org.opengis.filter.PropertyIsNotEqualTo;
-import org.opengis.filter.PropertyIsNull;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.filter.spatial.Beyond;
-import org.opengis.filter.spatial.BinarySpatialOperator;
-import org.opengis.filter.spatial.Contains;
-import org.opengis.filter.spatial.Crosses;
-import org.opengis.filter.spatial.DWithin;
-import org.opengis.filter.spatial.Disjoint;
-import org.opengis.filter.spatial.Equals;
-import org.opengis.filter.spatial.Intersects;
-import org.opengis.filter.spatial.Overlaps;
-import org.opengis.filter.spatial.Touches;
-import org.opengis.filter.spatial.Within;
-import org.opengis.filter.temporal.After;
-import org.opengis.filter.temporal.AnyInteracts;
-import org.opengis.filter.temporal.Before;
-import org.opengis.filter.temporal.Begins;
-import org.opengis.filter.temporal.BegunBy;
-import org.opengis.filter.temporal.During;
-import org.opengis.filter.temporal.EndedBy;
-import org.opengis.filter.temporal.Ends;
-import org.opengis.filter.temporal.Meets;
-import org.opengis.filter.temporal.MetBy;
-import org.opengis.filter.temporal.OverlappedBy;
-import org.opengis.filter.temporal.TContains;
-import org.opengis.filter.temporal.TEquals;
-import org.opengis.filter.temporal.TOverlaps;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Test reading CQL filters.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class FilterReadingTest extends CQLTestCase {
-
-    private static final double DELTA = 0.00000001;
-    private final Geometry baseGeometry = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[0]
-                );
-    private final Geometry baseGeometryPoint = GF.createPoint(
-                new Coordinate(12.1, 28.9));
-
-
-    @Test
-    public void testNullFilter() throws CQLException {
-        //this is not true cql but is since in commun use cases.
-        String cql = "";
-        Object obj = CQL.parseFilter(cql);
-        assertEquals(Filter.INCLUDE,obj);
-
-        cql = "*";
-        obj = CQL.parseFilter(cql);
-        assertEquals(Filter.INCLUDE,obj);
-    }
-
-    @Test
-    public void testAnd() throws CQLException {
-        final String cql = "att1 = 15 AND att2 = 30 AND att3 = 50";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Filter);
-        final Filter filter = (Filter) obj;
-        assertEquals(
-                FF.and(
-                UnmodifiableArrayList.wrap(new Filter[] {(Filter)
-                    FF.equals(FF.property("att1"), FF.literal(15)),
-                    FF.equals(FF.property("att2"), FF.literal(30)),
-                    FF.equals(FF.property("att3"), FF.literal(50))
-                })),
-                filter);
-    }
-
-    @Test
-    public void testOr() throws CQLException {
-        final String cql = "att1 = 15 OR att2 = 30 OR att3 = 50";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Filter);
-        final Filter filter = (Filter) obj;
-        assertEquals(
-                FF.or(
-                UnmodifiableArrayList.wrap(new Filter[] {(Filter)
-                    FF.equals(FF.property("att1"), FF.literal(15)),
-                    FF.equals(FF.property("att2"), FF.literal(30)),
-                    FF.equals(FF.property("att3"), FF.literal(50))
-                })),
-                filter);
-    }
-
-    @Ignore
-    @Test
-    public void testOrAnd1() throws CQLException {
-        final String cql = "Title = 'VMAI' OR (Title ILIKE 'LO?Li' AND DWITHIN(BoundingBox, POINT(12.1 28.9), 10, meters))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Filter);
-        final Filter filter = (Filter) obj;
-        assertEquals(
-                FF.or(
-                    FF.equals(FF.property("Title"), FF.literal("VMAI")),
-                    FF.and(
-                        FF.like(FF.property("Title"), "LO?Li","%","_","\\",false),
-                        FF.dwithin(FF.property("BoundingBox"), FF.literal(baseGeometryPoint), 10, "meters")
-                        )
-                ),
-                filter);
-    }
-
-    @Ignore
-    @Test
-    public void testOrAnd2() throws CQLException {
-        final Geometry geom =  GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 40),
-                        new Coordinate(20, 40),
-                        new Coordinate(20, 30),
-                        new Coordinate(10, 30),
-                        new Coordinate(10, 40)
-                    }),
-                new LinearRing[0]
-                );
-
-
-        final String cql = "NOT (INTERSECTS(BoundingBox, ENVELOPE(10, 20, 40, 30)) OR CONTAINS(BoundingBox, POINT(12.1 28.9))) AND BBOX(BoundingBox, 10,20,30,40)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Filter);
-        final Filter filter = (Filter) obj;
-        assertEquals(
-                FF.and(
-                    FF.not(
-                        FF.or(
-                            FF.intersects(FF.property("BoundingBox"), FF.literal(geom)),
-                            FF.contains(FF.property("BoundingBox"), FF.literal(baseGeometryPoint))
-                            )
-                    ),
-                    FF.bbox("BoundingBox",10,20,30,40,"")
-                ),
-                filter);
-    }
-
-    @Test
-    public void testNot() throws CQLException {
-        final String cql = "NOT att = 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Not);
-        final Not filter = (Not) obj;
-        assertEquals(FF.not(FF.equals(FF.property("att"), FF.literal(15))), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsBetween() throws CQLException {
-        final String cql = "att BETWEEN 15 AND 30";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsBetween);
-        final PropertyIsBetween filter = (PropertyIsBetween) obj;
-        assertEquals(FF.between(FF.property("att"), FF.literal(15), FF.literal(30)), filter);
-    }
-
-    @Test
-    public void testIn() throws CQLException {
-        final String cql = "att IN ( 15, 30, 'hello')";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Or);
-        final Or filter = (Or) obj;
-        assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter.getChildren().get(0));
-        assertEquals(FF.equals(FF.property("att"), FF.literal(30)), filter.getChildren().get(1));
-        assertEquals(FF.equals(FF.property("att"), FF.literal("hello")), filter.getChildren().get(2));
-    }
-
-    @Test
-    public void testNotIn() throws CQLException {
-        final String cql = "att NOT IN ( 15, 30, 'hello')";
-        Object obj = CQL.parseFilter(cql);
-        obj = ((Not)obj).getFilter();
-        assertTrue(obj instanceof Or);
-        final Or filter = (Or) obj;
-        assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter.getChildren().get(0));
-        assertEquals(FF.equals(FF.property("att"), FF.literal(30)), filter.getChildren().get(1));
-        assertEquals(FF.equals(FF.property("att"), FF.literal("hello")), filter.getChildren().get(2));
-    }
-
-    @Test
-    public void testPropertyIsEqualTo1() throws CQLException {
-        final String cql = "att=15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsEqualTo);
-        final PropertyIsEqualTo filter = (PropertyIsEqualTo) obj;
-        assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsEqualTo2() throws CQLException {
-        final String cql = "att = 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsEqualTo);
-        final PropertyIsEqualTo filter = (PropertyIsEqualTo) obj;
-        assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo() throws CQLException {
-        final String cql = "att <> 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsNotEqualTo);
-        final PropertyIsNotEqualTo filter = (PropertyIsNotEqualTo) obj;
-        assertEquals(FF.notEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsNotEqualTo2() throws CQLException {
-        final String cql = "att <>'15'";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsNotEqualTo);
-        final PropertyIsNotEqualTo filter = (PropertyIsNotEqualTo) obj;
-        assertEquals(FF.notEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThan() throws CQLException {
-        final String cql = "att > 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsGreaterThan);
-        final PropertyIsGreaterThan filter = (PropertyIsGreaterThan) obj;
-        assertEquals(FF.greater(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
-        final String cql = "att >= 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsGreaterThanOrEqualTo);
-        final PropertyIsGreaterThanOrEqualTo filter = (PropertyIsGreaterThanOrEqualTo) obj;
-        assertEquals(FF.greaterOrEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLessThan() throws CQLException {
-        final String cql = "att < 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsLessThan);
-        final PropertyIsLessThan filter = (PropertyIsLessThan) obj;
-        assertEquals(FF.less(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Test
-    public void testPropertyIsLessThanOrEqualTo() throws CQLException {
-        final String cql = "att <= 15";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsLessThanOrEqualTo);
-        final PropertyIsLessThanOrEqualTo filter = (PropertyIsLessThanOrEqualTo) obj;
-        assertEquals(FF.lessOrEqual(FF.property("att"), FF.literal(15)), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsLike() throws CQLException {
-        final String cql = "att LIKE '%hello_'";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsLike);
-        final PropertyIsLike filter = (PropertyIsLike) obj;
-        assertEquals(FF.like(FF.property("att"),"%hello_", "%", "_", "\\",true), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsNotLike() throws CQLException {
-        final String cql = "att NOT LIKE '%hello_'";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Not);
-        final Not filter = (Not) obj;
-        assertEquals(FF.not(FF.like(FF.property("att"),"%hello_", "%", "_", "\\",true)), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsLikeInsensitive() throws CQLException {
-        final String cql = "att ILIKE '%hello_'";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsLike);
-        final PropertyIsLike filter = (PropertyIsLike) obj;
-        assertEquals(FF.like(FF.property("att"),"%hello_", "%", "_", "\\",false), filter);
-    }
-
-    @Test
-    public void testPropertyIsNull() throws CQLException {
-        final String cql = "att IS NULL";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsNull);
-        final PropertyIsNull filter = (PropertyIsNull) obj;
-        assertEquals(FF.isNull(FF.property("att")), filter);
-    }
-
-    @Test
-    public void testPropertyIsNotNull() throws CQLException {
-        final String cql = "att IS NOT NULL";
-        Object obj = CQL.parseFilter(cql);
-        obj = ((Not)obj).getFilter();
-        assertTrue(obj instanceof PropertyIsNull);
-        final PropertyIsNull filter = (PropertyIsNull) obj;
-        assertEquals(FF.isNull(FF.property("att")), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testBBOX1() throws CQLException {
-        final String cql = "BBOX(\"att\" ,10, 20, 30, 40)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof BBOX);
-        final BinarySpatialOperator filter = (BBOX) obj;
-        assertEquals(FF.bbox(FF.property("att"), 10,20,30,40, null), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testBBOX2() throws CQLException {
-        final String cql = "BBOX(\"att\" ,10, 20, 30, 40, 'CRS:84')";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof BBOX);
-        final BBOX filter = (BBOX) obj;
-        assertEquals(FF.bbox(FF.property("att"), 10,20,30,40, "CRS:84"), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testBBOX3() throws CQLException {
-        final String cql = "BBOX(att ,10, 20, 30, 40, 'CRS:84')";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof BBOX);
-        final BBOX filter = (BBOX) obj;
-        assertEquals(FF.bbox(FF.property("att"), 10,20,30,40, "CRS:84"), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testBBOX4() throws CQLException {
-        final String cql = "BBOX(geometry,-10,-20,10,20)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof BBOX);
-        final BBOX filter = (BBOX) obj;
-        assertEquals(FF.bbox(FF.property("geometry"), -10,-20,10,20,null), filter);
-    }
-
-    @Ignore
-    @Test
-    public void testBeyond() throws CQLException {
-        final String cql = "BEYOND(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)), 10, meters)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Beyond);
-        final Beyond filter = (Beyond) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testContains() throws CQLException {
-        final String cql = "CONTAINS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Contains);
-        final Contains filter = (Contains) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testCrosses() throws CQLException {
-        final String cql = "CROSSES(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Crosses);
-        final Crosses filter = (Crosses) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testDisjoint() throws CQLException {
-        final String cql = "DISJOINT(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Disjoint);
-        final Disjoint filter = (Disjoint) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testDWithin() throws CQLException {
-        final String cql = "DWITHIN(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)), 10, 'meters')";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof DWithin);
-        final DWithin filter = (DWithin) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertEquals(10.0, filter.getDistance(), DELTA);
-        assertEquals("meters", filter.getDistanceUnits());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testDWithin2() throws CQLException {
-        //there is an error in this syntax, meters is a literal so it should be writen 'meters"
-        //but this writing is commun so we tolerate it
-        final String cql = "DWITHIN(BoundingBox, POINT(12.1 28.9), 10, meters)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof DWithin);
-        final DWithin filter = (DWithin) obj;
-
-        assertEquals(FF.property("BoundingBox"), filter.getExpression1());
-        assertEquals(10.0, filter.getDistance(), DELTA);
-        assertEquals("meters", filter.getDistanceUnits());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometryPoint.equalsExact(filtergeo));
-
-    }
-
-    @Ignore
-    @Test
-    public void testEquals() throws CQLException {
-        final String cql = "EQUALS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Equals);
-        final Equals filter = (Equals) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testIntersects() throws CQLException {
-        final String cql = "INTERSECTS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Intersects);
-        final Intersects filter = (Intersects) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testOverlaps() throws CQLException {
-        final String cql = "OVERLAPS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Overlaps);
-        final Overlaps filter = (Overlaps) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testTouches() throws CQLException {
-        final String cql = "TOUCHES(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Touches);
-        final Touches filter = (Touches) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testWithin() throws CQLException {
-        final String cql = "WITHIN(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Within);
-        final Within filter = (Within) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
-        final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
-        assertTrue(baseGeometry.equalsExact(filtergeo));
-    }
-
-    @Ignore
-    @Test
-    public void testCombine1() throws CQLException {
-        final String cql = "NOT att = 15 OR att BETWEEN 15 AND 30";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Or);
-        final Or filter = (Or) obj;
-        assertEquals(
-                FF.or(
-                    FF.not(FF.equals(FF.property("att"), FF.literal(15))),
-                    FF.between(FF.property("att"), FF.literal(15), FF.literal(30))
-                ),
-                filter
-                );
-    }
-
-    @Ignore
-    @Test
-    public void testCombine2() throws CQLException {
-        final String cql = "(NOT att = 15) OR (att BETWEEN 15 AND 30)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Or);
-        final Or filter = (Or) obj;
-        assertEquals(
-                FF.or(
-                    FF.not(FF.equals(FF.property("att"), FF.literal(15))),
-                    FF.between(FF.property("att"), FF.literal(15), FF.literal(30))
-                ),
-                filter
-                );
-    }
-
-    @Ignore
-    @Test
-    public void testCombine3() throws CQLException {
-        final String cql = "(NOT att1 = 15) AND (att2 = 15 OR att3 BETWEEN 15 AND 30) AND (att4 BETWEEN 1 AND 2)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof And);
-        final And filter = (And) obj;
-        assertEquals(
-                FF.and(
-                    UnmodifiableArrayList.wrap(new Filter[] {(Filter)
-                        FF.not(FF.equals(FF.property("att1"), FF.literal(15))),
-                        FF.or(
-                            FF.equals(FF.property("att2"), FF.literal(15)),
-                            FF.between(FF.property("att3"), FF.literal(15), FF.literal(30))
-                        ),
-                        FF.between(FF.property("att4"), FF.literal(1), FF.literal(2))
-                    })
-                ),
-                filter
-                );
-    }
-
-    @Test
-    public void testCombine4() throws CQLException {
-        final String cql = "(x+7) <= (y-9)";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof PropertyIsLessThanOrEqualTo);
-        final PropertyIsLessThanOrEqualTo filter = (PropertyIsLessThanOrEqualTo) obj;
-        assertEquals(
-                FF.lessOrEqual(
-                    FF.add(FF.property("x"), FF.literal(7)),
-                    FF.subtract(FF.property("y"), FF.literal(9))
-                ),
-                filter
-                );
-    }
-
-    @Ignore
-    @Test
-    public void testAfter() throws CQLException, ParseException {
-        final String cql = "att AFTER 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof After);
-        final After filter = (After) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testAnyInteracts() throws CQLException, ParseException {
-        final String cql = "att ANYINTERACTS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof AnyInteracts);
-        final AnyInteracts filter = (AnyInteracts) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testBefore() throws CQLException, ParseException {
-        final String cql = "att BEFORE 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Before);
-        final Before filter = (Before) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testBegins() throws CQLException, ParseException {
-        final String cql = "att BEGINS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Begins);
-        final Begins filter = (Begins) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testBegunBy() throws CQLException, ParseException {
-        final String cql = "att BEGUNBY 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof BegunBy);
-        final BegunBy filter = (BegunBy) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testDuring() throws CQLException, ParseException {
-        final String cql = "att DURING 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof During);
-        final During filter = (During) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testEndedBy() throws CQLException, ParseException {
-        final String cql = "att ENDEDBY 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof EndedBy);
-        final EndedBy filter = (EndedBy) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testEnds() throws CQLException, ParseException {
-        final String cql = "att ENDS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Ends);
-        final Ends filter = (Ends) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testMeets() throws CQLException, ParseException {
-        final String cql = "att MEETS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof Meets);
-        final Meets filter = (Meets) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testMetBy() throws CQLException, ParseException {
-        final String cql = "att METBY 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof MetBy);
-        final MetBy filter = (MetBy) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testOverlappedBy() throws CQLException, ParseException {
-        final String cql = "att OVERLAPPEDBY 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof OverlappedBy);
-        final OverlappedBy filter = (OverlappedBy) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testTcontains() throws CQLException, ParseException {
-        final String cql = "att TCONTAINS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof TContains);
-        final TContains filter = (TContains) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testTequals() throws CQLException, ParseException {
-        final String cql = "att TEQUALS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof TEquals);
-        final TEquals filter = (TEquals) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-    @Ignore
-    @Test
-    public void testToverlaps() throws CQLException, ParseException {
-        final String cql = "att TOVERLAPS 2012-03-21T05:42:36Z";
-        final Object obj = CQL.parseFilter(cql);
-        assertTrue(obj instanceof TOverlaps);
-        final TOverlaps filter = (TOverlaps) obj;
-
-        assertEquals(FF.property("att"), filter.getExpression1());
-        assertTrue(filter.getExpression2() instanceof Literal);
-        assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Date);
-        final Date filterdate = (Date) ((Literal)filter.getExpression2()).getValue();
-        assertEquals(parseDate("2012-03-21T05:42:36Z"), filterdate);
-    }
-
-}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
deleted file mode 100644
index ff908a8..0000000
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
+++ /dev/null
@@ -1,413 +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.cql;
-
-import java.text.ParseException;
-import java.util.Collections;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-import org.opengis.filter.Filter;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Test writing in CQL filters.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class FilterWritingTest extends CQLTestCase {
-
-    private final Geometry baseGeometry = GF.createPolygon(
-                GF.createLinearRing(
-                    new Coordinate[]{
-                        new Coordinate(10, 20),
-                        new Coordinate(30, 40),
-                        new Coordinate(50, 60),
-                        new Coordinate(10, 20)
-                    }),
-                new LinearRing[0]
-                );
-
-    @Test
-    public void testExcludeFilter() throws CQLException {
-        final Filter filter = Filter.EXCLUDE;
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("1=0", cql);
-    }
-
-    @Test
-    public void testIncludeFilter() throws CQLException {
-        final Filter filter = Filter.INCLUDE;
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("1=1", cql);
-    }
-
-    @Test
-    public void testAnd() throws CQLException {
-        final Filter filter = FF.and(
-                UnmodifiableArrayList.wrap(new Filter[] {(Filter)
-                    FF.equals(FF.property("att1"), FF.literal(15)),
-                    FF.equals(FF.property("att2"), FF.literal(30)),
-                    FF.equals(FF.property("att3"), FF.literal(50))
-                }));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("(\"att1\" = 15 AND \"att2\" = 30 AND \"att3\" = 50)", cql);
-    }
-
-    @Test
-    public void testOr() throws CQLException {
-        final Filter filter = FF.or(
-                UnmodifiableArrayList.wrap(new Filter[] {(Filter)
-                    FF.equals(FF.property("att1"), FF.literal(15)),
-                    FF.equals(FF.property("att2"), FF.literal(30)),
-                    FF.equals(FF.property("att3"), FF.literal(50))
-                }));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("(\"att1\" = 15 OR \"att2\" = 30 OR \"att3\" = 50)", cql);
-    }
-
-    @Test
-    public void testId() throws CQLException {
-        final Filter filter = FF.id(Collections.singleton(FF.featureId("test-1")));
-        try{
-            final String cql = CQL.write(filter);
-            fail("ID filter does not exist in CQL");
-        }catch(UnsupportedOperationException ex){
-            //ok
-        }
-    }
-
-    @Test
-    public void testNot() throws CQLException {
-        final Filter filter = FF.not(FF.equals(FF.property("att"), FF.literal(15)));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("NOT att = 15", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsBetween() throws CQLException {
-        final Filter filter = FF.between(FF.property("att"), FF.literal(15), FF.literal(30));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BETWEEN 15 AND 30", cql);
-    }
-
-    @Test
-    public void testPropertyIsEqualTo() throws CQLException {
-        final Filter filter = FF.equals(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att = 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsNotEqualTo() throws CQLException {
-        final Filter filter = FF.notEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att <> 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThan() throws CQLException {
-        final Filter filter = FF.greater(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att > 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
-        final Filter filter = FF.greaterOrEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att >= 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsLessThan() throws CQLException {
-        final Filter filter = FF.less(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att < 15", cql);
-    }
-
-    @Test
-    public void testPropertyIsLessThanOrEqualTo() throws CQLException {
-        final Filter filter = FF.lessOrEqual(FF.property("att"), FF.literal(15));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att <= 15", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testPropertyIsLike() throws CQLException {
-        final Filter filter = FF.like(FF.property("att"),"%hello");
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ILIKE '%hello'", cql);
-    }
-
-    @Test
-    public void testPropertyIsNull() throws CQLException {
-        final Filter filter = FF.isNull(FF.property("att"));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att IS NULL", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBBOX() throws CQLException {
-        final Filter filter = FF.bbox(FF.property("att"), 10,20,30,40, null);
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("BBOX(att,10.0,30.0,20.0,40.0)", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBeyond() throws CQLException {
-        final Filter filter = FF.beyond(FF.property("att"), FF.literal(baseGeometry), 0, "");
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("BEYOND(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testContains() throws CQLException {
-        final Filter filter = FF.contains(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("CONTAINS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testCrosses() throws CQLException {
-        final Filter filter = FF.crosses(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("CROSSES(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testDisjoint() throws CQLException {
-        final Filter filter = FF.disjoint(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("DISJOINT(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testDWithin() throws CQLException {
-        final Filter filter = FF.dwithin(FF.property("att"), FF.literal(baseGeometry), 0, "");
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("DWITHIN(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testEquals() throws CQLException {
-        final Filter filter = FF.equal(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("EQUALS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testIntersects() throws CQLException {
-        final Filter filter = FF.intersects(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("INTERSECTS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testOverlaps() throws CQLException {
-        final Filter filter = FF.overlaps(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("OVERLAPS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testTouches() throws CQLException {
-        final Filter filter = FF.touches(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("TOUCHES(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testWithin() throws CQLException {
-        final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("WITHIN(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testAfter() throws CQLException, ParseException {
-        final Filter filter = FF.after(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att AFTER 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testAnyInteracts() throws CQLException, ParseException {
-        final Filter filter = FF.anyInteracts(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ANYINTERACTS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBefore() throws CQLException, ParseException {
-        final Filter filter = FF.before(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEFORE 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBegins() throws CQLException, ParseException {
-        final Filter filter = FF.begins(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEGINS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testBegunBy() throws CQLException, ParseException {
-        final Filter filter = FF.begunBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att BEGUNBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testDuring() throws CQLException, ParseException {
-        final Filter filter = FF.during(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att DURING 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testEndedBy() throws CQLException, ParseException {
-        final Filter filter = FF.endedBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ENDEDBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testEnds() throws CQLException, ParseException {
-        final Filter filter = FF.ends(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att ENDS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testMeets() throws CQLException, ParseException {
-        final Filter filter = FF.meets(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att MEETS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testMetBy() throws CQLException, ParseException {
-        final Filter filter = FF.metBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att METBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testOverlappedBy() throws CQLException, ParseException {
-        final Filter filter = FF.overlappedBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att OVERLAPPEDBY 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testTcontains() throws CQLException, ParseException {
-        final Filter filter = FF.tcontains(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TCONTAINS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testTequals() throws CQLException, ParseException {
-        final Filter filter = FF.tequals(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TEQUALS 2012-03-21T05:42:36Z", cql);
-    }
-
-    @Ignore
-    @Test
-    public void testToverlaps() throws CQLException, ParseException {
-        final Filter filter = FF.toverlaps(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
-        final String cql = CQL.write(filter);
-        assertNotNull(cql);
-        assertEquals("att TOVERLAPS 2012-03-21T05:42:36Z", cql);
-    }
-
-}
diff --git a/core/sis-feature/pom.xml b/core/sis-feature/pom.xml
index b4a0029..41d3ac6 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.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
index f86e825..1968bda 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -63,15 +63,12 @@
  * </div>
  *
  * <h2>Relationship with metadata</h2>
- * This class provides the same information than ISO 19115 {@link org.opengis.metadata.content.SampleDimension},
+ * This class provides the same information than ISO 19115 {@code org.opengis.metadata.content.SampleDimension},
  * but organized in a different way. The use of the same name may seem a risk, but those two types are typically
  * not used in same time.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.0
- *
- * @see org.opengis.metadata.content.SampleDimension
- *
  * @since 1.0
  * @module
  */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
index df8037e..fa89597 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.coverage;
 
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * Thrown when an operation can only be applied on a subspace of a multi-dimensional coverage,
@@ -34,7 +32,7 @@
  * @since 1.0
  * @module
  */
-public class SubspaceNotSpecifiedException extends CannotEvaluateException {
+public class SubspaceNotSpecifiedException extends RuntimeException {
     /**
      * Serial number for inter-operability with different versions.
      */
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
index efbb0ab..26b815e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
@@ -17,8 +17,6 @@
 package org.apache.sis.coverage.grid;
 
 import java.util.Arrays;
-import org.opengis.coverage.grid.GridCoordinates;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 
 
@@ -26,12 +24,16 @@
  * A view over the low or high grid envelope coordinates.
  * This is not a general-purpose grid coordinates since it assumes a {@link GridExtent} coordinates layout.
  *
+ * <div class="note"><b>Upcoming API generalization:</b>
+ * this class may implement the {@code GridCoordinates} interface in a future Apache SIS version.
+ * This is pending GeoAPI update.</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-final class GridCoordinatesView implements GridCoordinates {
+final class GridCoordinatesView {
     /**
      * A reference to the coordinate array of the enclosing grid envelope.
      */
@@ -54,7 +56,6 @@
     /**
      * Returns the number of dimension.
      */
-    @Override
     public final int getDimension() {
         return coordinates.length >>> 1;
     }
@@ -62,7 +63,6 @@
     /**
      * Returns all coordinate values.
      */
-    @Override
     public final long[] getCoordinateValues() {
         return Arrays.copyOfRange(coordinates, offset, offset + getDimension());
     }
@@ -70,7 +70,6 @@
     /**
      * Returns the coordinate value for the specified dimension.
      */
-    @Override
     public final long getCoordinateValue(final int index) {
         ArgumentChecks.ensureValidIndex(getDimension(), index);
         return coordinates[offset + index];
@@ -79,10 +78,9 @@
     /**
      * Do not allow modification of grid coordinates since they are backed by {@link GridExtent}.
      */
-    @Override
-    public void setCoordinateValue(final int index, long value) {
-        throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "GridCoordinates"));
-    }
+//  public void setCoordinateValue(final int index, long value) {
+//      throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, "GridCoordinates"));
+//  }
 
     /**
      * Returns a string representation of this grid coordinates for debugging purpose.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 4e1005e..d7e2f02 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@ -33,9 +33,6 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 
-// Branch-specific imports
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * Base class of coverages with domains defined as a set of grid points.
@@ -221,9 +218,9 @@
      * @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}.
      * @throws SubspaceNotSpecifiedException if the given argument is not sufficient for reducing the grid to a two-dimensional slice.
      * @throws DisjointExtentException if the given extent does not intersect this grid coverage.
-     * @throws CannotEvaluateException if this method can not produce the rendered image for another reason.
+     * @throws RuntimeException if this method can not produce the rendered image for another reason.
      */
-    public abstract RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException;
+    public abstract RenderedImage render(GridExtent sliceExtent);
 
     /**
      * Returns a string representation of this grid coverage for debugging purpose.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index e19b807..ba6bed5 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@ -51,9 +51,6 @@
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTable;
 
-// Branch-dependent imports
-import org.opengis.coverage.PointOutsideCoverageException;
-
 
 /**
  * Creates a new grid geometry derived from a base grid geometry with different extent or resolution.
@@ -761,7 +758,7 @@
      * @throws IncompleteGridGeometryException if the base grid geometry has no extent, no "grid to CRS" transform,
      *         or no CRS (unless {@code slicePoint} has no CRS neither, in which case the CRS are assumed the same).
      * @throws IllegalGridGeometryException if an error occurred while converting the point coordinates to grid coordinates.
-     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
+     * @throws RuntimeException if the given point is outside the grid extent.
      */
     public GridDerivation slice(final DirectPosition slicePoint) {
         ArgumentChecks.ensureNonNull("slicePoint", slicePoint);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index b2fa4ec..286b7eb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -56,12 +56,6 @@
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.iso.Types;
 
-// Branch-dependent imports
-import org.opengis.coverage.grid.GridEnvelope;
-import org.opengis.coverage.grid.GridCoordinates;
-import org.opengis.coverage.CannotEvaluateException;
-import org.opengis.coverage.PointOutsideCoverageException;
-
 
 /**
  * A range of grid coverage coordinates, also known as "grid envelope".
@@ -77,12 +71,16 @@
  * <p>{@code GridExtent} instances are immutable and thread-safe.
  * The same instance can be shared by different {@link GridGeometry} instances.</p>
  *
+ * <div class="note"><b>Upcoming API generalization:</b>
+ * this class may implement the {@code GridEnvelope} interface in a future Apache SIS version.
+ * This is pending GeoAPI update.</div>
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-public class GridExtent implements GridEnvelope, Serializable {
+public class GridExtent implements Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -465,50 +463,12 @@
     }
 
     /**
-     * Creates a new grid envelope as a copy of the given one.
-     *
-     * @param  extent  the grid envelope to copy.
-     * @throws IllegalArgumentException if a coordinate value in the low part is
-     *         greater than the corresponding coordinate value in the high part.
-     *
-     * @see #castOrCopy(GridEnvelope)
-     */
-    protected GridExtent(final GridEnvelope extent) {
-        ArgumentChecks.ensureNonNull("extent", extent);
-        final int dimension = extent.getDimension();
-        coordinates = allocate(dimension);
-        for (int i=0; i<dimension; i++) {
-            coordinates[i] = extent.getLow(i);
-            coordinates[i + dimension] = extent.getHigh(i);
-        }
-        types = (extent instanceof GridExtent) ? ((GridExtent) extent).types : null;
-        validateCoordinates();
-    }
-
-    /**
-     * Returns the given grid envelope as a {@code GridExtent} implementation.
-     * If the given extent is already a {@code GridExtent} instance or is null, then it is returned as-is.
-     * Otherwise a new extent is created using the {@linkplain #GridExtent(GridEnvelope) copy constructor}.
-     *
-     * @param  extent  the grid envelope to cast or copy, or {@code null}.
-     * @return the grid envelope as a {@code GridExtent}, or {@code null} if the given extent was null.
-     */
-    public static GridExtent castOrCopy(final GridEnvelope extent) {
-        if (extent == null || extent instanceof GridExtent) {
-            return (GridExtent) extent;
-        } else {
-            return new GridExtent(extent);
-        }
-    }
-
-    /**
      * Returns the number of dimensions.
      *
      * @return the number of dimensions.
      *
      * @see #reduce(int...)
      */
-    @Override
     public final int getDimension() {
         return coordinates.length >>> 1;
     }
@@ -534,8 +494,7 @@
      *
      * @return the valid minimum grid coordinates, inclusive.
      */
-    @Override
-    public GridCoordinates getLow() {
+    GridCoordinatesView getLow() {
         return new GridCoordinatesView(coordinates, 0);
     }
 
@@ -545,8 +504,7 @@
      *
      * @return the valid maximum grid coordinates, <strong>inclusive</strong>.
      */
-    @Override
-    public GridCoordinates getHigh() {
+    GridCoordinatesView getHigh() {
         return new GridCoordinatesView(coordinates, getDimension());
     }
 
@@ -558,10 +516,8 @@
      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
      *         than the {@linkplain #getDimension() grid dimension}.
      *
-     * @see #getLow()
      * @see #getHigh(int)
      */
-    @Override
     public long getLow(final int index) {
         ArgumentChecks.ensureValidIndex(getDimension(), index);
         return coordinates[index];
@@ -575,10 +531,8 @@
      * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
      *         than the {@linkplain #getDimension() grid dimension}.
      *
-     * @see #getHigh()
      * @see #getLow(int)
      */
-    @Override
     public long getHigh(final int index) {
         final int dimension = getDimension();
         ArgumentChecks.ensureValidIndex(dimension, index);
@@ -598,7 +552,6 @@
      * @see #getLow(int)
      * @see #getHigh(int)
      */
-    @Override
     public long getSize(final int index) {
         final int dimension = getDimension();
         ArgumentChecks.ensureValidIndex(dimension, index);
@@ -677,18 +630,18 @@
      * If there is less than <var>s</var> dimensions having a size greater than 1, then the returned list of
      * dimensions is completed with some dimensions of size 1, starting with the first dimensions in this grid
      * extent, until there is exactly <var>s</var> dimensions. This this grid extent does not have <var>s</var>
-     * dimensions, then a {@link CannotEvaluateException} is thrown.
+     * dimensions, then a {@code CannotEvaluateException} is thrown.
      *
      * @param  s  number of dimensions of the sub-space.
      * @return indices of sub-space dimensions, in increasing order in an array of length <var>s</var>.
      * @throws SubspaceNotSpecifiedException if there is more than <var>s</var> dimensions having a size greater than 1.
-     * @throws CannotEvaluateException if this grid extent does not have at least <var>s</var> dimensions.
+     * @throws RuntimeException if this grid extent does not have at least <var>s</var> dimensions.
      */
     public int[] getSubspaceDimensions(final int s) {
         ArgumentChecks.ensurePositive("s", s);
         final int m = getDimension();
         if (s > m) {
-            throw new CannotEvaluateException(Resources.format(Resources.Keys.GridEnvelopeMustBeNDimensional_1, s));
+            throw new RuntimeException(Resources.format(Resources.Keys.GridEnvelopeMustBeNDimensional_1, s));
         }
         final int[] selected = new int[s];
         int count = 0;
@@ -1115,7 +1068,7 @@
      * @param  modifiedDimensions   mapping from {@code slicePoint} dimensions to this {@code GridExtent} dimensions,
      *                              or {@code null} if {@code slicePoint} contains all grid dimensions in same order.
      * @return a grid extent for the specified slice.
-     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
+     * @throws RuntimeException if the given point is outside the grid extent.
      */
     final GridExtent slice(final DirectPosition slicePoint, final int[] modifiedDimensions) {
         final GridExtent slice = new GridExtent(this);
@@ -1138,7 +1091,7 @@
                         if (Double.isNaN(p)) b.append("NaN");
                         else b.append(Math.round(p));
                     }
-                    throw new PointOutsideCoverageException(Resources.format(Resources.Keys.GridCoordinateOutsideCoverage_4,
+                    throw new RuntimeException(Resources.format(Resources.Keys.GridCoordinateOutsideCoverage_4,
                             getAxisIdentification(i,k), low, high, b.toString()));
                 }
             }
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 3b1680f..480544c 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
@@ -24,14 +24,6 @@
 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.
@@ -55,7 +47,7 @@
  * @since 0.5
  * @module
  */
-public abstract class AbstractAssociation extends Field<Feature> implements FeatureAssociation, Cloneable, Serializable {
+public abstract class AbstractAssociation extends Field<AbstractFeature> implements Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -64,16 +56,16 @@
     /**
      * Information about the association.
      */
-    final FeatureAssociationRole role;
+    final DefaultAssociationRole role;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      *
-     * @see #create(FeatureAssociationRole)
+     * @see #create(DefaultAssociationRole)
      */
-    protected AbstractAssociation(final FeatureAssociationRole role) {
+    protected AbstractAssociation(final DefaultAssociationRole role) {
         this.role = role;
     }
 
@@ -85,7 +77,7 @@
      *
      * @see DefaultAssociationRole#newInstance()
      */
-    public static AbstractAssociation create(final FeatureAssociationRole role) {
+    public static AbstractAssociation create(final DefaultAssociationRole role) {
         ArgumentChecks.ensureNonNull("role", role);
         return isSingleton(role.getMaximumOccurs())
                ? new SingletonAssociation(role)
@@ -99,16 +91,16 @@
      * @param  value  the initial value (may be {@code null}).
      * @return the new association.
      */
-    static AbstractAssociation create(final FeatureAssociationRole role, final Object value) {
+    static AbstractAssociation create(final DefaultAssociationRole role, final Object value) {
         ArgumentChecks.ensureNonNull("role", role);
         return isSingleton(role.getMaximumOccurs())
-               ? new SingletonAssociation(role, (Feature) value)
+               ? new SingletonAssociation(role, (AbstractFeature) value)
                : new MultiValuedAssociation(role, value);
     }
 
     /**
      * Returns the name of this association as defined by its {@linkplain #getRole() role}.
-     * This convenience method delegates to {@link FeatureAssociationRole#getName()}.
+     * This convenience method delegates to {@link DefaultAssociationRole#getName()}.
      *
      * @return the association name specified by its role.
      */
@@ -120,10 +112,12 @@
     /**
      * 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.
      */
-    @Override
-    public FeatureAssociationRole getRole() {
+    public DefaultAssociationRole getRole() {
         return role;
     }
 
@@ -132,13 +126,16 @@
      * 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 MultiValuedPropertyException if this association contains more than one value.
+     * @throws IllegalStateException if this association contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract Feature getValue() throws MultiValuedPropertyException;
+    public abstract AbstractFeature getValue() throws IllegalStateException;
 
     /**
      * Returns all features, or an empty collection if none.
@@ -151,13 +148,16 @@
      * @return the features in a <cite>live</cite> collection.
      */
     @Override
-    public Collection<Feature> getValues() {
+    public Collection<AbstractFeature> 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>
+     *
      * <h4>Validation</h4>
      * 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
@@ -165,24 +165,24 @@
      * A more exhaustive verification can be performed by invoking the {@link #quality()} method.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
+     * @throws IllegalArgumentException if the given feature is not valid for this association.
      *
      * @see AbstractFeature#setPropertyValue(String, Object)
      */
     @Override
-    public abstract void setValue(final Feature value) throws InvalidPropertyValueException;
+    public abstract void setValue(final AbstractFeature value) throws IllegalArgumentException;
 
     /**
      * 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(Feature)}.</p>
+     * then delegates to {@link #setValue(AbstractFeature)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends Feature> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends AbstractFeature> values) throws IllegalArgumentException {
         super.setValues(values);
     }
 
@@ -190,9 +190,9 @@
      * Ensures that storing a feature of the given type is valid for an association
      * expecting the given base type.
      */
-    final void ensureValid(final FeatureType base, final FeatureType type) {
+    final void ensureValid(final DefaultFeatureType base, final DefaultFeatureType type) {
         if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-            throw new InvalidPropertyValueException(
+            throw new IllegalArgumentException(
                     Resources.format(Resources.Keys.IllegalFeatureType_3, getName(), base.getName(), type.getName()));
         }
     }
@@ -227,7 +227,7 @@
     @Override
     public String toString() {
         final String pt = DefaultAssociationRole.getTitleProperty(role);
-        final Iterator<Feature> it = getValues().iterator();
+        final Iterator<AbstractFeature> 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 3cf8027..d367c24 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
@@ -31,12 +31,6 @@
 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.
@@ -79,7 +73,7 @@
  * @module
  */
 @SuppressWarnings("CloneInNonCloneableClass")       // Decision left to subclasses - see javadoc
-public abstract class AbstractAttribute<V> extends Field<V> implements Attribute<V>, Serializable {
+public abstract class AbstractAttribute<V> extends Field<V> implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -88,7 +82,7 @@
     /**
      * Information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    final AttributeType<V> type;
+    final DefaultAttributeType<V> type;
 
     /**
      * Other attributes that describes this attribute, or {@code null} if not yet created.
@@ -103,16 +97,16 @@
      *
      * @see #characteristics()
      */
-    private transient Map<String,Attribute<?>> characteristics;
+    private transient Map<String,AbstractAttribute<?>> 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(AttributeType)
+     * @see #create(DefaultAttributeType)
      */
-    protected AbstractAttribute(final AttributeType<V> type) {
+    protected AbstractAttribute(final DefaultAttributeType<V> type) {
         this.type = type;
     }
 
@@ -126,7 +120,7 @@
      *
      * @see DefaultAttributeType#newInstance()
      */
-    public static <V> AbstractAttribute<V> create(final AttributeType<V> type) {
+    public static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type) {
         ArgumentChecks.ensureNonNull("type", type);
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type)
@@ -142,7 +136,7 @@
      * @param  value  the initial value (may be {@code null}).
      * @return the new attribute.
      */
-    static <V> AbstractAttribute<V> create(final AttributeType<V> type, final Object value) {
+    static <V> AbstractAttribute<V> create(final DefaultAttributeType<V> type, final Object value) {
         ArgumentChecks.ensureNonNull("type", type);
         return isSingleton(type.getMaximumOccurs())
                ? new SingletonAttribute<>(type, value)
@@ -157,9 +151,9 @@
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
-        final Attribute<?>[] characterizedBy;
+        final AbstractAttribute<?>[] characterizedBy;
         if (characteristics instanceof CharacteristicMap) {
-            characterizedBy = characteristics.values().toArray(new Attribute<?>[characteristics.size()]);
+            characterizedBy = characteristics.values().toArray(new AbstractAttribute<?>[characteristics.size()]);
         } else {
             characterizedBy = null;
         }
@@ -176,7 +170,7 @@
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final Attribute<?>[] characterizedBy = (Attribute<?>[]) in.readObject();
+            final AbstractAttribute<?>[] characterizedBy = (AbstractAttribute<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = newCharacteristicsMap();
                 characteristics.values().addAll(Arrays.asList(characterizedBy));
@@ -189,7 +183,7 @@
 
     /**
      * Returns the name of this attribute as defined by its {@linkplain #getType() type}.
-     * This convenience method delegates to {@link AttributeType#getName()}.
+     * This convenience method delegates to {@link DefaultAttributeType#getName()}.
      *
      * @return the attribute name specified by its type.
      */
@@ -201,10 +195,12 @@
     /**
      * 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.
      */
-    @Override
-    public AttributeType<V> getType() {
+    public DefaultAttributeType<V> getType() {
         return type;
     }
 
@@ -214,12 +210,12 @@
      * of attribute values is restricted to 1 or 0.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this attribute contains more than one value.
+     * @throws IllegalStateException if this attribute contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws MultiValuedPropertyException;
+    public abstract V getValue() throws IllegalStateException;
 
     /**
      * Returns all attribute values, or an empty collection if none.
@@ -246,13 +242,13 @@
      * 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 InvalidPropertyValueException if this method verifies argument validity and the given value
+     * @throws IllegalArgumentException 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 InvalidPropertyValueException;
+    public abstract void setValue(final V value) throws IllegalArgumentException;
 
     /**
      * Sets the attribute values. All previous values are replaced by the given collection.
@@ -261,10 +257,10 @@
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
     @Override
-    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
         super.setValues(values);
     }
 
@@ -343,9 +339,8 @@
      *
      * @see DefaultAttributeType#characteristics()
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Map<String,Attribute<?>> characteristics() {
+    public Map<String,AbstractAttribute<?>> characteristics() {
         if (characteristics == null) {
             characteristics = newCharacteristicsMap();
         }
@@ -357,13 +352,13 @@
      * 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,Attribute<?>> newCharacteristicsMap() {
+    private Map<String,AbstractAttribute<?>> newCharacteristicsMap() {
         if (type instanceof DefaultAttributeType<?>) {
-            Map<String, AttributeType<?>> map = ((DefaultAttributeType<?>) type).characteristics();
+            Map<String, DefaultAttributeType<?>> map = type.characteristics();
             if (!map.isEmpty()) {
                 if (!(map instanceof CharacteristicTypeMap)) {
-                    final Collection<AttributeType<?>> types = map.values();
-                    map = CharacteristicTypeMap.create(type, types.toArray(new AttributeType<?>[types.size()]));
+                    final Collection<DefaultAttributeType<?>> types = map.values();
+                    map = CharacteristicTypeMap.create(type, types.toArray(new DefaultAttributeType<?>[types.size()]));
                 }
                 return new CharacteristicMap(this, (CharacteristicTypeMap) map);
             }
@@ -376,7 +371,7 @@
      * 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,Attribute<?>> characteristicsReadOnly() {
+    final Map<String,AbstractAttribute<?>> characteristicsReadOnly() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -473,7 +468,7 @@
         if (characteristics != null && !characteristics.isEmpty()) {
             buffer.append(System.lineSeparator());
             String separator = "└─ characteristics: ";
-            for (final Map.Entry<String,Attribute<?>> entry : characteristics.entrySet()) {
+            for (final Map.Entry<String,AbstractAttribute<?>> entry : characteristics.entrySet()) {
                 buffer.append(separator).append(entry.getKey()).append('=').append(entry.getValue().getValue());
                 separator = ", ";
             }
@@ -498,7 +493,7 @@
     @SuppressWarnings("unchecked")
     public AbstractAttribute<V> clone() throws CloneNotSupportedException {
         final AbstractAttribute<V> clone = (AbstractAttribute<V>) super.clone();
-        final Map<String,Attribute<?>> c = clone.characteristics;
+        final Map<String,AbstractAttribute<?>> 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 8cfb110..478b265 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,20 +32,6 @@
 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.
@@ -85,7 +71,7 @@
  * @since 0.5
  * @module
  */
-public abstract class AbstractFeature implements Feature, Serializable {
+public abstract class AbstractFeature implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -94,7 +80,7 @@
     /**
      * Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    final FeatureType type;
+    final DefaultFeatureType type;
 
     /**
      * Creates a new feature of the given type.
@@ -103,7 +89,7 @@
      *
      * @see DefaultFeatureType#newInstance()
      */
-    protected AbstractFeature(final FeatureType type) {
+    protected AbstractFeature(final DefaultFeatureType type) {
         ArgumentChecks.ensureNonNull("type", type);
         this.type = type;
     }
@@ -119,10 +105,12 @@
     /**
      * 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.
      */
-    @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -147,15 +135,17 @@
      * Implementers 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 PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see #getPropertyValue(String)
      * @see DefaultFeatureType#getProperty(String)
      */
-    @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         return PropertyView.create(this, type.getProperty(name));
     }
 
@@ -190,22 +180,24 @@
      * Implementers 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 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 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 IllegalArgumentException if the property can not be set for another reason.
      *
      * @see #setPropertyValue(String, Object)
      */
-    @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
+    public void setProperty(final Object property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
-        if (property instanceof Attribute<?> && !Containers.isNullOrEmpty(((Attribute<?>) property).characteristics())) {
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
+        if (property instanceof AbstractAttribute<?> && !Containers.isNullOrEmpty(((AbstractAttribute<?>) property).characteristics())) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.CanNotAssignCharacteristics_1, name));
         }
-        setPropertyValue(name, property.getValue());
+        setPropertyValue(name, ((Property) property).getValue());
     }
 
     /**
@@ -217,11 +209,11 @@
      * @return a {@code Property} wrapping the given value.
      */
     final Property createProperty(final String name, final Object 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);
+        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);
         } else {
             // Should never happen, unless the user gave us some mutable FeatureType.
             throw new CorruptedObjectException(Errors.format(Errors.Keys.UnknownType_1, pt));
@@ -233,15 +225,15 @@
      *
      * @param  name  the name of the property to create.
      * @return a {@code Property} of the given name.
-     * @throws PropertyNotFoundException if the given argument is not the name of an attribute or
+     * @throws IllegalArgumentException if the given argument is not the name of an attribute or
      *         feature association of this feature.
      */
-    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();
+    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();
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
         }
@@ -252,14 +244,14 @@
      *
      * @see #getOperationValue(String)
      */
-    final Property getOperationResult(final String name) {
+    final Object 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(((DefaultFeatureType) type).indices().get(name)) : name;
-        return ((Operation) type.getProperty(name)).apply(this, null);
+        assert DefaultFeatureType.OPERATION_INDEX.equals(type.indices().get(name)) : name;
+        return ((AbstractOperation) type.getProperty(name)).apply(this, null);
     }
 
     /**
@@ -268,14 +260,14 @@
      *
      * @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 PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException 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();
+    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();
             return maximumOccurs > 1 ? Collections.EMPTY_LIST : null;       // No default value for associations.
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -285,7 +277,7 @@
     /**
      * Returns the default value to be returned by {@link #getPropertyValue(String)} for the given attribute type.
      */
-    private static <V> Object getDefaultValue(final AttributeType<V> attribute) {
+    private static <V> Object getDefaultValue(final DefaultAttributeType<V> attribute) {
         final V defaultValue = attribute.getDefaultValue();
         if (Field.isSingleton(attribute.getMaximumOccurs())) {
             return defaultValue;
@@ -303,10 +295,10 @@
      * <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>{@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>
+     *   <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>
      * </table>
      *
      * <div class="note"><b>Note:</b> “max. occurs” is the {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
@@ -327,12 +319,11 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      *
      * @see AbstractAttribute#getValue()
      */
-    @Override
-    public abstract Object getPropertyValue(final String name) throws PropertyNotFoundException;
+    public abstract Object getPropertyValue(final String name) throws IllegalArgumentException;
 
     /**
      * Sets the value for the property of the given name.
@@ -345,13 +336,12 @@
      *
      * @param  name   the attribute name.
      * @param  value  the new value for the given attribute (may be {@code null}).
-     * @throws PropertyNotFoundException if the given name is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException 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 InvalidPropertyValueException if the given value is not valid for a reason other than its type.
+     * @throws IllegalArgumentException 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;
 
     /**
@@ -374,21 +364,21 @@
      * }
      *
      * @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 Operation}.
+     *               property type for that name is an instance of {@link AbstractOperation}.
      * @return the result value of the given operation, or {@code null} if none.
      *
      * @since 0.8
      */
     protected Object getOperationValue(final String name) {
-        final Operation operation = (Operation) type.getProperty(name);
+        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             return getPropertyValue(((LinkOperation) operation).referentName);
         }
-        final Property result = operation.apply(this, null);
-        if (result instanceof Attribute<?>) {
-            return getAttributeValue((Attribute<?>) result);
-        } else if (result instanceof FeatureAssociation) {
-            return getAssociationValue((FeatureAssociation) result);
+        final Object result = operation.apply(this, null);
+        if (result instanceof AbstractAttribute<?>) {
+            return getAttributeValue((AbstractAttribute<?>) result);
+        } else if (result instanceof AbstractAssociation) {
+            return getAssociationValue((AbstractAssociation) result);
         } else {
             return null;
         }
@@ -401,20 +391,20 @@
      * 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 Operation}.
+     *                property type for that name is an instance of {@link AbstractOperation}.
      * @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 Operation operation = (Operation) type.getProperty(name);
+        final AbstractOperation operation = (AbstractOperation) type.getProperty(name);
         if (operation instanceof LinkOperation) {
             setPropertyValue(((LinkOperation) operation).referentName, value);
         } else {
-            final Property result = operation.apply(this, null);
-            if (result != null) {
-                setPropertyValue(result, value);
+            final Object result = operation.apply(this, null);
+            if (result instanceof Property) {
+                setPropertyValue((Property) result, value);
             } else {
                 throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetPropertyValue_1, name));
             }
@@ -425,7 +415,7 @@
      * 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 Attribute<?> property) {
+    static Object getAttributeValue(final AbstractAttribute<?> property) {
         return Field.isSingleton(property.getType().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -433,7 +423,7 @@
      * 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 FeatureAssociation property) {
+    static Object getAssociationValue(final AbstractAssociation property) {
         return Field.isSingleton(property.getRole().getMaximumOccurs()) ? property.getValue() : property.getValues();
     }
 
@@ -441,10 +431,10 @@
      * Sets the value of the given property, with some minimal checks.
      */
     static void setPropertyValue(final Property property, final Object value) {
-        if (property instanceof Attribute<?>) {
-            setAttributeValue((Attribute<?>) property, value);
-        } else if (property instanceof FeatureAssociation) {
-            setAssociationValue((FeatureAssociation) property, value);
+        if (property instanceof AbstractAttribute<?>) {
+            setAttributeValue((AbstractAttribute<?>) property, value);
+        } else if (property instanceof AbstractAssociation) {
+            setAssociationValue((AbstractAssociation) property, value);
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
         }
@@ -456,9 +446,9 @@
      * use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static <V> void setAttributeValue(final Attribute<V> attribute, final Object value) {
+    private static <V> void setAttributeValue(final AbstractAttribute<V> attribute, final Object value) {
         if (value != null) {
-            final AttributeType<V> pt = attribute.getType();
+            final DefaultAttributeType<V> pt = attribute.getType();
             final Class<?> base = pt.getValueClass();
             if (!base.isInstance(value)) {
                 Object element = value;
@@ -469,7 +459,7 @@
                      */
                     final Iterator<?> it = ((Collection<?>) value).iterator();
                     do if (!it.hasNext()) {
-                        ((Attribute) attribute).setValues((Collection) value);
+                        ((AbstractAttribute) attribute).setValues((Collection) value);
                         return;
                     } while ((element = it.next()) == null || base.isInstance(element));
                     // Found an illegal value. Exeption is thrown below.
@@ -477,7 +467,7 @@
                 throw new ClassCastException(illegalValueClass(pt, base, element));         // 'element' can not be null here.
             }
         }
-        ((Attribute) attribute).setValue(value);
+        ((AbstractAttribute) attribute).setValue(value);
     }
 
     /**
@@ -485,24 +475,24 @@
      * For a more exhaustive validation, use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static void setAssociationValue(final FeatureAssociation association, final Object value) {
+    private static void setAssociationValue(final AbstractAssociation association, final Object value) {
         if (value != null) {
-            final FeatureAssociationRole role = association.getRole();
-            final FeatureType base = role.getValueType();
-            if (value instanceof Feature) {
-                final FeatureType actual = ((Feature) value).getType();
+            final DefaultAssociationRole role = association.getRole();
+            final DefaultFeatureType base = role.getValueType();
+            if (value instanceof AbstractFeature) {
+                final DefaultFeatureType actual = ((AbstractFeature) value).getType();
                 if (base != actual && !DefaultFeatureType.maybeAssignableFrom(base, actual)) {
-                    throw new InvalidPropertyValueException(illegalFeatureType(role, base, actual));
+                    throw new IllegalArgumentException(illegalFeatureType(role, base, actual));
                 }
             } else if (value instanceof Collection<?>) {
                 verifyAssociationValues(role, (Collection<?>) value);
-                association.setValues((Collection<? extends Feature>) value);
+                association.setValues((Collection<? extends AbstractFeature>) value);
                 return;                                 // Skip the setter at the end of this method.
             } else {
-                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
             }
         }
-        association.setValue((Feature) value);
+        association.setValue((AbstractFeature) value);
     }
 
     /**
@@ -520,7 +510,7 @@
             if (value == null) {
                 return true;
             }
-            if (previous.getClass() == value.getClass() && !(value instanceof Feature)) {
+            if (previous.getClass() == value.getClass() && !(value instanceof AbstractFeature)) {
                 return true;
             }
         }
@@ -534,19 +524,19 @@
      * @param  property  the property to verify.
      */
     final void verifyPropertyType(final String name, final Property property) {
-        final PropertyType pt, base = type.getProperty(name);
-        if (property instanceof Attribute<?>) {
-            pt = ((Attribute<?>) property).getType();
-        } else if (property instanceof FeatureAssociation) {
-            pt = ((FeatureAssociation) property).getRole();
+        final AbstractIdentifiedType pt, base = type.getProperty(name);
+        if (property instanceof AbstractAttribute<?>) {
+            pt = ((AbstractAttribute<?>) property).getType();
+        } else if (property instanceof AbstractAssociation) {
+            pt = ((AbstractAssociation) property).getRole();
         } else {
-            throw new InvalidPropertyValueException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
+            throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalPropertyType_2, base.getName(), property.getClass()));
         }
         if (pt != base) {
             if (base == null) {
-                throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name));
             } else {
-                throw new InvalidPropertyValueException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
+                throw new IllegalArgumentException(Resources.format(Resources.Keys.MismatchedPropertyType_1, name));
             }
         }
     }
@@ -556,14 +546,14 @@
      * 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 PropertyType pt = type.getProperty(name);
-        if (pt instanceof AttributeType<?>) {
+        final AbstractIdentifiedType pt = type.getProperty(name);
+        if (pt instanceof DefaultAttributeType<?>) {
             if (value != null) {
-                return verifyAttributeValue((AttributeType<?>) pt, value);
+                return verifyAttributeValue((DefaultAttributeType<?>) pt, value);
             }
-        } else if (pt instanceof FeatureAssociationRole) {
+        } else if (pt instanceof DefaultAssociationRole) {
             if (value != null) {
-                return verifyAssociationValue((FeatureAssociationRole) pt, value);
+                return verifyAssociationValue((DefaultAssociationRole) pt, value);
             }
         } else {
             throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
@@ -581,7 +571,7 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static <T> Object verifyAttributeValue(final AttributeType<T> type, final Object value) {
+    private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) {
         final Class<T> valueClass = type.getValueClass();
         final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs());
         if (valueClass.isInstance(value)) {
@@ -603,42 +593,42 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static Object verifyAssociationValue(final FeatureAssociationRole role, final Object value) {
+    private static Object verifyAssociationValue(final DefaultAssociationRole role, final Object value) {
         final boolean isSingleton = Field.isSingleton(role.getMaximumOccurs());
-        if (value instanceof Feature) {
+        if (value instanceof AbstractFeature) {
             /*
              * 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 FeatureType valueType = ((Feature) value).getType();
-            final FeatureType base = role.getValueType();
+            final DefaultFeatureType valueType = ((AbstractFeature) value).getType();
+            final DefaultFeatureType base = role.getValueType();
             if (base == valueType || DefaultFeatureType.maybeAssignableFrom(base, valueType)) {
-                return isSingleton ? value : singletonList(Feature.class, role.getMinimumOccurs(), value);
+                return isSingleton ? value : singletonList(AbstractFeature.class, role.getMinimumOccurs(), value);
             } else {
-                throw new InvalidPropertyValueException(illegalFeatureType(role, base, valueType));
+                throw new IllegalArgumentException(illegalFeatureType(role, base, valueType));
             }
         } else if (!isSingleton && value instanceof Collection<?>) {
             verifyAssociationValues(role, (Collection<?>) value);
-            return CheckedArrayList.castOrCopy((Collection<?>) value, Feature.class);
+            return CheckedArrayList.castOrCopy((Collection<?>) value, AbstractFeature.class);
         } else {
-            throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+            throw new ClassCastException(illegalValueClass(role, AbstractFeature.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 FeatureAssociationRole role, final Collection<?> values) {
-        final FeatureType base = role.getValueType();
+    private static void verifyAssociationValues(final DefaultAssociationRole role, final Collection<?> values) {
+        final DefaultFeatureType base = role.getValueType();
         int index = 0;
         for (final Object value : values) {
             ArgumentChecks.ensureNonNullElement("values", index, value);
-            if (!(value instanceof Feature)) {
-                throw new ClassCastException(illegalValueClass(role, Feature.class, value));
+            if (!(value instanceof AbstractFeature)) {
+                throw new ClassCastException(illegalValueClass(role, AbstractFeature.class, value));
             }
-            final FeatureType type = ((Feature) value).getType();
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) {
-                throw new InvalidPropertyValueException(illegalFeatureType(role, base, type));
+                throw new IllegalArgumentException(illegalFeatureType(role, base, type));
             }
             index++;
         }
@@ -664,7 +654,7 @@
      */
     static String propertyNotFound(final FeatureType type, final Object feature, final String property) {
         GenericName ambiguous = null;
-        for (final IdentifiedType p : type.getProperties(true)) {
+        for (final AbstractIdentifiedType p : type.getProperties(true)) {
             final GenericName next = p.getName();
             GenericName name = next;
             do {
@@ -682,7 +672,7 @@
 
     /**
      * Returns the exception message for a property type which is neither an attribute or an association.
-     * This method is invoked after a {@link PropertyType} has been found for the user-supplied name,
+     * This method is invoked after a {@code 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) {
@@ -694,7 +684,7 @@
      *
      * @param  value  the value, which shall be non-null.
      */
-    private static String illegalValueClass(final IdentifiedType property, final Class<?> expected, final Object value) {
+    private static String illegalValueClass(final AbstractIdentifiedType property, final Class<?> expected, final Object value) {
         return Resources.format(Resources.Keys.IllegalPropertyValueClass_3,
                                 property.getName(), expected, value.getClass());
     }
@@ -703,7 +693,7 @@
      * Returns the exception message for an association value of wrong type.
      */
     private static String illegalFeatureType(
-            final FeatureAssociationRole association, final FeatureType expected, final FeatureType actual)
+            final DefaultAssociationRole association, final FeatureType expected, final FeatureType actual)
     {
         return Resources.format(Resources.Keys.IllegalFeatureType_3,
                                 association.getName(), expected.getName(), actual.getName());
@@ -799,7 +789,7 @@
     @Override
     public int hashCode() {
         int code = type.hashCode() * 37;
-        for (final PropertyType pt : type.getProperties(true)) {
+        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
             final String name = pt.getName().toString();
             if (name != null) {                                             // Paranoiac check.
                 final Object value = getPropertyValue(name);
@@ -843,7 +833,7 @@
             if (!type.equals(that.type)) {
                 return false;
             }
-            for (final PropertyType pt : type.getProperties(true)) {
+            for (final AbstractIdentifiedType 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 658d299..b5f65cc 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,19 +30,21 @@
 
 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 IdentifiedType, Deprecable, Serializable {
+public class AbstractIdentifiedType implements Deprecable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -254,7 +256,6 @@
      *
      * @return the type name.
      */
-    @Override
     public final GenericName getName() {
         return name;
     }
@@ -264,7 +265,6 @@
      *
      * @return concise definition of the element.
      */
-    @Override
     public InternationalString getDefinition() {
         return definition;
     }
@@ -275,7 +275,6 @@
      *
      * @return natural language designator for the element, or {@code null} if none.
      */
-    @Override
     public InternationalString getDesignation() {
         return designation;
     }
@@ -289,7 +288,6 @@
      *
      * @return information beyond that required for concise definition of the element, or {@code null} if none.
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
@@ -379,7 +377,9 @@
      * @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 IdentifiedType container, final String argument, final int index) {
+    static String toString(final GenericName name, final AbstractIdentifiedType 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 fa35d1a..52ec3d5 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
@@ -32,14 +32,6 @@
 import org.apache.sis.util.Classes;
 
 // 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;
 
 
 /**
@@ -54,8 +46,8 @@
  * <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 {@link #apply(Feature, ParameterValueGroup)}.
- * If the value is modifiable, new value can be set by call to {@link Attribute#setValue(Object)}.
+ * 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)}.
  *
  * <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>
@@ -68,8 +60,8 @@
  * @since 0.6
  * @module
  */
-public abstract class AbstractOperation extends AbstractIdentifiedType implements Operation,
-        BiFunction<Feature, ParameterValueGroup, Property>
+public abstract class AbstractOperation extends AbstractIdentifiedType
+        implements BiFunction<AbstractFeature, ParameterValueGroup, Object>
 {
     /**
      * For cross-version compatibility.
@@ -157,16 +149,17 @@
      *
      * @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.
      */
-    @Override
-    public abstract IdentifiedType getResult();
+    public abstract AbstractIdentifiedType getResult();
 
     /**
      * Executes the operation on the specified feature with the specified parameters.
@@ -175,11 +168,11 @@
      * <ul>
      *   <li>If {@code getResult()} returns {@code null},
      *       then this method should return {@code null}.</li>
-     *   <li>If {@code getResult()} returns an instance of {@link AttributeType},
-     *       then this method shall return an instance of {@link Attribute}
+     *   <li>If {@code getResult()} returns an instance of {@code AttributeType},
+     *       then this method shall return an instance of {@code Attribute}
      *       and the {@code Attribute.getType() == getResult()} relation should hold.</li>
-     *   <li>If {@code getResult()} returns an instance of {@link org.opengis.feature.FeatureAssociationRole},
-     *       then this method shall return an instance of {@link FeatureAssociation}
+     *   <li>If {@code getResult()} returns an instance of {@code FeatureAssociationRole},
+     *       then this method shall return an instance of {@code FeatureAssociation}
      *       and the {@code FeatureAssociation.getRole() == getResult()} relation should hold.</li>
      * </ul>
      *
@@ -189,15 +182,18 @@
      * 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 Property apply(Feature feature, ParameterValueGroup parameters) throws FeatureOperationException;
+    public abstract Object apply(AbstractFeature feature, ParameterValueGroup parameters);
 
     /**
      * Returns the names of feature properties that this operation needs for performing its task.
@@ -268,11 +264,11 @@
         if (name != null) {
             buffer.append('”');
         }
-        final IdentifiedType result = getResult();
+        final AbstractIdentifiedType result = getResult();
         if (result != null) {
             final Object type;
-            if (result instanceof AttributeType<?>) {
-                type = Classes.getShortName(((AttributeType<?>) result).getValueClass());
+            if (result instanceof DefaultAttributeType<?>) {
+                type = Classes.getShortName(((DefaultAttributeType<?>) 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 529bed1..a97ac0e 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,9 +20,7 @@
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureAssociation;
-import org.opengis.feature.FeatureAssociationRole;
+import java.util.Objects;
 
 
 /**
@@ -39,23 +37,30 @@
  * @since   0.8
  * @module
  */
-class AssociationView extends PropertyView<Feature> implements FeatureAssociation {
+class AssociationView extends AbstractAssociation {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = -148100967531766909L;
 
     /**
-     * The role of this association. Must be one of the properties listed in the {@link #feature}.
+     * The feature from which to read and where to write the attribute or association value.
      */
-    private final FeatureAssociationRole role;
+    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;
 
     /**
      * Creates a new association which will delegate its work to the given feature.
      */
-    private AssociationView(final Feature feature, final FeatureAssociationRole role) {
-        super(feature, role.getName().toString());
-        this.role = role;
+    private AssociationView(final AbstractFeature feature, final DefaultAssociationRole role) {
+        super(role);
+        this.feature = feature;
+        this.name = role.getName().toString();
     }
 
     /**
@@ -65,7 +70,7 @@
      * @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 FeatureAssociation create(final Feature feature, final FeatureAssociationRole role) {
+    static AbstractAssociation create(final AbstractFeature feature, final DefaultAssociationRole role) {
         if (isSingleton(role.getMaximumOccurs())) {
             return new Singleton(feature, role);
         } else {
@@ -81,20 +86,24 @@
         return role.getName();
     }
 
-    /**
-     * Returns the role specified at construction time.
-     */
     @Override
-    public final FeatureAssociationRole getRole() {
-        return role;
+    public AbstractFeature getValue() {
+        return (AbstractFeature) PropertyView.getValue(feature, name);
     }
 
-    /**
-     * Returns the class of values.
-     */
     @Override
-    final Class<Feature> getValueClass() {
-        return Feature.class;
+    public void setValue(final AbstractFeature value) {
+        PropertyView.setValue(feature, name, value);
+    }
+
+    @Override
+    public Collection<AbstractFeature> getValues() {
+        return PropertyView.getValues(feature, name, AbstractFeature.class);
+    }
+
+    @Override
+    public final void setValues(final Collection<? extends AbstractFeature> values) {
+        PropertyView.setValues(feature, name, values);
     }
 
     /**
@@ -111,7 +120,7 @@
         /**
          * Creates a new association which will delegate its work to the given feature.
          */
-        Singleton(final Feature feature, final FeatureAssociationRole role) {
+        Singleton(final AbstractFeature feature, final DefaultAssociationRole role) {
             super(feature, role);
         }
 
@@ -119,17 +128,17 @@
          * Returns the single value, or {@code null} if none.
          */
         @Override
-        public Feature getValue() {
-            return (Feature) this.feature.getPropertyValue(this.name);
+        public AbstractFeature getValue() {
+            return (AbstractFeature) this.feature.getPropertyValue(this.name);
         }
 
         /**
          * Sets the value of this association. This method assumes that the
-         * {@link Feature#setPropertyValue(String, Object)} implementation
+         * {@code Feature.setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
-        public void setValue(final Feature value) {
+        public void setValue(final AbstractFeature value) {
             this.feature.setPropertyValue(this.name, value);
         }
 
@@ -137,8 +146,30 @@
          * Wraps the property value in a set.
          */
         @Override
-        public Collection<Feature> getValues() {
-            return singletonOrEmpty(getValue());
+        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);
+        }
+        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 613c30e..c93e657 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,9 +22,7 @@
 import org.opengis.util.GenericName;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+import java.util.Objects;
 
 
 /**
@@ -41,23 +39,30 @@
  * @since   0.8
  * @module
  */
-class AttributeView<V> extends PropertyView<V> implements Attribute<V> {
+class AttributeView<V> extends AbstractAttribute<V> {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 3617999929561826634L;
 
     /**
-     * The type of this attribute. Must be one of the properties listed in the {@link #feature}.
+     * The feature from which to read and where to write the attribute or association value.
      */
-    final AttributeType<V> type;
+    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;
 
     /**
      * Creates a new attribute which will delegate its work to the given feature.
      */
-    private AttributeView(final Feature feature, final AttributeType<V> type) {
-        super(feature, type.getName().toString());
-        this.type = type;
+    private AttributeView(final AbstractFeature feature, final DefaultAttributeType<V> type) {
+        super(type);
+        this.feature = feature;
+        this.name = type.getName().toString();
     }
 
     /**
@@ -67,7 +72,7 @@
      * @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> Attribute<V> create(final Feature feature, final AttributeType<V> type) {
+    static <V> AbstractAttribute<V> create(final AbstractFeature feature, final DefaultAttributeType<V> type) {
         if (isSingleton(type.getMaximumOccurs())) {
             return new Singleton<>(feature, type);
         } else {
@@ -83,27 +88,31 @@
         return type.getName();
     }
 
-    /**
-     * Returns the type specified at construction time.
-     */
     @Override
-    public final AttributeType<V> getType() {
-        return type;
+    public V getValue() {
+        return type.getValueClass().cast(PropertyView.getValue(feature, name));
     }
 
-    /**
-     * Returns the class of values.
-     */
     @Override
-    final Class<V> getValueClass() {
-        return type.getValueClass();
+    public void setValue(final V value) {
+        PropertyView.setValue(feature, name, value);
+    }
+
+    @Override
+    public Collection<V> getValues() {
+        return PropertyView.getValues(feature, name, type.getValueClass());
+    }
+
+    @Override
+    public final void setValues(final Collection<? extends V> values) {
+        PropertyView.setValues(feature, name, values);
     }
 
     /**
      * Returns an empty map since this simple view does not support characteristics.
      */
     @Override
-    public final Map<String,Attribute<?>> characteristics() {
+    public final Map<String,AbstractAttribute<?>> characteristics() {
         return Collections.emptyMap();
     }
 
@@ -121,7 +130,7 @@
         /**
          * Creates a new attribute which will delegate its work to the given feature.
          */
-        Singleton(final Feature feature, final AttributeType<V> type) {
+        Singleton(final AbstractFeature feature, final DefaultAttributeType<V> type) {
             super(feature, type);
         }
 
@@ -135,7 +144,7 @@
 
         /**
          * Sets the value of this attribute. This method assumes that the
-         * {@link Feature#setPropertyValue(String, Object)} implementation
+         * {@code Feature.setPropertyValue(String, Object)} implementation
          * will verify the argument type.
          */
         @Override
@@ -148,7 +157,29 @@
          */
         @Override
         public Collection<V> getValues() {
-            return singletonOrEmpty(getValue());
+            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 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 21539d5..6cdf076 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,12 +24,6 @@
 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.
@@ -40,16 +34,16 @@
  * @since   0.5
  * @module
  */
-final class CharacteristicMap extends AbstractMap<String,Attribute<?>> implements Cloneable {
+final class CharacteristicMap extends AbstractMap<String,AbstractAttribute<?>> implements Cloneable {
     /**
      * The attribute source for which to provide characteristics.
      */
-    private final Attribute<?> source;
+    private final AbstractAttribute<?> source;
 
     /**
      * Characteristics of the {@code source} attribute, created when first needed.
      */
-    Attribute<?>[] characterizedBy;
+    AbstractAttribute<?>[] characterizedBy;
 
     /**
      * Description of the attribute characteristics.
@@ -62,7 +56,7 @@
      * @param  source  the attribute which is characterized by {@code characterizedBy}.
      * @param  types   description of the characteristics of {@code source}.
      */
-    CharacteristicMap(final Attribute<?> source, final CharacteristicTypeMap types) {
+    CharacteristicMap(final AbstractAttribute<?> source, final CharacteristicTypeMap types) {
         this.source = source;
         this.types  = types;
     }
@@ -75,14 +69,14 @@
     @Override
     public CharacteristicMap clone() throws CloneNotSupportedException {
         final CharacteristicMap clone = (CharacteristicMap) super.clone();
-        Attribute<?>[] c = clone.characterizedBy;
+        AbstractAttribute<?>[] 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 Attribute<?> attribute = c[i];
+                final AbstractAttribute<?> attribute = c[i];
                 if (attribute instanceof Cloneable) {
-                    c[i] = (Attribute<?>) cloner.clone(attribute);
+                    c[i] = (AbstractAttribute<?>) cloner.clone(attribute);
                 }
             }
         }
@@ -108,7 +102,7 @@
     @Override
     public boolean isEmpty() {
         if (characterizedBy != null) {
-            for (final Attribute<?> attribute : characterizedBy) {
+            for (final AbstractAttribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     return false;
                 }
@@ -124,7 +118,7 @@
     public int size() {
         int n = 0;
         if (characterizedBy != null) {
-            for (final Attribute<?> attribute : characterizedBy) {
+            for (final AbstractAttribute<?> attribute : characterizedBy) {
                 if (attribute != null) {
                     n++;
                 }
@@ -137,7 +131,7 @@
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public Attribute<?> get(final Object key) {
+    public AbstractAttribute<?> get(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
@@ -151,11 +145,11 @@
      * Removes the attribute characteristic for the given name.
      */
     @Override
-    public Attribute<?> remove(final Object key) {
+    public AbstractAttribute<?> remove(final Object key) {
         if (characterizedBy != null) {
             final Integer index = types.indices.get(key);
             if (index != null) {
-                final Attribute<?> previous = characterizedBy[index];
+                final AbstractAttribute<?> previous = characterizedBy[index];
                 characterizedBy[index] = null;
                 return previous;
             }
@@ -168,13 +162,13 @@
      *
      * @param  key  the name for which to get the characteristic index.
      * @return the index for the characteristic of the given name.
-     * @throws PropertyNotFoundException if the given key is not the name of a characteristic in this map.
+     * @throws IllegalArgumentException 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 PropertyNotFoundException(Resources.format(
+            throw new IllegalArgumentException(Resources.format(
                     Resources.Keys.CharacteristicsNotFound_2, source.getName(), key));
         }
         return index;
@@ -188,12 +182,12 @@
      * @param  index  index of the expected attribute type.
      * @param  type   the actual attribute type.
      */
-    final void verifyAttributeType(final int index, final AttributeType<?> type) {
-        final AttributeType<?> expected = types.characterizedBy[index];
+    final void verifyAttributeType(final int index, final DefaultAttributeType<?> type) {
+        final DefaultAttributeType<?> expected = types.characterizedBy[index];
         if (!expected.equals(type)) {
             final GenericName en = expected.getName();
             final GenericName an = type.getName();
-            throw new InvalidPropertyValueException(String.valueOf(en).equals(String.valueOf(an))
+            throw new IllegalArgumentException(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));
         }
@@ -206,14 +200,14 @@
      * @throws IllegalArgumentException if the given key is not the name of a characteristic in this map.
      */
     @Override
-    public Attribute<?> put(final String key, final Attribute<?> value) {
+    public AbstractAttribute<?> put(final String key, final AbstractAttribute<?> value) {
         final int index = indexOf(key);
         ArgumentChecks.ensureNonNull("value", value);
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
-        final Attribute<?> previous = characterizedBy[index];
+        final AbstractAttribute<?> previous = characterizedBy[index];
         characterizedBy[index] = value;
         return previous;
     }
@@ -230,7 +224,7 @@
     protected boolean addKey(final String name) {
         final int index = indexOf(name);
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
         if (characterizedBy[index] == null) {
             characterizedBy[index] = types.characterizedBy[index].newInstance();
@@ -248,14 +242,14 @@
      * @throws IllegalStateException if another characteristic already exists for the characteristic name.
      */
     @Override
-    protected boolean addValue(final Attribute<?> value) {
+    protected boolean addValue(final AbstractAttribute<?> value) {
         ArgumentChecks.ensureNonNull("value", value);
         final int index = indexOf(value.getName().toString());
         verifyAttributeType(index, value.getType());
         if (characterizedBy == null) {
-            characterizedBy = new Attribute<?>[types.characterizedBy.length];
+            characterizedBy = new AbstractAttribute<?>[types.characterizedBy.length];
         }
-        final Attribute<?> previous = characterizedBy[index];
+        final AbstractAttribute<?> previous = characterizedBy[index];
         if (previous == null) {
             characterizedBy[index] = value;
             return true;
@@ -271,16 +265,16 @@
      * Returns an iterator over the entries.
      */
     @Override
-    protected EntryIterator<String, Attribute<?>> entryIterator() {
+    protected EntryIterator<String, AbstractAttribute<?>> entryIterator() {
         if (characterizedBy == null) {
             return null;
         }
-        return new EntryIterator<String, Attribute<?>>() {
+        return new EntryIterator<String, AbstractAttribute<?>>() {
             /** 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 Attribute<?> value;
+            private AbstractAttribute<?> value;
 
             /** Returns {@code true} if there is more entries in the iteration. */
             @Override protected boolean next() {
@@ -298,12 +292,12 @@
             }
 
             /** Returns the attribute characteristic (never {@code null}). */
-            @Override protected Attribute<?> getValue() {
+            @Override protected AbstractAttribute<?> getValue() {
                 return value;
             }
 
             /** Creates and return the next entry. */
-            @Override protected Map.Entry<String, Attribute<?>> getEntry() {
+            @Override protected Map.Entry<String, AbstractAttribute<?>> getEntry() {
                 return new Entry(index, value);
             }
 
@@ -317,17 +311,17 @@
     /**
      * 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 {@link #setValue(Attribute)} operation.
+     * This entry supports the {@code setValue(Attribute)} operation.
      */
-    private final class Entry extends AbstractMapEntry<String, Attribute<?>> {
+    private final class Entry extends AbstractMapEntry<String, AbstractAttribute<?>> {
         /** 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 Attribute<?> value;
+        private AbstractAttribute<?> value;
 
         /** Creates a new entry for the characteristic at the given index. */
-        Entry(final int index, final Attribute<?> value) {
+        Entry(final int index, final AbstractAttribute<?> value) {
             this.index = index;
             this.value = value;
         }
@@ -338,15 +332,15 @@
         }
 
         /** Returns the attribute characteristic (never {@code null}). */
-        @Override public Attribute<?> getValue() {
+        @Override public AbstractAttribute<?> getValue() {
             return value;
         }
 
         /** Sets the attribute characteristic. */
-        @Override public Attribute<?> setValue(final Attribute<?> value) {
+        @Override public AbstractAttribute<?> setValue(final AbstractAttribute<?> value) {
             ArgumentChecks.ensureNonNull("value", value);
             verifyAttributeType(index, value.getType());
-            final Attribute<?> previous = this.value;
+            final AbstractAttribute<?> 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 af664d1..da37c07 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,9 +28,6 @@
 
 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()}.
@@ -40,7 +37,7 @@
  * 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,AttributeType>} would work as well or better. However this level of indirection
+ * standard {@code HashMap<String,DefaultAttributeType>} 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.
@@ -50,17 +47,17 @@
  * @since   0.5
  * @module
  */
-final class CharacteristicTypeMap extends AbstractMap<String,AttributeType<?>> {
+final class CharacteristicTypeMap extends AbstractMap<String,DefaultAttributeType<?>> {
     /**
      * For sharing the same {@code CharacteristicTypeMap} instances among the attribute types
      * having the same characteristics.
      */
     @SuppressWarnings("unchecked")
-    private static final WeakValueHashMap<AttributeType<?>[],CharacteristicTypeMap> SHARED =
-            new WeakValueHashMap<>((Class) AttributeType[].class);
+    private static final WeakValueHashMap<DefaultAttributeType<?>[],CharacteristicTypeMap> SHARED =
+            new WeakValueHashMap<>((Class) DefaultAttributeType[].class);
 
     /*
-     * This class has intentionally no reference to the AttributeType for which we are providing characteristics.
+     * This class has intentionally no reference to the DefaultAttributeType 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).
      */
@@ -69,7 +66,7 @@
      * Characteristics of an other attribute type (the {@code source} attribute given to the constructor).
      * This array shall not be modified.
      */
-    final AttributeType<?>[] characterizedBy;
+    final DefaultAttributeType<?>[] characterizedBy;
 
     /**
      * The names of attribute types listed in the {@link #characterizedBy} array,
@@ -88,7 +85,7 @@
      * @return a map for this given characteristics.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    static CharacteristicTypeMap create(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
+    static CharacteristicTypeMap create(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] characterizedBy) {
         CharacteristicTypeMap map;
         synchronized (SHARED) {
             map = SHARED.get(characterizedBy);
@@ -110,13 +107,13 @@
      * @param  characterizedBy  characteristics of {@code source}. Should not be empty.
      * @throws IllegalArgumentException if two characteristics have the same name.
      */
-    private CharacteristicTypeMap(final AttributeType<?> source, final AttributeType<?>[] characterizedBy) {
+    private CharacteristicTypeMap(final DefaultAttributeType<?> source, final DefaultAttributeType<?>[] 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 AttributeType<?> attribute = characterizedBy[i];
+            final DefaultAttributeType<?> attribute = characterizedBy[i];
             ensureNonNullElement("characterizedBy", i, attribute);
             GenericName name = attribute.getName();
             String key = AbstractIdentifiedType.toString(name, source, "characterizedBy", i);
@@ -181,7 +178,7 @@
      */
     @Override
     public boolean containsValue(final Object key) {
-        for (final AttributeType<?> type : characterizedBy) {
+        for (final DefaultAttributeType<?> type : characterizedBy) {
             if (type.equals(key)) {
                 return true;
             }
@@ -193,7 +190,7 @@
      * Returns the attribute characteristic for the given name, or {@code null} if none.
      */
     @Override
-    public AttributeType<?> get(final Object key) {
+    public DefaultAttributeType<?> get(final Object key) {
         final Integer index = indices.get(key);
         return (index != null) ? characterizedBy[index] : null;
     }
@@ -203,13 +200,13 @@
      * This is not the iterator returned by public API like {@code Map.entrySet().iterator()}.
      */
     @Override
-    protected EntryIterator<String, AttributeType<?>> entryIterator() {
-        return new EntryIterator<String, AttributeType<?>>() {
+    protected EntryIterator<String, DefaultAttributeType<?>> entryIterator() {
+        return new EntryIterator<String, DefaultAttributeType<?>>() {
             /** Index of the next element to return in the iteration. */
             private int index;
 
             /** Value of current entry. */
-            private AttributeType<?> value;
+            private DefaultAttributeType<?> value;
 
             /**
              * Returns {@code true} if there is more entries in the iteration.
@@ -235,7 +232,7 @@
              * Returns the attribute characteristic contained in this entry.
              */
             @Override
-            protected AttributeType<?> getValue() {
+            protected DefaultAttributeType<?> getValue() {
                 return value;
             }
         };
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java b/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java
index 18c30b0..6e224e9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/CommonParentFinder.java
@@ -20,13 +20,10 @@
 import java.util.LinkedHashMap;
 import java.util.function.Predicate;
 
-// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Finds a feature type common to all given types. This is either one of the given types, or a parent common to all types.
- * A feature <var>F</var> is considered a common parent if <code>F.{@link DefaultFeatureType#isAssignableFrom(FeatureType)
+ * A feature <var>F</var> is considered a common parent if <code>F.{@link DefaultFeatureType#isAssignableFrom(DefaultFeatureType)
  * isAssignableFrom}(type)</code> returns {@code true} for all elements <var>type</var> in the given array.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -41,16 +38,16 @@
      * @param  types  types for which to find a common type.
      * @return a feature type which is assignable from all given types, or {@code null} if none.
      */
-    static FeatureType select(final Iterable<? extends FeatureType> types) {
+    static DefaultFeatureType select(final Iterable<? extends DefaultFeatureType> types) {
         /*
          * Get a set of unique feature types. Since the process done in this method may be relatively costly,
          * we want to avoid doing the same work twice. The purpose of Boolean value will be explained later.
          */
-        final Map<FeatureType,Boolean> allTypes = new LinkedHashMap<>();
+        final Map<DefaultFeatureType,Boolean> allTypes = new LinkedHashMap<>();
         types.forEach((type) -> allTypes.putIfAbsent(type, Boolean.FALSE));
         allTypes.remove(null);
         int count = allTypes.size();
-        final FeatureType[] required = allTypes.keySet().toArray(new FeatureType[count]);
+        final DefaultFeatureType[] required = allTypes.keySet().toArray(new DefaultFeatureType[count]);
         /*
          * We are going to iterate over the `required` array many times. For performance reason, this array should be as
          * short as possible. We can make it shorter by removing any element A which is assignable to another element B,
@@ -58,7 +55,7 @@
          * If this simplification results in an array of only one element, we are done.
          */
         for (int i=0; i<count; i++) {
-            final FeatureType parent = required[i];
+            final DefaultFeatureType parent = required[i];
             for (int j=count; --j >= 0;) {                 // Reverse order so that removed elements do not impact index j.
                 if (j != i && parent.isAssignableFrom(required[j])) {
                     System.arraycopy(required, j+1, required, j, --count - j);      // If A is assignable from B, remove B.
@@ -84,13 +81,13 @@
      * updated after we find that a type {@linkplain #isAssignableFromAll is assignable from
      * all required types}.
      */
-    private final Map<FeatureType,Boolean> allTypes;
+    private final Map<DefaultFeatureType,Boolean> allTypes;
 
     /**
      * The features types which must be assignable to the common parent.
      * This array may not contain all feature types given by user, since we try to remove redundant elements.
      */
-    private final FeatureType[] required;
+    private final DefaultFeatureType[] required;
 
     /**
      * Number of valid elements in the {@link #required} array.
@@ -101,7 +98,7 @@
      * Creates a finder for a common parent of the given types.
      * Invokes {@link #select()} after construction time for getting the parent.
      */
-    private CommonParentFinder(final Map<FeatureType,Boolean> allTypes, final FeatureType[] required, final int count) {
+    private CommonParentFinder(final Map<DefaultFeatureType,Boolean> allTypes, final DefaultFeatureType[] required, final int count) {
         this.allTypes = allTypes;
         this.required = required;
         this.count    = count;
@@ -114,9 +111,9 @@
      * Returns {@code true} if the given parent candidate is assignable from all required types.
      * The feature type to be returned by {@link #select()} must met that condition.
      */
-    private boolean isAssignableFromAll(final FeatureType parent) {
+    private boolean isAssignableFromAll(final DefaultFeatureType parent) {
         for (int i=0; i<count; i++) {
-            final FeatureType type = required[i];
+            final DefaultFeatureType type = required[i];
             if (type != parent && !parent.isAssignableFrom(type)) {
                 return false;
             }
@@ -129,8 +126,8 @@
      * This method verifies recursively parents of parents, skipping types that have already
      * been examined in a previous invocation of this method.
      */
-    private void scanParents(final FeatureType type) {
-        for (final FeatureType parent : type.getSuperTypes()) {
+    private void scanParents(final DefaultFeatureType type) {
+        for (final DefaultFeatureType parent : type.getSuperTypes()) {
             if (allTypes.putIfAbsent(parent, Boolean.FALSE) == null) {
                 if (isAssignableFromAll(parent)) {
                     allTypes.put(parent, Boolean.TRUE);     // Found a candidate.
@@ -146,9 +143,9 @@
      * Invoked when the given feature type is assignable from all required types.
      * There is no need to verify the parents since they are not going to be a better match.
      */
-    private void skipParents(final FeatureType type) {
+    private void skipParents(final DefaultFeatureType type) {
         assert isAssignableFromAll(type);
-        for (final FeatureType parent : type.getSuperTypes()) {
+        for (final DefaultFeatureType parent : type.getSuperTypes()) {
             if (Boolean.TRUE.equals(allTypes.put(parent, Boolean.FALSE))) {
                 // If `parent` was previously a candidate, its parents have already been set to `FALSE`.
             } else {
@@ -161,11 +158,11 @@
      * Invoked after all feature types have been examined. This method removes all features types that
      * are not parent of required types, then select the one having the greatest number of properties.
      */
-    FeatureType select() {
+    DefaultFeatureType select() {
         allTypes.values().removeIf(Predicate.isEqual(Boolean.FALSE));
-        FeatureType best = null;
+        DefaultFeatureType best = null;
         int numProperties = 0;
-        for (final FeatureType type : allTypes.keySet()) {
+        for (final DefaultFeatureType type : allTypes.keySet()) {
             final int n = type.getProperties(true).size();
             if (best == null || n > numProperties) {
                 best = type;
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 847ae2d..12690bc 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
@@ -29,14 +29,6 @@
 
 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.
@@ -64,7 +56,7 @@
  * @since 0.5
  * @module
  */
-public class DefaultAssociationRole extends FieldType implements FeatureAssociationRole {
+public class DefaultAssociationRole extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -81,8 +73,6 @@
      * 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;
 
@@ -133,7 +123,7 @@
      *
      * @see org.apache.sis.feature.builder.AssociationRoleBuilder
      */
-    public DefaultAssociationRole(final Map<String,?> identification, final FeatureType valueType,
+    public DefaultAssociationRole(final Map<String,?> identification, final DefaultFeatureType valueType,
             final int minimumOccurs, final int maximumOccurs)
     {
         super(identification, minimumOccurs, maximumOccurs);
@@ -224,7 +214,7 @@
      *         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<PropertyType> properties) {
+    final boolean resolve(final DefaultFeatureType creating, final Collection<AbstractIdentifiedType> properties) {
         final FeatureType type = valueType;
         if (type instanceof NamedFeatureType) {
             FeatureType resolved = ((NamedFeatureType) type).resolved;
@@ -238,7 +228,7 @@
                      * 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<FeatureType> deferred = new ArrayList<>();
+                    final List<DefaultFeatureType> deferred = new ArrayList<>();
                     resolved = search(creating, properties, name, deferred);
                     if (resolved == null) {
                         /*
@@ -273,8 +263,8 @@
      * @return the feature of the given name, or {@code null} if none.
      */
     @SuppressWarnings("null")
-    private static FeatureType search(final FeatureType feature, Collection<? extends PropertyType> properties,
-            final GenericName name, final List<FeatureType> deferred)
+    private static DefaultFeatureType search(final DefaultFeatureType feature, Collection<? extends AbstractIdentifiedType> properties,
+            final GenericName name, final List<DefaultFeatureType> deferred)
     {
         /*
          * Search only in associations declared in the given feature, not in inherited associations.
@@ -284,21 +274,17 @@
         if (properties == null) {
             properties = feature.getProperties(false);
         }
-        for (final PropertyType property : properties) {
-            if (property instanceof FeatureAssociationRole) {
+        for (final AbstractIdentifiedType property : properties) {
+            if (property instanceof DefaultAssociationRole) {
                 final FeatureType valueType;
-                if (property instanceof DefaultAssociationRole) {
-                    valueType = ((DefaultAssociationRole) property).valueType;
-                    if (valueType instanceof NamedFeatureType) {
-                        continue;                                   // Skip unresolved feature types.
-                    }
-                } else {
-                    valueType = ((FeatureAssociationRole) property).getValueType();
+                valueType = ((DefaultAssociationRole) property).valueType;
+                if (valueType instanceof NamedFeatureType) {
+                    continue;                                       // Skip unresolved feature types.
                 }
                 if (name.equals(valueType.getName())) {
-                    return valueType;
+                    return (DefaultFeatureType) valueType;
                 }
-                deferred.add(valueType);
+                deferred.add((DefaultFeatureType) valueType);
             }
         }
         /*
@@ -307,7 +293,7 @@
          * but not necessarily the same feature type (may be a subtype). This is equivalent to
          * "covariant return type" in the Java language.
          */
-        for (FeatureType type : feature.getSuperTypes()) {
+        for (DefaultFeatureType type : feature.getSuperTypes()) {
             if (name.equals(type.getName())) {
                 return type;
             }
@@ -320,7 +306,7 @@
     }
 
     /**
-     * Potentially invoked after {@link #search(FeatureType, Collection, GenericName, List)} for searching
+     * Potentially invoked after {@code 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,
@@ -328,14 +314,14 @@
      * later. We rather put a warning in {@link #DefaultAssociationRole(Map, GenericName, int, int)}
      * javadoc.</p>
      *
-     * @param  deferred  the feature types collected by {@link #search(FeatureType, Collection, GenericName, List)}.
+     * @param  deferred  the feature types collected by {@code 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 FeatureType deepSearch(final List<FeatureType> deferred, final GenericName name) {
+    private static DefaultFeatureType deepSearch(final List<DefaultFeatureType> deferred, final GenericName name) {
         final Map<FeatureType,Boolean> done = new IdentityHashMap<>(8);
         for (int i=0; i<deferred.size();) {
-            FeatureType valueType = deferred.get(i++);
+            DefaultFeatureType 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);
@@ -352,19 +338,20 @@
      * 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(PropertyType)
+     * However it is still possible to {@linkplain Features#getValueTypeName
      * 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)
      */
-    @Override
-    public final FeatureType getValueType() {
+    public final DefaultFeatureType 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
@@ -378,15 +365,16 @@
             }
             valueType = type;
         }
-        return type;
+        return (DefaultFeatureType) 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 FeatureAssociationRole role) {
-        return (role instanceof DefaultAssociationRole ? ((DefaultAssociationRole) role).valueType : role.getValueType()).getName();
+    static GenericName getValueTypeName(final DefaultAssociationRole role) {
+        // Method is static for compatibility with branches on GeoAPI snapshots.
+        return role.valueType.getName();
     }
 
     /**
@@ -404,34 +392,35 @@
      *
      * 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 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;
+    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;
         }
-        return searchTitleProperty(role.getValueType());
+        p = searchTitleProperty(role.getValueType());
+        role.titleProperty = (p != null) ? p : "";
+        return p;
     }
 
     /**
-     * Implementation of {@link #getTitleProperty(FeatureAssociationRole)} for first search,
+     * Implementation of {@link #getTitleProperty(DefaultAssociationRole)} for first search,
      * or for non-SIS {@code FeatureAssociationRole} implementations.
      */
-    private static String searchTitleProperty(final FeatureType ft) {
+    private static String searchTitleProperty(final DefaultFeatureType ft) {
         String fallback = null;
         try {
             return ft.getProperty(AttributeConvention.IDENTIFIER).getName().toString();
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             // Ignore.
         }
-        for (final PropertyType type : ft.getProperties(true)) {
-            if (type instanceof AttributeType<?>) {
-                final AttributeType<?> pt = (AttributeType<?>) type;
+        for (final AbstractIdentifiedType type : ft.getProperties(true)) {
+            if (type instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) type;
                 final Class<?> valueClass = pt.getValueClass();
                 if (CharSequence.class.isAssignableFrom(valueClass) ||
                     GenericName .class.isAssignableFrom(valueClass) ||
@@ -478,10 +467,9 @@
      *
      * @return a new association instance.
      *
-     * @see AbstractAssociation#create(FeatureAssociationRole)
+     * @see AbstractAssociation#create(DefaultAssociationRole)
      */
-    @Override
-    public FeatureAssociation newInstance() {
+    public AbstractAssociation 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 7312ab1..3c0bcf7 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
@@ -30,10 +30,6 @@
 
 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.
@@ -47,6 +43,11 @@
  * 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>
+ *
  * <h2>Value type</h2>
  * Attributes can be used for both spatial and non-spatial properties.
  * Some examples are:
@@ -105,7 +106,7 @@
  * @since 0.5
  * @module
  */
-public class DefaultAttributeType<V> extends FieldType implements AttributeType<V> {
+public class DefaultAttributeType<V> extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -188,7 +189,7 @@
     @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 AttributeType<?>... characterizedBy)
+            final DefaultAttributeType<?>... characterizedBy)
     {
         super(identification, minimumOccurs, maximumOccurs);
         ensureNonNull("valueClass",   valueClass);
@@ -221,7 +222,7 @@
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         try {
-            final AttributeType<?>[] characterizedBy = (AttributeType<?>[]) in.readObject();
+            final DefaultAttributeType<?>[] characterizedBy = (DefaultAttributeType<?>[]) in.readObject();
             if (characterizedBy != null) {
                 characteristics = CharacteristicTypeMap.create(this, characterizedBy);
             }
@@ -235,10 +236,7 @@
      * Returns the type of attribute values.
      *
      * @return the type of attribute values.
-     *
-     * @see Features#getValueClass(PropertyType)
      */
-    @Override
     public final Class<V> getValueClass() {
         return valueClass;
     }
@@ -290,7 +288,6 @@
      *
      * @return the default value for the attribute, or {@code null} if none.
      */
-    @Override
     public V getDefaultValue() {
         return defaultValue;
     }
@@ -314,8 +311,7 @@
      *
      * @see AbstractAttribute#characteristics()
      */
-    @Override
-    public Map<String,AttributeType<?>> characteristics() {
+    public Map<String,DefaultAttributeType<?>> characteristics() {
         return (characteristics != null) ? characteristics : Collections.emptyMap();
     }
 
@@ -324,10 +320,9 @@
      *
      * @return a new attribute instance.
      *
-     * @see AbstractAttribute#create(AttributeType)
+     * @see AbstractAttribute#create(DefaultAttributeType)
      */
-    @Override
-    public Attribute<V> newInstance() {
+    public AbstractAttribute<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 aad374f..f72e4c0 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,17 +40,6 @@
 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
@@ -60,6 +49,11 @@
  * 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>
+ *
  * <h2>Naming</h2>
  * 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.
@@ -92,7 +86,7 @@
  *
  * <h2>Immutability and thread safety</h2>
  * Instances of this class are immutable if all properties ({@link GenericName} and {@link InternationalString}
- * instances) and all arguments ({@link AttributeType} instances) given to the constructor are also immutable.
+ * instances) and all arguments ({@code 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)
@@ -140,7 +134,7 @@
      * 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 {@link #resolve(FeatureType, Map)} should check again.
+     * and that {@code 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
@@ -157,13 +151,13 @@
      *
      * @see #getSuperTypes()
      */
-    private final Set<FeatureType> superTypes;
+    private final Set<DefaultFeatureType> 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(FeatureType)}.
+     * 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)}.
      *
-     * @see #isAssignableFrom(FeatureType)
+     * @see #isAssignableFrom(DefaultFeatureType)
      */
     private transient Set<GenericName> assignableTo;
 
@@ -174,7 +168,7 @@
      *
      * @see #getProperties(boolean)
      */
-    private final List<PropertyType> properties;
+    private final List<AbstractIdentifiedType> properties;
 
     /**
      * All properties, including the ones declared in the super-types.
@@ -182,7 +176,7 @@
      *
      * @see #getProperties(boolean)
      */
-    private transient Collection<PropertyType> allProperties;
+    private transient Collection<AbstractIdentifiedType> allProperties;
 
     /**
      * A lookup table for fetching properties by name, including the properties from super-types.
@@ -190,7 +184,7 @@
      *
      * @see #getProperty(String)
      */
-    private transient Map<String, PropertyType> byName;
+    private transient Map<String, AbstractIdentifiedType> byName;
 
     /**
      * Indices of properties in an array of properties similar to {@link #properties},
@@ -247,6 +241,12 @@
      *   </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 @@
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
     public DefaultFeatureType(final Map<String,?> identification, final boolean isAbstract,
-            final FeatureType[] superTypes, final PropertyType... properties)
+            final DefaultFeatureType[] superTypes, final AbstractIdentifiedType... properties)
     {
         super(identification);
         ArgumentChecks.ensureNonNull("properties", properties);
@@ -278,9 +278,9 @@
          * 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<PropertyType> sourceProperties = new ArrayList<>(properties.length);
+        final List<AbstractIdentifiedType> sourceProperties = new ArrayList<>(properties.length);
         for (int i=0; i<properties.length; i++) {
-            final PropertyType property = properties[i];
+            final AbstractIdentifiedType property = properties[i];
             ArgumentChecks.ensureNonNullElement("properties", i, property);
             sourceProperties.add(property);
         }
@@ -289,13 +289,13 @@
         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 PropertyType[size])); break;
+            default: this.properties = UnmodifiableArrayList.wrap(sourceProperties.toArray(new AbstractIdentifiedType[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 PropertyType property : allProperties) {
+        for (final AbstractIdentifiedType property : allProperties) {
             if (property instanceof AbstractOperation) {
                 for (final String dependency : ((AbstractOperation) property).getDependencies()) {
                     if (!byName.containsKey(dependency)) {
@@ -343,17 +343,17 @@
      *
      * @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 {@link #scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
+     *         See {@code scanPropertiesFrom(FeatureType, Collection)} javadoc for more explanation.
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void computeTransientFields(final List<PropertyType> properties) {
+    private void computeTransientFields(final List<AbstractIdentifiedType> 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 PropertyType[byName.size()]));
+        allProperties = UnmodifiableArrayList.wrap(byName.values().toArray(new AbstractIdentifiedType[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 @@
         isSimple = true;
         int index = 0;
         int mandatory = 0;                                                  // Count of mandatory properties.
-        for (final Map.Entry<String,PropertyType> entry : byName.entrySet()) {
+        for (final Map.Entry<String,AbstractIdentifiedType> entry : byName.entrySet()) {
             final int minimumOccurs, maximumOccurs;
-            final PropertyType property = entry.getValue();
-            if (property instanceof AttributeType<?>) {
-                minimumOccurs = ((AttributeType<?>) property).getMinimumOccurs();
-                maximumOccurs = ((AttributeType<?>) property).getMaximumOccurs();
+            final AbstractIdentifiedType property = entry.getValue();
+            if (property instanceof DefaultAttributeType<?>) { // Other SIS branches check for AttributeType instead.
+                minimumOccurs = ((DefaultAttributeType<?>) property).getMinimumOccurs();
+                maximumOccurs = ((DefaultAttributeType<?>) property).getMaximumOccurs();
                 isSimple &= (minimumOccurs == maximumOccurs);
-            } else if (property instanceof FeatureAssociationRole) {
-                minimumOccurs = ((FeatureAssociationRole) property).getMinimumOccurs();
-                maximumOccurs = ((FeatureAssociationRole) property).getMaximumOccurs();
+            } else if (property instanceof FieldType) { // TODO: check for AssociationRole instead (after GeoAPI upgrade).
+                minimumOccurs = ((FieldType) property).getMinimumOccurs();
+                maximumOccurs = ((FieldType) property).getMaximumOccurs();
                 isSimple = false;
             } else {
                 if (isParameterlessOperation(property)) {
@@ -394,8 +394,8 @@
          *
          * In the 'aliases' map below, null values will be assigned to ambiguous short names.
          */
-        final Map<String, PropertyType> aliases = new LinkedHashMap<>();
-        for (final PropertyType property : allProperties) {
+        final Map<String, AbstractIdentifiedType> aliases = new LinkedHashMap<>();
+        for (final AbstractIdentifiedType 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 @@
                 aliases.put(key, aliases.containsKey(key) ? null : property);
             }
         }
-        for (final Map.Entry<String,PropertyType> entry : aliases.entrySet()) {
-            final PropertyType property = entry.getValue();
+        for (final Map.Entry<String,AbstractIdentifiedType> entry : aliases.entrySet()) {
+            final AbstractIdentifiedType property = entry.getValue();
             if (property != null) {
                 final String tip = entry.getKey();
                 if (byName.putIfAbsent(tip, property) == null) {
@@ -451,7 +451,7 @@
      * <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 {@link #DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
+     *   <li>Allow the {@code DefaultFeatureType(Map, boolean, FeatureType[], PropertyType[])} constructor
      *       to pass a temporary modifiable list that allow element removal.</li>
      * </ul>
      *
@@ -459,18 +459,20 @@
      * @param  sourceProperties  {@code source.getProperties(false)} (see above method javadoc).
      * @throws IllegalArgumentException if two properties have the same name.
      */
-    private void scanPropertiesFrom(final FeatureType source, final Collection<? extends PropertyType> sourceProperties) {
-        for (final FeatureType parent : source.getSuperTypes()) {
+    private void scanPropertiesFrom(final DefaultFeatureType source,
+            final Collection<? extends AbstractIdentifiedType> sourceProperties)
+    {
+        for (final DefaultFeatureType parent : source.getSuperTypes()) {
             if (assignableTo.add(parent.getName())) {
                 scanPropertiesFrom(parent, parent.getProperties(false));
             }
         }
         int index = -1;
-        final Iterator<? extends PropertyType> it = sourceProperties.iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = sourceProperties.iterator();
         while (it.hasNext()) {
-            final PropertyType property = it.next();
+            final AbstractIdentifiedType property = it.next();
             final String name = toString(property.getName(), source, "properties", ++index);
-            final PropertyType previous = byName.put(name, property);
+            final AbstractIdentifiedType previous = byName.put(name, property);
             if (previous != null) {
                 if (previous.equals(property)) {
                     byName.put(name, previous);         // Keep the instance declared in super-type.
@@ -490,14 +492,18 @@
      * 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 FeatureType type, final Collection<? extends PropertyType> properties,
-            final PropertyType toSearch)
+    private static GenericName ownerOf(final DefaultFeatureType type, final Collection<? extends AbstractIdentifiedType> properties,
+            final AbstractIdentifiedType toSearch)
     {
         if (properties.contains(toSearch)) {
             return type.getName();
         }
-        for (final FeatureType superType : type.getSuperTypes()) {
+        for (final DefaultFeatureType superType : type.getSuperTypes()) {
             final GenericName owner = ownerOf(superType, superType.getProperties(false), toSearch);
             if (owner != null) {
                 return owner;
@@ -521,23 +527,16 @@
      * @param  previous  previous results, for avoiding never ending loop.
      * @return {@code true} if all names have been resolved.
      */
-    private boolean resolve(final FeatureType feature, final Map<FeatureType,Boolean> previous) {
+    private boolean resolve(final DefaultFeatureType 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.  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.
+         * we have determined that there is no unresolved name.
          */
-        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());
-        }
+        return feature.isResolved = resolve(feature, feature.properties, previous, feature.isResolved);
     }
 
     /**
-     * Implementation of {@link #resolve(FeatureType, Map)}, also to be invoked from the constructor.
+     * Implementation of {@code 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>
@@ -548,21 +547,19 @@
      * @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 FeatureType feature, final Collection<? extends PropertyType> toUpdate,
+    private boolean resolve(final DefaultFeatureType feature, final Collection<? extends AbstractIdentifiedType> toUpdate,
             Map<FeatureType,Boolean> previous, boolean resolved)
     {
         if (!resolved) {
             resolved = true;
-            for (final FeatureType type : feature.getSuperTypes()) {
+            for (final DefaultFeatureType type : feature.getSuperTypes()) {
                 resolved &= resolve(type, previous);
             }
-            for (final PropertyType property : toUpdate) {
-                if (property instanceof FeatureAssociationRole) {
-                    if (property instanceof DefaultAssociationRole) {
-                        if (!((DefaultAssociationRole) property).resolve(this, properties)) {
-                            resolved = false;
-                            continue;
-                        }
+            for (final AbstractIdentifiedType property : toUpdate) {
+                if (property instanceof DefaultAssociationRole) {
+                    if (!((DefaultAssociationRole) property).resolve(this, properties)) {
+                        resolved = false;
+                        continue;
                     }
                     /*
                      * Resolve recursively the associated features, with a check against infinite recursivity.
@@ -570,7 +567,7 @@
                      * may not be the most accurate answer, but will not cause any more hurt than checking more
                      * often than necessary.
                      */
-                    final FeatureType valueType = ((FeatureAssociationRole) property).getValueType();
+                    final DefaultFeatureType valueType = ((DefaultAssociationRole) property).getValueType();
                     if (valueType != this) {
                         if (previous == null) {
                             previous = new IdentityHashMap<>(8);
@@ -593,11 +590,11 @@
      *
      * @see #OPERATION_INDEX
      */
-    static boolean isParameterlessOperation(final PropertyType type) {
-        if (type instanceof Operation) {
-            final ParameterDescriptorGroup parameters = ((Operation) type).getParameters();
+    static boolean isParameterlessOperation(final AbstractIdentifiedType type) {
+        if (type instanceof AbstractOperation) {
+            final ParameterDescriptorGroup parameters = ((AbstractOperation) type).getParameters();
             return ((parameters == null) || parameters.descriptors().isEmpty())
-                   && ((Operation) type).getResult() != null;
+                   && ((AbstractOperation) type).getResult() != null;
         }
         return false;
     }
@@ -612,7 +609,6 @@
      *
      * @return {@code true} if the feature type acts as an abstract super-type.
      */
-    @Override
     public final boolean isAbstract() {
         return isAbstract;
     }
@@ -632,31 +628,22 @@
      *
      * @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(FeatureType)}.
+     * the name as a criterion. This is a faster check than {@link #isAssignableFrom(DefaultFeatureType)}.
      *
      * <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 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;
+    static boolean maybeAssignableFrom(final DefaultFeatureType base, final DefaultFeatureType type) {
+        return type.assignableTo.contains(base.getName());
     }
 
     /**
@@ -679,7 +666,7 @@
      * @return {@code true} if instances of the given type can be assigned to association of this type.
      */
     @Override
-    public boolean isAssignableFrom(final FeatureType type) {
+    public boolean isAssignableFrom(final DefaultFeatureType type) {
         if (type == this) {
             return true;                            // Optimization for a common case.
         }
@@ -691,11 +678,11 @@
          * 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, PropertyType> entry : byName.entrySet()) {
-            final PropertyType other;
+        for (final Map.Entry<String, AbstractIdentifiedType> entry : byName.entrySet()) {
+            final AbstractIdentifiedType other;
             try {
                 other = type.getProperty(entry.getKey());
-            } catch (PropertyNotFoundException e) {
+            } catch (IllegalArgumentException 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
@@ -716,22 +703,25 @@
      * 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 PropertyType base, final PropertyType other) {
+    private static boolean isAssignableIgnoreName(final AbstractIdentifiedType base, final AbstractIdentifiedType 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] multiplicity.
+             *
+             * Note: other SIS branches use AttributeType and FeatureAssociationRole
+             *       instead than DefaultAttributeType and DefaultAssociationRole.
              */
-            if (base instanceof AttributeType<?>) {
-                final AttributeType<?> p0 = (AttributeType<?>) base;
-                final AttributeType<?> p1;
-                if (other instanceof AttributeType<?>) {
-                    p1 = (AttributeType<?>) other;
+            if (base instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> p0 = (DefaultAttributeType<?>) base;
+                final DefaultAttributeType<?> p1;
+                if (other instanceof DefaultAttributeType<?>) {
+                    p1 = (DefaultAttributeType<?>) other;
                 } else if (isParameterlessOperation(other)) {
-                    final IdentifiedType result = ((Operation) other).getResult();
-                    if (result instanceof AttributeType<?>) {
-                        p1 = (AttributeType<?>) result;
+                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
+                    if (result instanceof DefaultAttributeType<?>) {
+                        p1 = (DefaultAttributeType<?>) result;
                     } else {
                         return false;
                     }
@@ -752,15 +742,15 @@
              * 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 FeatureAssociationRole) {
-                final FeatureAssociationRole p0 = (FeatureAssociationRole) base;
-                final FeatureAssociationRole p1;
-                if (other instanceof FeatureAssociationRole) {
-                    p1 = (FeatureAssociationRole) other;
+            if (base instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole p0 = (DefaultAssociationRole) base;
+                final DefaultAssociationRole p1;
+                if (other instanceof DefaultAssociationRole) {
+                    p1 = (DefaultAssociationRole) other;
                 } else if (isParameterlessOperation(other)) {
-                    final IdentifiedType result = ((Operation) other).getResult();
-                    if (result instanceof FeatureAssociationRole) {
-                        p1 = (FeatureAssociationRole) result;
+                    final AbstractIdentifiedType result = ((AbstractOperation) other).getResult();
+                    if (result instanceof DefaultAssociationRole) {
+                        p1 = (DefaultAssociationRole) result;
                     } else {
                         return false;
                     }
@@ -774,8 +764,8 @@
                 {
                     return false;
                 }
-                final FeatureType f0 = p0.getValueType();
-                final FeatureType f1 = p1.getValueType();
+                final DefaultFeatureType f0 = p0.getValueType();
+                final DefaultFeatureType f1 = p1.getValueType();
                 if (f0 != f1 && !f0.isAssignableFrom(f1)) {
                     return false;
                 }
@@ -785,11 +775,11 @@
              * In the special case of parameterless operations, can also be overridden by
              * AttributeType or FeatureAssociationRole.
              */
-            if (base instanceof Operation) {
-                final Operation p0 = (Operation) base;
-                final IdentifiedType r1;
-                if (other instanceof Operation) {
-                    final Operation p1 = (Operation) other;
+            if (base instanceof AbstractOperation) {
+                final AbstractOperation p0 = (AbstractOperation) base;
+                final AbstractIdentifiedType r1;
+                if (other instanceof AbstractOperation) {
+                    final AbstractOperation p1 = (AbstractOperation) other;
                     if (!Objects.equals(p0.getParameters(), p1.getParameters())) {
                         return false;
                     }
@@ -799,15 +789,14 @@
                 } else {
                     return false;
                 }
-                final IdentifiedType r0 = p0.getResult();
+                final AbstractIdentifiedType r0 = p0.getResult();
                 if (r0 != r1) {
-                    if (r0 instanceof FeatureType) {
-                        if (!(r1 instanceof FeatureType) || !((FeatureType) r0).isAssignableFrom((FeatureType) r1)) {
+                    if (r0 instanceof DefaultFeatureType) {
+                        if (!(r1 instanceof DefaultFeatureType) || !((FeatureType) r0).isAssignableFrom((DefaultFeatureType) r1)) {
                             return false;
                         }
-                    }
-                    if (r0 instanceof PropertyType) {
-                        if (!(r1 instanceof PropertyType) || !isAssignableIgnoreName((PropertyType) r0, (PropertyType) r1)) {
+                    } else if (r0 != null) {
+                        if (r1 == null || !isAssignableIgnoreName(r0, r1)) {
                             return false;
                         }
                     }
@@ -825,6 +814,10 @@
      * 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
@@ -833,9 +826,8 @@
      *
      * @return  the parents of this feature type, or an empty set if none.
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public final Set<FeatureType> getSuperTypes() {
+    public final Set<DefaultFeatureType> getSuperTypes() {
         return superTypes;      // Immutable
     }
 
@@ -845,32 +837,39 @@
      * 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 explicitly 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<PropertyType> getProperties(final boolean includeSuperTypes) {
+    public Collection<AbstractIdentifiedType> 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 PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      *
      * @see AbstractFeature#getProperty(String)
      */
-    @Override
-    public PropertyType getProperty(final String name) throws PropertyNotFoundException {
-        final PropertyType pt = byName.get(name);
+    public AbstractIdentifiedType getProperty(final String name) throws IllegalArgumentException {
+        final AbstractIdentifiedType pt = byName.get(name);
         if (pt != null) {
             return pt;
         }
-        throw new PropertyNotFoundException(AbstractFeature.propertyNotFound(this, getName(), name));
+        throw new IllegalArgumentException(AbstractFeature.propertyNotFound(this, getName(), name));
     }
 
     /**
@@ -890,12 +889,11 @@
      * then this method is equivalent to {@link Class#newInstance()}.</div>
      *
      * @return a new feature instance.
-     * @throws FeatureInstantiationException if this feature type {@linkplain #isAbstract() is abstract}.
+     * @throws IllegalStateException if this feature type {@linkplain #isAbstract() is abstract}.
      */
-    @Override
-    public Feature newInstance() throws FeatureInstantiationException {
+    public AbstractFeature newInstance() throws IllegalStateException {
         if (isAbstract) {
-            throw new FeatureInstantiationException(Resources.format(Resources.Keys.AbstractFeatureType_1, getName()));
+            throw new IllegalStateException(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 79b8204..73e471b 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,12 +23,6 @@
 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
@@ -85,14 +79,14 @@
      * @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 PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
-    private int getIndex(final String name) throws PropertyNotFoundException {
+    private int getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -100,10 +94,10 @@
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         final int index = getIndex(name);
         if (index < 0) {
@@ -136,10 +130,10 @@
      *         known to this feature, or if the property can not be set or another reason.
      */
     @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
+    public void setProperty(final Object property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
         if (!(properties instanceof Property[])) {
             wrapValuesInProperties();
         }
@@ -176,10 +170,10 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
+    public Object getPropertyValue(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         final int index = getIndex(name);
         if (index < 0) {
@@ -190,10 +184,10 @@
             if (element != null) {
                 if (!(properties instanceof Property[])) {
                     return element; // Most common case.
-                } else if (element instanceof Attribute<?>) {
-                    return getAttributeValue((Attribute<?>) element);
-                } else if (element instanceof FeatureAssociation) {
-                    return getAssociationValue((FeatureAssociation) element);
+                } else if (element instanceof AbstractAttribute<?>) {
+                    return getAttributeValue((AbstractAttribute<?>) element);
+                } else if (element instanceof AbstractAssociation) {
+                    return getAssociationValue((AbstractAssociation) element);
                 } else {
                     throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
                 }
@@ -305,10 +299,10 @@
                 for (final Property p : (Property[]) properties) {
                     code = 31 * code;
                     final Object value;
-                    if (p instanceof Attribute<?>) {
-                        value = getAttributeValue((Attribute<?>) p);
-                    } else if (p instanceof FeatureAssociation) {
-                        value = getAssociationValue((FeatureAssociation) p);
+                    if (p instanceof AbstractAttribute<?>) {
+                        value = getAttributeValue((AbstractAttribute<?>) p);
+                    } else if (p instanceof AbstractAssociation) {
+                        value = getAssociationValue((AbstractAssociation) 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 8e49d8c..19415c3 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,15 +38,6 @@
 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.
@@ -113,7 +104,7 @@
     /**
      * The type of the result returned by the envelope operation.
      */
-    private final AttributeType<Envelope> resultType;
+    private final DefaultAttributeType<Envelope> resultType;
 
     /**
      * Creates a new operation computing the envelope of features of the given type.
@@ -123,7 +114,7 @@
      * @param geometryAttributes  the operation or attribute type from which to get geometry values.
      */
     EnvelopeOperation(final Map<String,?> identification, CoordinateReferenceSystem crs,
-            final PropertyType[] geometryAttributes) throws FactoryException
+            final AbstractIdentifiedType[] geometryAttributes) throws FactoryException
     {
         super(identification);
         String defaultGeometry = null;
@@ -138,7 +129,7 @@
          */
         boolean characterizedByCRS = false;
         final Map<String,CoordinateReferenceSystem> names = new LinkedHashMap<>(4);
-        for (IdentifiedType property : geometryAttributes) {
+        for (AbstractIdentifiedType property : geometryAttributes) {
             if (AttributeConvention.isGeometryAttribute(property)) {
                 final GenericName name = property.getName();
                 final String attributeName = (property instanceof LinkOperation)
@@ -148,8 +139,8 @@
                     defaultGeometry = attributeName;
                 }
                 CoordinateReferenceSystem attributeCRS = null;
-                while (property instanceof Operation) {
-                    property = ((Operation) property).getResult();
+                while (property instanceof AbstractOperation) {
+                    property = ((AbstractOperation) property).getResult();
                 }
                 /*
                  * At this point 'property' is an attribute, otherwise isGeometryAttribute(property) would have
@@ -157,7 +148,7 @@
                  * 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 AttributeType<?> at = ((AttributeType<?>) property).characteristics().get(characteristicName);
+                final DefaultAttributeType<?> at = ((DefaultAttributeType<?>) property).characteristics().get(characteristicName);
                 if (at != null && CoordinateReferenceSystem.class.isAssignableFrom(at.getValueClass())) {
                     attributeCRS = (CoordinateReferenceSystem) at.getDefaultValue();              // May still null.
                     if (crs == null && isDefault) {
@@ -223,7 +214,7 @@
      * @return an {@code AttributeType<Envelope>}.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -248,7 +239,7 @@
      * @return the envelope of geometries in feature property values.
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(feature);
     }
 
@@ -266,14 +257,14 @@
         private static final long serialVersionUID = 926172863066901618L;
 
         /**
-         * The feature specified to the {@link StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
+         * The feature specified to the {@code StringJoinOperation.apply(Feature, ParameterValueGroup)} method.
          */
-        private final Feature feature;
+        private final AbstractFeature feature;
 
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType);
             this.feature = feature;
         }
@@ -308,7 +299,7 @@
                      * 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 = feature.getProperty(name);
+                    final Property property = (Property) feature.getProperty(name);
                     genv = Geometries.getEnvelope(property.getValue());
                     if (genv == null) continue;
                     /*
@@ -317,7 +308,7 @@
                      * cases where a CRS characteristic is associated to a particular feature, we will let
                      * Envelopes.transform(…) searches a coordinate operation.
                      */
-                    final Attribute<?> at = ((Attribute<?>) property).characteristics()
+                    final AbstractAttribute<?> at = ((AbstractAttribute<?>) property).characteristics()
                                     .get(AttributeConvention.CRS_CHARACTERISTIC.toString());
                     try {
                         if (at == null) {
@@ -351,7 +342,7 @@
          */
         @Override
         public void setValue(Envelope value) {
-            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.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 7bcc201..441db55 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
@@ -49,18 +49,6 @@
 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;
-
 
 /**
  * Formats {@linkplain AbstractFeature features} or {@linkplain DefaultFeatureType feature types} in a tabular format.
@@ -158,7 +146,7 @@
 
     /**
      * 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 {@link Feature} and {@link FeatureType}.
+     * since it is the only common parent to {@code Feature} and {@link FeatureType}.
      *
      * @return {@code Object.class}
      */
@@ -225,20 +213,20 @@
     public enum Column {
         /**
          * Natural language designator for the property.
-         * This is the character sequence returned by {@link PropertyType#getDesignation()}.
+         * This is the character sequence returned by {@link AbstractIdentifiedType#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 PropertyType#getName()}.
+         * This is the character sequence returned by {@link AbstractIdentifiedType#getName()}.
          */
         NAME(Vocabulary.Keys.Name),
 
         /**
-         * Type of property values. This is the type returned by {@link AttributeType#getValueClass()} or
-         * {@link FeatureAssociationRole#getValueType()}.
+         * Type of property values. This is the type returned by {@link DefaultAttributeType#getValueClass()} or
+         * {@link DefaultAssociationRole#getValueType()}.
          */
         TYPE(Vocabulary.Keys.Type),
 
@@ -246,21 +234,21 @@
          * Cardinality (for attributes) or multiplicity (for attribute types).
          * The cardinality is the actual number of attribute values.
          * The multiplicity is the minimum and maximum occurrences of attribute values.
-         * The multiplicity is made from the numbers returned by {@link AttributeType#getMinimumOccurs()}
-         * and {@link AttributeType#getMaximumOccurs()}.
+         * The multiplicity is made from the numbers returned by {@link DefaultAttributeType#getMinimumOccurs()}
+         * and {@link DefaultAttributeType#getMaximumOccurs()}.
          */
         CARDINALITY(Vocabulary.Keys.Cardinality),
 
         /**
          * Property value (for properties) or default value (for property types).
-         * This is the value returned by {@link Attribute#getValue()}, {@link FeatureAssociation#getValue()}
-         * or {@link AttributeType#getDefaultValue()}.
+         * This is the value returned by {@link AbstractAttribute#getValue()}, {@link AbstractAssociation#getValue()}
+         * or {@link DefaultAttributeType#getDefaultValue()}.
          */
         VALUE(Vocabulary.Keys.Value),
 
         /**
          * Other attributes that describes the attribute.
-         * This is made from the map returned by {@link Attribute#characteristics()}.
+         * This is made from the map returned by {@link AbstractAttribute#characteristics()}.
          * This column is omitted if no property has characteristics.
          */
         CHARACTERISTICS(Vocabulary.Keys.Characteristics),
@@ -297,8 +285,8 @@
      * The object may be an instance of any of the following types:
      *
      * <ul>
-     *   <li>{@link Feature}</li>
-     *   <li>{@link FeatureType}</li>
+     *   <li>{@code Feature}</li>
+     *   <li>{@code FeatureType}</li>
      * </ul>
      *
      * @throws IOException if an error occurred while writing to the given appendable.
@@ -311,13 +299,13 @@
         /*
          * Separate the Feature (optional) and the FeatureType (mandatory) instances.
          */
-        final FeatureType featureType;
-        final Feature feature;
-        if (object instanceof Feature) {
-            feature     = (Feature) object;
+        final DefaultFeatureType featureType;
+        final AbstractFeature feature;
+        if (object instanceof AbstractFeature) {
+            feature     = (AbstractFeature) object;
             featureType = feature.getType();
-        } else if (object instanceof FeatureType) {
-            featureType = (FeatureType) object;
+        } else if (object instanceof DefaultFeatureType) {
+            featureType = (DefaultFeatureType) object;
             feature     = null;
         } else {
             throw new IllegalArgumentException(Errors.getResources(displayLocale)
@@ -334,12 +322,12 @@
             boolean hasDesignation     = false;
             boolean hasCharacteristics = false;
             boolean hasDeprecatedTypes = false;
-            for (final PropertyType propertyType : featureType.getProperties(true)) {
+            for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
                 if (!hasDesignation) {
                     hasDesignation = propertyType.getDesignation() != null;
                 }
-                if (!hasCharacteristics && propertyType instanceof AttributeType<?>) {
-                    hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty();
+                if (!hasCharacteristics && propertyType instanceof DefaultAttributeType<?>) {
+                    hasCharacteristics = !((DefaultAttributeType<?>) propertyType).characteristics().isEmpty();
                 }
                 if (!hasDeprecatedTypes && propertyType instanceof Deprecable) {
                     hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated();
@@ -400,26 +388,26 @@
         final StringBuffer  buffer  = new StringBuffer();
         final FieldPosition dummyFP = new FieldPosition(-1);
         final List<String>  remarks = new ArrayList<>();
-        for (final PropertyType propertyType : featureType.getProperties(true)) {
+        for (final AbstractIdentifiedType propertyType : featureType.getProperties(true)) {
             Object value = null;
             int cardinality = -1;
             if (feature != null) {
-                if (!(propertyType instanceof AttributeType<?>) &&
-                    !(propertyType instanceof FeatureAssociationRole) &&
+                if (!(propertyType instanceof DefaultAttributeType<?>) &&
+                    !(propertyType instanceof DefaultAssociationRole) &&
                     !DefaultFeatureType.isParameterlessOperation(propertyType))
                 {
                     continue;
                 }
                 value = feature.getPropertyValue(propertyType.getName().toString());
                 if (value == null) {
-                    if (propertyType instanceof AttributeType<?>
-                            && ((AttributeType<?>) propertyType).getMinimumOccurs() == 0
-                            && ((AttributeType<?>) propertyType).characteristics().isEmpty())
+                    if (propertyType instanceof DefaultAttributeType<?>
+                            && ((DefaultAttributeType<?>) propertyType).getMinimumOccurs() == 0
+                            && ((DefaultAttributeType<?>) propertyType).characteristics().isEmpty())
                     {
                         continue;                           // If optional, no value and no characteristics, skip the full row.
                     }
-                    if (propertyType instanceof FeatureAssociationRole
-                            && ((FeatureAssociationRole) propertyType).getMinimumOccurs() == 0)
+                    if (propertyType instanceof DefaultAssociationRole
+                            && ((DefaultAssociationRole) propertyType).getMinimumOccurs() == 0)
                     {
                         continue;                           // If optional and no value, skip the full row.
                     }
@@ -429,16 +417,12 @@
                 } else {
                     cardinality = 1;
                 }
-            } else if (propertyType instanceof AttributeType<?>) {
-                value = ((AttributeType<?>) propertyType).getDefaultValue();
-            } else if (propertyType instanceof Operation) {
+            } else if (propertyType instanceof DefaultAttributeType<?>) {
+                value = ((DefaultAttributeType<?>) propertyType).getDefaultValue();
+            } else if (propertyType instanceof AbstractOperation) {
                 buffer.append(" = ");
                 try {
-                    if (propertyType instanceof AbstractOperation) {
-                        ((AbstractOperation) propertyType).formatResultFormula(buffer);
-                    } else {
-                        AbstractOperation.defaultFormula(((Operation) propertyType).getParameters(), buffer);
-                    }
+                    ((AbstractOperation) propertyType).formatResultFormula(buffer);
                 } catch (IOException e) {
                     throw new UncheckedIOException(e);      // Should never happen since we write in a StringBuffer.
                 }
@@ -448,25 +432,25 @@
             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 IdentifiedType resultType;                // Result of operation if applicable.
-            if (propertyType instanceof Operation) {
-                resultType = ((Operation) propertyType).getResult();                // May be null
+            final AbstractIdentifiedType resultType;        // Result of operation if applicable.
+            if (propertyType instanceof AbstractOperation) {
+                resultType = ((AbstractOperation) propertyType).getResult();        // May be null
             } else {
                 resultType = propertyType;
             }
-            if (resultType instanceof AttributeType<?>) {
-                final AttributeType<?> pt = (AttributeType<?>) resultType;
+            if (resultType instanceof DefaultAttributeType<?>) {
+                final DefaultAttributeType<?> pt = (DefaultAttributeType<?>) 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 FeatureAssociationRole) {
-                final FeatureAssociationRole pt = (FeatureAssociationRole) resultType;
+            } else if (resultType instanceof DefaultAssociationRole) {
+                final DefaultAssociationRole pt = (DefaultAssociationRole) resultType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueType     = toString(DefaultAssociationRole.getValueTypeName(pt));
-                valueClass    = Feature.class;
+                valueClass    = AbstractFeature.class;
             } else {
                 valueType  = (resultType != null) ? toString(resultType.getName()) : "";
                 valueClass = null;
@@ -553,10 +537,10 @@
                         while (it.hasNext()) {
                             value = it.next();
                             if (value != null) {
-                                if (propertyType instanceof FeatureAssociationRole) {
-                                    final String p = DefaultAssociationRole.getTitleProperty((FeatureAssociationRole) propertyType);
+                                if (propertyType instanceof DefaultAssociationRole) {
+                                    final String p = DefaultAssociationRole.getTitleProperty((DefaultAssociationRole) propertyType);
                                     if (p != null) {
-                                        value = ((Feature) value).getPropertyValue(p);
+                                        value = ((AbstractFeature) value).getPropertyValue(p);
                                         if (value == null) continue;
                                     }
                                 } else if (format != null && valueClass.isInstance(value)) {    // Null safe because of getFormat(valueClass) contract.
@@ -610,10 +594,10 @@
                      * Characteristics are handled as "attributes of attributes".
                      */
                     case CHARACTERISTICS: {
-                        if (propertyType instanceof AttributeType<?>) {
+                        if (propertyType instanceof DefaultAttributeType<?>) {
                             int length = 0;
                             String separator = "";
-format:                     for (final AttributeType<?> ct : ((AttributeType<?>) propertyType).characteristics().values()) {
+format:                     for (final DefaultAttributeType<?> ct : ((DefaultAttributeType<?>) 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
@@ -631,9 +615,9 @@
                                      * given by the default value 'cv'.  Nevertheless we have to check if current
                                      * feature overrides this characteristic.
                                      */
-                                    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());
+                                    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());
                                         if (ca != null) cv = ca.getValues();
                                     }
                                 }
@@ -717,8 +701,8 @@
             text = ((InternationalString) value).toString(displayLocale);
         } else if (value instanceof GenericName) {
             text = toString((GenericName) value);
-        } else if (value instanceof IdentifiedType) {
-            text = toString(((IdentifiedType) value).getName());
+        } else if (value instanceof AbstractIdentifiedType) {
+            text = toString(((AbstractIdentifiedType) 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 b75c880..6601a4f 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,11 +27,6 @@
 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.
@@ -115,7 +110,7 @@
     /**
      * The pool of operations or operation dependencies created so far, for sharing exiting instances.
      */
-    static final WeakHashSet<PropertyType> POOL = new WeakHashSet<>(PropertyType.class);
+    static final WeakHashSet<AbstractIdentifiedType> POOL = new WeakHashSet<>(AbstractIdentifiedType.class);
 
     /**
      * Do not allow instantiation of this class.
@@ -152,11 +147,15 @@
      * 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 Operation link(final Map<String,?> identification, final PropertyType referent) {
+    public static AbstractOperation link(final Map<String,?> identification, final AbstractIdentifiedType referent) {
         ArgumentChecks.ensureNonNull("referent", referent);
         return POOL.unique(new LinkOperation(identification, referent));
     }
@@ -187,6 +186,10 @@
      * 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.
@@ -201,8 +204,8 @@
      *
      * @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key on Wikipedia</a>
      */
-    public static Operation compound(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final PropertyType... singleAttributes)
+    public static AbstractOperation compound(final Map<String,?> identification, final String delimiter,
+            final String prefix, final String suffix, final AbstractIdentifiedType... singleAttributes)
             throws UnconvertibleObjectException
     {
         ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
@@ -218,8 +221,8 @@
             }
             case 1: {
                 if ((prefix == null || prefix.isEmpty()) && (suffix == null || suffix.isEmpty())) {
-                    final PropertyType at = singleAttributes[0];
-                    if (!(at instanceof FeatureAssociationRole)) {
+                    final AbstractIdentifiedType at = singleAttributes[0];
+                    if (!(at instanceof DefaultAssociationRole)) {
                         return link(identification, at);
                     }
                 }
@@ -254,6 +257,10 @@
      * 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.
@@ -261,8 +268,8 @@
      * @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 Operation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
-            final PropertyType... geometryAttributes) throws FactoryException
+    public static AbstractOperation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs,
+            final AbstractIdentifiedType... 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
new file mode 100644
index 0000000..5f5e078
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureType.java
@@ -0,0 +1,41 @@
+/*
+ * 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 7ff600f..48c694c 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,7 +19,6 @@
 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;
@@ -29,17 +28,6 @@
 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.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.PropertyType;
-
 
 /**
  * Static methods working on features or attributes.
@@ -71,7 +59,7 @@
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> AttributeType<V> cast(final AttributeType<?> type, final Class<V> valueClass)
+    public static <V> DefaultAttributeType<V> cast(final DefaultAttributeType<?> type, final Class<V> valueClass)
             throws ClassCastException
     {
         if (type != null) {
@@ -85,7 +73,7 @@
                         type.getName(), valueClass, actual));
             }
         }
-        return (AttributeType<V>) type;
+        return (DefaultAttributeType<V>) type;
     }
 
     /**
@@ -102,7 +90,7 @@
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <V> Attribute<V> cast(final Attribute<?> attribute, final Class<V> valueClass)
+    public static <V> AbstractAttribute<V> cast(final AbstractAttribute<?> attribute, final Class<V> valueClass)
             throws ClassCastException
     {
         if (attribute != null) {
@@ -116,7 +104,7 @@
                         attribute.getName(), valueClass, actual));
             }
         }
-        return (Attribute<V>) attribute;
+        return (AbstractAttribute<V>) attribute;
     }
 
     /**
@@ -128,62 +116,33 @@
      * @param  types  types for which to find a common type, or {@code null}.
      * @return a feature type which is assignable from all given types, or {@code null} if none.
      *
-     * @see FeatureType#isAssignableFrom(FeatureType)
+     * @see DefaultFeatureType#isAssignableFrom(DefaultFeatureType)
      *
      * @since 1.0
      */
-    public static FeatureType findCommonParent(final Iterable<? extends FeatureType> types) {
+    public static DefaultFeatureType findCommonParent(final Iterable<? extends DefaultFeatureType> types) {
         return (types != null) ? CommonParentFinder.select(types) : null;
     }
 
-    /**
-     * Returns the type of values provided by the given property. For {@linkplain AttributeType attributes}
-     * (which is the most common case), the value type is given by {@link AttributeType#getValueClass()}.
-     * For {@linkplain FeatureAssociationRole feature associations}, the value type is {@link Feature}.
-     * For {@linkplain Operation operations}, the value type is determined recursively from the
-     * {@linkplain Operation#getResult() operation result}.
-     * If the value type can not be determined, then this method returns {@code null}.
+    /*
+     * Following method is omitted on master because it depends on GeoAPI interfaces not yet published:
      *
-     * @param  type  the property for which to get the type of values, or {@code null}.
-     * @return the type of values provided by the given property, or {@code null} if unknown.
-     *
-     * @see AttributeType#getValueClass()
-     *
-     * @since 1.0
+     *     public static Class<?> getValueClass(PropertyType type)
      */
-    public static Class<?> getValueClass(PropertyType type) {
-        while (type instanceof Operation) {
-            final IdentifiedType result = ((Operation) type).getResult();
-            if (result != type && result instanceof PropertyType) {
-                type = (PropertyType) result;
-            } else if (result instanceof FeatureType) {
-                return Feature.class;
-            } else {
-                break;
-            }
-        }
-        if (type instanceof AttributeType<?>) {
-            return ((AttributeType<?>) type).getValueClass();
-        } else if (type instanceof FeatureAssociationRole) {
-            return Feature.class;
-        } else {
-            return null;
-        }
-    }
 
     /**
      * Returns the name of the type of values that the given property can take.
-     * The type of value can be a {@link Class}, a {@link org.opengis.feature.FeatureType}
+     * The type of value can be a {@link Class}, a {@code FeatureType}
      * or another {@code PropertyType} depending on given argument:
      *
      * <ul>
-     *   <li>If {@code property} is an {@link AttributeType}, then this method gets the
+     *   <li>If {@code property} is an {@code 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 {@link FeatureAssociationRole}, then this method gets
+     *   <li>If {@code property} is a {@code 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 {@link Operation}, then this method returns the name of the
+     *   <li>If {@code property} is an {@code Operation}, then this method returns the name of the
      *       {@linkplain AbstractOperation#getResult() result type}.</li>
      * </ul>
      *
@@ -192,15 +151,15 @@
      *
      * @since 0.8
      */
-    public static GenericName getValueTypeName(final PropertyType property) {
-        if (property instanceof FeatureAssociationRole) {
+    public static GenericName getValueTypeName(final AbstractIdentifiedType property) {
+        if (property instanceof DefaultAssociationRole) {
             // Tested first because this is the main interest for this method.
-            return DefaultAssociationRole.getValueTypeName((FeatureAssociationRole) property);
-        } else if (property instanceof AttributeType<?>) {
+            return DefaultAssociationRole.getValueTypeName((DefaultAssociationRole) property);
+        } else if (property instanceof DefaultAttributeType<?>) {
             final DefaultNameFactory factory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
-            return factory.toTypeName(((AttributeType<?>) property).getValueClass());
-        } else if (property instanceof Operation) {
-            final IdentifiedType result = ((Operation) property).getResult();
+            return factory.toTypeName(((DefaultAttributeType<?>) property).getValueClass());
+        } else if (property instanceof AbstractOperation) {
+            final AbstractIdentifiedType result = ((AbstractOperation) property).getResult();
             if (result != null) {
                 return result.getName();
             }
@@ -222,24 +181,13 @@
      * {@code InvalidPropertyValueException} is thrown. Otherwise this method returns doing nothing.
      *
      * @param  feature  the feature to validate, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is non-null and does not pass validation.
+     * @throws IllegalArgumentException if the given feature is non-null and does not pass validation.
      *
      * @since 0.7
      */
-    public static void validate(final Feature feature) throws InvalidPropertyValueException {
+    public static void validate(final AbstractFeature feature) throws IllegalArgumentException {
         if (feature != null) {
-            /*
-             * 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;
-            }
+            final DataQuality quality = feature.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 9a6e94f..df704ee 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,12 +20,6 @@
 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;
 
 
@@ -38,7 +32,7 @@
  * @since   0.5
  * @module
  */
-abstract class Field<V> implements Property {
+abstract class Field<V> extends Property {
     /**
      * For subclass constructors.
      */
@@ -60,12 +54,12 @@
      * Returns the field feature or attribute value, or {@code null} if none.
      *
      * @return the feature or attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this field contains more than one value.
+     * @throws IllegalStateException if this field contains more than one value.
      *
      * @see AbstractFeature#getPropertyValue(String)
      */
     @Override
-    public abstract V getValue() throws MultiValuedPropertyException;
+    public abstract V getValue() throws IllegalStateException;
 
     /**
      * Returns all features or attribute values, or an empty collection if none.
@@ -94,16 +88,16 @@
      * then delegates to {@link #setValue(Object)}.</p>
      *
      * @param  values  the new values.
-     * @throws InvalidPropertyValueException if the given collection contains too many elements.
+     * @throws IllegalArgumentException if the given collection contains too many elements.
      */
-    public void setValues(final Collection<? extends V> values) throws InvalidPropertyValueException {
+    public void setValues(final Collection<? extends V> values) throws IllegalArgumentException {
         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 InvalidPropertyValueException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, getName()));
             }
         }
         setValue(value);
@@ -112,7 +106,7 @@
     /**
      * Returns whether the given property is deprecated.
      */
-    static boolean isDeprecated(final PropertyType type) {
+    static boolean isDeprecated(final AbstractIdentifiedType 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 022a8bd..68adf1e 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,9 +21,6 @@
 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 multiplicity.
@@ -39,7 +36,7 @@
  * @since   0.5
  * @module
  */
-abstract class FieldType extends AbstractIdentifiedType implements PropertyType {
+abstract class FieldType extends AbstractIdentifiedType {
     /**
      * 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 6f2b9e8..6a56bdb 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,27 +19,19 @@
 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(Feature)
+ * @see Features#validate(AbstractFeature)
  *
  * @since 0.7
  * @module
  */
-final class InvalidFeatureException extends InvalidPropertyValueException implements LocalizedException {
+final class InvalidFeatureException extends IllegalArgumentException 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 7e9e7fb..4153fa4 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,12 +26,6 @@
 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.
@@ -51,7 +45,7 @@
     /**
      * The type of the result.
      */
-    private final PropertyType result;
+    private final AbstractIdentifiedType result;
 
     /**
      * The name of the referenced attribute or feature association.
@@ -63,10 +57,8 @@
      *
      * @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, PropertyType referent) {
+    LinkOperation(final Map<String,?> identification, AbstractIdentifiedType referent) {
         super(identification);
         if (referent instanceof LinkOperation) {
             referent = ((LinkOperation) referent).result;
@@ -91,7 +83,7 @@
      * Returns the expected result type.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return result;
     }
 
@@ -111,7 +103,7 @@
      * @return the linked property from the given feature.
      */
     @Override
-    public Property apply(final Feature feature, final ParameterValueGroup parameters) {
+    public Object apply(final AbstractFeature 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 ebfe2aa..c6867da 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,12 +21,6 @@
 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.
@@ -60,16 +54,16 @@
     /**
      * The association values.
      */
-    private CheckedArrayList<Feature> values;
+    private CheckedArrayList<AbstractFeature> values;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role Information about the association.
      */
-    public MultiValuedAssociation(final FeatureAssociationRole role) {
+    public MultiValuedAssociation(final DefaultAssociationRole role) {
         super(role);
-        values = new CheckedArrayList<>(Feature.class);
+        values = new CheckedArrayList<>(AbstractFeature.class);
     }
 
     /**
@@ -78,12 +72,12 @@
      * @param role   Information about the association.
      * @param values The initial values, or {@code null} for initializing to an empty list.
      */
-    MultiValuedAssociation(final FeatureAssociationRole role, final Object values) {
+    MultiValuedAssociation(final DefaultAssociationRole role, final Object values) {
         super(role);
         if (values == null) {
-            this.values = new CheckedArrayList<>(Feature.class);
+            this.values = new CheckedArrayList<>(AbstractFeature.class);
         } else {
-            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, Feature.class);
+            this.values = CheckedArrayList.castOrCopy((CheckedArrayList<?>) values, AbstractFeature.class);
         }
     }
 
@@ -91,14 +85,14 @@
      * Returns the feature, or {@code null} if none.
      *
      * @return the feature (may be {@code null}).
-     * @throws MultiValuedPropertyException if this association contains more than one value.
+     * @throws IllegalStateException if this association contains more than one value.
      */
     @Override
-    public Feature getValue() {
+    public AbstractFeature getValue() {
         switch (values.size()) {
             case 0:  return null;
             case 1:  return values.get(0);
-            default: throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
         }
     }
 
@@ -111,7 +105,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Collection<Feature> getValues() {
+    public Collection<AbstractFeature> getValues() {
         return values;
     }
 
@@ -121,7 +115,7 @@
      * @param  value  the new value, or {@code null} for removing all values from this association.
      */
     @Override
-    public void setValue(final Feature value) {
+    public void setValue(final AbstractFeature value) {
         values.clear();
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
@@ -135,12 +129,12 @@
      * @param  newValues  the new values.
      */
     @Override
-    public void setValues(final Collection<? extends Feature> newValues) {
+    public void setValues(final Collection<? extends AbstractFeature> newValues) {
         if (newValues != values) {
             ArgumentChecks.ensureNonNull("values", newValues);  // The parameter name in public API is "values".
-            final FeatureType base = role.getValueType();
+            final DefaultFeatureType base = role.getValueType();
             values.clear();
-            for (final Feature value : newValues) {
+            for (final AbstractFeature value : newValues) {
                 ensureValid(base, value.getType());
                 values.add(value);
             }
@@ -159,7 +153,7 @@
     @SuppressWarnings("unchecked")
     public MultiValuedAssociation clone() throws CloneNotSupportedException {
         final MultiValuedAssociation clone = (MultiValuedAssociation) super.clone();
-        clone.values = (CheckedArrayList<Feature>) clone.values.clone();
+        clone.values = (CheckedArrayList<AbstractFeature>) 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 cf61e49..6d34b5e 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,10 +23,6 @@
 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.
@@ -73,7 +69,7 @@
      *
      * @param  type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public MultiValuedAttribute(final AttributeType<V> type) {
+    public MultiValuedAttribute(final DefaultAttributeType<V> type) {
         super(type);
         values = new CheckedArrayList<>(type.getValueClass());
         final V value = type.getDefaultValue();
@@ -90,7 +86,7 @@
      * @param  values  the initial values, or {@code null} for initializing to an empty list.
      */
     @SuppressWarnings("unchecked")
-    MultiValuedAttribute(final AttributeType<V> type, final Object values) {
+    MultiValuedAttribute(final DefaultAttributeType<V> type, final Object values) {
         super(type);
         final Class<V> valueClass = type.getValueClass();
         if (values == null) {
@@ -109,14 +105,14 @@
      * Returns the attribute value, or {@code null} if none.
      *
      * @return the attribute value (may be {@code null}).
-     * @throws MultiValuedPropertyException if this attribute contains more than one value.
+     * @throws IllegalStateException 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 MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, getName()));
+            default: throw new IllegalStateException(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 f25f109..b260dc9 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,19 +16,11 @@
  */
 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.util.Strings;
-import org.apache.sis.internal.feature.Resources;
 
 
 /**
@@ -76,51 +68,10 @@
     }
 
     /**
-     * Undefined.
-     */
-    @Override
-    public InternationalString getDefinition() {
-        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<? 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() {
+    public Collection<AbstractIdentifiedType> getProperties(final boolean includeSuperTypes) {
         return Collections.emptySet();
     }
 
@@ -128,26 +79,8 @@
      * This feature type is considered independent of all other feature types except itself.
      */
     @Override
-    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()));
+    public boolean isAssignableFrom(final DefaultFeatureType type) {
+        return false;
     }
 
     /**
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
new file mode 100644
index 0000000..00b4ff3
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Property.java
@@ -0,0 +1,38 @@
+/*
+ * 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 c8e414b..bffabc3 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,21 +21,12 @@
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Collections;
-import java.io.Serializable;
+import org.opengis.util.GenericName;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Classes;
 import org.apache.sis.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.
@@ -51,32 +42,11 @@
  * @since   0.8
  * @module
  */
-abstract class PropertyView<V> extends Field<V> implements Property, Serializable {
+final class PropertyView {
     /**
-     * For cross-version compatibility.
+     * Do not allow instantiation of this class.
      */
-    private static final long serialVersionUID = -5605415150581699255L;
-
-    /**
-     * 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.
-     */
-    PropertyView(final Feature feature, final String name) {
-        this.feature = feature;
-        this.name = name;
+    private PropertyView() {
     }
 
     /**
@@ -86,32 +56,26 @@
      * @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 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);
+    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);
         } 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 {@link Feature#getPropertyValue(String)} method contract. Then this method verifies that
+     * to our {@code 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}.
      */
-    @Override
-    public V getValue() throws MultiValuedPropertyException {
+    static Object getValue(final AbstractFeature feature, final String name) {
         Object value = feature.getPropertyValue(name);
         if (value instanceof Iterable<?>) {
             final Iterator<?> it = ((Iterable<?>) value).iterator();
@@ -120,19 +84,18 @@
             }
             value = it.next();
             if (it.hasNext()) {
-                throw new MultiValuedPropertyException(Resources.format(Resources.Keys.NotASingleton_1, name));
+                throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, name));
             }
         }
-        return getValueClass().cast(value);
+        return 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
-     * {@link Feature#setPropertyValue(String, Object)} implementation will verify the argument type.
+     * {@code Feature.setPropertyValue(String, Object)} implementation will verify the argument type.
      */
-    @Override
-    public void setValue(final V value) {
+    static void setValue(final AbstractFeature feature, final String name, final Object value) {
         feature.setPropertyValue(name, singletonOrEmpty(value));
     }
 
@@ -152,13 +115,11 @@
      * 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.
-    public Collection<V> getValues() {
+    static <V> Collection<V> getValues(final AbstractFeature feature, final String name, final Class<V> expected) {
         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));
@@ -166,49 +127,31 @@
             }
             return (Collection<V>) values;
         } else {
-            return singletonOrEmpty(getValueClass().cast(values));
+            return singletonOrEmpty(expected.cast(values));
         }
     }
 
     /**
      * Sets the values of the given attribute. This method assumes that the
-     * {@link Feature#setPropertyValue(String, Object)} implementation will
+     * {@code Feature.setPropertyValue(String, Object)} implementation will
      * verify the argument type.
      */
-    @Override
-    public final void setValues(final Collection<? extends V> values) {
+    static void setValues(final AbstractFeature feature, final String name, final Collection<?> values) {
         feature.setPropertyValue(name, values);
     }
 
     /**
      * Returns a hash code value for this property.
      */
-    @Override
-    public final int hashCode() {
+    static int hashCode(final AbstractFeature feature, final String name) {
         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.
      */
-    @Override
-    public final String toString() {
-        return FieldType.toString(false, getClass().getSimpleName(), getName(),
-                Classes.getShortName(getValueClass()), getValues().iterator()).toString();
+    static String toString(final Class<?> classe, final Class<?> valueClass, final GenericName name, final Collection<?> values) {
+        return FieldType.toString(false, classe.getSimpleName(), name,
+                Classes.getShortName(valueClass), values.iterator()).toString();
     }
 }
diff --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 a037e34..17bb4f3 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,11 +18,6 @@
 
 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.
@@ -54,14 +49,14 @@
     /**
      * The associated feature.
      */
-    private Feature value;
+    private AbstractFeature value;
 
     /**
      * Creates a new association of the given role.
      *
      * @param role  information about the association.
      */
-    public SingletonAssociation(final FeatureAssociationRole role) {
+    public SingletonAssociation(final DefaultAssociationRole role) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
     }
@@ -72,7 +67,7 @@
      * @param role   information about the association.
      * @param value  the initial value (may be {@code null}).
      */
-    SingletonAssociation(final FeatureAssociationRole role, final Feature value) {
+    SingletonAssociation(final DefaultAssociationRole role, final AbstractFeature value) {
         super(role);
         assert isSingleton(role.getMaximumOccurs());
         this.value = value;
@@ -87,7 +82,7 @@
      * @return the associated feature (may be {@code null}).
      */
     @Override
-    public Feature getValue() {
+    public AbstractFeature getValue() {
         return value;
     }
 
@@ -95,10 +90,10 @@
      * Sets the associated feature.
      *
      * @param  value  the new value, or {@code null}.
-     * @throws InvalidPropertyValueException if the given feature is not valid for this association.
+     * @throws IllegalArgumentException if the given feature is not valid for this association.
      */
     @Override
-    public void setValue(final Feature value) throws InvalidPropertyValueException {
+    public void setValue(final AbstractFeature value) throws IllegalArgumentException {
         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 8b3b073..a088473 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,7 +19,6 @@
 import java.util.Objects;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
 
 
 /**
@@ -65,7 +64,7 @@
      *
      * @param type  information about the attribute (base Java class, domain of values, <i>etc.</i>).
      */
-    public SingletonAttribute(final AttributeType<V> type) {
+    public SingletonAttribute(final DefaultAttributeType<V> type) {
         super(type);
         assert isSingleton(type.getMaximumOccurs());
         value = type.getDefaultValue();
@@ -78,7 +77,7 @@
      * @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 AttributeType<V> type, final Object value) {
+    SingletonAttribute(final DefaultAttributeType<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 59562b1..fbe19e8 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,12 +26,6 @@
 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
@@ -120,14 +114,14 @@
      * @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 PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
-    private int getIndex(final String name) throws PropertyNotFoundException {
+    private int getIndex(final String name) throws IllegalArgumentException {
         final Integer index = indices.get(name);
         if (index != null) {
             return index;
         }
-        throw new PropertyNotFoundException(propertyNotFound(type, getName(), name));
+        throw new IllegalArgumentException(propertyNotFound(type, getName(), name));
     }
 
     /**
@@ -172,10 +166,10 @@
      *
      * @param  name  the property name.
      * @return the property of the given name.
-     * @throws PropertyNotFoundException if the given argument is not a property name of this feature.
+     * @throws IllegalArgumentException if the given argument is not a property name of this feature.
      */
     @Override
-    public Property getProperty(final String name) throws PropertyNotFoundException {
+    public Object getProperty(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         requireMapOfProperties();
         return getPropertyInstance(name);
@@ -185,11 +179,11 @@
      * 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 PropertyNotFoundException {
+    private Property getPropertyInstance(final String name) throws IllegalArgumentException {
         assert valuesKind == PROPERTIES : valuesKind;
         final Integer index = getIndex(name);
         if (index < 0) {
-            return getOperationResult(name);
+            return (Property) getOperationResult(name);
         }
         Property property = (Property) properties.get(index);
         if (property == null) {
@@ -207,10 +201,10 @@
      *         known to this feature, or if the property can not be set for another reason.
      */
     @Override
-    public void setProperty(final Property property) throws IllegalArgumentException {
+    public void setProperty(final Object property) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("property", property);
-        final String name = property.getName().toString();
-        verifyPropertyType(name, property);
+        final String name = ((Property) property).getName().toString();
+        verifyPropertyType(name, (Property) property);
         requireMapOfProperties();
         /*
          * Following index should never be OPERATION_INDEX (a negative value) because the call
@@ -224,10 +218,10 @@
      *
      * @param  name  the property name.
      * @return the value for the given property, or {@code null} if none.
-     * @throws PropertyNotFoundException if the given argument is not an attribute or association name of this feature.
+     * @throws IllegalArgumentException if the given argument is not an attribute or association name of this feature.
      */
     @Override
-    public Object getPropertyValue(final String name) throws PropertyNotFoundException {
+    public Object getPropertyValue(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         final Integer index = getIndex(name);
         if (index < 0) {
@@ -237,10 +231,10 @@
         if (element != null) {
             if (valuesKind == VALUES) {
                 return element; // Most common case.
-            } else if (element instanceof Attribute<?>) {
-                return getAttributeValue((Attribute<?>) element);
-            } else if (element instanceof FeatureAssociation) {
-                return getAssociationValue((FeatureAssociation) element);
+            } else if (element instanceof AbstractAttribute<?>) {
+                return getAttributeValue((AbstractAttribute<?>) element);
+            } else if (element instanceof AbstractAssociation) {
+                return getAssociationValue((AbstractAssociation) element);
             } else if (valuesKind == PROPERTIES) {
                 throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
             } else {
@@ -373,10 +367,10 @@
             for (final Map.Entry<Integer,Object> entry : properties.entrySet()) {
                 final Object p = entry.getValue();
                 final Object value;
-                if (p instanceof Attribute<?>) {
-                    value = getAttributeValue((Attribute<?>) p);
-                } else if (p instanceof FeatureAssociation) {
-                    value = getAssociationValue((FeatureAssociation) p);
+                if (p instanceof AbstractAttribute<?>) {
+                    value = getAttributeValue((AbstractAttribute<?>) p);
+                } else if (p instanceof AbstractAssociation) {
+                    value = getAssociationValue((AbstractAssociation) 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 29bf49d..b64bf6a 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,18 +38,6 @@
 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.
@@ -126,7 +114,7 @@
         @Override public Class<Object>                  getTargetClass() {return Object.class;}
         @Override public Object apply(final Object f) {
             return (f != null) ? format(converter.inverse(),
-                    ((Feature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
+                    ((AbstractFeature) f).getPropertyValue(AttributeConvention.IDENTIFIER)) : null;
         }
     }
 
@@ -155,7 +143,7 @@
     /**
      * The type of the result returned by the string concatenation operation.
      */
-    private final AttributeType<String> resultType;
+    private final DefaultAttributeType<String> resultType;
 
     /**
      * The characters to use at the beginning of the concatenated string, or an empty string if none.
@@ -177,11 +165,11 @@
      * 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, PropertyType...)
+     * @see FeatureOperations#compound(Map, String, String, String, AbstractIdentifiedType...)
      */
     @SuppressWarnings({"rawtypes", "unchecked"})                                        // Generic array creation.
     StringJoinOperation(final Map<String,?> identification, final String delimiter,
-            final String prefix, final String suffix, final PropertyType[] singleAttributes)
+            final String prefix, final String suffix, final AbstractIdentifiedType[] singleAttributes)
             throws UnconvertibleObjectException
     {
         super(identification);
@@ -200,29 +188,29 @@
              * which may in turn produce an AttributeType. We do not accept more complex
              * combinations (e.g. operation producing an association).
              */
-            IdentifiedType propertyType = singleAttributes[i];
+            AbstractIdentifiedType 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.
-            PropertyNotFoundException cause = null;             // In case of failure to find "sis:identifier" property.
-            final boolean isAssociation = (propertyType instanceof FeatureAssociationRole);
+            IllegalArgumentException cause = null;              // In case of failure to find "sis:identifier" property.
+            final boolean isAssociation = (propertyType instanceof DefaultAssociationRole);
             if (isAssociation) {
-                final FeatureAssociationRole role = (FeatureAssociationRole) propertyType;
-                final FeatureType ft = role.getValueType();
+                final DefaultAssociationRole role = (DefaultAssociationRole) propertyType;
+                final DefaultFeatureType ft = role.getValueType();
                 maximumOccurs = role.getMaximumOccurs();
                 try {
                     propertyType = ft.getProperty(AttributeConvention.IDENTIFIER);
-                } catch (PropertyNotFoundException e) {
+                } catch (IllegalArgumentException e) {
                     cause = e;
                 }
             }
-            if (propertyType instanceof Operation) {
-                propertyType = ((Operation) propertyType).getResult();
+            if (propertyType instanceof AbstractOperation) {
+                propertyType = ((AbstractOperation) propertyType).getResult();
             }
-            if (propertyType instanceof AttributeType) {
-                maximumOccurs |= ((AttributeType<?>) propertyType).getMaximumOccurs();
+            if (propertyType instanceof DefaultAttributeType) {
+                maximumOccurs |= ((DefaultAttributeType<?>) propertyType).getMaximumOccurs();
             } else {
-                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), PropertyType.class);
+                final Class<?>[] inf = Classes.getLeafInterfaces(Classes.getClass(propertyType), AbstractIdentifiedType.class);
                 throw new IllegalArgumentException(Resources.forProperties(identification)
                         .getString(Resources.Keys.IllegalPropertyType_2, name, (inf.length != 0) ? inf[0] : null), cause);
             }
@@ -236,7 +224,7 @@
              */
             attributeNames[i] = name.toString();
             ObjectConverter<? super String, ?> converter = ObjectConverters.find(
-                    String.class, ((AttributeType<?>) propertyType).getValueClass());
+                    String.class, ((DefaultAttributeType<?>) propertyType).getValueClass());
             if (isAssociation) {
                 converter = new ForFeature(converter);
             }
@@ -267,7 +255,7 @@
      * @return an {@code AttributeType<String>}.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return resultType;
     }
 
@@ -303,7 +291,7 @@
      * @return the concatenation of feature property values.
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Property apply(AbstractFeature feature, ParameterValueGroup parameters) {
         ArgumentChecks.ensureNonNull("feature", feature);
         return new Result(feature);
     }
@@ -322,14 +310,14 @@
         private static final long serialVersionUID = -8435975199763452547L;
 
         /**
-         * The feature specified to the {@link StringJoinOperation#apply(Feature, ParameterValueGroup)} method.
+         * The feature specified to the {@link StringJoinOperation#apply(AbstractFeature, ParameterValueGroup)} method.
          */
-        private final Feature feature;
+        private final AbstractFeature feature;
 
         /**
          * Creates a new attribute for the given feature.
          */
-        Result(final Feature feature) {
+        Result(final AbstractFeature feature) {
             super(resultType);
             this.feature = feature;
         }
@@ -389,14 +377,14 @@
          * parsed, then this method does not store any property value ("all or nothing" behavior).
          *
          * @param  value  the concatenated string.
-         * @throws InvalidPropertyValueException if one of the attribute values can not be parsed to the expected type.
+         * @throws IllegalArgumentException if one of the attribute values can not be parsed to the expected type.
          */
         @Override
-        public void setValue(final String value) throws InvalidPropertyValueException {
+        public void setValue(final String value) throws IllegalArgumentException {
             final int endAt = value.length() - suffix.length();
             final boolean prefixMatches = value.startsWith(prefix);
             if (!prefixMatches || !value.endsWith(suffix)) {
-                throw new InvalidPropertyValueException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedCharactersAtBound_4,
                         getName(),
                         prefixMatches ? 1 : 0,              // For "{1,choice,0#begin|1#end}" in message format.
                         prefixMatches ? suffix : prefix,
@@ -463,7 +451,7 @@
                     try {
                         values[count] = converter.apply(element);
                     } catch (UnconvertibleObjectException e) {
-                        throw new InvalidPropertyValueException(Errors.format(
+                        throw new IllegalArgumentException(Errors.format(
                                 Errors.Keys.CanNotAssign_2, attributeNames[count], element), e);
                     }
                 }
@@ -477,14 +465,14 @@
              * below do not fail).
              */
             if (values.length != count) {
-                throw new InvalidPropertyValueException(Resources.format(
+                throw new IllegalArgumentException(Resources.format(
                         Resources.Keys.UnexpectedNumberOfComponents_4, getName(), value, values.length, count));
             }
             for (int i=0; i < values.length; i++) {
-                Feature f   = feature;
+                AbstractFeature f = feature;
                 String name = attributeNames[i];
                 if (converters[i] instanceof ForFeature) {
-                    f = (Feature) f.getPropertyValue(name);
+                    f = (AbstractFeature) 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 20697e8..bb8933e 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
@@ -32,16 +32,6 @@
 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.
@@ -88,7 +78,7 @@
      * @return the {@code report}, or a new report if {@code report} was null.
      */
     private AbstractElement addViolationReport(AbstractElement report,
-            final PropertyType type, final InternationalString explanation)
+            final AbstractIdentifiedType type, final InternationalString explanation)
     {
         if (report == null) {
             final GenericName name = type.getName();
@@ -119,19 +109,19 @@
      * @param type     the type of the {@code feature} argument, provided explicitly for protecting from user overriding.
      * @param feature  the feature to validate.
      */
-    void validate(final FeatureType type, final Feature feature) {
-        for (final PropertyType pt : type.getProperties(true)) {
-            final Property property = feature.getProperty(pt.getName().toString());
+    void validate(final FeatureType type, final AbstractFeature feature) {
+        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+            final Object 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 Attribute<?>) {
-                validate(((Attribute<?>) property).getType(), ((Attribute<?>) property).getValues());
+            } else if (property instanceof AbstractAttribute<?>) {
+                validate(((AbstractAttribute<?>) property).getType(), ((AbstractAttribute<?>) property).getValues());
                 continue;
-            } else if (property instanceof FeatureAssociation) {
-                validate(((FeatureAssociation) property).getRole(), ((FeatureAssociation) property).getValues());
+            } else if (property instanceof AbstractAssociation) {
+                validate(((AbstractAssociation) property).getRole(), ((AbstractAssociation) property).getValues());
                 continue;
             } else {
                 continue;
@@ -146,21 +136,21 @@
      * 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 PropertyType type, final Object value) {
-        if (type instanceof AttributeType<?>) {
-            validate((AttributeType<?>) type, asList(value,
-                    ((AttributeType<?>) type).getMaximumOccurs()));
+    void validateAny(final AbstractIdentifiedType type, final Object value) {
+        if (type instanceof DefaultAttributeType<?>) {
+            validate((DefaultAttributeType<?>) type, asList(value,
+                    ((DefaultAttributeType<?>) type).getMaximumOccurs()));
         }
-        if (type instanceof FeatureAssociationRole) {
-            validate((FeatureAssociationRole) type, asList(value,
-                    ((FeatureAssociationRole) type).getMaximumOccurs()));
+        if (type instanceof DefaultAssociationRole) {
+            validate((DefaultAssociationRole) type, asList(value,
+                    ((DefaultAssociationRole) type).getMaximumOccurs()));
         }
     }
 
     /**
      * Verifies if the given values are valid for the given attribute type.
      */
-    void validate(final AttributeType<?> type, final Collection<?> values) {
+    void validate(final DefaultAttributeType<?> type, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
             /*
@@ -183,10 +173,10 @@
     /**
      * Verifies if the given value is valid for the given association role.
      */
-    void validate(final FeatureAssociationRole role, final Collection<?> values) {
+    void validate(final DefaultAssociationRole role, final Collection<?> values) {
         AbstractElement report = null;
         for (final Object value : values) {
-            final FeatureType type = ((Feature) value).getType();
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             final FeatureType valueType = role.getValueType();
             if (!valueType.isAssignableFrom(type)) {
                 report = addViolationReport(report, role, Errors.formatInternational(
@@ -204,7 +194,7 @@
      *
      * @param report  where to add the result, or {@code null} if not yet created.
      */
-    private void verifyCardinality(final AbstractElement report, final PropertyType type,
+    private void verifyCardinality(final AbstractElement report, final AbstractIdentifiedType 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 92b9cca..3094b1a 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,22 +21,21 @@
 import org.apache.sis.feature.DefaultAssociationRole;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
  * 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 {@link FeatureTypeBuilder#addAssociation(FeatureType)},
- * or in case of cyclic reference by {@link FeatureTypeBuilder#addAssociation(GenericName)}.
+ * to describe. Those instances are created preferably by {@code FeatureTypeBuilder.addAssociation(FeatureType)},
+ * or in case of cyclic reference by {@code FeatureTypeBuilder.addAssociation(GenericName)}.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
  * @see org.apache.sis.feature.DefaultAssociationRole
- * @see FeatureTypeBuilder#addAssociation(FeatureType)
+ * @see FeatureTypeBuilder#addAssociation(DefaultFeatureType)
  * @see FeatureTypeBuilder#addAssociation(GenericName)
  *
  * @since 0.8
@@ -46,7 +45,7 @@
     /**
      * The target feature type, or {@code null} if unknown.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * Name of the target feature type (never null).
@@ -57,7 +56,7 @@
      * 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 FeatureAssociationRole property;
+    private transient DefaultAssociationRole property;
 
     /**
      * Creates a new {@code AssociationRole} builder for values of the given type.
@@ -65,7 +64,7 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureType type, final GenericName typeName) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultFeatureType type, final GenericName typeName) {
         super(owner);
         this.type     = type;
         this.typeName = typeName;
@@ -76,12 +75,12 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureAssociationRole template) {
+    AssociationRoleBuilder(final FeatureTypeBuilder owner, final DefaultAssociationRole template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
-        if (template instanceof DefaultAssociationRole && !((DefaultAssociationRole) template).isResolved()) {
+        if (!template.isResolved()) {
             type     = null;
             typeName = Features.getValueTypeName(template);
         } else {
@@ -249,10 +248,13 @@
      * 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 FeatureAssociationRole build() {
+    public DefaultAssociationRole 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 3b5f450..905d4c7 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,7 +40,6 @@
 import org.apache.sis.util.UnconvertibleObjectException;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
 
 
 /**
@@ -104,7 +103,7 @@
      * 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 AttributeType<V> property;
+    private transient DefaultAttributeType<V> property;
 
     /**
      * Creates a new builder initialized to the values of the given builder.
@@ -139,16 +138,16 @@
      *
      * @param owner  the builder of the {@code FeatureType} for which to add the attribute.
      */
-    AttributeTypeBuilder(final FeatureTypeBuilder owner, final AttributeType<V> template) {
+    AttributeTypeBuilder(final FeatureTypeBuilder owner, final DefaultAttributeType<V> template) {
         super(owner);
         property      = template;
         minimumOccurs = template.getMinimumOccurs();
         maximumOccurs = template.getMaximumOccurs();
         valueClass    = template.getValueClass();
         defaultValue  = template.getDefaultValue();
-        final Map<String, AttributeType<?>> tc = template.characteristics();
+        final Map<String, DefaultAttributeType<?>> tc = template.characteristics();
         characteristics = new ArrayList<>(tc.size());
-        for (final AttributeType<?> c : tc.values()) {
+        for (final DefaultAttributeType<?> c : tc.values()) {
             characteristics.add(new CharacteristicTypeBuilder<>(this, c));
         }
         initialize(template);
@@ -494,13 +493,17 @@
      * 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 AttributeType<C> template) {
+    public <C> CharacteristicTypeBuilder<C> addCharacteristic(final DefaultAttributeType<C> template) {
         ensureNonNull("template", template);
         final CharacteristicTypeBuilder<C> characteristic = new CharacteristicTypeBuilder<>(this, template);
         characteristics.add(characteristic);
@@ -518,7 +521,7 @@
      *
      * @see #getCharacteristic(String)
      * @see #addCharacteristic(Class)
-     * @see #addCharacteristic(AttributeType)
+     * @see #addCharacteristic(DefaultAttributeType)
      * @see #setValidValues(Object...)
      * @see #setCRS(CoordinateReferenceSystem)
      */
@@ -721,12 +724,15 @@
      * 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 AttributeType<V> build() {
+    public DefaultAttributeType<V> build() {
         if (property == null) {
-            final AttributeType<?>[] chrts = new AttributeType<?>[characteristics.size()];
+            final DefaultAttributeType<?>[] chrts = new DefaultAttributeType<?>[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 45c316f..35d380c 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,7 +24,6 @@
 import org.apache.sis.util.UnconvertibleObjectException;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
 
 
 /**
@@ -70,7 +69,7 @@
      * 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 AttributeType<V> characteristic;
+    private transient DefaultAttributeType<V> characteristic;
 
     /**
      * Creates a new builder initialized to the values of the given builder but a different type.
@@ -105,7 +104,7 @@
      *
      * @param owner  the builder of the {@code AttributeType} for which to add this property.
      */
-    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final AttributeType<V> template) {
+    CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final DefaultAttributeType<V> template) {
         super(owner.getLocale());
         this.owner     = owner;
         characteristic = template;
@@ -321,10 +320,13 @@
      * 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 AttributeType<V> build() {
+    public DefaultAttributeType<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 29ed49a..9763b20 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,16 +43,14 @@
 import org.apache.sis.util.ArraysExt;
 
 // Branch-dependent imports
-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;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAssociationRole;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
- * Helper class for the creation of {@link FeatureType} instances.
+ * Helper class for the creation of {@code 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.
@@ -66,7 +64,7 @@
  *       and whether the feature type is {@linkplain #setAbstract abstract}.</li>
  *   <li>Convenience methods for setting the {@linkplain #setNameSpace name space} and the
  *       {@linkplain #setDefaultMultiplicity default multiplicity} of properties to be added to the feature type.</li>
- *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(FeatureType)
+ *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(DefaultFeatureType)
  *       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>
@@ -122,7 +120,7 @@
     /**
      * The parent of the feature to create. By default, new features have no parent.
      */
-    private final List<FeatureType> superTypes;
+    private final List<DefaultFeatureType> superTypes;
 
     /**
      * Whether the feature type is abstract. The default value is {@code false}.
@@ -191,7 +189,7 @@
      * 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 FeatureType feature;
+    private transient DefaultFeatureType feature;
 
     /**
      * Creates a new builder instance using the default name factory.
@@ -207,11 +205,13 @@
      * to values inferred from the given template. The properties list will contain properties
      * declared explicitly in the given template, not including properties inherited from super types.
      *
-     * @param template  an existing feature type to use as a template, or {@code null} if none.
+     * <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>
      *
-     * @see #setAll(FeatureType)
+     * @param template  an existing feature type to use as a template, or {@code null} if none.
      */
-    public FeatureTypeBuilder(final FeatureType template) {
+    public FeatureTypeBuilder(final DefaultFeatureType template) {
         this(null, null, null);
         if (template != null) {
             initialize(template);
@@ -267,14 +267,16 @@
     /**
      * 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(FeatureType) constructor}.
+     * 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>
      *
      * @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 FeatureType template) {
+    public FeatureTypeBuilder setAll(final DefaultFeatureType template) {
         clear();
         if (template != null) {
             initialize(template);
@@ -285,10 +287,8 @@
     /**
      * 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 FeatureType template) {
+    private void initialize(final DefaultFeatureType template) {
         super.initialize(template);
         feature    = template;
         isAbstract = template.isAbstract();
@@ -299,12 +299,12 @@
          * is not one of the operations automatically generated by this builder.
          */
         final Map<String,Set<AttributeRole>> propertyRoles = new HashMap<>();
-        for (final PropertyType property : template.getProperties(false)) {
+        for (final AbstractIdentifiedType property : template.getProperties(false)) {
             PropertyTypeBuilder builder;
-            if (property instanceof AttributeType<?>) {
-                builder = new AttributeTypeBuilder<>(this, (AttributeType<?>) property);
-            } else if (property instanceof FeatureAssociationRole) {
-                builder = new AssociationRoleBuilder(this, (FeatureAssociationRole) property);
+            if (property instanceof DefaultAttributeType<?>) {
+                builder = new AttributeTypeBuilder<>(this, (DefaultAttributeType<?>) property);
+            } else if (property instanceof DefaultAssociationRole) {
+                builder = new AssociationRoleBuilder(this, (DefaultAssociationRole) property);
             } else {
                 builder = null;                             // Do not create OperationWrapper now - see below.
             }
@@ -402,25 +402,33 @@
     /**
      * 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 FeatureType[] getSuperTypes() {
-        return superTypes.toArray(new FeatureType[superTypes.size()]);
+    public DefaultFeatureType[] getSuperTypes() {
+        return superTypes.toArray(new DefaultFeatureType[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 FeatureType... parents) {
+    public FeatureTypeBuilder setSuperTypes(final DefaultFeatureType... parents) {
         ensureNonNull("parents", parents);
-        final List<FeatureType> asList = Arrays.asList(parents);
+        final List<DefaultFeatureType> asList = Arrays.asList(parents);
         if (!superTypes.equals(asList)) {
             superTypes.clear();
             superTypes.addAll(asList);
@@ -593,7 +601,6 @@
      * @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);
@@ -619,10 +626,10 @@
      *
      * @see #getProperty(String)
      * @see #addAttribute(Class)
-     * @see #addAttribute(AttributeType)
-     * @see #addAssociation(FeatureType)
+     * @see #addAttribute(DefaultAttributeType)
+     * @see #addAssociation(DefaultFeatureType)
      * @see #addAssociation(GenericName)
-     * @see #addAssociation(FeatureAssociationRole)
+     * @see #addAssociation(DefaultAssociationRole)
      */
     public List<PropertyTypeBuilder> properties() {
         return new RemoveOnlyList<>(properties);
@@ -650,8 +657,6 @@
      * @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, true);
@@ -668,7 +673,7 @@
      * }
      *
      * The value class can not be {@code Feature.class} since features shall be handled
-     * as {@linkplain #addAssociation(FeatureType) associations} instead than attributes.
+     * as {@linkplain #addAssociation(DefaultFeatureType) 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}).
@@ -678,7 +683,7 @@
      */
     public <V> AttributeTypeBuilder<V> addAttribute(final Class<V> valueClass) {
         ensureNonNull("valueClass", valueClass);
-        if (Feature.class.isAssignableFrom(valueClass)) {
+        if (AbstractFeature.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));
         }
@@ -691,13 +696,17 @@
     /**
      * 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 AttributeType<V> template) {
+    public <V> AttributeTypeBuilder<V> addAttribute(final DefaultAttributeType<V> template) {
         ensureNonNull("template", template);
         final AttributeTypeBuilder<V> property = new AttributeTypeBuilder<>(this, template);
         properties.add(property);
@@ -760,12 +769,16 @@
      * 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 FeatureType type) {
+    public AssociationRoleBuilder addAssociation(final DefaultFeatureType type) {
         ensureNonNull("type", type);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, type, type.getName());
         properties.add(property);
@@ -775,7 +788,7 @@
 
     /**
      * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
-     * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
+     * This method can be invoked as an alternative to {@code 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.
@@ -795,12 +808,16 @@
      * 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 FeatureAssociationRole template) {
+    public AssociationRoleBuilder addAssociation(final DefaultAssociationRole template) {
         ensureNonNull("template", template);
         final AssociationRoleBuilder property = new AssociationRoleBuilder(this, template);
         properties.add(property);
@@ -812,12 +829,15 @@
      * Adds the given property in the feature type properties.
      * The given property shall be an instance of one of the following types:
      * <ul>
-     *   <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};
+     *   <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};
      *       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.
@@ -825,12 +845,12 @@
      * @see #properties()
      * @see #getProperty(String)
      */
-    public PropertyTypeBuilder addProperty(final PropertyType template) {
+    public PropertyTypeBuilder addProperty(final AbstractIdentifiedType template) {
         ensureNonNull("template", template);
-        if (template instanceof AttributeType<?>) {
-            return addAttribute((AttributeType<?>) template);
-        } else if (template instanceof FeatureAssociationRole) {
-            return addAssociation((FeatureAssociationRole) template);
+        if (template instanceof DefaultAttributeType<?>) {
+            return addAttribute((DefaultAttributeType<?>) template);
+        } else if (template instanceof DefaultAssociationRole) {
+            return addAssociation((DefaultAssociationRole) template);
         } else {
             final PropertyTypeBuilder property = new OperationWrapper(this, template);
             properties.add(property);
@@ -880,6 +900,9 @@
      * 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>
      *
@@ -889,7 +912,7 @@
      * @see #clear()
      */
     @Override
-    public FeatureType build() throws IllegalStateException {
+    public DefaultFeatureType build() throws IllegalStateException {
         if (feature == null) {
             /*
              * Creates an initial array of property types with up to 3 slots reserved for sis:identifier, sis:geometry
@@ -900,13 +923,13 @@
             int numSynthetic;                               // Number of synthetic properties that may be generated.
             int envelopeIndex = -1;
             int geometryIndex = -1;
-            final PropertyType[] identifierTypes;
+            final AbstractIdentifiedType[] identifierTypes;
             if (identifierCount == 0) {
                 numSynthetic    = 0;
                 identifierTypes = null;
             } else {
                 numSynthetic    = 1;
-                identifierTypes = new PropertyType[identifierCount];
+                identifierTypes = new AbstractIdentifiedType[identifierCount];
             }
             if (defaultGeometry != null) {
                 envelopeIndex = numSynthetic++;
@@ -914,12 +937,12 @@
                     geometryIndex = numSynthetic++;
                 }
             }
-            final PropertyType[] propertyTypes = new PropertyType[numSynthetic + numSpecified];
+            final AbstractIdentifiedType[] propertyTypes = new AbstractIdentifiedType[numSynthetic + numSpecified];
             int propertyCursor = numSynthetic;
             int identifierCursor = 0;
             for (int i=0; i<numSpecified; i++) {
                 final PropertyTypeBuilder builder = properties.get(i);
-                final PropertyType instance = builder.build();
+                final AbstractIdentifiedType instance = builder.build();
                 propertyTypes[propertyCursor] = instance;
                 /*
                  * Collect the attributes to use as identifier components while we loop over all properties.
@@ -981,7 +1004,7 @@
                 }
             }
             feature = new DefaultFeatureType(identification(), isAbstract(),
-                    superTypes.toArray(new FeatureType[superTypes.size()]),
+                    superTypes.toArray(new DefaultFeatureType[superTypes.size()]),
                     ArraysExt.resize(propertyTypes, propertyCursor));
         }
         return feature;
@@ -1025,7 +1048,7 @@
             buffer.insert(buffer.indexOf("[") + 1, "abstract ");
         }
         String separator = " : ";
-        for (final FeatureType parent : superTypes) {
+        for (final DefaultFeatureType 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 1858944..91d5d92 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.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -37,12 +37,12 @@
     /**
      * The wrapped operation.
      */
-    private final PropertyType operation;
+    private final AbstractIdentifiedType operation;
 
     /**
      * Creates a new wrapper for the given operation.
      */
-    OperationWrapper(final FeatureTypeBuilder owner, final PropertyType operation) {
+    OperationWrapper(final FeatureTypeBuilder owner, final AbstractIdentifiedType operation) {
         super(owner);
         this.operation = operation;
         minimumOccurs = 1;
@@ -54,7 +54,7 @@
      * Returns the wrapped operation.
      */
     @Override
-    public PropertyType build() {
+    public AbstractIdentifiedType 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 40515b1..0f94ed5 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,10 +20,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -33,10 +30,10 @@
  *
  * <ul>
  *   <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
- *   <li>{@link FeatureTypeBuilder#addAttribute(AttributeType)} for using an existing attribute as a template</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
+ *   <li>{@link FeatureTypeBuilder#addAttribute(DefaultAttributeType)} for using an existing attribute as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultFeatureType)}</li>
  *   <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
- *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureAssociationRole)} for using an existing association as a template</li>
+ *   <li>{@link FeatureTypeBuilder#addAssociation(DefaultAssociationRole)} for using an existing association as a template</li>
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
@@ -290,11 +287,14 @@
      * 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 PropertyType build() throws IllegalStateException;
+    public abstract AbstractIdentifiedType 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 468997f..45c54f4 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
@@ -33,10 +33,6 @@
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.Classes;
 
-// Branch-dependent imports
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyNotFoundException;
-
 
 /**
  * Information common to all kind of types (feature, association, characteristics).
@@ -120,7 +116,7 @@
      * 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 IdentifiedType template) {
+    final void initialize(final AbstractIdentifiedType template) {
         putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
         putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
         putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
@@ -459,7 +455,7 @@
             }
         }
         if (ambiguity != null && nonAmbiguous) {
-            throw new PropertyNotFoundException(errors().getString(
+            throw new IllegalArgumentException(errors().getString(
                     Errors.Keys.AmbiguousName_3, best.getName(), ambiguity.getName(), name));
         }
         return best;
@@ -571,8 +567,11 @@
      * 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 IdentifiedType build() throws IllegalStateException;
+    public abstract AbstractIdentifiedType build() throws IllegalStateException;
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
deleted file mode 100644
index 617849b..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
+++ /dev/null
@@ -1,281 +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.filter;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import org.apache.sis.util.Numbers;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.internal.util.Numerics;
-import org.apache.sis.math.Fraction;
-
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.expression.BinaryExpression;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
-
-
-/**
- * Arithmetic operations between two numerical values.
- * The nature of the operation depends on the subclass.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpression, FeatureExpression {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 2818625862630588268L;
-
-    /**
-     * Creates a new arithmetic function.
-     */
-    ArithmeticFunction(final Expression expression1, final Expression expression2) {
-        super(expression1, expression2);
-    }
-
-    /**
-     * Creates an attribute type for numeric values of the given name.
-     * The attribute is mandatory, unbounded and has no default value.
-     *
-     * @param  name  name of the attribute to create.
-     * @return an attribute of the given name for numbers.
-     */
-    static AttributeType<Number> createNumericType(final String name) {
-        return createType(Number.class, name);
-    }
-
-    /**
-     * Returns the type of results computed by this arithmetic function.
-     */
-    protected abstract AttributeType<Number> expectedType();
-
-    /**
-     * Provides the type of results computed by this expression. That type depends only
-     * on the {@code ArithmeticFunction} subclass and is given by {@link #expectedType()}.
-     */
-    @Override
-    public final PropertyTypeBuilder expectedType(FeatureType ignored, FeatureTypeBuilder addTo) {
-        return addTo.addProperty(expectedType());
-    }
-
-    /**
-     * Evaluates this expression based on the content of the given object. This method delegates to
-     * {@link #applyAsDouble(double, double)}, {@link #applyAsLong(long, long)} or similar methods
-     * depending on the value types.
-     *
-     * @throws ArithmeticException if the operation overflows the capacity of the type used.
-     */
-    @Override
-    public final Object evaluate(final Object feature) {
-        return evaluate(feature, Number.class);
-    }
-
-    /**
-     * Evaluates the expression for producing a result of the given type. This method delegates to
-     * {@link #applyAsDouble(double, double)}, {@link #applyAsLong(long, long)} or similar methods
-     * depending on the value types. If this method can not produce a value of the given type,
-     * then it returns {@code null}.
-     *
-     * @param  feature  to feature to evaluate with this expression.
-     * @param  target   the desired type for the expression result.
-     * @return the result, or {@code null} if it can not be of the specified type.
-     * @throws ClassCastException if an expression returned the value in an expected type.
-     * @throws ArithmeticException if the operation overflows the capacity of the type used.
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    public final <T> T evaluate(final Object feature, final Class<T> target) {
-        ArgumentChecks.ensureNonNull("target", target);
-        if (Number.class.isAssignableFrom(target)) try {
-            final Number left = (Number) expression1.evaluate(feature, target);
-            if (left != null) {
-                final Number right = (Number) expression2.evaluate(feature, target);
-                if (right != null) {
-                    final Number result = apply(left, right);
-                    final Number casted = Numbers.cast(result, (Class<? extends Number>) target);
-                    if (Numerics.equals(result.doubleValue(), casted.doubleValue())) {
-                        return (T) casted;
-                    }
-                }
-            }
-        } catch (ArithmeticException e) {
-            warning(e);
-        }
-        return null;
-    }
-
-
-    /**
-     * The "Add" (+) expression.
-     */
-    static final class Add extends ArithmeticFunction implements org.opengis.filter.expression.Add {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 5445433312445869201L;
-
-        /** Description of results of the {@value #NAME} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(NAME);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@value #NAME} operation. */
-        Add(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '+';}
-
-        /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble  (double     left, double     right) {return left + right;}
-        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.add(right);}
-        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.add(right);}
-        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.add(right);}
-        @Override protected Number applyAsLong    (long       left, long       right) {return Math.addExact(left, right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The "Sub" (−) expression.
-     */
-    static final class Subtract extends ArithmeticFunction implements org.opengis.filter.expression.Subtract {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 3048878022726271508L;
-
-        /** Description of results of the {@value #NAME} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(NAME);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@value #NAME} operation. */
-        Subtract(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '−';}
-
-        /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble  (double     left, double     right) {return left - right;}
-        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.subtract(right);}
-        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.subtract(right);}
-        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.subtract(right);}
-        @Override protected Number applyAsLong    (long       left, long       right) {return Math.subtractExact(left, right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The "Mul" (×) expression.
-     */
-    static final class Multiply extends ArithmeticFunction implements org.opengis.filter.expression.Multiply {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -1300022614832645625L;
-
-        /** Description of results of the {@value #NAME} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(NAME);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@value #NAME} operation. */
-        Multiply(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '×';}
-
-        /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble  (double     left, double     right) {return left * right;}
-        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.multiply(right);}
-        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.multiply(right);}
-        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.multiply(right);}
-        @Override protected Number applyAsLong    (long       left, long       right) {return Math.multiplyExact(left, right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The "Div" (÷) expression.
-     */
-    static final class Divide extends ArithmeticFunction implements org.opengis.filter.expression.Divide {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -7709291845568648891L;
-
-        /** Description of results of the {@value #NAME} expression. */
-        private static final AttributeType<Number> TYPE = createNumericType(NAME);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@value #NAME} operation. */
-        Divide(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '÷';}
-
-        /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble  (double     left, double     right) {return left / right;}
-        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.divide(right);}
-        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.divide(right);}
-        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {
-            BigInteger[] r = left.divideAndRemainder(right);
-            if (BigInteger.ZERO.equals(r[1])) {
-                return r[0];
-            } else {
-                return left.doubleValue() / right.doubleValue();
-            }
-        }
-
-        /** Divides the given integers, changing the type to a floating point type if the result is not an integer. */
-        @Override protected Number applyAsLong(final long left, final long right) {
-            if (left % right == 0) {
-                return left / right;
-            } else {
-                return left / (double) right;
-            }
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
deleted file mode 100644
index e7ddda1..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
+++ /dev/null
@@ -1,212 +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.filter;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.math.BigInteger;
-import java.math.BigDecimal;
-import org.apache.sis.util.Numbers;
-import org.apache.sis.math.Fraction;
-import org.apache.sis.math.DecimalFunctions;
-import org.apache.sis.util.ArgumentChecks;
-
-// Branch-dependent imports
-import org.opengis.filter.expression.Expression;
-
-
-/**
- * Base class for expressions, comparators or filters performing operations on two expressions.
- * The nature of the operation depends on the subclass. If operands are numerical values, they
- * may be converted to a common type before the operation is performed. That operation is not
- * necessarily an arithmetic operation; it may be a comparison for example.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class BinaryFunction extends Node {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -8632475810190545852L;
-
-    /**
-     * The first of the two expressions to be used by this function.
-     *
-     * @see #getExpression1()
-     */
-    protected final Expression expression1;
-
-    /**
-     * The second of the two expressions to be used by this function.
-     *
-     * @see #getExpression2()
-     */
-    protected final Expression expression2;
-
-    /**
-     * Creates a new binary function.
-     *
-     * @param  expression1  the first of the two expressions to be used by this function.
-     * @param  expression2  the second of the two expressions to be used by this function.
-     */
-    protected BinaryFunction(final Expression expression1, final Expression expression2) {
-        ArgumentChecks.ensureNonNull("expression1", expression1);
-        ArgumentChecks.ensureNonNull("expression2", expression2);
-        this.expression1 = expression1;
-        this.expression2 = expression2;
-    }
-
-    /**
-     * Returns the first of the two expressions to be used by this function.
-     * This is the value specified at construction time.
-     *
-     * @see org.opengis.filter.BinaryComparisonOperator#getExpression1()
-     * @see org.opengis.filter.temporal.BinaryTemporalOperator#getExpression1()
-     */
-    public final Expression getExpression1() {
-        return expression1;
-    }
-
-    /**
-     * Returns the second of the two expressions to be used by this function.
-     * This is the value specified at construction time.
-     *
-     * @see org.opengis.filter.BinaryComparisonOperator#getExpression1()
-     * @see org.opengis.filter.temporal.BinaryTemporalOperator#getExpression2()
-     */
-    public final Expression getExpression2() {
-        return expression2;
-    }
-
-    /**
-     * Returns the two expressions in a list of size 2.
-     * This is used for {@link #toString()} implementation.
-     */
-    @Override
-    protected final Collection<?> getChildren() {
-        return Arrays.asList(expression1, expression2);
-    }
-
-    /**
-     * Evaluates the expression for producing a result of numeric type.
-     * This method delegates to one of the {@code applyAs(…)} methods.
-     * If no {@code applyAs(…)} implementations can return null values,
-     * this this method never return {@code null}.
-     *
-     * @param  left   the left operand. Can not be null.
-     * @param  right  the right operand. Can not be null.
-     * @return result of this function applied on the two given operands.
-     *         May be {@code null} only if an {@code applyAs(…)} implementation returned a null value.
-     * @throws ArithmeticException if the operation overflows the capacity of the type used.
-     */
-    protected final Number apply(final Number left, final Number right) {
-        switch (Math.max(Numbers.getEnumConstant(left.getClass()),
-                         Numbers.getEnumConstant(right.getClass())))
-        {
-            case Numbers.BIG_DECIMAL: {
-                return applyAsDecimal(Numbers.cast(left,  BigDecimal.class),
-                                      Numbers.cast(right, BigDecimal.class));
-            }
-            case Numbers.BIG_INTEGER: {
-                return applyAsInteger(Numbers.cast(left,  BigInteger.class),
-                                      Numbers.cast(right, BigInteger.class));
-            }
-            case Numbers.FRACTION: {
-                return applyAsFraction(Numbers.cast(left,  Fraction.class),
-                                       Numbers.cast(right, Fraction.class));
-            }
-            case Numbers.LONG:
-            case Numbers.INTEGER:
-            case Numbers.SHORT:
-            case Numbers.BYTE: {
-                return applyAsLong(left.longValue(), right.longValue());
-            }
-        }
-        return applyAsDouble((left  instanceof Float) ? DecimalFunctions.floatToDouble((Float) left)  : left.doubleValue(),
-                             (right instanceof Float) ? DecimalFunctions.floatToDouble((Float) right) : right.doubleValue());
-    }
-
-    /**
-     * Calculates this function using given operands of {@code long} primitive type. If this function is a
-     * filter, then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
-     * Otherwise the result is usually a {@link Long}, except for division which may produce a floating point number.
-     * This method may return {@code null} if the operation can not apply on numbers.
-     *
-     * @throws ArithmeticException if the operation overflows the 64 bits integer capacity.
-     */
-    protected abstract Number applyAsLong(long left, long right);
-
-    /**
-     * Calculates this function using given operands of {@code double} primitive type. If this function is a
-     * filter, then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
-     * Otherwise the result is usually a {@link Double}.
-     * This method may return {@code null} if the operation can not apply on numbers.
-     */
-    protected abstract Number applyAsDouble(double left, double right);
-
-    /**
-     * Calculates this function using given operands of {@code Fraction} type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
-     * Otherwise the result is usually a {@link Fraction}.
-     * This method may return {@code null} if the operation can not apply on numbers.
-     */
-    protected abstract Number applyAsFraction(Fraction left, Fraction right);
-
-    /**
-     * Calculates this function using given operands of {@code BigInteger} type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
-     * Otherwise the result is usually a {@link BigInteger}.
-     * This method may return {@code null} if the operation can not apply on numbers.
-     */
-    protected abstract Number applyAsInteger(BigInteger left, BigInteger right);
-
-    /**
-     * Calculates this function using given operands of {@code BigDecimal} type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
-     * Otherwise the result is usually a {@link BigDecimal}.
-     * This method may return {@code null} if the operation can not apply on numbers.
-     */
-    protected abstract Number applyAsDecimal(BigDecimal left, BigDecimal right);
-
-    /**
-     * Returns a hash code value for this function.
-     */
-    @Override
-    public int hashCode() {
-        return (31 * expression1.hashCode() + expression2.hashCode()) ^ getClass().hashCode();
-    }
-
-    /**
-     * Compares this function with the given object for equality.
-     */
-    @Override
-    public boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj != null && obj.getClass() == getClass()) {
-            final BinaryFunction other = (BinaryFunction) obj;
-            return expression1.equals(other.expression1) &&
-                   expression2.equals(other.expression2);
-        }
-        return false;
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
deleted file mode 100644
index 3292bb1..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
+++ /dev/null
@@ -1,718 +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.filter;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.Calendar;
-import java.time.Instant;
-import java.time.LocalTime;
-import java.time.OffsetTime;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZonedDateTime;
-import java.time.ZoneId;
-import java.time.chrono.ChronoLocalDate;
-import java.time.chrono.ChronoLocalDateTime;
-import java.time.chrono.ChronoZonedDateTime;
-import java.time.temporal.ChronoField;
-import java.time.temporal.Temporal;
-import org.apache.sis.math.Fraction;
-import org.apache.sis.util.ArgumentChecks;
-
-// Branch-dependent imports
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.FilterVisitor;
-
-
-/**
- * Comparison operators between two values. Values are converted to the same before comparison, using a widening conversion
- * (for example from {@link Integer} to {@link Double}). If values can not be compared because they can not be converted to
- * a common type, or because a value is null or NaN, then the comparison result if {@code false}. A consequence of this rule
- * is that the two conditions {@literal A < B} and {@literal A ≧ B} may be false in same time.
- *
- * <p>If one operand is a collection, all collection elements may be compared to the other value.
- * Null elements in the collection (not to be confused with null operands) are ignored.
- * If both operands are collections, current implementation returns {@code false}.</p>
- *
- * <p>Comparisons between temporal objects are done with {@code isBefore(…)} or {@code isAfter(…)} methods when they
- * have a different semantic than the {@code compareTo(…)} methods. If the two temporal objects are not of the same
- * type, only the fields that are common two both types are compared. For example comparison between {@code LocalDate}
- * and {@code LocalDateTime} ignores the time fields.</p>
- *
- * <p>Comparisons of numerical types shall be done by overriding one of the {@code applyAs…} methods and
- * returning 0 if {@code false} or 1 if {@code true}. Comparisons of other types is done by overriding
- * the {@code compare(…)} methods.</p>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class ComparisonFunction extends BinaryFunction implements BinaryComparisonOperator {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 1228683039737814926L;
-
-    /**
-     * Specifies whether comparisons are case sensitive.
-     */
-    private final boolean isMatchingCase;
-
-    /**
-     * Specifies how the comparisons shall be evaluated for a collection of values.
-     * Values can be ALL, ANY or ONE.
-     */
-    private final MatchAction matchAction;
-
-    /**
-     * Creates a new comparator.
-     *
-     * @param  expression1     the first of the two expressions to be used by this comparator.
-     * @param  expression2     the second of the two expressions to be used by this comparator.
-     * @param  isMatchingCase  specifies whether comparisons are case sensitive.
-     * @param  matchAction     specifies how the comparisons shall be evaluated for a collection of values.
-     */
-    ComparisonFunction(final Expression expression1, final Expression expression2, final boolean isMatchingCase, final MatchAction matchAction) {
-        super(expression1, expression2);
-        this.isMatchingCase = isMatchingCase;
-        this.matchAction = matchAction;
-        ArgumentChecks.ensureNonNull("matchAction", matchAction);
-    }
-
-    /**
-     * Returns whether comparisons are case sensitive.
-     */
-    @Override
-    public final boolean isMatchingCase() {
-        return isMatchingCase;
-    }
-
-    /**
-     * Returns how the comparisons are evaluated for a collection of values.
-     */
-    @Override
-    public final MatchAction getMatchAction() {
-        return matchAction;
-    }
-
-    /**
-     * Takes in account the additional properties in hash code calculation.
-     */
-    @Override
-    public final int hashCode() {
-        return super.hashCode() + Boolean.hashCode(isMatchingCase) + 61 * matchAction.hashCode();
-    }
-
-    /**
-     * Takes in account the additional properties in object comparison.
-     */
-    @Override
-    public final boolean equals(final Object obj) {
-        if (super.equals(obj)) {
-            final ComparisonFunction other = (ComparisonFunction) obj;
-            return other.isMatchingCase == isMatchingCase && matchAction.equals(other.matchAction);
-        }
-        return false;
-    }
-
-    /**
-     * Determines if the test(s) represented by this filter passes with the given operands.
-     * Values of {@link #expression1} and {@link #expression2} can be two single values,
-     * or at most one expression can produce a collection.
-     */
-    @Override
-    public final boolean evaluate(final Object candidate) {
-        final Object left = expression1.evaluate(candidate);
-        if (left != null) {
-            final Object right = expression2.evaluate(candidate);
-            if (right != null) {
-                final Iterable<?> collection;
-                final boolean collectionFirst = (left instanceof Iterable<?>);
-                if (collectionFirst) {
-                    if (right instanceof Iterable<?>) {
-                        // Current implementation does not support collection on both sides. See class javadoc.
-                        return false;
-                    }
-                    collection = (Iterable<?>) left;
-                } else if (right instanceof Iterable<?>) {
-                    collection = (Iterable<?>) right;
-                } else {
-                    return evaluate(left, right);
-                }
-                /*
-                 * At this point, exactly one of the operands is a collection. It may be the left or right one.
-                 * All values in the collection may be compared to the other value until match condition is met.
-                 * Null elements in the collection are ignored.
-                 */
-                boolean match = false;
-                for (final Object element : collection) {
-                    if (element != null) {
-                        final boolean pass;
-                        if (collectionFirst) {
-                            pass = evaluate(element, right);
-                        } else {
-                            pass = evaluate(left, element);
-                        }
-                        switch (matchAction) {
-                            default: return false;              // Unknown enumeration.
-                            case ALL: {
-                                if (!pass) return false;
-                                match = true;                   // Remember that we have at least 1 value.
-                                break;
-                            }
-                            case ANY: {
-                                if (pass) return true;
-                                break;                          // `match` still false since no match.
-                            }
-                            case ONE: {
-                                if (pass) {
-                                    if (match) return false;    // If a value has been found previously.
-                                    match = true;               // Remember that we have exactly one value.
-                                }
-                            }
-                        }
-                    }
-                }
-                return match;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Compares the given objects. If both values are numerical, then this method delegates to an {@code applyAs…} method.
-     * For other kind of objects, this method delegates to a {@code compare(…)} method. If the two objects are not of the
-     * same type, then the less accurate one is converted to the most accurate type if possible.
-     *
-     * @param  left   the first object to compare. Must be non-null.
-     * @param  right  the second object to compare. Must be non-null.
-     */
-    @SuppressWarnings("null")
-    private boolean evaluate(Object left, Object right) {
-        /*
-         * For numbers, the apply(…) method inherited from parent class will delegate to specialized methods like
-         * applyAsDouble(…). All implementations of those specialized methods in ComparisonFunction return integer,
-         * so call to intValue() will not cause information lost.
-         */
-        if (left instanceof Number && right instanceof Number) {
-            final Number r = apply((Number) left, (Number) right);
-            if (r != null) return r.intValue() != 0;
-        }
-        /*
-         * For legacy java.util.Date, the compareTo(…) method is consistent only for dates of the same class.
-         * Otherwise A.compareTo(B) and B.compareTo(A) are inconsistent if one object is a java.util.Date and
-         * the other object is a java.sql.Timestamp. In such case, we compare the dates as java.time objects.
-         */
-        if (left instanceof Date && right instanceof Date) {
-            if (left.getClass() == right.getClass()) {
-                return fromCompareTo(((Date) left).compareTo((Date) right));
-            }
-            left  = fromLegacy((Date) left);
-            right = fromLegacy((Date) right);
-        }
-        /*
-         * Temporal objects have complex conversion rules. We take Instant as the most accurate and unambiguous type.
-         * So if at least one value is an Instant, try to unconditionally promote the other value to an Instant too.
-         * This conversion will fail if the other object has some undefined fields; for example java.sql.Date has no
-         * time fields (we do not assume that the values of those fields are zero).
-         *
-         * OffsetTime and OffsetDateTime are final classes that do not implement a java.time.chrono interface.
-         * Note that OffsetDateTime is convertible into OffsetTime by dropping the date fields, but we do not
-         * (for now) perform comparaisons that would ignore the date fields of an operand.
-         */
-        if (left instanceof Temporal || right instanceof Temporal) {        // Use || because an operand may be Date.
-            if (left instanceof Instant) {
-                final Instant t = toInstant(right);
-                if (t != null) return fromCompareTo(((Instant) left).compareTo(t));
-            } else if (right instanceof Instant) {
-                final Instant t = toInstant(left);
-                if (t != null) return fromCompareTo(t.compareTo((Instant) right));
-            } else if (left instanceof OffsetDateTime) {
-                final OffsetDateTime t = toOffsetDateTime(right);
-                if (t != null) return compare((OffsetDateTime) left, t);
-            } else if (right instanceof OffsetDateTime) {
-                final OffsetDateTime t = toOffsetDateTime(left);
-                if (t != null) return compare(t, (OffsetDateTime) right);
-            } else if (left instanceof OffsetTime && right instanceof OffsetTime) {
-                return compare((OffsetTime) left, (OffsetTime) right);
-            }
-            /*
-             * Comparisons of temporal objects implementing java.time.chrono interfaces. We need to check the most
-             * complete types first. If the type are different, we reduce to the type of the less smallest operand.
-             * For example if an operand is a date+time and the other operand is only a date, then the time fields
-             * will be ignored and a warning will be reported.
-             */
-            if (left instanceof ChronoLocalDateTime<?>) {
-                final ChronoLocalDateTime<?> t = toLocalDateTime(right);
-                if (t != null) return compare((ChronoLocalDateTime<?>) left, t);
-            } else if (right instanceof ChronoLocalDateTime<?>) {
-                final ChronoLocalDateTime<?> t = toLocalDateTime(left);
-                if (t != null) return compare(t, (ChronoLocalDateTime<?>) right);
-            }
-            if (left instanceof ChronoLocalDate) {
-                final ChronoLocalDate t = toLocalDate(right);
-                if (t != null) return compare((ChronoLocalDate) left, t);
-            } else if (right instanceof ChronoLocalDate) {
-                final ChronoLocalDate t = toLocalDate(left);
-                if (t != null) return compare(t, (ChronoLocalDate) right);
-            }
-            if (left instanceof LocalTime) {
-                final LocalTime t = toLocalTime(right);
-                if (t != null) return fromCompareTo(((LocalTime) left).compareTo(t));
-            } else if (right instanceof LocalTime) {
-                final LocalTime t = toLocalTime(left);
-                if (t != null) return fromCompareTo(t.compareTo((LocalTime) right));
-            }
-        }
-        /*
-         * Test character strings only after all specialized types have been tested. The intent is that if an
-         * object implements both CharSequence and a specialized interface, they have been compared as value
-         * objects before to be compared as strings.
-         */
-        if (left instanceof CharSequence || right instanceof CharSequence) {            // Really ||, not &&.
-            final String s1 = left.toString();
-            final String s2 = right.toString();
-            final int result;
-            if (isMatchingCase) {
-                result = s1.compareTo(s2);
-            } else {
-                result = s1.compareToIgnoreCase(s2);        // TODO: use Collator for taking locale in account.
-            }
-            return fromCompareTo(result);
-        }
-        /*
-         * Comparison using `compareTo` method should be last because it does not take in account
-         * the `isMatchingCase` flag and because the semantic is different than < or > comparator
-         * for numbers and dates.
-         */
-        if (left.getClass() == right.getClass() && (left instanceof Comparable<?>)) {
-            @SuppressWarnings("unchecked")
-            final int result = ((Comparable) left).compareTo(right);
-            return fromCompareTo(result);
-        }
-        // TODO: report a warning for non-comparable objects.
-        return false;
-    }
-
-    /**
-     * Converts a legacy {@code Date} object to an object from the {@link java.time} package.
-     * We performs this conversion before to compare to {@code Date} instances that are not of
-     * the same class, because the {@link Date#compareTo(Date)} method in such case is not well
-     * defined.
-     */
-    private static Temporal fromLegacy(final Date value) {
-        if (value instanceof java.sql.Timestamp) {
-            return ((java.sql.Timestamp) value).toLocalDateTime();
-        } else if (value instanceof java.sql.Date) {
-            return ((java.sql.Date) value).toLocalDate();
-        } else if (value instanceof java.sql.Time) {
-            return ((java.sql.Time) value).toLocalTime();
-        } else {
-            // Implementation of above toFoo() methods use system default time zone.
-            return LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault());
-        }
-    }
-
-    /**
-     * Converts the given object to an {@link Instant}, or returns {@code null} if unconvertible.
-     * This method handles a few types from the {@link java.time} package and legacy types like
-     * {@link Date} (with a special case for SQL dates) and {@link Calendar}.
-     */
-    static Instant toInstant(final Object value) {
-        if (value instanceof Instant) {
-            return (Instant) value;
-        } else if (value instanceof OffsetDateTime) {
-            return ((OffsetDateTime) value).toInstant();
-        } else if (value instanceof ChronoZonedDateTime) {
-            return ((ChronoZonedDateTime) value).toInstant();
-        } else if (value instanceof Date) {
-            try {
-                return ((Date) value).toInstant();
-            } catch (UnsupportedOperationException e) {
-                /*
-                 * java.sql.Date and java.sql.Time can not be converted to Instant because a part
-                 * of their coordinates on the timeline is undefined.  For example in the case of
-                 * java.sql.Date the hours, minutes and seconds are unspecified (which is not the
-                 * same thing than assuming that those values are zero).
-                 */
-            }
-        } else if (value instanceof Calendar) {
-            return ((Calendar) value).toInstant();
-        }
-        return null;
-    }
-
-    /**
-     * Converts the given object to an {@link OffsetDateTime}, or returns {@code null} if unconvertible.
-     */
-    private static OffsetDateTime toOffsetDateTime(final Object value) {
-        if (value instanceof OffsetDateTime) {
-            return (OffsetDateTime) value;
-        } else if (value instanceof ZonedDateTime) {
-            return ((ZonedDateTime) value).toOffsetDateTime();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts the given object to a {@link ChronoLocalDateTime}, or returns {@code null} if unconvertible.
-     * This method handles the case of legacy SQL {@link java.sql.Timestamp} objects.
-     * Conversion may lost timezone information.
-     */
-    private static ChronoLocalDateTime<?> toLocalDateTime(final Object value) {
-        if (value instanceof ChronoLocalDateTime<?>) {
-            return (ChronoLocalDateTime<?>) value;
-        } else if (value instanceof ChronoZonedDateTime) {
-            ignoringField(ChronoField.OFFSET_SECONDS);
-            return ((ChronoZonedDateTime) value).toLocalDateTime();
-        } else if (value instanceof OffsetDateTime) {
-            ignoringField(ChronoField.OFFSET_SECONDS);
-            return ((OffsetDateTime) value).toLocalDateTime();
-        } else if (value instanceof java.sql.Timestamp) {
-            return ((java.sql.Timestamp) value).toLocalDateTime();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts the given object to a {@link ChronoLocalDate}, or returns {@code null} if unconvertible.
-     * This method handles the case of legacy SQL {@link java.sql.Date} objects.
-     * Conversion may lost timezone information and time fields.
-     */
-    private static ChronoLocalDate toLocalDate(final Object value) {
-        if (value instanceof ChronoLocalDate) {
-            return (ChronoLocalDate) value;
-        } else if (value instanceof ChronoLocalDateTime) {
-            ignoringField(ChronoField.SECOND_OF_DAY);
-            return ((ChronoLocalDateTime) value).toLocalDate();
-        } else if (value instanceof ChronoZonedDateTime) {
-            ignoringField(ChronoField.SECOND_OF_DAY);
-            return ((ChronoZonedDateTime) value).toLocalDate();
-        } else if (value instanceof OffsetDateTime) {
-            ignoringField(ChronoField.SECOND_OF_DAY);
-            return ((OffsetDateTime) value).toLocalDate();
-        } else if (value instanceof java.sql.Date) {
-            return ((java.sql.Date) value).toLocalDate();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Converts the given object to a {@link LocalTime}, or returns {@code null} if unconvertible.
-     * This method handles the case of legacy SQL {@link java.sql.Time} objects.
-     * Conversion may lost timezone information.
-     */
-    private static LocalTime toLocalTime(final Object value) {
-        if (value instanceof LocalTime) {
-            return (LocalTime) value;
-        } else if (value instanceof OffsetTime) {
-            ignoringField(ChronoField.OFFSET_SECONDS);
-            return ((OffsetTime) value).toLocalTime();
-        } else if (value instanceof java.sql.Time) {
-            return ((java.sql.Time) value).toLocalTime();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Invoked when a conversion cause a field to be ignored. For example if a "date+time" object is compared
-     * with a "date" object, the "time" field is ignored. Expected values are:
-     *
-     * <ul>
-     *   <li>{@link ChronoField#OFFSET_SECONDS}: time zone is ignored.</li>
-     *   <li>{@link ChronoField#SECOND_OF_DAY}:  time of dat and time zone are ignored.</li>
-     * </ul>
-     *
-     * @param  field  the field which is ignored.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-460">SIS-460</a>
-     */
-    private static void ignoringField(final ChronoField field) {
-        // TODO
-    }
-
-    /**
-     * Converts the boolean result as an integer for use as a return value of the {@code applyAs…} methods.
-     * This is a helper class for subclasses.
-     */
-    private static Number number(final boolean result) {
-        return result ? 1 : 0;
-    }
-
-    /**
-     * Converts the result of {@link Comparable#compareTo(Object)}.
-     */
-    protected abstract boolean fromCompareTo(int result);
-
-    /**
-     * Compares two times with time-zone information. Implementations shall not use {@code compareTo(…)} because
-     * that method compares more information than desired in order to ensure consistency with {@code equals(…)}.
-     */
-    protected abstract boolean compare(OffsetTime left, OffsetTime right);
-
-    /**
-     * Compares two dates with time-zone information. Implementations shall not use {@code compareTo(…)} because
-     * that method compares more information than desired in order to ensure consistency with {@code equals(…)}.
-     */
-    protected abstract boolean compare(OffsetDateTime left, OffsetDateTime right);
-
-    /**
-     * Compares two dates without time-of-day and time-zone information. Implementations shall not use
-     * {@code compareTo(…)} because that method also compares chronology, which is not desired for the
-     * purpose of "is before" or "is after" comparison functions.
-     */
-    protected abstract boolean compare(ChronoLocalDate left, ChronoLocalDate right);
-
-    /**
-     * Compares two dates without time-zone information. Implementations shall not use {@code compareTo(…)}
-     * because that method also compares chronology, which is not desired for the purpose of "is before" or
-     * "is after" comparison functions.
-     */
-    protected abstract boolean compare(ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right);
-
-    /**
-     * Compares two dates with time-zone information. Implementations shall not use {@code compareTo(…)}
-     * because that method also compares chronology, which is not desired for the purpose of "is before"
-     * or "is after" comparison functions.
-     */
-    protected abstract boolean compare(ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right);
-
-    /** Delegates to {@link BigDecimal#compareTo(BigDecimal)} and interprets the result with {@link #fromCompareTo(int)}. */
-    @Override protected final Number applyAsDecimal (BigDecimal left, BigDecimal right) {return number(fromCompareTo(left.compareTo(right)));}
-    @Override protected final Number applyAsInteger (BigInteger left, BigInteger right) {return number(fromCompareTo(left.compareTo(right)));}
-    @Override protected final Number applyAsFraction(Fraction   left, Fraction   right) {return number(fromCompareTo(left.compareTo(right)));}
-
-
-    /**
-     * The {@value #NAME} {@literal (<)} filter.
-     */
-    static final class LessThan extends ComparisonFunction implements org.opengis.filter.PropertyIsLessThan {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 6126039112844823196L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        LessThan(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
-            super(expression1, expression2, isMatchingCase, matchAction);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '<';}
-
-        /** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
-        @Override protected boolean fromCompareTo(final int result) {return result < 0;}
-
-        /** Performs the comparison and returns the result as 0 (false) or 1 (true). */
-        @Override protected Number  applyAsDouble(double                 left, double                 right) {return number(left < right);}
-        @Override protected Number  applyAsLong  (long                   left, long                   right) {return number(left < right);}
-        @Override protected boolean compare      (OffsetTime             left, OffsetTime             right) {return left.isBefore(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, OffsetDateTime         right) {return left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, ChronoLocalDate        right) {return left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return left.isBefore(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return left.isBefore(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} (≤) filter.
-     */
-    static final class LessThanOrEqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsLessThanOrEqualTo {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 6357459227911760871L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        LessThanOrEqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
-            super(expression1, expression2, isMatchingCase, matchAction);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '≤';}
-
-        /** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
-        @Override protected boolean fromCompareTo(final int result) {return result <= 0;}
-
-        /** Performs the comparison and returns the result as 0 (false) or 1 (true). */
-        @Override protected Number  applyAsDouble(double                 left, double                 right) {return number(left <= right);}
-        @Override protected Number  applyAsLong  (long                   left, long                   right) {return number(left <= right);}
-        @Override protected boolean compare      (OffsetTime             left, OffsetTime             right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, OffsetDateTime         right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, ChronoLocalDate        right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return !left.isAfter(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return !left.isAfter(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} {@literal (>)} filter.
-     */
-    static final class GreaterThan extends ComparisonFunction implements org.opengis.filter.PropertyIsGreaterThan {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 8605517892232632586L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        GreaterThan(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
-            super(expression1, expression2, isMatchingCase, matchAction);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '>';}
-
-        /** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
-        @Override protected boolean fromCompareTo(final int result) {return result > 0;}
-
-        /** Performs the comparison and returns the result as 0 (false) or 1 (true). */
-        @Override protected Number  applyAsDouble(double                 left, double                 right) {return number(left > right);}
-        @Override protected Number  applyAsLong  (long                   left, long                   right) {return number(left > right);}
-        @Override protected boolean compare      (OffsetTime             left, OffsetTime             right) {return left.isAfter(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, OffsetDateTime         right) {return left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, ChronoLocalDate        right) {return left.isAfter(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return left.isAfter(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return left.isAfter(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} (≥) filter.
-     */
-    static final class GreaterThanOrEqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsGreaterThanOrEqualTo {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 1514185657159141882L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        GreaterThanOrEqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
-            super(expression1, expression2, isMatchingCase, matchAction);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '≥';}
-
-        /** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
-        @Override protected boolean fromCompareTo(final int result) {return result >= 0;}
-
-        /** Performs the comparison and returns the result as 0 (false) or 1 (true). */
-        @Override protected Number  applyAsDouble(double                 left, double                 right) {return number(left >= right);}
-        @Override protected Number  applyAsLong  (long                   left, long                   right) {return number(left >= right);}
-        @Override protected boolean compare      (OffsetTime             left, OffsetTime             right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, OffsetDateTime         right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, ChronoLocalDate        right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return !left.isBefore(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return !left.isBefore(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} (=) filter.
-     */
-    static final class EqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsEqualTo {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 8502612221498749667L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        EqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
-            super(expression1, expression2, isMatchingCase, matchAction);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '=';}
-
-        /** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
-        @Override protected boolean fromCompareTo(final int result) {return result == 0;}
-
-        /** Performs the comparison and returns the result as 0 (false) or 1 (true). */
-        @Override protected Number  applyAsDouble(double                 left, double                 right) {return number(left == right);}
-        @Override protected Number  applyAsLong  (long                   left, long                   right) {return number(left == right);}
-        @Override protected boolean compare      (OffsetTime             left, OffsetTime             right) {return left.isEqual(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, OffsetDateTime         right) {return left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, ChronoLocalDate        right) {return left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return left.isEqual(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return left.isEqual(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} (≠) filter.
-     */
-    static final class NotEqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsNotEqualTo {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -3295957142249035362L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        NotEqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
-            super(expression1, expression2, isMatchingCase, matchAction);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '≠';}
-
-        /** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
-        @Override protected boolean fromCompareTo(final int result) {return result != 0;}
-
-        /** Performs the comparison and returns the result as 0 (false) or 1 (true). */
-        @Override protected Number  applyAsDouble(double                 left, double                 right) {return number(left != right);}
-        @Override protected Number  applyAsLong  (long                   left, long                   right) {return number(left != right);}
-        @Override protected boolean compare      (OffsetTime             left, OffsetTime             right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (OffsetDateTime         left, OffsetDateTime         right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDate        left, ChronoLocalDate        right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return !left.isEqual(right);}
-        @Override protected boolean compare      (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return !left.isEqual(right);}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @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/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
deleted file mode 100644
index fad06e9..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
+++ /dev/null
@@ -1,932 +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.filter;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.Set;
-import org.opengis.filter.*;
-import org.opengis.filter.capability.*;
-import org.opengis.filter.capability.SpatialOperator;
-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.geometry.Envelope;
-import org.opengis.geometry.Geometry;
-import org.opengis.util.GenericName;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.system.SystemListener;
-import org.apache.sis.internal.feature.FunctionRegister;
-import org.apache.sis.internal.feature.Resources;
-
-
-/**
- * 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.1
- * @since   1.1
- * @module
- */
-public class DefaultFilterFactory implements FilterFactory2 {
-    /**
-     * All functions identified by a name like {@code "cos"}, {@code "hypot"}, <i>etc</i>.
-     * The actual function creations is delegated to an external factory such as {@link SQLMM}.
-     * The factories are fetched by {@link #function(String, Expression...)} when first needed.
-     * This factory is cleared if classpath changes, for allowing dynamic reloading.
-     *
-     * @see #function(String, Expression...)
-     */
-    private static final Map<String,FunctionRegister> FUNCTION_REGISTERS = new HashMap<>();
-    static {
-        SystemListener.add(new SystemListener(Modules.FEATURE) {
-            @Override protected void classpathChanged() {
-                synchronized (FUNCTION_REGISTERS) {
-                    FUNCTION_REGISTERS.clear();
-                }
-            }
-        });
-    }
-
-    /**
-     * 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) {
-        return new DefaultObjectId(id);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public GmlObjectId gmlObjectId(final String id) {
-        return new DefaultObjectId(id);
-    }
-
-    // FILTERS /////////////////////////////////////////////////////////////////
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public And and(final Filter filter1, final Filter filter2) {
-        return and(Arrays.asList(filter1, filter2));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public And and(final List<Filter> filters) {
-        return new LogicalFunction.And(filters);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Or or(final Filter filter1, final Filter filter2) {
-        return or(Arrays.asList(filter1, filter2));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Or or(final List<Filter> filters) {
-        return new LogicalFunction.Or(filters);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Not not(final Filter filter) {
-        return new UnaryFunction.Not(filter);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Id id(final Set<? extends Identifier> ids) {
-        return new FilterByIdentifier(ids);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyName property(final GenericName name) {
-        return property(name.toString());
-    }
-
-    /**
-     * Creates a new expression retrieving values from a property of the given name.
-     *
-     * @param  name  name of the property (usually a feature attribute).
-     */
-    @Override
-    public PropertyName property(final String name) {
-        return new LeafExpression.Property(name);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsBetween between(final Expression expression, final Expression lower, final Expression upper) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsEqualTo equals(final Expression expression1, final Expression expression2) {
-        return equal(expression1, expression2, true, MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsEqualTo equal(final Expression expression1, final Expression expression2,
-                                   final boolean isMatchingCase, final MatchAction matchAction)
-    {
-        return new ComparisonFunction.EqualTo(expression1, expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsNotEqualTo notEqual(final Expression expression1, final Expression expression2) {
-        return notEqual(expression1, expression2, true, MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsNotEqualTo notEqual(final Expression expression1, final Expression expression2,
-                                         final boolean isMatchingCase, final MatchAction matchAction)
-    {
-        return new ComparisonFunction.NotEqualTo(expression1, expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsGreaterThan greater(final Expression expression1, final Expression expression2) {
-        return greater(expression1, expression2, true, MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsGreaterThan greater(final Expression expression1, final Expression expression2,
-                                         final boolean isMatchingCase, final MatchAction matchAction)
-    {
-        return new ComparisonFunction.GreaterThan(expression1, expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsGreaterThanOrEqualTo greaterOrEqual(final Expression expression1, final Expression expression2) {
-        return greaterOrEqual(expression1, expression2, true, MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsGreaterThanOrEqualTo greaterOrEqual(final Expression expression1, final Expression expression2,
-                                                         final boolean isMatchingCase, final MatchAction matchAction)
-    {
-        return new ComparisonFunction.GreaterThanOrEqualTo(expression1, expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLessThan less(final Expression expression1, final Expression expression2) {
-        return less(expression1, expression2, true, MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLessThan less(final Expression expression1, final Expression expression2,
-                                   final boolean isMatchingCase, MatchAction matchAction)
-    {
-        return new ComparisonFunction.LessThan(expression1, expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression expression1, final Expression expression2) {
-        return lessOrEqual(expression1, expression2, true, MatchAction.ANY);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLessThanOrEqualTo lessOrEqual(final Expression expression1, final Expression expression2,
-                                                   final boolean isMatchingCase, final MatchAction matchAction)
-    {
-        return new ComparisonFunction.LessThanOrEqualTo(expression1, expression2, isMatchingCase, matchAction);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLike like(final Expression expression, final String pattern) {
-        return like(expression, pattern, "*", "?", "\\");
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLike like(final Expression expression, final String pattern,
-            final String wildcard, final String singleChar, final String escape)
-    {
-        return like(expression, pattern, wildcard, singleChar, escape, true);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsLike like(final Expression expression, final String pattern,
-            final String wildcard, final String singleChar,
-            final String escape, final boolean isMatchingCase)
-    {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsNull isNull(final Expression expression) {
-        return new UnaryFunction.IsNull(expression);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public PropertyIsNil isNil(Expression expression) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    // TEMPORAL FILTER /////////////////////////////////////////////////////////
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public After after(Expression expression1, Expression expression2) {
-        return new TemporalFunction.After(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public AnyInteracts anyInteracts(Expression expression1, Expression expression2) {
-        return new TemporalFunction.AnyInteracts(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Before before(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Before(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Begins begins(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Begins(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public BegunBy begunBy(Expression expression1, Expression expression2) {
-        return new TemporalFunction.BegunBy(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public During during(Expression expression1, Expression expression2) {
-        return new TemporalFunction.During(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Ends ends(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Ends(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public EndedBy endedBy(Expression expression1, Expression expression2) {
-        return new TemporalFunction.EndedBy(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Meets meets(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Meets(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public MetBy metBy(Expression expression1, Expression expression2) {
-        return new TemporalFunction.MetBy(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public OverlappedBy overlappedBy(Expression expression1, Expression expression2) {
-        return new TemporalFunction.OverlappedBy(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public TContains tcontains(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Contains(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public TEquals tequals(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Equals(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public TOverlaps toverlaps(Expression expression1, Expression expression2) {
-        return new TemporalFunction.Overlaps(expression1, expression2);
-    }
-
-    // EXPRESSIONS /////////////////////////////////////////////////////////////
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Add add(final Expression expression1, final Expression expression2) {
-        return new ArithmeticFunction.Add(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Divide divide(final Expression expression1, final Expression expression2) {
-        return new ArithmeticFunction.Divide(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Multiply multiply(final Expression expression1, final Expression expression2) {
-        return new ArithmeticFunction.Multiply(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Subtract subtract(final Expression expression1, final Expression expression2) {
-        return new ArithmeticFunction.Subtract(expression1, expression2);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Function function(final String name, final Expression... parameters) {
-        final FunctionRegister register;
-        synchronized (FUNCTION_REGISTERS) {
-            if (FUNCTION_REGISTERS.isEmpty()) {
-                /*
-                 * Load functions when first needed or if classpath changed since last invocation.
-                 * The SQLMM factory is hard-coded because it is considered as a basic service to
-                 * be provided by all DefaultFilterFactory implementations, and for avoiding the
-                 * need to make SQLMM class public.
-                 */
-                final SQLMM r = new SQLMM();
-                for (final String fn : r.getNames()) {
-                    FUNCTION_REGISTERS.put(fn, r);
-                }
-                for (final FunctionRegister er : ServiceLoader.load(FunctionRegister.class)) {
-                    for (final String fn : er.getNames()) {
-                        FUNCTION_REGISTERS.putIfAbsent(fn, er);
-                    }
-                }
-            }
-            register = FUNCTION_REGISTERS.get(name);
-        }
-        if (register == null) {
-            throw new IllegalArgumentException(Resources.format(Resources.Keys.UnknownFunction_1, name));
-        }
-        return register.create(name, parameters);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final Object value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final byte value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final short value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final int value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final long value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final float value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final double value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final char value) {
-        return new LeafExpression.Literal(value);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public Literal literal(final boolean value) {
-        return new LeafExpression.Literal(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/DefaultObjectId.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultObjectId.java
deleted file mode 100644
index b5c0f68..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultObjectId.java
+++ /dev/null
@@ -1,111 +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.filter;
-
-import java.io.Serializable;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-
-
-/**
- * Default implementation of a few interfaces from the {@link org.opengis.filter.identity} package.
- * Those objects are used for identifying GML objects or other kind of objects.
- *
- * @deprecated the purpose of {@link org.opengis.filter.identity} is questionable.
- *             See <a href="https://github.com/opengeospatial/geoapi/issues/32">GeoAPI issue #32</a>.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-@Deprecated
-final class DefaultObjectId implements FeatureId, GmlObjectId, Serializable {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -2877500277700165269L;
-
-    /**
-     * The identifier.
-     */
-    private final String identifier;
-
-    /**
-     * Creates a new identifier.
-     */
-    DefaultObjectId(final String id) {
-        ArgumentChecks.ensureNonNull("id", id);
-        identifier = id;
-    }
-
-    /**
-     * Returns the identifier specified at construction time.
-     */
-    @Override
-    public String getID() {
-        return identifier;
-    }
-
-    /**
-     * Returns {@code true} if the given object is a feature with the identifier expected by this class.
-     */
-    @Override
-    public boolean matches(final Object feature) {
-        if (feature instanceof Feature) try {
-            Object id = ((Feature) feature).getPropertyValue(AttributeConvention.IDENTIFIER);
-            return identifier.equals(String.valueOf(id));
-        } catch (PropertyNotFoundException ex) {
-            // Feature does not contain the identifier property.
-        }
-        return false;
-    }
-
-    /**
-     * Returns {@code true} if the given object is an identifier with equals {@link String} code than this object.
-     */
-    @Override
-    public boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj instanceof DefaultObjectId) {
-            return identifier.equals(((DefaultObjectId) obj).identifier);
-        }
-        return false;
-    }
-
-    /**
-     * Returns a hash code value based on the identifier specified at construction time.
-     */
-    @Override
-    public int hashCode() {
-        return identifier.hashCode() ^ (int) serialVersionUID;
-    }
-
-    /**
-     * Returns a string representation of this identifier.
-     */
-    @Override
-    public String toString() {
-        return "Id:".concat(identifier);
-    }
-}
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
deleted file mode 100644
index 56fc563..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java
+++ /dev/null
@@ -1,102 +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.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.1
- * @since   1.1
- * @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-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java
deleted file mode 100644
index 511b404..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java
+++ /dev/null
@@ -1,136 +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.filter;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Collection;
-import java.util.Collections;
-import org.apache.sis.util.collection.Containers;
-import org.apache.sis.internal.feature.AttributeConvention;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.Id;
-import org.opengis.filter.identity.Identifier;
-
-
-/**
- * Filter features using a set of predefined identifiers and discarding features
- * whose identifier is not in the set.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class FilterByIdentifier extends Node implements Id {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 1404452049863376235L;
-
-    /**
-     * The identifiers of features to retain. Filtering will use the keys. This map contains also
-     * the same identifiers as the original {@link Identifier} objects given at construction time,
-     * but those values are not used by this class.
-     */
-    private final Map<Object,Identifier> identifiers;
-
-    /**
-     * Creates a new filter using the given identifiers.
-     */
-    FilterByIdentifier(final Collection<? extends Identifier> ids) {
-        identifiers = new HashMap<>(Containers.hashMapCapacity(ids.size()));
-        for (Identifier id : ids) {
-            identifiers.put(id.getID(), id);
-        }
-    }
-
-    /**
-     * Returns a name identifying this kind of filter.
-     */
-    @Override
-    protected String getName() {
-        return "Id";
-    }
-
-    /**
-     * Returns the identifiers specified at construction time. This is used for {@link #toString()},
-     * {@link #hashCode()} and {@link #equals(Object)} implementations. Since all the keys in the
-     * {@link #identifiers} map were derived from the values, comparing those values is sufficient
-     * for determining if two {@code FilterByIdentifier} instances are equal.
-     */
-    @Override
-    protected Collection<?> getChildren() {
-        // Can not return identifiers.values() directly because that collection does not implement equals/hashCode.
-        return getIdentifiers();
-    }
-
-    /**
-     * Returns the identifiers of feature instances to accept.
-     */
-    @Override
-    public Set<Object> getIDs() {
-        return Collections.unmodifiableSet(identifiers.keySet());
-    }
-
-    /**
-     * Same identifiers than {@link #getIDs()} but as the instances specified at construction time.
-     * This is not used by this class.
-     */
-    @Override
-    public Set<Identifier> getIdentifiers() {
-        return new HashSet<>(identifiers.values());
-    }
-
-    /**
-     * Returns {@code true} if the given object is a {@link Feature} instance and its identifier
-     * is one of the identifier specified at {@code FilterByIdentifier} construction time.
-     */
-    @Override
-    public boolean evaluate(Object object) {
-        if (object instanceof Feature) try {
-            final Object id = ((Feature) object).getPropertyValue(AttributeConvention.IDENTIFIER);
-            if (identifiers.containsKey(id)) {
-                return true;
-            }
-            if (id != null && !(id instanceof String)) {
-                /*
-                 * Sometime web services specify the identifiers to use for filtering as Strings
-                 * while the types stored in the feature instances is different.
-                 */
-                return identifiers.containsKey(id.toString());
-            }
-        } catch (PropertyNotFoundException ex) {
-            // No identifier property. This is okay.
-        }
-        return false;
-    }
-
-    /**
-     * Implementation of the visitor pattern.
-     */
-    @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/InvalidExpressionException.java b/core/sis-feature/src/main/java/org/apache/sis/filter/InvalidExpressionException.java
deleted file mode 100644
index c5367a1..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/InvalidExpressionException.java
+++ /dev/null
@@ -1,92 +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.filter;
-
-import org.opengis.filter.expression.Expression;
-import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.util.Classes;
-import org.apache.sis.util.Workaround;
-
-
-/**
- * Thrown when an operation can not complete because an expression is illegal or unsupported.
- * The invalid {@link Expression} may be a component of a larger object such as another expression
- * or a query {@link org.apache.sis.storage.Query}.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public class InvalidExpressionException extends RuntimeException {
-    /**
-     * Serial number for inter-operability with different versions.
-     */
-    private static final long serialVersionUID = -2600709421042246855L;
-
-    /**
-     * Constructs an exception with no detail message.
-     */
-    public InvalidExpressionException() {
-    }
-
-    /**
-     * Constructs an exception with the specified detail message.
-     *
-     * @param  message  the detail message.
-     */
-    public InvalidExpressionException(final String message) {
-        super(message);
-    }
-
-    /**
-     * Constructs an exception with the specified detail message and cause.
-     *
-     * @param  message  the detail message.
-     * @param  cause    the cause for this exception.
-     */
-    public InvalidExpressionException(final String message, final Throwable cause) {
-        super(message, cause);
-    }
-
-    /**
-     * Constructs an exception with a message saying that the given expression is illegal or unsupported.
-     * This constructor assumes that the expression was part of a larger object containing many expressions
-     * identified by indices.
-     *
-     * @param  expression  the illegal expression, or {@code null} if unknown.
-     * @param  index       column number (or other kind of index) where the invalid expression has been found.
-     */
-    public InvalidExpressionException(final Expression expression, final int index) {
-        super(message(expression, index));
-    }
-
-    /**
-     * Work around for RFE #4093999 in Sun's bug database
-     * ("Relax constraint on placement of this()/super() call in constructors").
-     */
-    @Workaround(library="JDK", version="1.8")
-    private static String message(final Expression expression, final int index) {
-        final String name;
-        if (expression instanceof Node) {
-            name = ((Node) expression).getName();
-        } else {
-            name = Classes.getShortClassName(expression);
-        }
-        return Resources.format(Resources.Keys.InvalidExpression_2, name, index);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
deleted file mode 100644
index 0133a03..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
+++ /dev/null
@@ -1,259 +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.filter;
-
-import java.util.Map;
-import java.util.Collection;
-import java.util.Collections;
-import org.apache.sis.util.Classes;
-import org.apache.sis.util.iso.Names;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ObjectConverters;
-import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.util.collection.WeakValueHashMap;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
-
-
-/**
- * Expressions that do not depend on any other expression.
- * Those expression may read value from a feature property, or return a constant value.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class LeafExpression extends Node implements Expression, FeatureExpression {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 4262341851590811918L;
-
-    /**
-     * Creates a new property reader.
-     */
-    LeafExpression() {
-    }
-
-    /**
-     * Evaluates the expression for producing a result of the given type.
-     * If this method can not produce a value of the given type, then it returns {@code null}.
-     * This implementation evaluates the expression {@linkplain #evaluate(Object) in the default way},
-     * then tries to convert the result to the target type.
-     *
-     * @param  feature  to feature to evaluate with this expression.
-     * @param  target   the desired type for the expression result.
-     * @return the result, or {@code null} if it can not be of the specified type.
-     */
-    @Override
-    public final <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 e) {
-            warning(e);
-            return null;                    // As per method contract.
-        }
-    }
-
-
-
-
-    /**
-     * 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.
-     */
-    static final class Property extends LeafExpression implements org.opengis.filter.expression.PropertyName {
-        /** For cross-version compatibility. */
-        private static final long serialVersionUID = 3417789380239058201L;
-
-        /** 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. */
-        Property(final String name) {
-            ArgumentChecks.ensureNonNull("name", name);
-            this.name = name;
-        }
-
-        /** Identification of this expression. */
-        @Override protected String getName() {
-            return "PropertyName";
-        }
-
-        /** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */
-        @Override protected Collection<?> getChildren() {
-            return Collections.singleton(name);
-        }
-
-        /** Returns the name of the property whose value will be returned by the {@link #evaluate(Object)} 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 feature, 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) {
-                warning(ex);
-                // Null will be returned below.
-            } else if (candidate instanceof Map<?,?>) {
-                return ((Map<?,?>) candidate).get(name);
-            }
-            return null;
-        }
-
-        /**
-         * Provides the expected type of values produced by this expression when a feature of the given type is evaluated.
-         *
-         * @param  valueType  the type of features to be evaluated by the given expression.
-         * @param  addTo      where to add the type of properties evaluated by the given expression.
-         * @return builder of the added property, or {@code null} if this method can not add a property.
-         * @throws IllegalArgumentException if this method can not determine the property type for the given feature type.
-         */
-        @Override
-        public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-            PropertyType type = valueType.getProperty(name);        // May throw IllegalArgumentException.
-            while (type instanceof Operation) {
-                final IdentifiedType result = ((Operation) type).getResult();
-                if (result != type && result instanceof PropertyType) {
-                    type = (PropertyType) result;
-                } else if (result instanceof FeatureType) {
-                    return addTo.addAssociation((FeatureType) result).setName(name);
-                } else {
-                    return null;
-                }
-            }
-            return addTo.addProperty(type);
-        }
-
-        /** Implementation of the visitor pattern. */
-        @Override public Object accept(final ExpressionVisitor visitor, final Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-
-
-    /**
-     * A constant, literal value that can be used in expressions.
-     * The {@link #evaluate(Object)} method ignores the argument and always returns {@link #getValue()}.
-     */
-    static final class Literal extends LeafExpression implements org.opengis.filter.expression.Literal {
-        /** For cross-version compatibility. */
-        private static final long serialVersionUID = -8383113218490957822L;
-
-        /** The constant value to be returned by {@link #getValue()}. */
-        private final Object value;
-
-        /** Creates a new literal holding the given constant value. */
-        Literal(final Object value) {
-            ArgumentChecks.ensureNonNull("value", value);
-            this.value = value;
-        }
-
-        /** Identification of this expression. */
-        @Override protected String getName() {
-            return "Literal";
-        }
-
-        /** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */
-        @Override protected Collection<?> getChildren() {
-            return Collections.singleton(value);
-        }
-
-        /** Returns the constant value held by this object. */
-        @Override public Object getValue() {
-            return value;
-        }
-
-        /** Expression evaluation, which just returns the constant value. */
-        @Override public Object evaluate(Object ignored) {
-            return value;
-        }
-
-        /**
-         * Provides the type of values returned by {@link #evaluate(Object)}
-         * wrapped in an {@link AttributeType} named "Literal".
-         *
-         * @param  addTo  where to add the type of properties evaluated by the given expression.
-         * @return builder of the added property.
-         */
-        @Override
-        public PropertyTypeBuilder expectedType(FeatureType ignored, final FeatureTypeBuilder addTo) {
-            final Class<?> valueType = value.getClass();
-            AttributeType<?> propertyType = TYPES.get(valueType);
-            if (propertyType == null) {
-                final Class<?> standardType = Classes.getStandardType(valueType);
-                propertyType = TYPES.computeIfAbsent(standardType, Literal::newType);
-                if (valueType != standardType) {
-                    TYPES.put(valueType, propertyType);
-                }
-            }
-            return addTo.addProperty(propertyType);
-        }
-
-        /**
-         * A cache of {@link AttributeType} instances for literal classes. Used for avoiding to create
-         * duplicated instances when the literal is a common type like {@link String} or {@link Integer}.
-         */
-        @SuppressWarnings("unchecked")
-        private static final WeakValueHashMap<Class<?>, AttributeType<?>> TYPES = new WeakValueHashMap<>((Class) Class.class);
-
-        /**
-         * Invoked when a new attribute type need to be created for the given standard type.
-         * The given standard type should be a GeoAPI interface, not the implementation class.
-         */
-        private static <T> AttributeType<T> newType(final Class<T> standardType) {
-            return createType(standardType, Names.createLocalName(null, null, "Literal"));
-        }
-
-        /** Implementation of the visitor pattern. */
-        @Override public Object accept(final ExpressionVisitor visitor, final Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java
deleted file mode 100644
index b93019e..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java
+++ /dev/null
@@ -1,158 +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.filter;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Collection;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-
-// Branch-dependent imports
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterVisitor;
-
-
-/**
- * Logical filter (AND, OR) using an arbitrary number of filters.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class LogicalFunction extends Node {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 3696645262873257479L;
-
-    /**
-     * The filter on which to apply the logical operator.
-     */
-    protected final Filter[] filters;
-
-    /**
-     * Creates a new logical function applied on the given filters.
-     */
-    protected LogicalFunction(final Collection<? extends Filter> f) {
-        ArgumentChecks.ensureNonNull("filters", f);
-        filters = f.toArray(new Filter[f.size()]);
-        for (int i=0; i<filters.length; i++) {
-            ArgumentChecks.ensureNonNullElement("filters", i, filters[i]);
-        }
-        ArgumentChecks.ensureSizeBetween("filters", 2, Integer.MAX_VALUE, filters.length);
-    }
-
-    /**
-     * Returns a list containing all of the child filters of this object.
-     * This list will contain at least two elements.
-     */
-    @Override
-    public final List<Filter> getChildren() {
-        return UnmodifiableArrayList.wrap(filters);
-    }
-
-    /**
-     * Returns a hash code value for this filter.
-     */
-    @Override
-    public final int hashCode() {
-        return getClass().hashCode() ^ Arrays.hashCode(filters);
-    }
-
-    /**
-     * Compares this filter 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()) {
-            return Arrays.equals(filters, ((LogicalFunction) obj).filters);
-        }
-        return false;
-    }
-
-
-    /**
-     * The "And" operation (⋀).
-     */
-    static final class And extends LogicalFunction implements org.opengis.filter.And {
-        /** For cross-version compatibility. */
-        private static final long serialVersionUID = 152892064260384713L;
-
-        /** Creates a new expression for the given filters. */
-        And(final Collection<? extends Filter> filters) {
-            super(filters);
-        }
-
-        /** Returns a name for this filter. */
-        @Override protected String getName() {return "And";}
-        @Override protected char   symbol()  {return filters.length <= 2 ? '∧' : '⋀';}
-
-        /** Implementation of the visitor pattern. */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-
-        /** Executes the logical operation. */
-        @Override public boolean evaluate(final Object object) {
-            for (final Filter filter : filters) {
-                if (!filter.evaluate(object)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-
-    /**
-     * The "Or" operation (⋁).
-     */
-    static final class Or extends LogicalFunction implements org.opengis.filter.Or {
-        /** For cross-version compatibility. */
-        private static final long serialVersionUID = 3805785720811330282L;
-
-        /** Creates a new expression for the given filters. */
-        Or(final Collection<? extends Filter> filters) {
-            super(filters);
-        }
-
-        /** Returns a name for this filter. */
-        @Override protected String getName() {return "Or";}
-        @Override protected char   symbol()  {return filters.length <= 2 ? '∨' : '⋁';}
-
-        /** Implementation of the visitor pattern. */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-
-        /** Executes the logical operation. */
-        @Override public boolean evaluate(final Object object) {
-            for (Filter filter : filters) {
-                if (filter.evaluate(object)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/NamedFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/NamedFunction.java
deleted file mode 100644
index 47cda5b..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/NamedFunction.java
+++ /dev/null
@@ -1,210 +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.filter;
-
-import java.util.List;
-import java.util.Collection;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ObjectConverters;
-import org.apache.sis.util.UnconvertibleObjectException;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.feature.builder.AttributeTypeBuilder;
-
-
-/**
- * Base class of functions having a name and receiving an arbitrary amount of parameters.
- * This class differs from {@link UnaryFunction} and {@link BinaryFunction} in two ways:
- *
- * <ul>
- *   <li>It has a name used for invoking this function. By contrast, the unary and binary functions
- *       are represented by a symbol such as +, and their names are used only for debugging purposes.</li>
- *   <li>The number of parameters is not fixed to 1 (unary functions) or 2 (binary functions).</li>
- * </ul>
- *
- * Subclasses shall override at least the {@link #getName()} method, typically by returning a hard-coded name
- * that depends only on the class. If the name may vary for the same class, then the {@link #equals(Object)}
- * method should also be overridden.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class NamedFunction extends Node implements Function {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 6933519274722660893L;
-
-    /**
-     * The sub-expressions that will be evaluated to provide the parameters to the function.
-     * This list is unmodifiable.
-     *
-     * @see #getParameters()
-     */
-    protected final List<Expression> parameters;
-
-    /**
-     * Creates a new function with the given parameters. This constructor wraps the given array in a list directly
-     * (without defensive copy). it is caller's responsibility to ensure that the given array is non-null, has been
-     * cloned and does not contain null elements. Those steps are done by {@link SQLMM#create(String, Expression...)}.
-     *
-     * @param  parameters  the sub-expressions that will be evaluated to provide the parameters to the function.
-     */
-    NamedFunction(final Expression[] parameters) {
-        this.parameters = UnmodifiableArrayList.wrap(parameters);
-    }
-
-    /**
-     * Returns the name of the function to be called.
-     * For example, this might be "{@code cos}" or "{@code atan2}".
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * implementations typically return a hard-coded value. If the returned value may vary for the same class,
-     * then implementers should override also the {@link #equals(Object)} and {@link #hashCode()} methods.</div>
-     *
-     * @return the name of this function.
-     */
-    @Override
-    public abstract String getName();
-
-    /**
-     * Returns the sub-expressions that will be evaluated to provide the parameters to the function.
-     *
-     * @return the sub-expressions providing parameter values.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Safe because list is unmodifiable.
-    public final List<Expression> getParameters() {
-        return parameters;
-    }
-
-    /**
-     * Returns the children of this node, which are the {@linkplain #getParameters() parameters list}.
-     * This is used for information purpose only, for example in order to build a string representation.
-     *
-     * @return the children of this node.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Safe because list is unmodifiable.
-    protected final Collection<?> getChildren() {
-        return parameters;
-    }
-
-    /**
-     * Returns the default value to use if an implementation for this function is not available.
-     * The default implementation returns {@code null}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * implementations typically return a hard-coded value. If the returned value may vary for the same class,
-     * then implementers should override also the {@link #equals(Object)} and {@link #hashCode()} methods.</div>
-     *
-     * @return literal to use if an implementation for this function is not available, or {@code null} if none.
-     */
-    @Override
-    public Literal getFallbackValue() {
-        return null;
-    }
-
-    /**
-     * Evaluates the function for producing a result of the given type.
-     * If this method can not produce a value of the given type, then it returns {@code null}.
-     * The default implementation evaluates the expression {@linkplain #evaluate(Object) in the default way},
-     * then tries to convert the result to the target type.
-     *
-     * @param  object  to object to evaluate with this expression.
-     * @param  target  the desired type for the expression result.
-     * @return the result, or {@code null} if it can not be of the specified type.
-     */
-    @Override
-    public <T> T evaluate(final Object object, final Class<T> target) {
-        ArgumentChecks.ensureNonNull("target", target);
-        final Object value = evaluate(object);
-        try {
-            return ObjectConverters.convert(value, target);
-        } catch (UnconvertibleObjectException e) {
-            warning(e);
-            return null;                    // As per method contract.
-        }
-    }
-
-    /**
-     * Copies into the given builder the property type evaluated by the expression at the specified index.
-     * The property type is determined by the heuristic rules documented in {@link FeatureExpression}.
-     * This method returns the the property type builder for allowing caller to modify the property.
-     *
-     * @param  parameter  index of the expression for which to get the result type.
-     * @param  valueType  the type of features to be evaluated by the given expression.
-     * @param  addTo      where to add the type of properties evaluated by the specified expression.
-     * @return builder of type resulting from expression evaluation (never null).
-     * @throws IllegalArgumentException if this method can operate only on some feature types
-     *         and the given type is not one of them.
-     * @throws InvalidExpressionException if this method can not determine the result type of the expression
-     *         at given index. It may be because that expression is backed by an unsupported implementation.
-     */
-    final PropertyTypeBuilder copyType(final int parameter, final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        final Expression expression = parameters.get(parameter);
-        final PropertyTypeBuilder pt = FeatureExpression.expectedType(expression, valueType, addTo);
-        if (pt == null) {
-            throw new InvalidExpressionException(expression, parameter);
-        }
-        return pt;
-    }
-
-    /**
-     * Copies into the given builder the property type evaluated by the first expression.
-     * That property must be an attribute storing geometric object, otherwise an
-     * {@link IllegalArgumentException} will be thrown.
-     *
-     * @param  valueType  the type of features to be evaluated by the given expression.
-     * @param  addTo      where to add the type of properties evaluated by the specified expression.
-     * @return builder of type resulting from expression evaluation (never null).
-     * @throws IllegalArgumentException if the given feature type does not contain the expected properties.
-     * @throws InvalidExpressionException if this method can not determine the result type of the expression
-     *         at given index. It may be because that expression is backed by an unsupported implementation.
-     */
-    final AttributeTypeBuilder<?> copyGeometryType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        final PropertyTypeBuilder type = copyType(0, valueType, addTo);
-        if (type instanceof AttributeTypeBuilder<?>) {
-            AttributeTypeBuilder<?> att = (AttributeTypeBuilder<?>) type;
-            if (Geometries.isKnownType(att.getValueClass())) {
-                return att;
-            }
-        }
-        throw new IllegalArgumentException(Resources.format(Resources.Keys.NotAGeometryAtFirstExpression));
-    }
-
-    /**
-     * Implementation of the visitor pattern.
-     * Not used in Apache SIS implementation.
-     */
-    @Override
-    public Object accept(final ExpressionVisitor visitor, final Object extraData) {
-        return visitor.visit(this, extraData);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Node.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Node.java
deleted file mode 100644
index daea5fa..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Node.java
+++ /dev/null
@@ -1,174 +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.filter;
-
-import java.util.Map;
-import java.util.IdentityHashMap;
-import java.util.Collection;
-import java.io.Serializable;
-import java.util.Collections;
-import org.apache.sis.feature.DefaultAttributeType;
-import org.apache.sis.util.collection.DefaultTreeTable;
-import org.apache.sis.util.collection.TableColumn;
-import org.apache.sis.util.collection.TreeTable;
-import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.logging.Logging;
-import org.apache.sis.internal.system.Loggers;
-
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.BinaryLogicOperator;
-
-
-/**
- * Base class of Apache SIS implementation of OGC expressions, comparators or filters.
- * {@code Node} instances are associated together in a tree, which can be formatted
- * by {@link #toString()}.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class Node implements Serializable {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -749201100175374658L;
-
-    /**
-     * Creates a new expression, operator or filter.
-     */
-    protected Node() {
-    }
-
-    /**
-     * Creates an attribute type for values of the given type and name.
-     * The attribute is mandatory, unbounded and has no default value.
-     *
-     * @param  type  type of values in the attribute.
-     * @param  name  name of the attribute to create.
-     * @return an attribute of the given type and name.
-     */
-    static <T> AttributeType<T> createType(final Class<T> type, final Object name) {
-        return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),
-                                          type, 1, 1, null, (AttributeType<?>[]) null);
-    }
-
-    /**
-     * Returns the mathematical symbol for this binary function.
-     * For comparison operators, the symbol should be one of {@literal < > ≤ ≥ = ≠}.
-     * For arithmetic operators, the symbol should be one of {@literal + − × ÷}.
-     *
-     * @return the mathematical symbol, or 0 if none.
-     */
-    protected char symbol() {
-        return (char) 0;
-    }
-
-    /**
-     * Returns a name or symbol for this node. This is used for information purpose,
-     * for example in order to build a string representation.
-     *
-     * @return the name of this node.
-     */
-    protected abstract String getName();
-
-    /**
-     * Returns the children of this node, or an empty collection if none. This is used
-     * for information purpose, for example in order to build a string representation.
-     *
-     * <p>The name of this method is the same as {@link BinaryLogicOperator#getChildren()}
-     * in order to have only one method to override.</p>
-     *
-     * @return the children of this node, or an empty collection if none.
-     */
-    protected abstract Collection<?> getChildren();
-
-    /**
-     * Builds a tree representation of this node, including all children. This method expects an
-     * initially empty node, which will be set to the {@linkplain #getName() name} of this node.
-     * Then all children will be appended recursively, with a check against cyclic graph.
-     *
-     * @param  root     where to create a tree representation of this node.
-     * @param  visited  nodes already visited. This method will write in this map.
-     */
-    private void toTree(final TreeTable.Node root, final Map<Object,Boolean> visited) {
-        root.setValue(TableColumn.VALUE, getName());
-        for (final Object child : getChildren()) {
-            final TreeTable.Node node = root.newChild();
-            final String value;
-            if (child instanceof Node) {
-                if (visited.putIfAbsent(child, Boolean.TRUE) == null) {
-                    ((Node) child).toTree(node, visited);
-                    continue;
-                } else {
-                    value = Vocabulary.format(Vocabulary.Keys.CycleOmitted);
-                }
-            } else {
-                value = String.valueOf(child);
-            }
-            node.setValue(TableColumn.VALUE, value);
-        }
-    }
-
-    /**
-     * Returns a string representation of this node. This representation can be printed
-     * to the {@linkplain System#out standard output stream} (for example) if it uses a
-     * monospaced font and supports Unicode.
-     *
-     * @return a string representation of this filter.
-     */
-    @Override
-    public final String toString() {
-        final DefaultTreeTable table = new DefaultTreeTable(TableColumn.VALUE);
-        toTree(table.getRoot(), new IdentityHashMap<>());
-        return table.toString();
-    }
-
-    /**
-     * Returns a hash code value computed from the class and the children.
-     */
-    @Override
-    public int hashCode() {
-        return getClass().hashCode() + 37 * getChildren().hashCode();
-    }
-
-    /**
-     * Returns {@code true} if the given object is an instance of the same class with the equal children.
-     */
-    @Override
-    public boolean equals(final Object other) {
-        if (other != null && other.getClass() == getClass()) {
-            return getChildren().equals(((Node) other).getChildren());
-        }
-        return false;
-    }
-
-    /**
-     * Reports that an operation failed because of the given exception.
-     * This method assumes that the warning occurred in an {@code evaluate(…)} method.
-     *
-     * @todo Consider defining a {@code Context} class providing, among other information, listeners where to report warnings.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-460">SIS-460</a>
-     */
-    final void warning(final Exception e) {
-        Logging.recoverableException(Logging.getLogger(Loggers.FILTER), getClass(), "evaluate", e);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/SQLMM.java b/core/sis-feature/src/main/java/org/apache/sis/filter/SQLMM.java
deleted file mode 100644
index 8421887..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/SQLMM.java
+++ /dev/null
@@ -1,91 +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.filter;
-
-import java.util.Arrays;
-import java.util.Collection;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Function;
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.feature.FunctionRegister;
-import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.util.ArgumentChecks;
-
-
-/**
- * A register of functions defined by the SQL/MM standard.
- * This standard is defined by <a href="https://www.iso.org/standard/60343.html">ISO/IEC 13249-3:2016
- * Information technology — Database languages — SQL multimedia and application packages — Part 3: Spatial</a>.
- *
- * @todo Implement all SQL/MM specification functions.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class SQLMM implements FunctionRegister {
-    /**
-     * Creates the default register.
-     */
-    SQLMM() {
-    }
-
-    /**
-     * Returns a unique name for this factory.
-     */
-    @Override
-    public String getIdentifier() {
-        return "SQL/MM";
-    }
-
-    /**
-     * Returns the names of all functions known to this register.
-     */
-    @Override
-    public Collection<String> getNames() {
-        return Arrays.asList(ST_Transform.NAME, ST_Centroid.NAME, ST_Buffer.NAME);
-    }
-
-    /**
-     * Create a new function of the given name with given parameters.
-     *
-     * @param  name        name of the function to create.
-     * @param  parameters  function parameters.
-     * @return function for the given name and parameters.
-     * @throws IllegalArgumentException if function name is unknown or some parameters are illegal.
-     */
-    @Override
-    public Function create(final String name, Expression[] parameters) {
-        ArgumentChecks.ensureNonNull("name", name);
-        ArgumentChecks.ensureNonNull("parameters", parameters);
-        parameters = parameters.clone();
-        for (int i=0; i<parameters.length; i++) {
-            ArgumentChecks.ensureNonNullElement("parameters", i, parameters[i]);
-        }
-        try {
-            switch (name) {
-                case ST_Transform.NAME: return new ST_Transform(parameters);
-                case ST_Centroid.NAME:  return new ST_Centroid(parameters);
-                case ST_Buffer.NAME:    return new ST_Buffer(parameters);
-                default: throw new IllegalArgumentException(Resources.format(Resources.Keys.UnknownFunction_1, name));
-            }
-        } catch (FactoryException e) {
-            throw new IllegalArgumentException(e);
-        }
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Buffer.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Buffer.java
deleted file mode 100644
index b2a8652..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Buffer.java
+++ /dev/null
@@ -1,99 +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.filter;
-
-import org.apache.sis.feature.builder.AttributeTypeBuilder;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.util.ArgumentChecks;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.expression.Expression;
-
-
-/**
- * An expression which computes a geometry buffer.
- * This expression expects two arguments:
- *
- * <ol class="verbose">
- *   <li>An expression returning a geometry object. The evaluated value shall be an instance of
- *       one of the implementations enumerated in {@link org.apache.sis.setup.GeometryLibrary}.</li>
- *   <li>An expression returning a distance. The evaluated value shall be an instance of {@link Number}.
- *       Distance is expressed in units of the geometry Coordinate Reference System.</li>
- * </ol>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class ST_Buffer extends NamedFunction implements FeatureExpression {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 294412677972203232L;
-
-    /**
-     * Name of this function as defined by SQL/MM standard.
-     */
-    static final String NAME = "ST_Buffer";
-
-    /**
-     * Creates a new function with the given parameters. It is caller's responsibility to ensure
-     * that the given array is non-null, has been cloned and does not contain null elements.
-     *
-     * @throws IllegalArgumentException if the number of arguments is not equal to 2.
-     */
-    ST_Buffer(final Expression[] parameters) {
-        super(parameters);
-        ArgumentChecks.ensureExpectedCount("parameters", 2, parameters.length);
-    }
-
-    /**
-     * Returns the name of this function, which is {@value #NAME}.
-     */
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    /**
-     * Evaluates the first expression as a geometry object, gets the buffer of that geometry and
-     * returns the result. If the geometry is not a supported implementation, returns {@code null}.
-     */
-    @Override
-    public Object evaluate(final Object value) {
-        return Geometries.buffer(parameters.get(0).evaluate(value),
-                                 parameters.get(1).evaluate(value, Number.class).doubleValue());
-    }
-
-    /**
-     * Provides the type of values produced by this expression when a feature of the given type is evaluated.
-     *
-     * @param  valueType  the type of features on which to apply this expression.
-     * @param  addTo      where to add the type of properties evaluated by this expression.
-     * @return builder of type resulting from expression evaluation (never null).
-     * @throws IllegalArgumentException if the given feature type does not contain the expected properties.
-     */
-    @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        final AttributeTypeBuilder<?> pt = copyGeometryType(valueType, addTo);
-        return pt.setValueClass(Geometries.implementation(pt.getValueClass()).rootClass);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Centroid.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Centroid.java
deleted file mode 100644
index ec9d7bf..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Centroid.java
+++ /dev/null
@@ -1,96 +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.filter;
-
-import org.apache.sis.feature.builder.AttributeTypeBuilder;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.util.ArgumentChecks;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.expression.Expression;
-
-
-/**
- * An expression which computes the centroid of a geometry.
- * This expression expects one argument:
- *
- * <ol class="verbose">
- *   <li>An expression returning a geometry object. The evaluated value shall be an instance of
- *       one of the implementations enumerated in {@link org.apache.sis.setup.GeometryLibrary}.</li>
- * </ol>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class ST_Centroid extends NamedFunction implements FeatureExpression {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -3993074123019061170L;
-
-    /**
-     * Name of this function as defined by SQL/MM standard.
-     */
-    static final String NAME = "ST_Centroid";
-
-    /**
-     * Creates a new function with the given parameters. It is caller's responsibility to ensure
-     * that the given array is non-null, has been cloned and does not contain null elements.
-     *
-     * @throws IllegalArgumentException if the number of arguments is not equal to 1.
-     */
-    ST_Centroid(final Expression[] parameters) {
-        super(parameters);
-        ArgumentChecks.ensureExpectedCount("parameters", 1, parameters.length);
-    }
-
-    /**
-     * Returns the name of this function, which is {@value #NAME}.
-     */
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    /**
-     * Evaluates the first expression as a geometry object, gets the centroid of that geometry and
-     * returns the result. If the geometry is not a supported implementation, returns {@code null}.
-     */
-    @Override
-    public Object evaluate(final Object value) {
-        return Geometries.getCentroid(parameters.get(0).evaluate(value));
-    }
-
-    /**
-     * Provides the type of values produced by this expression when a feature of the given type is evaluated.
-     *
-     * @param  valueType  the type of features on which to apply this expression.
-     * @param  addTo      where to add the type of properties evaluated by this expression.
-     * @return builder of type resulting from expression evaluation (never null).
-     * @throws IllegalArgumentException if the given feature type does not contain the expected properties.
-     */
-    @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        final AttributeTypeBuilder<?> pt = copyGeometryType(valueType, addTo);
-        return pt.setValueClass(Geometries.implementation(pt.getValueClass()).pointClass);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Transform.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Transform.java
deleted file mode 100644
index ed29ce8..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Transform.java
+++ /dev/null
@@ -1,203 +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.filter;
-
-import java.util.Objects;
-import java.io.IOException;
-import java.io.InvalidObjectException;
-import java.io.ObjectInputStream;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Literal;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.util.FactoryException;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.internal.util.Constants;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.resources.Errors;
-
-
-/**
- * An expression which transforms a geometry from one CRS to another CRS.
- * This expression expects two arguments:
- *
- * <ol class="verbose">
- *   <li>An expression returning a geometry object. The evaluated value shall be an instance of
- *       one of the implementations enumerated in {@link org.apache.sis.setup.GeometryLibrary}.</li>
- *   <li>An expression returning the target CRS. This is typically a {@link Literal}, i.e. a constant for all geometries,
- *       but this implementation allows the expression to return different CRS for different geometries
- *       for example depending on the number of dimensions. This CRS can be specified in different ways:
- *     <ul>
- *       <li>As a {@link CoordinateReferenceSystem} instance.</li>
- *       <li>As a {@link String} instance of the form {@code "EPSG:xxxx"}, a URL or a URN.</li>
- *       <li>As an {@link Integer} instance specifying an EPSG code.</li>
- *     </ul>
- *   </li>
- * </ol>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-final class ST_Transform extends NamedFunction implements FeatureExpression {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = -5769818355081378907L;
-
-    /**
-     * Name of this function as defined by SQL/MM standard.
-     */
-    static final String NAME = "ST_Transform";
-
-    /**
-     * Identifier of the coordinate reference system in which to transform the geometry.
-     * This identifier is specified by the second expression and is stored in order to
-     * avoid computing {@link #targetCRS} many times when the SRID does not change.
-     */
-    private transient Object srid;
-
-    /**
-     * The coordinate reference system in which to transform the geometry, or {@code null}
-     * if not yet determined. This field is recomputed when the {@link #srid} change.
-     */
-    private transient CoordinateReferenceSystem targetCRS;
-
-    /**
-     * Whether the {@link #targetCRS} is defined by a literal.
-     * If {@code true}, then {@link #targetCRS} shall be effectively final.
-     */
-    private final boolean literalCRS;
-
-    /**
-     * Creates a new function with the given parameters. It is caller's responsibility to ensure
-     * that the given array is non-null, has been cloned and does not contain null elements.
-     *
-     * @throws IllegalArgumentException if the number of arguments is not equal to 2.
-     * @throws FactoryException if CRS can not be constructed from the second expression.
-     */
-    ST_Transform(final Expression[] parameters) throws FactoryException {
-        super(parameters);
-        ArgumentChecks.ensureExpectedCount("parameters", 2, parameters.length);
-        final Expression crs = parameters[1];
-        literalCRS = (crs instanceof Literal);
-        if (literalCRS) {
-            setTargetCRS(((Literal) crs).getValue());
-        }
-    }
-
-    /**
-     * Returns the name of this function, which is {@value #NAME}.
-     */
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    /**
-     * Invoked on deserialization for restoring the {@link #targetCRS} field.
-     *
-     * @param  in  the input stream from which to deserialize an attribute.
-     * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
-     * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
-     */
-    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
-        in.defaultReadObject();
-        if (literalCRS) try {
-            setTargetCRS(((Literal) parameters.get(1)).getValue());
-        } catch (FactoryException e) {
-            throw (IOException) new InvalidObjectException(e.getLocalizedMessage()).initCause(e);
-        }
-    }
-
-    /**
-     * Sets {@link #targetCRS} to a coordinate reference system inferred from the given value.
-     * The CRS argument shall be the result of {@code parameters.get(1).evaluate(object)}.
-     *
-     * @throws FactoryException if no CRS can be created from the given object.
-     */
-    private void setTargetCRS(final Object crs) throws FactoryException {
-        if (crs instanceof CoordinateReferenceSystem) {
-            targetCRS = (CoordinateReferenceSystem) crs;
-        } else {
-            final String code;
-            if (crs instanceof String) {
-                code = (String) crs;
-            } else if (crs instanceof Integer) {
-                code = Constants.EPSG + ':' + crs;
-            } else {
-                throw new InvalidGeodeticParameterException(crs == null
-                        ? Errors.format(Errors.Keys.UnspecifiedCRS)
-                        : Errors.format(Errors.Keys.IllegalCRSType_1, crs.getClass()));
-            }
-            targetCRS = CRS.forCode(code);
-        }
-        srid = crs;
-    }
-
-    /**
-     * Evaluates the first expression as a geometry object, transforms that geometry to the CRS given
-     * by the second expression and returns the result.
-     *
-     * @param  value  the object from which to get a geometry.
-     * @return the transformed geometry, or {@code null} if the given object is not an instance of
-     *         a supported geometry library (JTS, ERSI, Java2D…).
-     */
-    @Override
-    public Object evaluate(final Object value) {
-        Object geometry = parameters.get(0).evaluate(value);
-        if (geometry != null) try {
-            final CoordinateReferenceSystem targetCRS;
-            if (literalCRS) {
-                targetCRS = this.targetCRS;             // No need to synchronize because effectively final.
-            } else {
-                final Object crs = parameters.get(1).evaluate(value);
-                synchronized (this) {
-                    if (!Objects.equals(crs, srid)) {
-                        setTargetCRS(crs);
-                    }
-                    targetCRS = this.targetCRS;         // Must be inside synchronized block.
-                }
-            }
-            return Geometries.transform(geometry, targetCRS);
-        } catch (UnsupportedOperationException | FactoryException | TransformException e) {
-            warning(e);
-        }
-        return null;
-    }
-
-    /**
-     * Provides the type of values produced by this expression when a feature of the given type is evaluated.
-     *
-     * @param  valueType  the type of features on which to apply this expression.
-     * @param  addTo      where to add the type of properties evaluated by this expression.
-     * @return builder of type resulting from expression evaluation (never null).
-     * @throws IllegalArgumentException if the given feature type does not contain the expected properties.
-     */
-    @Override
-    public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        return copyGeometryType(valueType, addTo).setCRS(literalCRS ? targetCRS : null);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFunction.java
deleted file mode 100644
index 61c9e24..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/TemporalFunction.java
+++ /dev/null
@@ -1,722 +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.filter;
-
-import java.util.Date;
-import java.time.Instant;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.temporal.BinaryTemporalOperator;
-import org.opengis.temporal.Period;
-import org.apache.sis.math.Fraction;
-
-
-
-/**
- * Temporal operations between a period and an instant.
- * The nature of the operation depends on the subclass.
- * Subclasses shall override at least one of following methods:
- *
- * <ul>
- *   <li>{@link #evaluate(Instant, Instant)}</li>
- *   <li>{@link #evaluate(Period, Instant)}</li>
- *   <li>{@link #evaluate(Period, Period)}</li>
- * </ul>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-abstract class TemporalFunction extends BinaryFunction implements BinaryTemporalOperator {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 5392780837658687513L;
-
-    /**
-     * Creates a new temporal function.
-     *
-     * @param  expression1  the first of the two expressions to be used by this function.
-     * @param  expression2  the second of the two expressions to be used by this function.
-     */
-    TemporalFunction(final Expression expression1, final Expression expression2) {
-        super(expression1, expression2);
-    }
-
-    /**
-     * Converts a GeoAPI instant to a Java instant. This is a temporary method
-     * to be removed after we revisited {@link org.opengis.temporal} package.
-     *
-     * @param  instant  the GeoAPI instant, or {@code null}.
-     * @return the Java instant, or {@code null}.
-     */
-    private static Instant toInstant(final org.opengis.temporal.Instant instant) {
-        if (instant != null) {
-            final Date t = instant.getDate();
-            if (t != null) {
-                return t.toInstant();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns {@code true} if {@code self} is non null and before {@code other}.
-     * This is an helper function for {@code evaluate(…)} methods implementations.
-     */
-    private static boolean isBefore(final org.opengis.temporal.Instant self, final Instant other) {
-        final Instant t = toInstant(self);
-        return (t != null) && t.isBefore(other);
-    }
-
-    /**
-     * Returns {@code true} if {@code self} is non null and after {@code other}.
-     * This is an helper function for {@code evaluate(…)} methods implementations.
-     */
-    private static boolean isAfter(final org.opengis.temporal.Instant self, final Instant other) {
-        final Instant t = toInstant(self);
-        return (t != null) && t.isAfter(other);
-    }
-
-    /**
-     * Returns {@code true} if {@code self} is non null and equal to {@code other}.
-     * This is an helper function for {@code evaluate(…)} methods implementations.
-     */
-    private static boolean isEqual(final org.opengis.temporal.Instant self, final Instant other) {
-        final Instant t = toInstant(self);
-        return (t != null) && t.equals(other);
-    }
-
-    /**
-     * Returns {@code true} if {@code self} is non null and before {@code other}.
-     * This is an helper function for {@code evaluate(…)} methods implementations.
-     */
-    private static boolean isBefore(final org.opengis.temporal.Instant self,
-                                    final org.opengis.temporal.Instant other)
-    {
-        final Instant t, o;
-        return ((t = toInstant(self)) != null) && ((o = toInstant(other)) != null) && t.isBefore(o);
-    }
-
-    /**
-     * Returns {@code true} if {@code self} is non null and after {@code other}.
-     * This is an helper function for {@code evaluate(…)} methods implementations.
-     */
-    private static boolean isAfter(final org.opengis.temporal.Instant self,
-                                   final org.opengis.temporal.Instant other)
-    {
-        final Instant t, o;
-        return ((t = toInstant(self)) != null) && ((o = toInstant(other)) != null) && t.isAfter(o);
-    }
-
-    /**
-     * Returns {@code true} if {@code self} is non null and equal to {@code other}.
-     * This is an helper function for {@code evaluate(…)} methods implementations.
-     */
-    private static boolean isEqual(final org.opengis.temporal.Instant self,
-                                   final org.opengis.temporal.Instant other)
-    {
-        final Instant t = toInstant(self);
-        return (t != null) && t.equals(toInstant(other));
-    }
-
-    /**
-     * Determines if the test(s) represented by this filter passes with the given operands.
-     * Values of {@link #expression1} and {@link #expression2} shall be two single values.
-     */
-    @Override
-    public final boolean evaluate(final Object candidate) {
-        final Object left = expression1.evaluate(candidate);
-        if (left instanceof Period) {
-            final Object right = expression2.evaluate(candidate);
-            if (right instanceof Period) {
-                return evaluate((Period) left, (Period) right);
-            }
-            final Instant t = ComparisonFunction.toInstant(right);
-            if (t != null) {
-                return evaluate((Period) left, t);
-            }
-        } else {
-            final Instant t = ComparisonFunction.toInstant(left);
-            if (t != null) {
-                final Instant t2 = ComparisonFunction.toInstant(expression2.evaluate(candidate));
-                if (t2 != null) {
-                    return evaluate(t, t2);
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Evaluates the filter between two instants.
-     * Both arguments given to this method are non-null.
-     * The {@code self} and {@code other} argument names are chosen to match ISO 19108 tables.
-     */
-    protected boolean evaluate(Instant self, Instant other) {
-        return false;
-    }
-
-    /**
-     * Evaluates the filter between a period and an instant.
-     * Both arguments given to this method are non-null, but period begin or end instant may be null.
-     * The {@code self} and {@code other} argument names are chosen to match ISO 19108 tables.
-     */
-    protected boolean evaluate(Period self, Instant other) {
-        return false;
-    }
-
-    /**
-     * Evaluates the filter between two periods.
-     * Both arguments given to this method are non-null, but period begin or end instant may be null.
-     * The {@code self} and {@code other} argument names are chosen to match ISO 19108 tables.
-     */
-    protected boolean evaluate(Period self, Period other) {
-        return false;
-    }
-
-    /**
-     * No operation on numbers for now. We could revisit this policy in a future version if we
-     * allow the temporal function to have a CRS and to operate on temporal coordinate values.
-     */
-    @Override protected Number applyAsLong    (long       left, long       right) {return null;}
-    @Override protected Number applyAsDouble  (double     left, double     right) {return null;}
-    @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return null;}
-    @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return null;}
-    @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return null;}
-
-
-    /**
-     * The {@value #NAME} (=) filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self = other}</li>
-     *   <li>{@literal self.begin = other.begin  AND  self.end = other.end}</li>
-     * </ul>
-     */
-    static final class Equals extends TemporalFunction implements org.opengis.filter.temporal.TEquals {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -6060822291802339424L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Equals(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '=';}
-
-        /** Condition defined by ISO 19108:2002 §5.2.3.5. */
-        @Override protected boolean evaluate(final Instant self, final Instant other) {
-            return self.equals(other);
-        }
-
-        /** Extension to ISO 19108: handle instant as a tiny period. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isEqual(self.getBeginning(), other) &&
-                   isEqual(self.getEnding(),    other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual(self.getBeginning(), other.getBeginning()) &&
-                   isEqual(self.getEnding(),    other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} {@literal (<)} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self     < other}</li>
-     *   <li>{@literal self.end < other}</li>
-     *   <li>{@literal self.end < other.begin}</li>
-     * </ul>
-     */
-    static final class Before extends TemporalFunction implements org.opengis.filter.temporal.Before {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -3422629447456003982L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Before(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '<';}
-
-        /** Condition defined by ISO 19108:2002 §5.2.3.5. */
-        @Override protected boolean evaluate(final Instant self, final Instant other) {
-            return self.isBefore(other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isBefore(self.getEnding(), other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isBefore(self.getEnding(), other.getBeginning());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} {@literal (>)} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self       > other}</li>
-     *   <li>{@literal self.begin > other}</li>
-     *   <li>{@literal self.begin > other.end}</li>
-     * </ul>
-     */
-    static final class After extends TemporalFunction implements org.opengis.filter.temporal.After {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 5410476260417497682L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        After(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '>';}
-
-        /** Condition defined by ISO 19108:2002 §5.2.3.5. */
-        @Override protected boolean evaluate(final Instant self, final Instant other) {
-            return self.isAfter(other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isAfter(self.getBeginning(), other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isAfter(self.getBeginning(), other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin = other.begin  AND  self.end < other.end}</li>
-     * </ul>
-     */
-    static final class Begins extends TemporalFunction implements org.opengis.filter.temporal.Begins {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -7880699329127762233L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Begins(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual (self.getBeginning(), other.getBeginning()) &&
-                   isBefore(self.getEnding(),    other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin > other.begin  AND  self.end = other.end}</li>
-     * </ul>
-     */
-    static final class Ends extends TemporalFunction implements org.opengis.filter.temporal.Ends {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -5508229966320563437L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Ends(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual(self.getEnding(),    other.getEnding()) &&
-                   isAfter(self.getBeginning(), other.getBeginning());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin = other}</li>
-     *   <li>{@literal self.begin = other.begin  AND  self.end > other.end}</li>
-     * </ul>
-     */
-    static final class BegunBy extends TemporalFunction implements org.opengis.filter.temporal.BegunBy {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -7212413827394364384L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        BegunBy(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isEqual(self.getBeginning(), other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual(self.getBeginning(), other.getBeginning()) &&
-                   isAfter(self.getEnding(),    other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.end = other}</li>
-     *   <li>{@literal self.begin < other.begin  AND  self.end = other.end}</li>
-     * </ul>
-     */
-    static final class EndedBy extends TemporalFunction implements org.opengis.filter.temporal.EndedBy {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 8586566103462153666L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        EndedBy(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isEqual(self.getEnding(), other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual (self.getEnding(),    other.getEnding()) &&
-                   isBefore(self.getBeginning(), other.getBeginning());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.end = other.begin}</li>
-     * </ul>
-     */
-    static final class Meets extends TemporalFunction implements org.opengis.filter.temporal.Meets {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -3534843269384858443L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Meets(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Extension to ISO 19108: handle instant as a tiny period. */
-        @Override public boolean evaluate(final Instant self, final Instant other) {
-            return self.equals(other);
-        }
-
-        /** Extension to ISO 19108: handle instant as a tiny period. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isEqual(self.getEnding(), other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual(self.getEnding(), other.getBeginning());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin = other.end}</li>
-     * </ul>
-     */
-    static final class MetBy extends TemporalFunction implements org.opengis.filter.temporal.MetBy {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 5358059498707330482L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        MetBy(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Extension to ISO 19108: handle instant as a tiny period. */
-        @Override public boolean evaluate(final Instant self, final Instant other) {
-            return self.equals(other);
-        }
-
-        /** Extension to ISO 19108: handle instant as a tiny period. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isEqual(self.getBeginning(), other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isEqual(self.getBeginning(), other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin > other.begin  AND  self.end < other.end}</li>
-     * </ul>
-     */
-    static final class During extends TemporalFunction implements org.opengis.filter.temporal.During {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = -4674319635076886196L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        During(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '⊊';}      // `self` is a proper (or strict) subset of `other`.
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isAfter (self.getBeginning(), other.getBeginning()) &&
-                   isBefore(self.getEnding(),    other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin < other AND self.end > other}</li>
-     *   <li>{@literal self.begin < other.begin  AND  self.end > other.end}</li>
-     * </ul>
-     */
-    static final class Contains extends TemporalFunction implements org.opengis.filter.temporal.TContains {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 9107531246948034411L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Contains(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @Override protected char   symbol()  {return '⊋';}      // `self` is a proper (or strict) superset of `other`.
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Instant other) {
-            return isBefore(self.getBeginning(), other) &&
-                   isAfter (self.getEnding(),    other);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            return isBefore(self.getBeginning(), other.getBeginning()) &&
-                   isAfter (self.getEnding(),    other.getEnding());
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin < other.begin  AND  self.end > other.begin  AND  self.end < other.end}</li>
-     * </ul>
-     */
-    static final class Overlaps extends TemporalFunction implements org.opengis.filter.temporal.TOverlaps {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 1517443045593389773L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        Overlaps(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            final Instant selfEnd, otherBegin;
-            return ((selfEnd    = toInstant(self .getEnding()))    != null) &&
-                   ((otherBegin = toInstant(other.getBeginning())) != null) && selfEnd.isAfter(otherBegin) &&
-                   isBefore(self.getBeginning(), otherBegin) &&
-                   isAfter(other.getEnding(),    selfEnd);
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter. Defined by ISO 19108 as:
-     * <ul>
-     *   <li>{@literal self.begin > other.begin  AND  self.begin < other.end  AND  self.end > other.end}</li>
-     * </ul>
-     */
-    static final class OverlappedBy extends TemporalFunction implements org.opengis.filter.temporal.OverlappedBy {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 2228673820507226463L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        OverlappedBy(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-
-        /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            final Instant selfBegin, otherEnd;
-            return ((selfBegin = toInstant(self .getBeginning())) != null) &&
-                   ((otherEnd  = toInstant(other.getEnding()))    != null) && selfBegin.isBefore(otherEnd) &&
-                   isBefore(other.getBeginning(), selfBegin) &&
-                   isAfter (self .getEnding(),    otherEnd);
-        }
-    }
-
-
-    /**
-     * The {@value #NAME} filter.
-     * This is a shortcut for NOT (Before OR Meets OR MetBy OR After).
-     */
-    static final class AnyInteracts extends TemporalFunction implements org.opengis.filter.temporal.AnyInteracts {
-        /** For cross-version compatibility during (de)serialization. */
-        private static final long serialVersionUID = 5972351564286442392L;
-
-        /** Creates a new filter for the {@value #NAME} operation. */
-        AnyInteracts(Expression expression1, Expression expression2) {
-            super(expression1, expression2);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-
-        /** Condition defined by OGC filter specification. */
-        @Override public boolean evaluate(final Period self, final Period other) {
-            final Instant selfBegin, selfEnd, otherBegin, otherEnd;
-            return ((selfBegin  = toInstant(self .getBeginning())) != null) &&
-                   ((otherEnd   = toInstant(other.getEnding()))    != null) && selfBegin.isBefore(otherEnd) &&
-                   ((selfEnd    = toInstant(self .getEnding()))    != null) &&
-                   ((otherBegin = toInstant(other.getBeginning())) != null) && selfEnd.isAfter(otherBegin);
-        }
-
-        /** Implementation of the visitor pattern (not used by Apache SIS). */
-        @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/UnaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
deleted file mode 100644
index c9621ba..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/UnaryFunction.java
+++ /dev/null
@@ -1,170 +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.filter;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-import org.apache.sis.util.ArgumentChecks;
-import org.opengis.filter.Filter;
-
-// Branch-dependent imports
-import org.opengis.filter.FilterVisitor;
-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.1
- * @since   1.1
- * @module
- */
-abstract class UnaryFunction extends Node implements Serializable {
-    /**
-     * For cross-version compatibility.
-     */
-    private static final long serialVersionUID = 4441264252138631694L;
-
-    /**
-     * The expression to be used by this operator.
-     *
-     * @see #getExpression()
-     */
-    protected final Expression expression;
-
-    /**
-     * Creates a new unary operator.
-     */
-    UnaryFunction(final Expression expression) {
-        ArgumentChecks.ensureNonNull("expression", expression);
-        this.expression = expression;
-    }
-
-    /**
-     * Returns the expressions to be used by this operator.
-     */
-    public final Expression getExpression() {
-        return expression;
-    }
-
-    /**
-     * Returns the singleton expression tested by this operator.
-     */
-    @Override
-    protected final Collection<?> getChildren() {
-        return Collections.singleton(expression);
-    }
-
-    /**
-     * Returns a hash code value for this operator.
-     */
-    @Override
-    public final 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 final boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj != null && obj.getClass() == getClass()) {
-            return expression.equals(((UnaryFunction) obj).expression);
-        }
-        return false;
-    }
-
-
-    /**
-     * 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}.
-     */
-    static final class IsNull extends UnaryFunction implements org.opengis.filter.PropertyIsNull {
-        /** For cross-version compatibility. */
-        private static final long serialVersionUID = 5538743632585679484L;
-
-        /** Creates a new operator. */
-        IsNull(Expression expression) {
-            super(expression);
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return NAME;}
-        @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;
-        }
-
-        /** Implementation of the visitor pattern. */
-        @Override public Object accept(FilterVisitor visitor, Object extraData) {
-            return visitor.visit(this, extraData);
-        }
-    }
-
-
-    /**
-     * The negation filter (¬).
-     */
-    static final class Not extends Node implements org.opengis.filter.Not {
-        /** For cross-version compatibility. */
-        private static final long serialVersionUID = -1296823195138427781L;
-
-        /** The filter to negate. */
-        private final Filter filter;
-
-        /** Creates a new filter. */
-        Not(final Filter filter) {
-            ArgumentChecks.ensureNonNull("filter", filter);
-            this.filter = filter;
-        }
-
-        /** Identification of this operation. */
-        @Override protected String getName() {return "Not";}
-        @Override protected char   symbol()  {return '¬';}
-
-        /** Returns the singleton filter used by this operation. */
-        @Override protected Collection<Filter> getChildren() {
-            return Collections.singletonList(filter);
-        }
-
-        /** Returns */
-        @Override public Filter getFilter() {
-            return filter;
-        }
-
-        /** Evaluate this filter on the given object. */
-        @Override public boolean evaluate(final Object object) {
-            return !filter.evaluate(object);
-        }
-
-        /** Implementation of the visitor pattern. */
-        @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/image/DefaultIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
index d4f7b79..bd871ef 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/DefaultIterator.java
@@ -30,7 +30,6 @@
 import java.nio.IntBuffer;
 import java.nio.FloatBuffer;
 import java.nio.DoubleBuffer;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
index 7eb1688..7ed1c14 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/LinearIterator.java
@@ -24,7 +24,6 @@
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 import java.awt.image.RasterFormatException;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.internal.feature.Resources;
 
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
index 4235a5d..b739169 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PixelIterator.java
@@ -31,7 +31,6 @@
 import java.awt.image.SinglePixelPackedSampleModel;
 import java.awt.image.MultiPixelPackedSampleModel;
 import java.util.NoSuchElementException;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.measure.NumberRange;
@@ -274,7 +273,7 @@
          * @param  order  the desired iteration order, or {@code null} for a default order.
          * @return {@code this} for method call chaining.
          */
-        public Builder setIteratorOrder(final SequenceType order) {
+        final Builder setIteratorOrder(final SequenceType order) {
             if (order == null || order.equals(SequenceType.LINEAR)) {
                 this.order = order;
             } else {
@@ -479,7 +478,7 @@
      *
      * @return order in which pixels are traversed.
      */
-    public abstract Optional<SequenceType> getIterationOrder();
+    abstract Optional<SequenceType> getIterationOrder();
 
     /**
      * Returns the number of bands (samples per pixel) in the image or raster.
@@ -641,7 +640,7 @@
      * <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
      * Values are always stored with band index varying fastest, then column index, then row index.
      * Columns are traversed from left to right and rows are traversed from top to bottom
-     * ({@link SequenceType#LINEAR} iteration order).
+     * (linear iteration order).
      * That order is the same regardless the {@linkplain #getIterationOrder() iteration order} of this iterator.
      *
      * <div class="note"><b>Example:</b>
@@ -662,7 +661,7 @@
      * <div class="note"><b>Usage example:</b>
      * following code creates an iterator over the full area of given image, then a window of 5×5 pixels.
      * The window is moved over all the image area in iteration order. Inside the window, data are copied
-     * in {@linkplain SequenceType#LINEAR linear order} regardless the iteration order.
+     * in linear order regardless the iteration order.
      *
      * {@preformat java
      *     PixelIterator it = create(image, null, new Dimension(5, 5), null);     // Windows size will be 5×5 pixels.
@@ -706,8 +705,8 @@
          * capacity is <var>(number of bands)</var> × <var>(window width)</var> × <var>(window height)</var>.
          * Values are always stored with band index varying fastest, then column index, then row index.
          * Columns are traversed from left to right and rows are traversed from top to bottom
-         * ({@link SequenceType#LINEAR} iteration order).
-         * That order is the same regardless the {@linkplain PixelIterator#getIterationOrder() iteration order}
+         * (linear iteration order).
+         * That order is the same regardless the iteration order
          * of enclosing iterator.
          *
          * <p>Every time that {@link #update()} is invoked, the buffer content is replaced by sample values
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/image/SequenceType.java
similarity index 67%
rename from core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
rename to core/sis-feature/src/main/java/org/apache/sis/image/SequenceType.java
index 22783ba..241d372 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/SequenceType.java
@@ -14,16 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.sis.image;
+
 
 /**
- * Filters features according their properties.
- * A <cite>filter expression</cite> is a construct used to constraint a feature set to a subset.
+ * Placeholder for {@code org.opengis.coverage.grid.SequenceType}.
  *
- * <p>All filter and expression implementations provided by Apache SIS are thread-safe.
- * They are not necessarily stateless however; for example a filter may remember which
- * warnings have been reported in order to avoid to report the same warning twice.</p>
- *
- * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
  *
@@ -32,4 +28,6 @@
  * @since 1.1
  * @module
  */
-package org.apache.sis.filter;
+enum SequenceType {
+    LINEAR
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
index 0fae561..5f75624 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/BufferedGridCoverage.java
@@ -35,9 +35,6 @@
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 
-// Branch-specific imports
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * A {@link GridCoverage} with data stored in an in-memory Java2D buffer.
@@ -117,7 +114,7 @@
             renderer.setData(data);
             return renderer.image();
         } catch (IllegalArgumentException | ArithmeticException | RasterFormatException e) {
-            throw new CannotEvaluateException(e.getMessage(), e);
+            throw new RuntimeException(e.getMessage(), e);
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
index 8eeeb3d..cd4dd36 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
@@ -24,15 +24,12 @@
 import org.apache.sis.util.Static;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyNotFoundException;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -104,9 +101,9 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} can be
      * the {@link com.esri.core.geometry.Geometry} class from ESRI's API, or the {@code Geometry} class from
      * <cite>Java Topology Suite</cite> (JTS) library, or any other class defined in future SIS versions.
-     * See {@link #isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
+     * See {@code isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
      *
-     * @see #isGeometryAttribute(IdentifiedType)
+     * @see #isGeometryAttribute(AbstractIdentifiedType)
      */
     public static final ScopedName GEOMETRY_PROPERTY;
 
@@ -138,7 +135,7 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link org.opengis.referencing.crs.CoordinateReferenceSystem}.</p>
      *
-     * @see #getCRSCharacteristic(Property)
+     * @see #getCRSCharacteristic(Object)
      */
     public static final ScopedName CRS_CHARACTERISTIC;
 
@@ -152,7 +149,7 @@
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link Integer}.</p>
      *
-     * @see #getMaximalLengthCharacteristic(Property)
+     * @see #getMaximalLengthCharacteristic(Object)
      */
     public static final ScopedName MAXIMAL_LENGTH_CHARACTERISTIC;
 
@@ -176,13 +173,13 @@
 
     /**
      * String representation of the {@link #IDENTIFIER_PROPERTY} name.
-     * This can be used in calls to {@link Feature#getPropertyValue(String)}.
+     * This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
      */
     public static final String IDENTIFIER = "sis:identifier";
 
     /**
      * String representation of the {@link #GEOMETRY_PROPERTY} name.
-     * This can be used in calls to {@link Feature#getPropertyValue(String)}.
+     * This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
      */
     public static final String GEOMETRY = "sis:geometry";
 
@@ -221,17 +218,17 @@
      * @param  feature  the feature type to test, or {@code null}.
      * @return whether the given feature type is non-null and has a {@value #IDENTIFIER} property.
      */
-    public static boolean hasIdentifier(final FeatureType feature) {
+    public static boolean hasIdentifier(final DefaultFeatureType feature) {
         if (feature != null) try {
             return feature.getProperty(IDENTIFIER) != null;
-        } catch (PropertyNotFoundException e) {
+        } catch (IllegalArgumentException e) {
             // Ignore
         }
         return false;
     }
 
     /**
-     * Returns {@code true} if the given type is an {@link AttributeType} or an {@link Operation} computing
+     * Returns {@code true} if the given type is an {@code AttributeType} or an {@code Operation} computing
      * an attribute, and the attribute value is one of the geometry types recognized by SIS.
      * The types currently recognized by SIS are:
      *
@@ -247,11 +244,11 @@
      *
      * @see #GEOMETRY_PROPERTY
      */
-    public static boolean isGeometryAttribute(IdentifiedType type) {
-        while (type instanceof Operation) {
-            type = ((Operation) type).getResult();
+    public static boolean isGeometryAttribute(AbstractIdentifiedType type) {
+        while (type instanceof AbstractOperation) {
+            type = ((AbstractOperation) type).getResult();
         }
-        return (type instanceof AttributeType<?>) && Geometries.isKnownType(((AttributeType<?>) type).getValueClass());
+        return (type instanceof DefaultAttributeType<?>) && Geometries.isKnownType(((DefaultAttributeType<?>) type).getValueClass());
     }
 
     /**
@@ -262,7 +259,7 @@
      * @param  type  the operation or attribute type for which to get the CRS, or {@code null}.
      * @return {@code true} if a characteristic for Coordinate Reference System has been found.
      */
-    public static boolean characterizedByCRS(final IdentifiedType type) {
+    public static boolean characterizedByCRS(final AbstractIdentifiedType type) {
         return hasCharacteristic(type, CRS_CHARACTERISTIC.toString(), CoordinateReferenceSystem.class);
     }
 
@@ -277,7 +274,7 @@
      *
      * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setCRS(CoordinateReferenceSystem)
      */
-    public static CoordinateReferenceSystem getCRSCharacteristic(final Property attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final Object attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(attribute, CRS_CHARACTERISTIC.toString());
     }
 
@@ -287,7 +284,7 @@
      * If the given property is a link, then this method follows the link in the given feature type (if non-null).
      *
      * <p>This method should be used only when the actual property instance is unknown.
-     * Otherwise, {@link #getCRSCharacteristic(Property)} should be used because the CRS
+     * Otherwise, {@code getCRSCharacteristic(Property)} should be used because the CRS
      * may vary for each property instance.</p>
      *
      * @param  feature    the feature type in which to follow links, or {@code null} if none.
@@ -296,7 +293,7 @@
      * @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
      */
-    public static CoordinateReferenceSystem getCRSCharacteristic(final FeatureType feature, final PropertyType attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final DefaultFeatureType feature, final AbstractIdentifiedType attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(feature, attribute, CRS_CHARACTERISTIC.toString());
     }
 
@@ -308,7 +305,7 @@
      * @param  type  the operation or attribute type for which to get the maximal length, or {@code null}.
      * @return {@code true} if a characteristic for maximal length has been found.
      */
-    public static boolean characterizedByMaximalLength(final IdentifiedType type) {
+    public static boolean characterizedByMaximalLength(final AbstractIdentifiedType type) {
         return hasCharacteristic(type, MAXIMAL_LENGTH_CHARACTERISTIC.toString(), Integer.class);
     }
 
@@ -323,7 +320,7 @@
      *
      * @see org.apache.sis.feature.builder.AttributeTypeBuilder#setMaximalLength(Integer)
      */
-    public static Integer getMaximalLengthCharacteristic(final Property attribute) {
+    public static Integer getMaximalLengthCharacteristic(final Object attribute) {
         return (Integer) getCharacteristic(attribute, MAXIMAL_LENGTH_CHARACTERISTIC.toString());
     }
 
@@ -333,7 +330,7 @@
      * If the given property is a link, then this method follows the link in the given feature type (if non-null).
      *
      * <p>This method should be used only when the actual property instance is unknown.
-     * Otherwise, {@link #getMaximalLengthCharacteristic(Property)} should be used because
+     * Otherwise, {@code getMaximalLengthCharacteristic(Property)} should be used because
      * the maximal length may vary for each property instance.</p>
      *
      * @param  feature    the feature type in which to follow links, or {@code null} if none.
@@ -342,7 +339,7 @@
      * @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
      */
-    public static Integer getMaximalLengthCharacteristic(final FeatureType feature, final PropertyType attribute) {
+    public static Integer getMaximalLengthCharacteristic(final DefaultFeatureType feature, final AbstractIdentifiedType attribute) {
         return (Integer) getCharacteristic(feature, attribute, MAXIMAL_LENGTH_CHARACTERISTIC.toString());
     }
 
@@ -355,12 +352,12 @@
      * @param  valueClass  the expected characteristic values.
      * @return {@code true} if a characteristic of the given name exists and has values assignable to the given class.
      */
-    private static boolean hasCharacteristic(IdentifiedType type, final String name, final Class<?> valueClass) {
-        while (type instanceof Operation) {
-            type = ((Operation) type).getResult();
+    private static boolean hasCharacteristic(AbstractIdentifiedType type, final String name, final Class<?> valueClass) {
+        while (type instanceof AbstractOperation) {
+            type = ((AbstractOperation) type).getResult();
         }
-        if (type instanceof AttributeType<?>) {
-            final AttributeType<?> at = ((AttributeType<?>) type).characteristics().get(name);
+        if (type instanceof DefaultAttributeType<?>) {
+            final DefaultAttributeType<?> at = ((DefaultAttributeType<?>) type).characteristics().get(name);
             if (at != null) {
                 return valueClass.isAssignableFrom(at.getValueClass());
             }
@@ -377,16 +374,16 @@
      * @param  name       name of the characteristic to get.
      * @return the value or default value of the given characteristic in the given property, or {@code null} if none.
      */
-    private static Object getCharacteristic(final Property attribute, final String name) {
-        if (attribute instanceof Attribute<?>) {
-            final Attribute<?> at = ((Attribute<?>) attribute).characteristics().get(name);
+    private static Object getCharacteristic(final Object attribute, final String name) {
+        if (attribute instanceof AbstractAttribute<?>) {
+            final AbstractAttribute<?> at = ((AbstractAttribute<?>) attribute).characteristics().get(name);
             if (at != null) {
                 final Object value = at.getValue();
                 if (value != null) {
                     return value;
                 }
             }
-            final AttributeType<?> type = ((Attribute<?>) attribute).getType().characteristics().get(name);
+            final DefaultAttributeType<?> type = ((AbstractAttribute<?>) attribute).getType().characteristics().get(name);
             if (type != null) {
                 return type.getDefaultValue();
             }
@@ -404,13 +401,13 @@
      * @param  characteristic  name of the characteristic from which to get the default value.
      * @return the default value of the named characteristic in the given property, or {@code null} if none.
      */
-    private static Object getCharacteristic(final FeatureType feature, PropertyType property, final String characteristic) {
+    private static Object getCharacteristic(final DefaultFeatureType feature, AbstractIdentifiedType property, final String characteristic) {
         final String referent = FeatureUtilities.linkOf(property);
         if (referent != null && feature != null) {
             property = feature.getProperty(referent);
         }
-        if (property instanceof AttributeType<?>) {
-            final AttributeType<?> type = ((AttributeType<?>) property).characteristics().get(characteristic);
+        if (property instanceof DefaultAttributeType<?>) {
+            final DefaultAttributeType<?> type = ((DefaultAttributeType<?>) property).characteristics().get(characteristic);
             if (type != null) {
                 return type.getDefaultValue();
             }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
deleted file mode 100644
index 1aeea6b..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureExpression.java
+++ /dev/null
@@ -1,97 +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.internal.feature;
-
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.expression.Expression;
-import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-
-
-/**
- * OGC expressions or other functions operating on feature instances.
- * This interface adds an additional method, {@link #expectedType(FeatureType, FeatureTypeBuilder)},
- * for fetching in advance the expected type of expression results.
- *
- * <p>This is an experimental interface which may be removed in any future version.</p>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public interface FeatureExpression {
-    /**
-     * Provides the expected type of values produced by this expression when a feature of the given
-     * type is evaluated. The resulting type shall describe a "static" property, i.e. it can be an
-     * {@link AttributeType} or a {@link org.opengis.feature.FeatureAssociationRole}
-     * but not an {@link org.opengis.feature.Operation}.
-     *
-     * @param  valueType  the type of features to be evaluated by the given expression.
-     * @param  addTo      where to add the type of properties evaluated by this expression.
-     * @return builder of the added property, or {@code null} if this method can not add a property.
-     * @throws IllegalArgumentException if this method can operate only on some feature types
-     *         and the given type is not one of them.
-     */
-    PropertyTypeBuilder expectedType(FeatureType valueType, FeatureTypeBuilder addTo);
-
-    /**
-     * Provides the type of results computed by the given expression.
-     * This method executes the first of the following choices that apply:
-     *
-     * <ol>
-     *   <li>If the expression implements {@link FeatureExpression}, delegate to {@link #expectedType(FeatureType,
-     *       FeatureTypeBuilder)}. Note that the invoked method may throw an {@link IllegalArgumentException}.</li>
-     *   <li>Otherwise if {@link Expression#evaluate(Object, Class)} with a {@code PropertyType.class} argument
-     *       returns a non-null property, adds that property to the given builder.</li>
-     *   <li>Otherwise if the given feature type contains exactly one property (including inherited properties),
-     *       adds that property to the given builder.</li>
-     *   <li>Otherwise returns {@code null}.</li>
-     * </ol>
-     *
-     * It is caller's responsibility to verify if this method returns {@code null} and to throw an exception in such case.
-     * We leave that responsibility to the caller because (s)he may be able to provide better error messages.
-     *
-     * @param  expression  the expression for which to get the result type, or {@code null}.
-     * @param  valueType   the type of features to be evaluated by the given expression.
-     * @param  addTo       where to add the type of properties evaluated by the given expression.
-     * @return builder of the added property, or {@code null} if this method can not add a property.
-     * @throws IllegalArgumentException if this method can operate only on some feature types
-     *         and the given type is not one of them.
-     */
-    public static PropertyTypeBuilder expectedType(final Expression expression, final FeatureType valueType, final FeatureTypeBuilder addTo) {
-        if (expression instanceof FeatureExpression) {
-            return ((FeatureExpression) expression).expectedType(valueType, addTo);
-        }
-        PropertyType pt = null;
-        if (expression != null) {
-            // TODO: remove this hack if we can get more type-safe Expression.
-            pt = expression.evaluate(valueType, PropertyType.class);
-        }
-        if (pt == null) {
-            pt = CollectionsExt.singletonOrNull(valueType.getProperties(true));
-            if (pt == null) {
-                return null;
-            }
-        }
-        return addTo.addProperty(pt);
-    }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
index f37c3ec..8df7642 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
@@ -32,7 +32,7 @@
 import org.apache.sis.util.Static;
 
 // Branch-dependent imports
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -80,7 +80,7 @@
      * @param  property  the property to test, or {@code null} if none.
      * @return the referenced property name, or {@code null} if none.
      */
-    static String linkOf(final PropertyType property) {
+    static String linkOf(final AbstractIdentifiedType property) {
         if (property instanceof AbstractOperation) {
             final AbstractOperation op = (AbstractOperation) property;
             if (op.getParameters() == LINK_PARAMS) {
@@ -102,14 +102,14 @@
      * @param  properties  the properties for which to get the names, or {@code null}.
      * @return the name of all given properties, or {@code null} if the given list was null.
      */
-    public static String[] getNames(final Collection<? extends PropertyType> properties) {
+    public static String[] getNames(final Collection<? extends AbstractIdentifiedType> properties) {
         if (properties == null) {
             return null;
         }
         final String[] names = new String[properties.size()];
-        final Iterator<? extends PropertyType> it = properties.iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = properties.iterator();
         for (int i=0; i < names.length; i++) {
-            final PropertyType property = it.next();
+            final AbstractIdentifiedType property = it.next();
             if (property != null) {
                 final GenericName name = property.getName();
                 if (name != null) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FunctionRegister.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FunctionRegister.java
deleted file mode 100644
index b4af7b3..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FunctionRegister.java
+++ /dev/null
@@ -1,68 +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.internal.feature;
-
-import java.util.Collection;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Function;
-
-
-/**
- * A factory of {@link org.opengis.filter} functions identified by their names.
- * Each factory can provide an arbitrary number of functions, enumerated by {@link #getNames()}.
- * The {@link org.apache.sis.filter.DefaultFilterFactory#function(String, Expression...)} method
- * delegates to this interface for creating the function implementation for a given name.
- *
- * <p><b>Warning:</b> there is currently no mechanism for avoiding name collision.
- * It is implementer responsibility to keep trace of the whole universe of functions and avoid collision.
- * This interface is hidden in internal API (for now) for that reason, and also because the API may change
- * in any future Apache SIS version.</p>
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- *
- * @see org.opengis.filter.FilterFactory#function(String, Expression...)
- */
-public interface FunctionRegister {
-    /**
-     * Returns a unique name for this factory.
-     *
-     * @return factory unique name.
-     */
-    String getIdentifier();
-
-    /**
-     * Returns the names of all functions that this factory can create.
-     * It is currently implementer responsibility to ensure that there is no name collision with
-     * functions provided by other factories (this problem may be improved in future SIS release).
-     *
-     * @return set of supported function names.
-     */
-    Collection<String> getNames();
-
-    /**
-     * Create a new function of the given name with given parameters.
-     *
-     * @param  name        name of the function to create (not null).
-     * @param  parameters  function parameters.
-     * @return function for the given name and parameters.
-     * @throws IllegalArgumentException if function name is unknown or some parameters are illegal.
-     */
-    Function create(String name, Expression[] parameters) throws IllegalArgumentException;
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
index 62db662..93297c6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
@@ -16,16 +16,9 @@
  */
 package org.apache.sis.internal.feature;
 
-import java.util.Set;
 import java.util.Objects;
-import org.opengis.geometry.Boundary;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.Geometry;
-import org.opengis.geometry.TransfiniteSet;
-import org.opengis.geometry.complex.Complex;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
 
 
 /**
@@ -59,50 +52,11 @@
         this.envelope = envelope;
     }
 
-    /**
-     * Returns the geometry CRS, which is taken from the envelope CRS.
-     *
-     * @return the geometry CRS.
-     */
     @Override
-    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
-        return envelope.getCoordinateReferenceSystem();
+    public Geometry clone() throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
     }
 
-    /**
-     * Returns the envelope specified at construction time.
-     */
-    @Override public Envelope getEnvelope() {
-        return envelope;
-    }
-
-    @Override public Geometry       getMbRegion()                             {throw new UnsupportedOperationException();}
-    @Override public DirectPosition getRepresentativePoint()                  {throw new UnsupportedOperationException();}
-    @Override public Boundary       getBoundary()                             {throw new UnsupportedOperationException();}
-    @Override public Complex        getClosure()                              {throw new UnsupportedOperationException();}
-    @Override public boolean        isSimple()                                {throw new UnsupportedOperationException();}
-    @Override public boolean        isCycle()                                 {throw new UnsupportedOperationException();}
-    @Override public double         distance(Geometry geometry)               {throw new UnsupportedOperationException();}
-    @Override public int            getDimension(DirectPosition point)        {throw new UnsupportedOperationException();}
-    @Override public int            getCoordinateDimension()                  {throw new UnsupportedOperationException();}
-    @Override public Set<Complex>   getMaximalComplex()                       {throw new UnsupportedOperationException();}
-    @Override public DirectPosition getCentroid()                             {throw new UnsupportedOperationException();}
-    @Override public Geometry       getConvexHull()                           {throw new UnsupportedOperationException();}
-    @Override public Geometry       getBuffer(double distance)                {throw new UnsupportedOperationException();}
-    @Override public boolean        isMutable()                               {throw new UnsupportedOperationException();}
-    @Override public Geometry       toImmutable()                             {throw new UnsupportedOperationException();}
-    @Override public Geometry       clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();}
-    @Override public boolean        contains(TransfiniteSet pointSet)         {throw new UnsupportedOperationException();}
-    @Override public boolean        contains(DirectPosition point)            {throw new UnsupportedOperationException();}
-    @Override public boolean        intersects(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Override public boolean        equals(TransfiniteSet pointSet)           {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet union(TransfiniteSet pointSet)            {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet intersection(TransfiniteSet pointSet)     {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet difference(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet symmetricDifference(TransfiniteSet ps)    {throw new UnsupportedOperationException();}
-    @Override public Geometry       transform(CoordinateReferenceSystem crs)  {throw new UnsupportedOperationException();}
-    @Override public Geometry       transform(CoordinateReferenceSystem crs, MathTransform tr) {throw new UnsupportedOperationException();}
-
     @Override
     public boolean equals(final Object obj) {
         return (obj instanceof GeometryWrapper) && Objects.equals(((GeometryWrapper) obj).geometry, geometry);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
index c60dbd9..6aa1811 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/MovingFeature.java
@@ -28,14 +28,13 @@
 import org.opengis.util.LocalName;
 import org.apache.sis.math.Vector;
 import org.apache.sis.util.iso.Names;
-import org.apache.sis.feature.DefaultAttributeType;
 import org.apache.sis.util.CorruptedObjectException;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -52,7 +51,7 @@
     /**
      * Definition of characteristics containing a list of time instants in chronological order, without duplicates.
      */
-    public static final AttributeType<Instant> TIME;
+    public static final DefaultAttributeType<Instant> TIME;
     static {
         final LocalName scope = Names.createLocalName("OGC", null, "MF");
         final Map<String,Object> properties = new HashMap<>(4);
@@ -128,7 +127,7 @@
 
     /**
      * Adds a time range.
-     * The minimal and maximal values will be used by {@link #storeTimeRange(String, String, Feature)}.
+     * The minimal and maximal values will be used by {@code storeTimeRange(String, String, Feature)}.
      *
      * @param  startTime  beginning in milliseconds since Java epoch of the period when the property value is valid.
      * @param  endTime    end in milliseconds since Java epoch of the period when the the property value is valid.
@@ -163,7 +162,7 @@
      * @param  endTime    name of the property where to store the end time.
      * @param  dest       feature where to store the start time and end time.
      */
-    public final void storeTimeRange(final String startTime, final String endTime, final Feature dest) {
+    public final void storeTimeRange(final String startTime, final String endTime, final AbstractFeature dest) {
         if (tmin < tmax) {
             final Instant t = Instant.ofEpochMilli(tmin);
             dest.setPropertyValue(startTime, t);
@@ -180,7 +179,7 @@
      * @param  dest   attribute where to store the value.
      */
     @SuppressWarnings("unchecked")
-    public final <V> void storeAttribute(final int index, final Attribute<V> dest) {
+    public final <V> void storeAttribute(final int index, final AbstractAttribute<V> dest) {
         int n = count[index];
         final long[] times  = new long[n];
         final V[]    values = (V[]) Array.newInstance(dest.getType().getValueClass(), n);
@@ -193,7 +192,7 @@
             throw new CorruptedObjectException();
         }
         dest.setValues(UnmodifiableArrayList.wrap(values));
-        final Attribute<Instant> c = TIME.newInstance();
+        final AbstractAttribute<Instant> c = TIME.newInstance();
         c.setValues(new DateList(times));
         dest.characteristics().values().add(c);
     }
@@ -212,7 +211,7 @@
      *                          source method name and logger name, then forward to a {@code WarningListener}.
      */
     public final <G> void storeGeometry(final String featureName, final int index, final int dimension,
-            final Geometries<G> factory, final Attribute<G> dest, final Consumer<LogRecord> warningListener)
+            final Geometries<G> factory, final AbstractAttribute<G> dest, final Consumer<LogRecord> warningListener)
     {
         int n = count[index];
         final Vector[] vectors = new Vector[n];
@@ -292,7 +291,7 @@
          * Store the geometry and characteristics in the attribute.
          */
         dest.setValue(factory.createPolyline(dimension, vectors));
-        final Attribute<Instant> c = TIME.newInstance();
+        final AbstractAttribute<Instant> c = TIME.newInstance();
         c.setValues(new DateList(times));
         dest.characteristics().values().add(c);
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 22ee513..f37e0b6 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -21,7 +21,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.metadata.spatial.DimensionNameType;
-import org.opengis.coverage.PointOutsideCoverageException;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.GeneralDirectPosition;
@@ -206,7 +205,7 @@
         try {
             extent.slice(slicePoint, new int[] {1, 2});
             fail("Expected PointOutsideCoverageException");
-        } catch (PointOutsideCoverageException e) {
+        } catch (RuntimeException e) {
             final String message = e.getLocalizedMessage();
             assertTrue(message, message.contains("(900, 47)"));     // See above comment.
         }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
index e4498f3..f6f4f15 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/PixelTranslationTest.java
@@ -26,7 +26,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java
index 010801d..a70df46 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/AbstractFeatureTest.java
@@ -23,12 +23,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Property;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests some default method implementations provided in {@link AbstractFeature}.
@@ -68,9 +62,9 @@
          */
         CustomFeature(final DefaultFeatureType type) {
             super(type);
-            for (final PropertyType pt : type.getProperties(true)) {
-                if (pt instanceof AttributeType<?>) {
-                    Object value = ((AttributeType<?>) pt).getDefaultValue();
+            for (final AbstractIdentifiedType pt : type.getProperties(true)) {
+                if (pt instanceof DefaultAttributeType<?>) {
+                    Object value = ((DefaultAttributeType<?>) pt).getDefaultValue();
                     if (isMultiValued(pt)) {
                         value = new ArrayList<>(PropertyView.singletonOrEmpty(value));
                     }
@@ -84,8 +78,8 @@
         /**
          * Returns {@code true} if the given property can contains more than one value.
          */
-        private static boolean isMultiValued(final PropertyType pt) {
-            return (pt instanceof AttributeType<?>) && (((AttributeType<?>) pt).getMaximumOccurs() > 1);
+        private static boolean isMultiValued(final AbstractIdentifiedType pt) {
+            return (pt instanceof DefaultAttributeType<?>) && (((DefaultAttributeType<?>) pt).getMaximumOccurs() > 1);
         }
 
         /**
@@ -108,15 +102,15 @@
          */
         @Override
         public void setPropertyValue(final String name, Object value) {
-            final PropertyType type = getType().getProperty(name);
+            final AbstractIdentifiedType type = getType().getProperty(name);
             final boolean isMultiValued = isMultiValued(type);
             if (isMultiValued && !(value instanceof Collection<?>)) {
                 value = new ArrayList<>(PropertyView.singletonOrEmpty(value));
             }
             if (value != null) {
                 final Class<?> base;
-                if (type instanceof AttributeType<?>) {
-                    base = ((AttributeType<?>) type).getValueClass();
+                if (type instanceof DefaultAttributeType<?>) {
+                    base = ((DefaultAttributeType<?>) type).getValueClass();
                 } else {
                     base = FeatureType.class;
                 }
@@ -151,10 +145,7 @@
      */
     @Override
     boolean assertSameProperty(final String name, final Property expected, final boolean modified) {
-        final Property actual = feature.getProperty(name);
-        if ((expected instanceof PropertyView) == (actual instanceof PropertyView)) {
-            assertEquals(name, expected, actual);
-        }
+        final Property actual = (Property) feature.getProperty(name);
         assertSame("name", expected.getName(), actual.getName());
         if (!modified) {
             assertSame("value", expected.getValue(), actual.getValue());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
index 841275e..c40f504 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
@@ -26,9 +26,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-
 
 /**
  * Tests {@link CharacteristicMap} indirectly, through {@link AbstractAttribute} construction.
@@ -61,14 +58,14 @@
     }
 
     /**
-     * Tests adding explicitly a characteristic with {@link CharacteristicMap#put(String, Attribute)}.
+     * Tests adding explicitly a characteristic with {@code CharacteristicMap.put(String, Attribute)}.
      */
     @Test
     public void testPut() {
-        final AbstractAttribute<?>     temperature     = temperature();
-        final AbstractAttribute<?>     units           = create(temperature, "units");
-        final AbstractAttribute<?>     accuracy        = create(temperature, "accuracy");
-        final Map<String,Attribute<?>> characteristics = temperature.characteristics();
+        final AbstractAttribute<?> temperature = temperature();
+        final AbstractAttribute<?> units       = create(temperature, "units");
+        final AbstractAttribute<?> accuracy    = create(temperature, "accuracy");
+        final Map<String,AbstractAttribute<?>> characteristics = temperature.characteristics();
         /*
          * Verify that the map is initially empty.
          */
@@ -148,16 +145,16 @@
     }
 
     /**
-     * Tests adding a characteristic indirectly with {@link CharacteristicMap#addValue(Attribute)}.
+     * Tests adding a characteristic indirectly with {@code CharacteristicMap.addValue(Attribute)}.
      */
     @Test
     @DependsOnMethod("testPut")
     public void testAddValue() {
-        final AbstractAttribute<?>     temperature     = temperature();
-        final AbstractAttribute<?>     units           = create(temperature, "units");
-        final AbstractAttribute<?>     accuracy        = create(temperature, "accuracy");
-        final Map<String,Attribute<?>> characteristics = temperature.characteristics();
-        final Collection<Attribute<?>> values          = characteristics.values();
+        final AbstractAttribute<?> temperature = temperature();
+        final AbstractAttribute<?> units       = create(temperature, "units");
+        final AbstractAttribute<?> accuracy    = create(temperature, "accuracy");
+        final Map<String,AbstractAttribute<?>> characteristics = temperature.characteristics();
+        final Collection<AbstractAttribute<?>> values          = characteristics.values();
         /*
          * Verify that the collection is initially empty.
          */
@@ -211,10 +208,10 @@
     @Test
     @DependsOnMethod("testPut")
     public void testAddKey() {
-        final Attribute<?> units, accuracy;
-        final AbstractAttribute<?>     temperature     = temperature();
-        final Map<String,Attribute<?>> characteristics = temperature.characteristics();
-        final Collection<String>       keys            = characteristics.keySet();
+        final AbstractAttribute<?> units, accuracy;
+        final AbstractAttribute<?> temperature = temperature();
+        final Map<String,AbstractAttribute<?>> characteristics = temperature.characteristics();
+        final Collection<String> keys = characteristics.keySet();
         /*
          * Verify that the collection is initially empty.
          */
@@ -272,8 +269,8 @@
      * @param  accuracy         the second expected value in iteration order.
      * @param  characteristics  the map to verify.
      */
-    private static void assertEntriesEqual(final Attribute<?> units, final Attribute<?> accuracy,
-            final Map<String,Attribute<?>> characteristics)
+    private static void assertEntriesEqual(final AbstractAttribute<?> units, final AbstractAttribute<?> accuracy,
+            final Map<String,AbstractAttribute<?>> characteristics)
     {
         assertArrayEquals("keySet", new String[] {"accuracy", "units"}, characteristics.keySet().toArray());
         assertArrayEquals("values", new Object[] { accuracy ,  units }, characteristics.values().toArray());
@@ -292,7 +289,7 @@
      */
     private static void setAccuracy(final AbstractAttribute<Float> temperature, final boolean isFirstTime, final float value) {
         assertEquals("keySet.add", isFirstTime, temperature.characteristics().keySet().add("accuracy"));
-        final Attribute<Float> accuracy = Features.cast(temperature.characteristics().get("accuracy"), Float.class);
+        final AbstractAttribute<Float> accuracy = Features.cast(temperature.characteristics().get("accuracy"), Float.class);
         accuracy.setValue(value);
     }
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
index 86cb6d5..d7ae539 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicTypeMapTest.java
@@ -26,11 +26,8 @@
 import static org.apache.sis.feature.DefaultAssociationRole.NAME_KEY;
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
 import org.apache.sis.util.iso.Names;
 
-
 /**
  * Tests {@link CharacteristicTypeMap} indirectly, through {@link DefaultAttributeType} construction.
  *
@@ -75,9 +72,9 @@
      */
     @Test
     public void testMapMethods() {
-        final AttributeType<?> units, accuracy;
+        final DefaultAttributeType<?> units, accuracy;
         final DefaultAttributeType<Float> temperature = temperature();
-        final Map<String, AttributeType<?>> characteristics = temperature.characteristics();
+        final Map<String, DefaultAttributeType<?>> characteristics = temperature.characteristics();
 
         assertFalse  ("isEmpty",        characteristics.isEmpty());
         assertEquals ("size", 2,        characteristics.size());
@@ -122,7 +119,7 @@
         a3 = new DefaultAttributeType<>(singletonMap(NAME_KEY, Names.parseGenericName(null, null, "ns2:s3:units")), String.class, 1, 1, "°C");
         tp = new DefaultAttributeType<>(singletonMap(NAME_KEY, "temperature"), Float.class, 1, 1, null, a1, a2, a3);
 
-        final Map<String, AttributeType<?>> characteristics = tp.characteristics();
+        final Map<String, DefaultAttributeType<?>> characteristics = tp.characteristics();
         assertSame("ns1:accuracy", a1, characteristics.get("ns1:accuracy"));
         assertSame("ns2:accuracy", a2, characteristics.get("ns2:accuracy"));
         assertSame("ns2:s3:units", a3, characteristics.get("ns2:s3:units"));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java b/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
index 1e9183f..1935f65 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
@@ -25,9 +25,6 @@
 
 import static java.util.Collections.singleton;
 
-// Branch-dependent imports
-import org.opengis.feature.AttributeType;
-
 
 /**
  * For testing {@link AbstractAttribute} customization.
@@ -53,7 +50,7 @@
     /**
      * Creates a new attribute.
      */
-    public CustomAttribute(final AttributeType<V> type) {
+    public CustomAttribute(final DefaultAttributeType<V> type) {
         super(type);
         value = type.getDefaultValue();
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
index 67d15d1..ab4c8a1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
@@ -19,7 +19,6 @@
 import java.util.Map;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
-import org.opengis.feature.FeatureAssociationRole;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -31,9 +30,6 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-
 
 /**
  * Tests {@link DefaultAssociationRole}.
@@ -74,7 +70,7 @@
      */
     static DefaultFeatureType twinTownCity(final boolean cyclic) {
         final DefaultAssociationRole twinTown = twinTown(cyclic);
-        final FeatureType parent = cyclic ? DefaultFeatureTypeTest.city() : twinTown.getValueType();
+        final DefaultFeatureType parent = cyclic ? DefaultFeatureTypeTest.city() : twinTown.getValueType();
         return createType("Twin town", parent, twinTown);
     }
 
@@ -87,10 +83,10 @@
      * @return the feature type to use for testing purpose.
      */
     private static DefaultFeatureType createType(final Object name,
-            final FeatureType parent, final FeatureAssociationRole... property)
+            final DefaultFeatureType parent, final DefaultAssociationRole... property)
     {
         return new DefaultFeatureType(singletonMap(NAME_KEY, name),
-                false, new FeatureType[] {parent}, property);
+                false, new DefaultFeatureType[] {parent}, property);
     }
 
     /**
@@ -103,7 +99,7 @@
     }
 
     /**
-     * Tests {@link DefaultAssociationRole#getTitleProperty(FeatureAssociationRole)}.
+     * Tests {@code DefaultAssociationRole.getTitleProperty(FeatureAssociationRole)}.
      */
     @Test
     public void testGetTitleProperty() {
@@ -126,7 +122,7 @@
     @Test
     public void testBidirectionalAssociation() {
         final DefaultFeatureType twinTown = twinTownCity(true);
-        final FeatureAssociationRole association = (FeatureAssociationRole) twinTown.getProperty("twin town");
+        final DefaultAssociationRole association = (DefaultAssociationRole) twinTown.getProperty("twin town");
         assertSame("twinTown.property(“twin town”).valueType", twinTown, association.getValueType());
         /*
          * Creates a FeatureType copy containing the same properties. Used for verifying
@@ -141,7 +137,7 @@
     }
 
     /**
-     * Tests {@link DefaultFeatureType#isAssignableFrom(FeatureType)} and {@link DefaultFeatureType#equals(Object)}
+     * Tests {@code DefaultFeatureType.isAssignableFrom(FeatureType)} and {@code DefaultFeatureType.equals(Object)}
      * on a feature type having a bidirectional association to an other feature. This test will fall in an infinite
      * loop if the implementation does not have proper guard against infinite recursivity.
      */
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
index 141118f..0cba383 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
@@ -32,10 +32,6 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 import static java.util.Collections.singletonMap;
 
-// Branch-dependent imports
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-
 
 /**
  * Tests {@link DefaultFeatureType}.
@@ -205,7 +201,7 @@
             final String... expected)
     {
         int index = 0;
-        for (final PropertyType property : feature.getProperties(includeSuperTypes)) {
+        for (final AbstractIdentifiedType property : feature.getProperties(includeSuperTypes)) {
             assertTrue("Found more properties than expected.", index < expected.length);
             final String name = expected[index++];
             assertNotNull(name, property);
@@ -276,8 +272,8 @@
                 name("Festival"), false, null, city, population, festival);
 
         assertUnmodifiable(complex);
-        final Collection<PropertyType> properties = complex.getProperties(false);
-        final Iterator<PropertyType> it = properties.iterator();
+        final Collection<AbstractIdentifiedType> properties = complex.getProperties(false);
+        final Iterator<AbstractIdentifiedType> it = properties.iterator();
 
         assertEquals("name",            "Festival",                     complex.getName().toString());
         assertTrue  ("superTypes",                                      complex.getSuperTypes().isEmpty());
@@ -337,7 +333,7 @@
         final DefaultFeatureType feature = new DefaultFeatureType(
                 name("City"), false, null, city, cityId, population);
 
-        final Iterator<PropertyType> it = feature.getProperties(false).iterator();
+        final Iterator<AbstractIdentifiedType> it = feature.getProperties(false).iterator();
         assertSame ("properties[0]", city,       it.next());
         assertSame ("properties[1]", cityId,     it.next());
         assertSame ("properties[2]", population, it.next());
@@ -403,7 +399,7 @@
          * Try to add an operation that depends on a non-existent property.
          * Such construction shall not be allowed.
          */
-        final PropertyType parliament = new LinkOperation(identifierName, DefaultAttributeTypeTest.parliament());
+        final AbstractIdentifiedType parliament = new LinkOperation(identifierName, DefaultAttributeTypeTest.parliament());
         try {
             final DefaultFeatureType illegal = new DefaultFeatureType(featureName, false, parent, parliament);
             fail("Should not have been allowed to create this feature:\n" + illegal);
@@ -471,7 +467,7 @@
         assertPropertiesEquals(metroCapital, false, "country");
         assertPropertiesEquals(metroCapital, true, "city", "population", "region", "isGlobal", "parliament", "country");
         assertEquals("property(“region”).valueClass", CharSequence.class,
-                ((AttributeType<?>) metroCapital.getProperty("region")).getValueClass());
+                ((DefaultAttributeType<?>) metroCapital.getProperty("region")).getValueClass());
 
         // Check based only on name.
         assertTrue ("maybeAssignableFrom", DefaultFeatureType.maybeAssignableFrom(capital, metroCapital));
@@ -515,7 +511,7 @@
         assertPropertiesEquals(worldMetropolis, false, "region", "temperature");
         assertPropertiesEquals(worldMetropolis, true, "city", "population", "region", "isGlobal", "universities", "temperature");
         assertEquals("property(“region”).valueClass", InternationalString.class,
-                ((AttributeType<?>) worldMetropolis.getProperty("region")).getValueClass());
+                ((DefaultAttributeType<?>) worldMetropolis.getProperty("region")).getValueClass());
 
         // Check based only on name.
         assertTrue ("maybeAssignableFrom", DefaultFeatureType.maybeAssignableFrom(metropolis, worldMetropolis));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
index 79482ee..bcc21b7 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/EnvelopeOperationTest.java
@@ -36,9 +36,6 @@
 
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests {@link EnvelopeOperation}.
@@ -73,7 +70,7 @@
         final DefaultAttributeType<?> normalizedCRS = new DefaultAttributeType<>(
                 name(AttributeConvention.CRS_CHARACTERISTIC), CoordinateReferenceSystem.class, 1, 1, HardCodedCRS.WGS84);
 
-        final PropertyType[] attributes = {
+        final AbstractIdentifiedType[] attributes = {
             new DefaultAttributeType<>(name("name"),          String.class,  1, 1, null),
             new DefaultAttributeType<>(name("classes"),       Polygon.class, 1, 1, null, standardCRS),
             new DefaultAttributeType<>(name("climbing wall"), Point.class,   1, 1, null, standardCRS),
@@ -103,7 +100,7 @@
      */
     @Test
     public void testConstruction() throws FactoryException {
-        final PropertyType property = school(3).getProperty("bounds");
+        final AbstractIdentifiedType property = school(3).getProperty("bounds");
         assertInstanceOf("bounds", EnvelopeOperation.class, property);
         final EnvelopeOperation op = (EnvelopeOperation) property;
         assertSame("crs", HardCodedCRS.WGS84, op.crs);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
index 091b18e..dff7027 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
@@ -30,9 +30,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent import
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests {@link FeatureFormat}.
@@ -86,7 +83,7 @@
     @SuppressWarnings("serial")
     public void testFeatureTypeWithOperations() {
         DefaultFeatureType feature = DefaultFeatureTypeTest.city();
-        final PropertyType city = feature.getProperty("city");
+        final AbstractIdentifiedType city = feature.getProperty("city");
         feature = new DefaultFeatureType(name("Identified city"), false, new DefaultFeatureType[] {feature},
                 FeatureOperations.link(name("someId"), city),
                 FeatureOperations.compound(name("anotherId"), ":", "<", ">", city, feature.getProperty("population")),
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java
index d613565..e420655 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureMemoryBenchmark.java
@@ -20,7 +20,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
-import org.opengis.feature.Feature;
 import org.apache.sis.internal.util.StandardDateFormat;
 
 import static java.util.Collections.singletonMap;
@@ -126,7 +125,7 @@
         final Float  latitude  = random.nextFloat() * 180 -  90;
         final Float  longitude = random.nextFloat() * 360 - 180;
         if (type != null) {
-            final Feature feature = type.newInstance();
+            final AbstractFeature feature = type.newInstance();
             feature.setPropertyValue("city",      city);
             feature.setPropertyValue("latitude",  latitude);
             feature.setPropertyValue("longitude", longitude);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
index 7f31f3b..760a14f 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
@@ -32,11 +32,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Property;
-
 
 /**
  * Tests common to {@link DenseFeatureTest} and {@link SparseFeatureTest}.
@@ -77,7 +72,7 @@
 
     /**
      * Asserts that {@link AbstractFeature#getProperty(String)} returns the given instance.
-     * This assertion is verified after a call to {@link AbstractFeature#setProperty(Property)}
+     * This assertion is verified after a call to {@code AbstractFeature.setProperty(Property)}
      * and should be true for all Apache SIS concrete implementations. But it is not guaranteed
      * to be true for non-SIS implementations, for example built on top of {@code AbstractFeature}
      * without overriding {@code setProperty(Property)}.
@@ -106,7 +101,7 @@
              *   - Attribute value shall be the same than the one we got at the beginning of this method.
              *   - Attribute values (as a collection) is either empty or contains the same value.
              */
-            final Attribute<?> property = (Attribute<?>) feature.getProperty(name);
+            final AbstractAttribute<?> property = (AbstractAttribute<?>) feature.getProperty(name);
             assertSame(name, feature.getType().getProperty(name), property.getType());
             assertSame(name, value, property.getValue());
             final Collection<?> values = property.getValues();
@@ -162,9 +157,9 @@
         feature.setPropertyValue("REF_INSEE",   "92007");
         feature.setPropertyValue("CODE_POSTAL", "92220");
 
-        assertEquals("CODE_POSTAL", "92220",   feature.getProperty("CODE_POSTAL").getValue());
-        assertEquals("REF_INSEE",   "92007",   feature.getProperty("REF_INSEE")  .getValue());
-        assertEquals("COMMUNE",     "Bagneux", feature.getProperty("COMMUNE")    .getValue());
+        assertEquals("CODE_POSTAL", "92220",   ((AbstractAttribute) feature.getProperty("CODE_POSTAL")).getValue());
+        assertEquals("REF_INSEE",   "92007",   ((AbstractAttribute) feature.getProperty("REF_INSEE"))  .getValue());
+        assertEquals("COMMUNE",     "Bagneux", ((AbstractAttribute) feature.getProperty("COMMUNE"))    .getValue());
 
         assertEquals("CODE_POSTAL", "92220",   feature.getPropertyValue("CODE_POSTAL"));
         assertEquals("REF_INSEE",   "92007",   feature.getPropertyValue("REF_INSEE"));
@@ -301,7 +296,7 @@
     }
 
     /**
-     * Tests the possibility to plugin custom attributes via {@link AbstractFeature#setProperty(Property)}.
+     * Tests the possibility to plugin custom attributes via {@code AbstractFeature.setProperty(Property)}.
      */
     @Test
     @DependsOnMethod({"testSimpleValues", "testSimpleProperties"})
@@ -309,7 +304,7 @@
         feature = createFeature(DefaultFeatureTypeTest.city());
         final AbstractAttribute<String> wrong = SingletonAttributeTest.parliament();
         final CustomAttribute<String> city = new CustomAttribute<>(Features.cast(
-                (AttributeType<?>) feature.getType().getProperty("city"), String.class));
+                (DefaultAttributeType<?>) feature.getType().getProperty("city"), String.class));
 
         feature.setProperty(city);
         setAttributeValue("city", "Utopia", "Atlantide");
@@ -438,13 +433,13 @@
          * Force the conversion of a property value into a full Property object on one and only one of
          * the Features to be compared. The implementation shall be able to wrap or unwrap the values.
          */
-        assertEquals("Tokyo", clone.getProperty("city").getValue());
+        assertEquals("Tokyo", ((AbstractAttribute) clone.getProperty("city")).getValue());
         assertEquals("hashCode", feature.hashCode(), clone.hashCode());
         assertEquals("equals", feature, clone);
         /*
          * For the other Feature instance to contain full Property object and test again.
          */
-        assertEquals("Tokyo", feature.getProperty("city").getValue());
+        assertEquals("Tokyo", ((AbstractAttribute) feature.getProperty("city")).getValue());
         assertEquals("hashCode", feature.hashCode(), clone.hashCode());
         assertEquals("equals", feature, clone);
     }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
index 73972bb..73b4721 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
@@ -22,10 +22,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Tests {@link Features}.
@@ -39,7 +35,7 @@
 @DependsOn(SingletonAttributeTest.class)
 public final strictfp class FeaturesTest extends TestCase {
     /**
-     * Tests {@link Features#cast(AttributeType, Class)}.
+     * Tests {@code Features.cast(AttributeType, Class)}.
      */
     @Test
     public void testCastAttributeType() {
@@ -57,7 +53,7 @@
     }
 
     /**
-     * Tests {@link Features#cast(Attribute, Class)}.
+     * Tests {@code Features.cast(Attribute, Class)}.
      */
     @Test
     public void testCastAttributeInstance() {
@@ -75,17 +71,17 @@
     }
 
     /**
-     * Tests {@link Features#validate(Feature)}.
+     * Tests {@code Features.validate(Feature)}.
      */
     @Test
     public void testValidate() {
-        final Feature feature = DefaultFeatureTypeTest.city().newInstance();
+        final AbstractFeature feature = DefaultFeatureTypeTest.city().newInstance();
 
         // Should not pass validation.
         try {
             Features.validate(feature);
             fail("Feature is invalid because of missing property “population”. Validation should have raised an exception.");
-        } catch (InvalidPropertyValueException ex) {
+        } catch (IllegalArgumentException ex) {
             String message = ex.getMessage();
             assertTrue(message, message.contains("city") || message.contains("population"));
         }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java b/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java
index f680a4f..86870d9 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/NoOperation.java
@@ -20,11 +20,6 @@
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Property;
-
 
 /**
  * An operation that does nothing.
@@ -45,7 +40,7 @@
     /**
      * The type of the result, or {@code null} if none.
      */
-    private final IdentifiedType result;
+    private final AbstractIdentifiedType result;
 
     /**
      * Constructs an operation from the given properties. The identification map is given unchanged to
@@ -56,7 +51,7 @@
      * @param  result          the type of the result, or {@code null} if none.
      */
     NoOperation(final Map<String,?> identification,
-            final ParameterDescriptorGroup parameters, final IdentifiedType result)
+            final ParameterDescriptorGroup parameters, final AbstractIdentifiedType result)
     {
         super(identification);
         this.parameters = parameters;
@@ -79,7 +74,7 @@
      * @return the type of the result, or {@code null} if none.
      */
     @Override
-    public IdentifiedType getResult() {
+    public AbstractIdentifiedType getResult() {
         return result;
     }
 
@@ -89,7 +84,7 @@
      * @return {@code null}
      */
     @Override
-    public Property apply(Feature feature, ParameterValueGroup parameters) {
+    public Object apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return null;
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
index 179584b..89e4c05 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
@@ -23,10 +23,6 @@
 import static java.util.Collections.singletonMap;
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
-
 
 /**
  * Tests {@link SingletonAssociation}.
@@ -48,7 +44,7 @@
      * and Le Mans, France in 836.” — source: Wikipedia</blockquote>
      */
     static AbstractAssociation twinTown() {
-        final Feature twinTown = DefaultFeatureTypeTest.city().newInstance();
+        final AbstractFeature twinTown = DefaultFeatureTypeTest.city().newInstance();
         twinTown.setPropertyValue("city", "Le Mans");
         twinTown.setPropertyValue("population", 143240); // In 2011.
         final AbstractAssociation association = new SingletonAssociation(DefaultAssociationRoleTest.twinTown(false));
@@ -61,9 +57,9 @@
      */
     @Test
     public void testWrongValue() {
-        final AbstractAssociation association  = twinTown();
-        final PropertyType population   = association.getRole().getValueType().getProperty("population");
-        final Feature      otherFeature = new DefaultFeatureType(
+        final AbstractAssociation    association  = twinTown();
+        final AbstractIdentifiedType population   = association.getRole().getValueType().getProperty("population");
+        final AbstractFeature        otherFeature = new DefaultFeatureType(
                 singletonMap(DefaultFeatureType.NAME_KEY, "Population"), false, null, population).newInstance();
         try {
             association.setValue(otherFeature);
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java
index 453c728..aa94159 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/StringJoinOperationTest.java
@@ -26,12 +26,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.InvalidPropertyValueException;
-
 
 /**
  * Tests {@link StringJoinOperation}.
@@ -61,9 +55,9 @@
      * @return the feature for a person.
      */
     private static DefaultFeatureType person() {
-        final PropertyType nameType = new DefaultAttributeType<>(name("name"), String.class, 1, 1, null);
-        final PropertyType ageType  = new DefaultAttributeType<>(name("age"), Integer.class, 1, 1, null);
-        final PropertyType cmpType  = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", nameType, ageType);
+        final AbstractIdentifiedType nameType = new DefaultAttributeType<>(name("name"), String.class, 1, 1, null);
+        final AbstractIdentifiedType ageType  = new DefaultAttributeType<>(name("age"), Integer.class, 1, 1, null);
+        final AbstractIdentifiedType cmpType  = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", nameType, ageType);
         return new DefaultFeatureType(name("person"), false, null, nameType, ageType, cmpType);
     }
 
@@ -146,7 +140,7 @@
         try {
             feature.setPropertyValue("concat", "((:marc/21:>>");
             fail("Should fail because of mismatched prefix.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains("<<:"));
             assertTrue(message, message.contains("(("));
@@ -154,7 +148,7 @@
         try {
             feature.setPropertyValue("concat", "<<:marc/21:))");
             fail("Should fail because of mismatched suffix.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains(":>>"));
             assertTrue(message, message.contains("))"));
@@ -162,14 +156,14 @@
         try {
             feature.setPropertyValue("concat", "<<:marc/21/julie:>>");
             fail("Should fail because of too many components.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains("<<:marc/21/julie:>>"));
         }
         try {
             feature.setPropertyValue("concat", "<<:marc/julie:>>");
             fail("Should fail because of unparsable number.");
-        } catch (InvalidPropertyValueException e) {
+        } catch (IllegalArgumentException e) {
             String message = e.getMessage();
             assertTrue(message, message.contains("julie"));
             assertTrue(message, message.contains("age"));
@@ -182,12 +176,12 @@
      */
     @Test
     public void testFeatureAssociation() {
-        final PropertyType id1 = new DefaultAttributeType<>(name(AttributeConvention.IDENTIFIER_PROPERTY), String.class, 1, 1, null);
-        final FeatureType  ft1 = new DefaultFeatureType(name("Child feature"), false, null, id1);
-        final PropertyType  p1 = new DefaultAssociationRole(name("first"), ft1, 1, 1);
-        final PropertyType  p2 = new DefaultAttributeType<>(name("second"), Integer.class, 1, 1, null);
-        final PropertyType idc = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", p1, p2);
-        final Feature  feature = new DefaultFeatureType(name("Parent feature"), false, null, p1, p2, idc).newInstance();
+        final AbstractIdentifiedType id1 = new DefaultAttributeType<>(name(AttributeConvention.IDENTIFIER_PROPERTY), String.class, 1, 1, null);
+        final DefaultFeatureType     ft1 = new DefaultFeatureType(name("Child feature"), false, null, id1);
+        final AbstractIdentifiedType  p1 = new DefaultAssociationRole(name("first"), ft1, 1, 1);
+        final AbstractIdentifiedType  p2 = new DefaultAttributeType<>(name("second"), Integer.class, 1, 1, null);
+        final AbstractIdentifiedType idc = FeatureOperations.compound(name("concat"), "/", "<<:", ":>>", p1, p2);
+        final AbstractFeature    feature = new DefaultFeatureType(name("Parent feature"), false, null, p1, p2, idc).newInstance();
         /*
          * For empty feature, should have only the prefix, delimiter and suffix.
          */
@@ -201,7 +195,7 @@
          * Create the associated feature and set its identifier.
          * The compound identifier shall be updated accordingly.
          */
-        final Feature f1 = ft1.newInstance();
+        final AbstractFeature f1 = ft1.newInstance();
         feature.setPropertyValue("first", f1);
         f1.setPropertyValue("sis:identifier", "SomeKey");
         assertEquals("<<:SomeKey/21:>>", feature.getPropertyValue("concat"));
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
index dd0c896..0f6ff88 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AssociationRoleBuilderTest.java
@@ -24,7 +24,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
@@ -50,7 +50,7 @@
                 .setMaximumOccurs(2)
                 .setMinimumOccurs(1);
 
-        final FeatureAssociationRole role = builder.build();
+        final DefaultAssociationRole role = builder.build();
         assertEquals("minimumOccurs", 1, role.getMinimumOccurs());
         assertEquals("maximumOccurs", 2, role.getMaximumOccurs());
         assertEquals("designation", new SimpleInternationalString("A designation"),          role.getDesignation());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
index b709322..52de49c 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/AttributeTypeBuilderTest.java
@@ -31,7 +31,7 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -55,7 +55,7 @@
         assertEquals("default name", "string", builder.getName().toString());
 
         builder.setName("myScope", "myName");
-        final AttributeType<?> att = builder.build();
+        final DefaultAttributeType<?> att = builder.build();
 
         assertEquals("name", "myScope:myName",   att.getName().toString());
         assertEquals("valueClass", String.class, att.getValueClass());
@@ -81,7 +81,7 @@
         assertSame(builder, builder.setDefaultValue("test default value."));
         assertSame(builder, builder.setMinimumOccurs(10).setMaximumOccurs(60));
         assertSame(builder, builder.setMaximalLength(80));
-        final AttributeType<?> att = builder.build();
+        final DefaultAttributeType<?> att = builder.build();
 
         assertEquals("name",          "myScope:myName",      att.getName().toString());
         assertEquals("definition",    "test definition",     att.getDefinition().toString());
@@ -141,7 +141,7 @@
         /*
          * Verify the attribute created by the builder.
          */
-        final AttributeType<?> att = newb.build();
+        final DefaultAttributeType<?> att = newb.build();
         assertEquals("name",          "temperature",      att.getName().toString());
         assertEquals("definition",    "test definition",  att.getDefinition().toString());
         assertEquals("description",   "test description", att.getDescription().toString());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
index 2605e49..2fe330f 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/CharacteristicTypeBuilderTest.java
@@ -23,7 +23,7 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -79,7 +79,7 @@
         /*
          * Verify the characteristic created by the builder.
          */
-        final AttributeType<?> att = newb.build();
+        final DefaultAttributeType<?> att = newb.build();
         assertEquals("name",          "stddev",           att.getName().toString());
         assertEquals("definition",    "test definition",  att.getDefinition().toString());
         assertEquals("description",   "test description", att.getDescription().toString());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
index c7282fa..fc3df72 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/builder/FeatureTypeBuilderTest.java
@@ -35,11 +35,9 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.Operation;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -55,13 +53,13 @@
 @DependsOn(AttributeTypeBuilderTest.class)
 public final strictfp class FeatureTypeBuilderTest extends TestCase {
     /**
-     * Verifies that {@link FeatureTypeBuilder#setSuperTypes(FeatureType...)} ignores null parents.
+     * Verifies that {@code FeatureTypeBuilder.setSuperTypes(FeatureType...)} ignores null parents.
      * This method tests only the builder state without creating feature type.
      */
     @Test
     public void testNullParents() {
         final FeatureTypeBuilder builder = new FeatureTypeBuilder(null);
-        assertSame(builder, builder.setSuperTypes(new FeatureType[6]));
+        assertSame(builder, builder.setSuperTypes(new DefaultFeatureType[6]));
         assertEquals(0, builder.getSuperTypes().length);
     }
 
@@ -115,7 +113,7 @@
             assertTrue(message, message.contains("name"));
         }
         assertSame(builder, builder.setName("scope", "test"));
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
 
         assertEquals("name", "scope:test",   type.getName().toString());
         assertFalse ("isAbstract",           type.isAbstract());
@@ -140,18 +138,18 @@
         builder.addAttribute(Point  .class).setName("location").setCRS(HardCodedCRS.WGS84);
         builder.addAttribute(Double .class).setName("score").setDefaultValue(10.0).setMinimumOccurs(5).setMaximumOccurs(50);
 
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
         assertEquals("name",        "myScope:myName",   type.getName().toString());
         assertEquals("definition",  "test definition",  type.getDefinition().toString());
         assertEquals("description", "test description", type.getDescription().toString());
         assertEquals("designation", "test designation", type.getDesignation().toString());
         assertTrue  ("isAbstract",                      type.isAbstract());
 
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final AttributeType<?> a0 = (AttributeType<?>) it.next();
-        final AttributeType<?> a1 = (AttributeType<?>) it.next();
-        final AttributeType<?> a2 = (AttributeType<?>) it.next();
-        final AttributeType<?> a3 = (AttributeType<?>) it.next();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final DefaultAttributeType<?> a0 = (DefaultAttributeType<?>) it.next();
+        final DefaultAttributeType<?> a1 = (DefaultAttributeType<?>) it.next();
+        final DefaultAttributeType<?> a2 = (DefaultAttributeType<?>) it.next();
+        final DefaultAttributeType<?> a3 = (DefaultAttributeType<?>) it.next();
         assertFalse("properties count", it.hasNext());
 
         assertEquals("name", "name",     a0.getName().toString());
@@ -201,16 +199,16 @@
                 .setCRS(HardCodedCRS.WGS84)
                 .addRole(AttributeRole.DEFAULT_GEOMETRY);
 
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
         assertEquals("name", "scope:test", type.getName().toString());
         assertFalse ("isAbstract", type.isAbstract());
 
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final PropertyType a0 = it.next();
-        final PropertyType a1 = it.next();
-        final PropertyType a2 = it.next();
-        final PropertyType a3 = it.next();
-        final PropertyType a4 = it.next();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final AbstractIdentifiedType a0 = it.next();
+        final AbstractIdentifiedType a1 = it.next();
+        final AbstractIdentifiedType a2 = it.next();
+        final AbstractIdentifiedType a3 = it.next();
+        final AbstractIdentifiedType a4 = it.next();
         assertFalse("properties count", it.hasNext());
 
         assertEquals("name", AttributeConvention.IDENTIFIER_PROPERTY, a0.getName());
@@ -232,15 +230,15 @@
         assertSame(builder, builder.setName("City"));
         builder.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY).addRole(AttributeRole.IDENTIFIER_COMPONENT);
         builder.addAttribute(Integer.class).setName("population");
-        final FeatureType type = builder.build();
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final PropertyType a0 = it.next();
-        final PropertyType a1 = it.next();
+        final DefaultFeatureType type = builder.build();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final AbstractIdentifiedType a0 = it.next();
+        final AbstractIdentifiedType a1 = it.next();
         assertFalse("properties count", it.hasNext());
         assertEquals("name", AttributeConvention.IDENTIFIER_PROPERTY, a0.getName());
-        assertEquals("type", String.class,  ((AttributeType<?>) a0).getValueClass());
+        assertEquals("type", String.class,  ((DefaultAttributeType<?>) a0).getValueClass());
         assertEquals("name", "population", a1.getName().toString());
-        assertEquals("type", Integer.class, ((AttributeType<?>) a1).getValueClass());
+        assertEquals("type", Integer.class, ((DefaultAttributeType<?>) a1).getValueClass());
     }
 
     /**
@@ -255,17 +253,17 @@
         assertSame(builder, builder.setName("City"));
         builder.addAttribute(Point.class).setName(AttributeConvention.GEOMETRY_PROPERTY).addRole(AttributeRole.DEFAULT_GEOMETRY);
         builder.addAttribute(Integer.class).setName("population");
-        final FeatureType type = builder.build();
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        final PropertyType a0 = it.next();
-        final PropertyType a1 = it.next();
-        final PropertyType a2 = it.next();
+        final DefaultFeatureType type = builder.build();
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        final AbstractIdentifiedType a0 = it.next();
+        final AbstractIdentifiedType a1 = it.next();
+        final AbstractIdentifiedType a2 = it.next();
         assertFalse("properties count", it.hasNext());
         assertEquals("name", AttributeConvention.ENVELOPE_PROPERTY, a0.getName());
         assertEquals("name", AttributeConvention.GEOMETRY_PROPERTY, a1.getName());
-        assertEquals("type", Point.class,   ((AttributeType<?>) a1).getValueClass());
+        assertEquals("type", Point.class,   ((DefaultAttributeType<?>) a1).getValueClass());
         assertEquals("name", "population", a2.getName().toString());
-        assertEquals("type", Integer.class, ((AttributeType<?>) a2).getValueClass());
+        assertEquals("type", Integer.class, ((DefaultAttributeType<?>) a2).getValueClass());
     }
 
     /**
@@ -297,7 +295,7 @@
         builder.addAttribute(Integer .class).setName("population");
         builder.addAttribute(Geometry.class).setName("area").roles().add(AttributeRole.DEFAULT_GEOMETRY);
 
-        final FeatureType type = builder.build();
+        final DefaultFeatureType type = builder.build();
         builder = new FeatureTypeBuilder(type);
         assertEquals("name", "City", builder.getName().toString());
         assertEquals("superTypes", 0, builder.getSuperTypes().length);
@@ -328,8 +326,8 @@
     @DependsOnMethod("testAddAttribute")
     public void testBuildCache() {
         final FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("City");
-        final AttributeType<String> name = builder.addAttribute(String.class).setName("name").build();
-        final FeatureType city = builder.build();
+        final DefaultAttributeType<String> name = builder.addAttribute(String.class).setName("name").build();
+        final DefaultFeatureType city = builder.build();
         assertSame("Should return the existing AttributeType.", name, city.getProperty("name"));
         assertSame("Should return the existing FeatureType.", city, builder.build());
 
@@ -351,13 +349,13 @@
     public void testEnvelopeOverride() {
         FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("CoverageRecord").setAbstract(true);
         builder.addAttribute(Geometry.class).setName(AttributeConvention.GEOMETRY_PROPERTY).addRole(AttributeRole.DEFAULT_GEOMETRY);
-        final FeatureType parentType = builder.build();
+        final DefaultFeatureType parentType = builder.build();
 
         builder = new FeatureTypeBuilder().setName("Record").setSuperTypes(parentType);
         builder.addAttribute(Envelope.class).setName(AttributeConvention.ENVELOPE_PROPERTY);
-        final FeatureType childType = builder.build();
+        final DefaultFeatureType childType = builder.build();
 
-        final Iterator<? extends PropertyType> it = childType.getProperties(true).iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = childType.getProperties(true).iterator();
         assertPropertyEquals("sis:envelope", Envelope.class, it.next());
         assertPropertyEquals("sis:geometry", Geometry.class, it.next());
         assertFalse(it.hasNext());
@@ -370,15 +368,15 @@
     @Test
     public void testOverrideByOperation() {
         FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("Parent").setAbstract(true);
-        final AttributeType<Integer> pa = builder.addAttribute(Integer.class).setName("A").build();
+        final DefaultAttributeType<Integer> pa = builder.addAttribute(Integer.class).setName("A").build();
         builder.addAttribute(Integer.class).setName("B");
-        final FeatureType parentType = builder.build();
+        final DefaultFeatureType parentType = builder.build();
 
         builder = new FeatureTypeBuilder().setName("Child").setSuperTypes(parentType);
         builder.addProperty(FeatureOperations.link(Collections.singletonMap(AbstractOperation.NAME_KEY, "B"), pa));
-        final FeatureType childType = builder.build();
+        final DefaultFeatureType childType = builder.build();
 
-        final Iterator<? extends PropertyType> it = childType.getProperties(true).iterator();
+        final Iterator<? extends AbstractIdentifiedType> it = childType.getProperties(true).iterator();
         assertPropertyEquals("A", Integer.class, it.next());
         assertPropertyEquals("B", Integer.class, it.next());
         assertFalse(it.hasNext());
@@ -387,11 +385,11 @@
     /**
      * Verifies that the given property is an attribute with the given name and value class.
      */
-    private static void assertPropertyEquals(final String name, final Class<?> valueClass, IdentifiedType property) {
+    private static void assertPropertyEquals(final String name, final Class<?> valueClass, AbstractIdentifiedType property) {
         assertEquals("name", name, property.getName().toString());
-        if (property instanceof Operation) {
-            property = ((Operation) property).getResult();
+        if (property instanceof AbstractOperation) {
+            property = ((AbstractOperation) property).getResult();
         }
-        assertEquals("valueClass", valueClass, ((AttributeType<?>) property).getValueClass());
+        assertEquals("valueClass", valueClass, ((DefaultAttributeType<?>) property).getValueClass());
     }
 }
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java
deleted file mode 100644
index 9766e1f..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/ArithmeticFunctionTest.java
+++ /dev/null
@@ -1,80 +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.filter;
-
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.expression.Expression;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link ArithmeticFunction} implementations.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class ArithmeticFunctionTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory2 factory = new DefaultFilterFactory();
-
-    /**
-     * Tests "Add" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testAdd() {
-        Expression op = factory.add(factory.literal(10.0), factory.literal(20.0));
-        assertEquals(30.0, op.evaluate(null));
-        assertSerializedEquals(op);
-    }
-
-    /**
-     * Tests "Subtract" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testSubtract() {
-        Expression op = factory.subtract(factory.literal(10.0), factory.literal(20.0));
-        assertEquals(-10.0, op.evaluate(null));
-        assertSerializedEquals(op);
-    }
-
-    /**
-     * Tests "Multiply" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testMultiply() {
-        Expression op = factory.multiply(factory.literal(10.0), factory.literal(20.0));
-        assertEquals(200.0, op.evaluate(null));
-        assertSerializedEquals(op);
-    }
-
-    /**
-     * Tests "Divide" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testDivide() {
-        Expression op = factory.divide(factory.literal(10.0), factory.literal(20.0));
-        assertEquals(0.5, op.evaluate(null));
-        assertSerializedEquals(op);
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFunctionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFunctionTest.java
deleted file mode 100644
index 4069869..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/ComparisonFunctionTest.java
+++ /dev/null
@@ -1,151 +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.filter;
-
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link ComparisonFunction} implementations.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class ComparisonFunctionTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory factory;
-
-    /**
-     * Expressions used as constant for the tests.
-     */
-    private final Literal c05, c10, c20;
-
-    /**
-     * Expected name of the filter to be evaluated. The {@link #evaluate(BinaryComparisonOperator)} method
-     * will compare {@link ComparisonFunction#getName()} against this value.
-     */
-    private String expectedName;
-
-    /**
-     * The filter tested by last call to {@link #evaluate(BinaryComparisonOperator)}.
-     */
-    private BinaryComparisonOperator filter;
-
-    /**
-     * Creates a new test case.
-     */
-    public ComparisonFunctionTest() {
-        factory = new DefaultFilterFactory();
-        c05 = factory.literal(5);
-        c10 = factory.literal(10);
-        c20 = factory.literal(20);
-    }
-
-    /**
-     * Evaluates the given filter. The {@link #expectedName} field must be set before this method is invoked.
-     * This method assumes that the first expression of all filters is {@link #c10}.
-     */
-    private boolean evaluate(final BinaryComparisonOperator filter) {
-        this.filter = filter;
-        assertInstanceOf("Expected SIS implementation.", ComparisonFunction.class, filter);
-        assertEquals("name", expectedName, ((ComparisonFunction) filter).getName());
-        assertSame("expression1", c10, filter.getExpression1());
-        return filter.evaluate(null);
-    }
-
-    /**
-     * Tests "LessThan" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testLess() {
-        expectedName = "LessThan";
-        assertTrue (evaluate(factory.less(c10, c20)));
-        assertFalse(evaluate(factory.less(c10, c10)));
-        assertFalse(evaluate(factory.less(c10, c05)));
-        assertSerializedEquals(filter);
-    }
-
-    /**
-     * Tests "LessThanOrEqualTo" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testLessOrEqual() {
-        expectedName = "LessThanOrEqualTo";
-        assertTrue (evaluate(factory.lessOrEqual(c10, c20)));
-        assertTrue (evaluate(factory.lessOrEqual(c10, c10)));
-        assertFalse(evaluate(factory.lessOrEqual(c10, c05)));
-        assertSerializedEquals(filter);
-    }
-
-    /**
-     * Tests "GreaterThan" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testGreater() {
-        expectedName = "GreaterThan";
-        assertFalse(evaluate(factory.greater(c10, c20)));
-        assertFalse(evaluate(factory.greater(c10, c10)));
-        assertTrue (evaluate(factory.greater(c10, c05)));
-        assertSerializedEquals(filter);
-    }
-
-    /**
-     * Tests "GreaterThanOrEqualTo" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testGreaterOrEqual() {
-        expectedName = "GreaterThanOrEqualTo";
-        assertFalse(evaluate(factory.greaterOrEqual(c10, c20)));
-        assertTrue (evaluate(factory.greaterOrEqual(c10, c10)));
-        assertTrue (evaluate(factory.greaterOrEqual(c10, c05)));
-        assertSerializedEquals(filter);
-    }
-
-    /**
-     * Tests "EqualTo" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testEqual() {
-        expectedName = "EqualTo";
-        assertFalse(evaluate(factory.equals(c10, c20)));
-        assertTrue (evaluate(factory.equals(c10, c10)));
-        assertFalse(evaluate(factory.equals(c10, c05)));
-        assertSerializedEquals(filter);
-    }
-
-    /**
-     * Tests "NotEqualTo" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testNotEqual() {
-        expectedName = "NotEqualTo";
-        assertTrue (evaluate(factory.notEqual(c10, c20)));
-        assertFalse(evaluate(factory.notEqual(c10, c10)));
-        assertTrue (evaluate(factory.notEqual(c10, c05)));
-        assertSerializedEquals(filter);
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/DefaultObjectIdTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/DefaultObjectIdTest.java
deleted file mode 100644
index 016518b..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/DefaultObjectIdTest.java
+++ /dev/null
@@ -1,100 +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.filter;
-
-import org.apache.sis.feature.builder.AttributeRole;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-import org.opengis.feature.Feature;
-import org.opengis.filter.FilterFactory2;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link DefaultObjectId}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class DefaultObjectIdTest extends TestCase {
-    /**
-     * Test factory.
-     */
-    @Test
-    public void testConstructor() {
-        final FilterFactory2 factory = new DefaultFilterFactory();
-        assertNotNull(factory.featureId("abc"));
-        assertNotNull(factory.gmlObjectId("abc"));
-    }
-
-    /**
-     * Creates 3 features for testing purpose. Features are (in that order):
-     *
-     * <ol>
-     *   <li>A feature type with an identifier as a string.</li>
-     *   <li>A feature type with an integer identifier.</li>
-     *   <li>A feature type with no identifier.</li>
-     * </ol>
-     */
-    private static Feature[] features() {
-        final Feature[] features = new Feature[3];
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-        ftb.setName("type1");
-        ftb.addAttribute(String.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        Feature f = ftb.build().newInstance();
-        f.setPropertyValue("att", "123");
-        features[0] = f;
-
-        ftb.clear();
-        ftb.setName("type2");
-        ftb.addAttribute(Integer.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        f = ftb.build().newInstance();
-        f.setPropertyValue("att", 123);
-        features[1] = f;
-
-        ftb.clear();
-        ftb.setName("type3");
-        f = ftb.build().newInstance();
-        features[2] = f;
-
-        return features;
-    }
-
-    /**
-     * Tests evaluation.
-     */
-    @Test
-    public void testEvaluate() {
-        final DefaultObjectId fid = new DefaultObjectId("123");
-        final Feature[] features = features();
-        assertTrue (fid.matches(features[0]));
-        assertTrue (fid.matches(features[1]));
-        assertFalse(fid.matches(features[2]));
-    }
-
-    /**
-     * Tests serialization.
-     */
-    @Test
-    public void testSerialize() {
-        assertSerializedEquals(new DefaultObjectId("abc"));
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/FilterByIdentifierTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/FilterByIdentifierTest.java
deleted file mode 100644
index 683aa55..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/FilterByIdentifierTest.java
+++ /dev/null
@@ -1,92 +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.filter;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import org.apache.sis.feature.builder.AttributeRole;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.identity.Identifier;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link FilterByIdentifier}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class FilterByIdentifierTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory2 factory = new DefaultFilterFactory();
-
-    /**
-     * Test factory.
-     */
-    @Test
-    public void testConstructor() {
-        assertNotNull(factory.id(Collections.singleton(factory.featureId("abc"))));
-    }
-
-    /**
-     * Tests evaluation.
-     */
-    @Test
-    public void testEvaluate() {
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-        ftb.setName("type");
-        ftb.addAttribute(String.class).setName("att").addRole(AttributeRole.IDENTIFIER_COMPONENT);
-        final FeatureType type = ftb.build();
-
-        final Feature feature1 = type.newInstance();
-        feature1.setPropertyValue("att", "123");
-
-        final Feature feature2 = type.newInstance();
-        feature2.setPropertyValue("att", "abc");
-
-        final Feature feature3 = type.newInstance();
-        feature3.setPropertyValue("att", "abc123");
-
-        final Set<Identifier> ids = new HashSet<>();
-        ids.add(factory.featureId("abc"));
-        ids.add(factory.featureId("123"));
-        final FilterByIdentifier id = new FilterByIdentifier(ids);
-
-        assertTrue (id.evaluate(feature1));
-        assertTrue (id.evaluate(feature2));
-        assertFalse(id.evaluate(feature3));
-    }
-
-    /**
-     * Tests serialization.
-     */
-    @Test
-    public void testSerialize() {
-        assertSerializedEquals(new FilterByIdentifier(Collections.singleton(factory.featureId("abc"))));
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
deleted file mode 100644
index a408227..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LeafExpressionTest.java
+++ /dev/null
@@ -1,123 +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.filter;
-
-import java.util.Date;
-import java.util.Map;
-import java.util.HashMap;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.PropertyName;
-import org.apache.sis.util.iso.Names;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link LeafExpression}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class LeafExpressionTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory2 factory = new DefaultFilterFactory();
-
-    /**
-     * Test creation of "PropertyName".
-     */
-    @Test
-    public void testPropertyConstructor() {
-        assertNotNull(factory.property(Names.parseGenericName(null, null, "type")));
-        assertNotNull(factory.property("type"));
-    }
-
-    /**
-     * Test creation of "Literal".
-     */
-    @Test
-    public void testLiteralConstructor() {
-        assertNotNull(factory.literal(true));
-        assertNotNull(factory.literal("a text string"));
-        assertNotNull(factory.literal('x'));
-        assertNotNull(factory.literal(122));
-        assertNotNull(factory.literal(45.56d));
-    }
-
-    /**
-     * Tests evaluation of "PropertyName".
-     */
-    @Test
-    public void testPropertyEvaluate() {
-        final Map<String,String> candidate = new HashMap<>();
-
-        final PropertyName prop = factory.property("type");
-        assertEquals("type", prop.getPropertyName());
-
-        assertNull(prop.evaluate(candidate));
-        assertNull(prop.evaluate(null));
-
-        candidate.put("type", "road");
-        assertEquals("road", prop.evaluate(candidate));
-        assertEquals("road", prop.evaluate(candidate, String.class));
-
-        candidate.put("type", "45.1");
-        assertEquals("45.1", prop.evaluate(candidate));
-        assertEquals("45.1", prop.evaluate(candidate, Object.class));
-        assertEquals("45.1", prop.evaluate(candidate, String.class));
-        assertEquals( 45.1,  prop.evaluate(candidate, Double.class), STRICT);
-    }
-
-    /**
-     * Tests evaluation of "Literal".
-     */
-    @Test
-    public void testLiteralEvaluate() {
-        final Literal literal = factory.literal(12.45);
-        assertEquals(12.45,   literal.getValue());
-        assertEquals(12.45,   literal.evaluate(null));
-        assertEquals(12.45,   literal.evaluate(null, Double.class), STRICT);
-        assertEquals("12.45", literal.evaluate(null, String.class));
-        assertNull  (         literal.evaluate(null, Date.class));
-    }
-
-    /**
-     * Tests serialization of "PropertyName".
-     */
-    @Test
-    public void testPropertySerialize() {
-        assertSerializedEquals(factory.property("type"));
-    }
-
-    /**
-     * Tests serialization of "Literal".
-     */
-    @Test
-    public void testLiteralSerialize() {
-        assertSerializedEquals(factory.literal(true));
-        assertSerializedEquals(factory.literal("a text string"));
-        assertSerializedEquals(factory.literal('x'));
-        assertSerializedEquals(factory.literal(122));
-        assertSerializedEquals(factory.literal(45.56d));
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java
deleted file mode 100644
index 30710c2..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/LogicalFunctionTest.java
+++ /dev/null
@@ -1,146 +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.filter;
-
-import java.util.Map;
-import java.util.Arrays;
-import java.util.Collections;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.expression.Literal;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link LogicalFunction} implementations.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class LogicalFunctionTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory2 factory = new DefaultFilterFactory();
-
-    /**
-     * Tests creation of "And" expression from the factory.
-     */
-    @Test
-    public void testAndConstructor() {
-        final Filter filter = factory.isNull(factory.literal("text"));
-        assertNotNull(factory.and(filter, filter));
-        assertNotNull(factory.and(Arrays.asList(filter, filter, filter)));
-        try {
-            factory.and(null, null);
-            fail("Creation of an AND with a null child filter must raise an exception");
-        } catch (NullPointerException ex) {}
-        try {
-            factory.and(filter, null);
-            fail("Creation of an AND with a null child filter must raise an exception");
-        } catch (NullPointerException ex) {}
-        try {
-            factory.and(null, filter);
-            fail("Creation of an AND with a null child filter must raise an exception");
-        } catch (NullPointerException ex) {}
-        try {
-            factory.and(Arrays.asList(filter));
-            fail("Creation of an AND with less then two children filters must raise an exception");
-        } catch (IllegalArgumentException ex) {}
-    }
-
-    /**
-     * Tests creation of "Or" expression from the factory.
-     */
-    @Test
-    public void testOrConstructor() {
-        final Filter filter = factory.isNull(factory.literal("text"));
-        assertNotNull(factory.or(filter, filter));
-        assertNotNull(factory.or(Arrays.asList(filter, filter, filter)));
-        try {
-            factory.or(null, null);
-            fail("Creation of an OR with a null child filter must raise an exception");
-        } catch (NullPointerException ex) {}
-        try {
-            factory.or(filter, null);
-            fail("Creation of an OR with a null child filter must raise an exception");
-        } catch (NullPointerException ex) {}
-        try {
-            factory.or(null, filter);
-            fail("Creation of an OR with a null child filter must raise an exception");
-        } catch (NullPointerException ex) {}
-        try {
-            factory.or(Arrays.asList(filter));
-            fail("Creation of an OR with less then two children filters must raise an exception");
-        } catch (IllegalArgumentException ex) {}
-    }
-
-    /**
-     * Tests evaluation of "And" expression.
-     */
-    @Test
-    public void testAndEvaluate() {
-        final Filter filterTrue  = factory.isNull(factory.property("attNull"));
-        final Filter filterFalse = factory.isNull(factory.property("attNotNull"));
-        final Map<String,String> feature = Collections.singletonMap("attNotNull", "text");
-
-        assertTrue (factory.and(filterTrue,  filterTrue ).evaluate(feature));
-        assertFalse(factory.and(filterFalse, filterTrue ).evaluate(feature));
-        assertFalse(factory.and(filterTrue,  filterFalse).evaluate(feature));
-        assertFalse(factory.and(filterFalse, filterFalse).evaluate(feature));
-    }
-
-    /**
-     * Tests evaluation of "Or" expression.
-     */
-    @Test
-    public void testOrEvaluate() {
-        final Filter filterTrue  = factory.isNull(factory.property("attNull"));
-        final Filter filterFalse = factory.isNull(factory.property("attNotNull"));
-        final Map<String,String> feature = Collections.singletonMap("attNotNull", "text");
-
-        assertTrue (factory.or(filterTrue,  filterTrue ).evaluate(feature));
-        assertTrue (factory.or(filterFalse, filterTrue ).evaluate(feature));
-        assertTrue (factory.or(filterTrue,  filterFalse).evaluate(feature));
-        assertFalse(factory.or(filterFalse, filterFalse).evaluate(feature));
-    }
-
-    /**
-     * Tests serialization of "And" expression.
-     */
-    @Test
-    public void testAndSerialize() {
-        final Literal literal = factory.literal("text");
-        final Filter  filter  = factory.isNull(literal);
-        assertSerializedEquals(factory.and(filter, filter));
-    }
-
-    /**
-     * Tests serialization of "Or" expression.
-     */
-    @Test
-    public void testOrSerialize() {
-        final Literal literal = factory.literal("text");
-        final Filter  filter  = factory.isNull(literal);
-        assertSerializedEquals(factory.or(filter, filter));
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java b/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java
deleted file mode 100644
index 3adffe2..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/PeriodLiteral.java
+++ /dev/null
@@ -1,115 +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.filter;
-
-import java.util.Date;
-import java.io.Serializable;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.Duration;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalPosition;
-import org.opengis.temporal.TemporalPrimitive;
-import org.opengis.temporal.TemporalGeometricPrimitive;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.ExpressionVisitor;
-import org.opengis.referencing.ReferenceIdentifier;
-import org.apache.sis.test.TestUtilities;
-
-
-/**
- * A literal expression which returns a period computed from {@link #begin} and {@link #end} fields.
- * This is used for testing purpose only.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-@SuppressWarnings("serial")
-final strictfp class PeriodLiteral implements Period, Literal, Serializable {
-    /**
-     * Period beginning and ending, in milliseconds since Java epoch.
-     */
-    public long begin, end;
-
-    /**
-     * Returns the constant value held by this object.
-     * Returns value should be interpreted as a {@link Period}.
-     */
-    @Override public Object getValue()                     {return this;}
-    @Override public Object evaluate(Object o)             {return this;}
-    @Override public <T> T  evaluate(Object o, Class<T> c) {return c.cast(this);}
-
-    /** Implements the visitor pattern (not used by Apache SIS). */
-    @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
-        return visitor.visit(this, extraData);
-    }
-
-    /** Returns a bound of this period. */
-    @Override public org.opengis.temporal.Instant getBeginning() {return instant(begin);}
-    @Override public org.opengis.temporal.Instant getEnding()    {return instant(end);}
-
-    /** Wraps the value that defines a period. */
-    private static org.opengis.temporal.Instant instant(final long t) {
-        return new org.opengis.temporal.Instant() {
-            @Override public Date   getDate()  {return new Date(t);}
-            @Override public String toString() {return "Instant[" + TestUtilities.format(getDate()) + '[';}
-
-            /** Not needed for the tests. */
-            @Override public ReferenceIdentifier getName()                           {throw new UnsupportedOperationException();}
-            @Override public TemporalPosition getTemporalPosition()                  {throw new UnsupportedOperationException();}
-            @Override public RelativePosition relativePosition(TemporalPrimitive o)  {throw new UnsupportedOperationException();}
-            @Override public Duration         distance(TemporalGeometricPrimitive o) {throw new UnsupportedOperationException();}
-            @Override public Duration         length()                               {throw new UnsupportedOperationException();}
-        };
-    }
-
-    /** Not needed for the tests. */
-    @Override public ReferenceIdentifier getName()                           {throw new UnsupportedOperationException();}
-    @Override public RelativePosition relativePosition(TemporalPrimitive o)  {throw new UnsupportedOperationException();}
-    @Override public Duration         distance(TemporalGeometricPrimitive o) {throw new UnsupportedOperationException();}
-    @Override public Duration         length()                               {throw new UnsupportedOperationException();}
-
-    /**
-     * Hash code value. Used by the tests for checking the results of deserialization.
-     */
-    @Override
-    public int hashCode() {
-        return Long.hashCode(begin) + Long.hashCode(end) * 7;
-    }
-
-    /**
-     * Compare this period with given object. Used by the tests for checking the results of deserialization.
-     */
-    @Override
-    public boolean equals(final Object other) {
-        if (other instanceof PeriodLiteral) {
-            final PeriodLiteral p = (PeriodLiteral) other;
-            return begin == p.begin && end == p.end;
-        }
-        return false;
-    }
-
-    /**
-     * Returns a string representation for debugging purposes.
-     */
-    @Override
-    public String toString() {
-        return "Period[" + TestUtilities.format(new Date(begin)) +
-                 " ... " + TestUtilities.format(new Date(end)) + ']';
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
deleted file mode 100644
index 985609c..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
+++ /dev/null
@@ -1,200 +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.filter;
-
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.expression.Function;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.referencing.crs.HardCodedCRS;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.opengis.test.Assert.*;
-
-
-/**
- * Tests {@link SQLMM} functions implementations.
- * Current implementation tests Java Topology Suite (JTS) implementation only.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class SQLMMTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory factory;
-
-    /**
-     * The factory to use for creating Java Topology Suite (JTS) objects.
-     */
-    private final GeometryFactory geometryFactory;
-
-    /**
-     * Creates a new test case.
-     */
-    public SQLMMTest() {
-        factory = new DefaultFilterFactory();
-        geometryFactory = new GeometryFactory();
-    }
-
-    /**
-     * Verifies that attempts to create a function of the given name fail if no argument is provided.
-     */
-    private void assertRequireArguments(final String functionName) {
-        try {
-            factory.function(functionName);
-            fail("Creation with no argument should fail");
-        } catch (IllegalArgumentException ex) {
-            final String message = ex.getMessage();
-            assertTrue(message, message.contains("parameters"));
-        }
-    }
-
-    /**
-     * Wraps the given geometry in a feature object. The geometry will be stored in a property named {@code "geom"}.
-     *
-     * @param  geometry  the geometry to wrap in a feature.
-     * @param  crs       the coordinate reference system to assign to the geometry.
-     */
-    private static Feature wrapInFeature(final Geometry geometry, final CoordinateReferenceSystem crs) {
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder().setName("test");
-        ftb.addAttribute(Point.class).setName("geom").setCRS(crs);
-        final FeatureType type = ftb.build();
-        geometry.setUserData(crs);
-        final Feature feature = type.newInstance();
-        feature.setPropertyValue("geom", geometry);
-        return feature;
-    }
-
-    /**
-     * Evaluates the given function and returns its result as an object of the given type.
-     *
-     * @param  expectedType  the expected type of the result.
-     * @param  feature       the feature to use as input value. May be {@code null}.
-     * @param  testing       the function to test.
-     * @return evaluation result.
-     */
-    private static <G extends Geometry> G evaluate(final Class<G> expectedType, final Feature feature, final Function testing) {
-        final Object result = testing.evaluate(feature);
-        assertInstanceOf("Expected JTS geometry.", expectedType, result);
-        return expectedType.cast(result);
-    }
-
-    /**
-     * Test SQL/MM {@link ST_Transform} function.
-     */
-    @Test
-    public void testTransform() {
-        /*
-         * Verify that creation of a function without arguments is not allowed.
-         */
-        assertRequireArguments("ST_Transform");
-        /*
-         * Create a feature to be used for testing purpose. For this test, the CRS transformation
-         * will be simply a change of axis order from (λ,φ) to (φ,λ).
-         */
-        final Point geometry = geometryFactory.createPoint(new Coordinate(10, 30));
-        final Feature feature = wrapInFeature(geometry, HardCodedCRS.WGS84);
-        /*
-         * Test transform function using the full CRS object, then using only EPSG code.
-         */
-        testTransform(feature, HardCodedCRS.WGS84_φλ, HardCodedCRS.WGS84_φλ);
-        testTransform(feature, "EPSG:4326", CommonCRS.WGS84.geographic());
-    }
-
-    /**
-     * Tests {@link ST_Transform} on the given feature. The feature must have a property named {@code "geom"}.
-     * The result is expected to be a geometry using WGS84 datum with (φ,λ) axis order.
-     *
-     * @param  feature       the feature to use for testing the function.
-     * @param  specifiedCRS  the argument to give to the {@code "ST_Transform"} function.
-     * @param  expectedCRS   the CRS expected as a result of the transform function.
-     */
-    private void testTransform(final Feature feature, final Object specifiedCRS, final CoordinateReferenceSystem expectedCRS) {
-        final Point result = evaluate(Point.class, feature, factory.function("ST_Transform",
-                factory.property("geom"), factory.literal(specifiedCRS)));
-        assertEquals("userData", expectedCRS, result.getUserData());
-        assertEquals(30, result.getX(), STRICT);
-        assertEquals(10, result.getY(), STRICT);
-    }
-
-    /**
-     * Test SQL/MM {@link ST_Centroid} function.
-     */
-    @Test
-    public void testCentroid() {
-        assertRequireArguments("ST_Centroid");
-        /*
-         * Creates a single linestring for testing the centroid function. The CRS is not used by this computation,
-         * but we declare it in order to verify that the information is propagated to the result.
-         */
-        final LineString geometry = geometryFactory.createLineString(new Coordinate[] {
-            new Coordinate(10, 20),
-            new Coordinate(30, 20)
-        });
-        geometry.setSRID(4326);
-        geometry.setUserData(HardCodedCRS.WGS84_φλ);
-        /*
-         * Execute the function and check the result.
-         */
-        final Point result = evaluate(Point.class, null, factory.function("ST_Centroid", factory.literal(geometry)));
-        assertEquals("userData", HardCodedCRS.WGS84_φλ, result.getUserData());
-        assertEquals("SRID", 4326, result.getSRID());
-        assertEquals(20, result.getX(), STRICT);
-        assertEquals(20, result.getY(), STRICT);
-    }
-
-    /**
-     * Test SQL/MM {@link ST_Buffer} function.
-     */
-    @Test
-    public void ST_BufferTest() {
-        assertRequireArguments("ST_Buffer");
-        /*
-         * Creates a single point for testing the buffer function. The CRS is not used by this computation,
-         * but we declare it in order to verify that the information is propagated to the result.
-         */
-        final Point geometry = geometryFactory.createPoint(new Coordinate(10, 20));
-        geometry.setUserData(HardCodedCRS.WGS84_φλ);
-        geometry.setSRID(4326);
-        /*
-         * Execute the function and check the result.
-         */
-        final Polygon result = evaluate(Polygon.class, null, factory.function("ST_Buffer", factory.literal(geometry), factory.literal(1)));
-        assertEquals("userData", HardCodedCRS.WGS84_φλ, result.getUserData());
-        assertEquals("SRID", 4326, result.getSRID());
-        final Envelope env = result.getEnvelopeInternal();
-        assertEquals( 9, env.getMinX(), STRICT);
-        assertEquals(11, env.getMaxX(), STRICT);
-        assertEquals(19, env.getMinY(), STRICT);
-        assertEquals(21, env.getMaxY(), STRICT);
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFunctionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFunctionTest.java
deleted file mode 100644
index 501270c..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/TemporalFunctionTest.java
+++ /dev/null
@@ -1,341 +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.filter;
-
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.temporal.BinaryTemporalOperator;
-import org.apache.sis.test.TestUtilities;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.apache.sis.test.Assert.*;
-import static org.apache.sis.internal.util.StandardDateFormat.MILLISECONDS_PER_DAY;
-
-
-/**
- * Tests {@link TemporalFunction} implementations.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class TemporalFunctionTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory factory;
-
-    /**
-     * The filter to test. This field shall be assigned by each {@code testFoo()} method by invoking
-     * a {@link #factory} method with {@link #expression1} and {@link #expression2} in arguments.
-     */
-    private BinaryTemporalOperator filter;
-
-    /**
-     * The expression to test. They are the arguments to be given to {@link #factory} method.
-     * Each expression will return a period made of {@link PeriodLiteral#begin} and {@link PeriodLiteral#end}.
-     */
-    private final PeriodLiteral expression1, expression2;
-
-    /**
-     * Creates a new test case.
-     */
-    public TemporalFunctionTest() {
-        factory = new DefaultFilterFactory();
-        expression1 = new PeriodLiteral();
-        expression2 = new PeriodLiteral();
-        expression1.begin = expression2.begin = TestUtilities.date("2000-01-01 09:00:00").getTime();
-        expression1.end   = expression2.end   = TestUtilities.date("2000-01-05 10:00:00").getTime();
-    }
-
-    /**
-     * Performs some validation on newly created filter.
-     * The {@link #filter} field must be initialized before this method is invoked.
-     *
-     * @param  name  expected filter name.
-     */
-    private void validate(final String name) {
-        assertInstanceOf("Expected SIS implementation.", TemporalFunction.class, filter);
-        assertEquals("name", name, ((TemporalFunction) filter).getName());
-        assertSame("expression1", expression1, filter.getExpression1());
-        assertSame("expression2", expression2, filter.getExpression2());
-        assertSerializedEquals(filter);
-    }
-
-    /**
-     * Evaluates the filter.
-     */
-    private boolean evaluate() {
-        return filter.evaluate(null);
-    }
-
-    /**
-     * Tests "TEquals" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testEquals() {
-        filter = factory.tequals(expression1, expression2);
-        validate("TEquals");
-        assertTrue(evaluate());
-
-        // Break the "self.end = other.end" condition.
-        expression1.end++;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "Before" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testBefore() {
-        filter = factory.before(expression1, expression2);
-        validate("Before");
-        assertFalse(evaluate());
-
-        // Move before expression 2.
-        expression1.begin -= 10 * MILLISECONDS_PER_DAY;
-        expression1.end   -= 10 * MILLISECONDS_PER_DAY;
-        assertTrue(evaluate());
-
-        // Break the "self.end < other.begin" condition.
-        expression1.end = expression2.begin;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "After" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testAfter() {
-        filter = factory.after(expression1, expression2);
-        validate("After");
-        assertFalse(evaluate());
-
-        // Move after expression 2.
-        expression1.begin += 10 * MILLISECONDS_PER_DAY;
-        expression1.end   += 10 * MILLISECONDS_PER_DAY;
-        assertTrue(evaluate());
-
-        // Break the "self.begin > other.end" condition.
-        expression1.begin = expression2.end;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "Begins" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testBegins() {
-        filter = factory.begins(expression1, expression2);
-        validate("Begins");
-        assertFalse(evaluate());
-
-        // End before ending of expression 2.
-        expression1.end--;
-        assertTrue(evaluate());
-
-        // Break the "self.begin = other.begin" condition.
-        expression1.begin++;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "Ends" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testEnds() {
-        filter = factory.ends(expression1, expression2);
-        validate("Ends");
-        assertFalse(evaluate());
-
-        // Begin after beginning of expression 2.
-        expression1.begin++;
-        assertTrue(evaluate());
-
-        // Break the "self.end = other.end" condition.
-        expression1.end--;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "BegunBy" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testBegunBy() {
-        filter = factory.begunBy(expression1, expression2);
-        validate("BegunBy");
-        assertFalse(evaluate());
-
-        // End after ending of expression 2.
-        expression1.end++;
-        assertTrue(evaluate());
-
-        // Break the "self.begin = other.begin" condition.
-        expression1.begin--;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "EndedBy" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testEndedBy() {
-        filter = factory.endedBy(expression1, expression2);
-        validate("EndedBy");
-        assertFalse(evaluate());
-
-        // Begin before beginning of expression 2.
-        expression1.begin--;
-        assertTrue(evaluate());
-
-        // Break the "self.end = other.end" condition.
-        expression1.end++;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "Meets" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testMeets() {
-        filter = factory.meets(expression1, expression2);
-        validate("Meets");
-        assertFalse(evaluate());
-
-        // Move before expression 2.
-        expression1.begin -= 10 * MILLISECONDS_PER_DAY;
-        expression1.end   -= 10 * MILLISECONDS_PER_DAY;
-        assertFalse(evaluate());
-
-        // Met the "self.end = other.begin" condition.
-        expression1.end = expression2.begin;
-        assertTrue(evaluate());
-    }
-
-    /**
-     * Tests "MetBy" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testMetBy() {
-        filter = factory.metBy(expression1, expression2);
-        validate("MetBy");
-        assertFalse(evaluate());
-
-        // Move after expression 2.
-        expression1.begin += 10 * MILLISECONDS_PER_DAY;
-        expression1.end   += 10 * MILLISECONDS_PER_DAY;
-        assertFalse(evaluate());
-
-        // Met the "self.begin = other.end" condition.
-        expression1.begin = expression2.end;
-        assertTrue(evaluate());
-    }
-
-    /**
-     * Tests "During" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testDuring() {
-        filter = factory.during(expression1, expression2);
-        validate("During");
-        assertFalse(evaluate());
-
-        // Shrink inside expression 2.
-        expression1.begin++;
-        expression1.end--;
-        assertTrue(evaluate());
-
-        // Break the "self.end < other.end" condition.
-        expression1.end += 2;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "TContains" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testContains() {
-        filter = factory.tcontains(expression1, expression2);
-        validate("TContains");
-        assertFalse(evaluate());
-
-        // Expand to encompass expression 2.
-        expression1.begin--;
-        expression1.end++;
-        assertTrue(evaluate());
-
-        // Break the "self.end > other.end" condition.
-        expression1.end -= 2;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "TOverlaps" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testOverlaps() {
-        filter = factory.toverlaps(expression1, expression2);
-        validate("TOverlaps");
-        assertFalse(evaluate());
-
-        // Translate to overlap left part of expression 2.
-        expression1.begin--;
-        expression1.end--;
-        assertTrue(evaluate());
-
-        // Break the "self.end < other.end" condition.
-        expression1.end += 2;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "OverlappedBy" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testOverlappedBy() {
-        filter = factory.overlappedBy(expression1, expression2);
-        validate("OverlappedBy");
-        assertFalse(evaluate());
-
-        // Translate to overlap right part of expression 2.
-        expression1.begin++;
-        expression1.end++;
-        assertTrue(evaluate());
-
-        // Break the "self.end > other.end" condition.
-        expression1.end -= 2;
-        assertFalse(evaluate());
-    }
-
-    /**
-     * Tests "AnyInteracts" (construction, evaluation, serialization, equality).
-     */
-    @Test
-    public void testAnyInteracts() {
-        filter = factory.anyInteracts(expression1, expression2);
-        validate("AnyInteracts");
-        assertTrue(evaluate());
-        expression1.begin++;
-        assertTrue(evaluate());
-        expression1.begin -= 2;
-        assertTrue(evaluate());
-        expression1.end += 2;
-        assertTrue(evaluate());
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/UnaryFunctionTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/UnaryFunctionTest.java
deleted file mode 100644
index 69fde93..0000000
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/UnaryFunctionTest.java
+++ /dev/null
@@ -1,76 +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.filter;
-
-import java.util.Map;
-import java.util.Collections;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory2;
-import org.opengis.filter.expression.Literal;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests {@link UnaryFunction}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.1
- * @since   1.1
- * @module
- */
-public final strictfp class UnaryFunctionTest extends TestCase {
-    /**
-     * The factory to use for creating the objects to test.
-     */
-    private final FilterFactory2 factory = new DefaultFilterFactory();
-
-    /**
-     * Test factory with the "Not" expression.
-     */
-    @Test
-    public void testNotConstructor() {
-        final Literal literal = factory.literal("text");
-        final Filter  filter  = factory.isNull(literal);
-        assertNotNull(factory.not(filter));
-    }
-
-    /**
-     * Tests evaluation of "Not" expression.
-     */
-    @Test
-    public void testNotEvaluate() {
-        final Filter filterTrue  = factory.isNull(factory.property("attNull"));
-        final Filter filterFalse = factory.isNull(factory.property("attNotNull"));
-        final Map<String,String> feature = Collections.singletonMap("attNotNull", "text");
-
-        assertFalse(factory.not(filterTrue ).evaluate(feature));
-        assertTrue (factory.not(filterFalse).evaluate(feature));
-    }
-
-    /**
-     * Tests serialization of "Not" expression.
-     */
-    @Test
-    public void testNotSerialize() {
-        final Literal literal = factory.literal("text");
-        final Filter  filter  = factory.isNull(literal);
-        assertSerializedEquals(factory.not(filter));
-    }
-}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
index 724defe..a324d13 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
@@ -26,7 +26,6 @@
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
 import java.nio.FloatBuffer;
-import org.opengis.coverage.grid.SequenceType;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
index de7a87d..b99a426 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/LinearIteratorTest.java
@@ -21,7 +21,6 @@
 import java.awt.image.DataBuffer;
 import java.awt.image.WritableRaster;
 import java.awt.image.WritableRenderedImage;
-import org.opengis.coverage.grid.SequenceType;
 
 import static org.junit.Assert.*;
 
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
index 22423e2..231bd46 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeConventionTest.java
@@ -31,9 +31,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Property;
-import org.opengis.feature.Operation;
-import org.opengis.feature.IdentifiedType;
+import org.apache.sis.feature.AbstractOperation;
 
 
 /**
@@ -57,7 +55,7 @@
     }
 
     /**
-     * Tests {@link AttributeConvention#isGeometryAttribute(IdentifiedType)} method.
+     * Tests {@code AttributeConvention.isGeometryAttribute(IdentifiedType)} method.
      */
     @Test
     public void testIsGeometryAttribute() {
@@ -71,8 +69,8 @@
     }
 
     /**
-     * Tests {@link AttributeConvention#characterizedByCRS(IdentifiedType)} and
-     * {@link AttributeConvention#getCRSCharacteristic(Property)} methods.
+     * Tests {@code AttributeConvention.characterizedByCRS(IdentifiedType)} and
+     * {@code AttributeConvention.getCRSCharacteristic(Property)} methods.
      */
     @Test
     public void testGetCrsCharacteristic() {
@@ -95,15 +93,15 @@
         /*
          * Test again AttributeConvention.getCRSCharacteristic(…, PropertyType), but following link.
          */
-        final Operation link = FeatureOperations.link(Collections.singletonMap(DefaultAttributeType.NAME_KEY, "geom"), type);
+        final AbstractOperation link = FeatureOperations.link(Collections.singletonMap(DefaultAttributeType.NAME_KEY, "geom"), type);
         final DefaultFeatureType feat = new DefaultFeatureType(Collections.singletonMap(DefaultAttributeType.NAME_KEY, "feat"), false, null, type, link);
         assertEquals(HardCodedCRS.WGS84, AttributeConvention.getCRSCharacteristic(feat, link));
         assertNull(                      AttributeConvention.getCRSCharacteristic(null, link));
     }
 
     /**
-     * Tests {@link AttributeConvention#characterizedByMaximalLength(IdentifiedType)} and
-     * {@link AttributeConvention#getMaximalLengthCharacteristic(Property)} methods.
+     * Tests {@code AttributeConvention.characterizedByMaximalLength(IdentifiedType)} and
+     * {@code AttributeConvention.getMaximalLengthCharacteristic(Property)} methods.
      */
     @Test
     public void testGetMaximalLengthCharacteristic() {
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index 7c06225..744eed8 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -49,15 +49,6 @@
     org.apache.sis.feature.EnvelopeOperationTest.class,
     org.apache.sis.feature.FeatureFormatTest.class,
     org.apache.sis.feature.FeaturesTest.class,
-    org.apache.sis.filter.LeafExpressionTest.class,
-    org.apache.sis.filter.LogicalFunctionTest.class,
-    org.apache.sis.filter.UnaryFunctionTest.class,
-    org.apache.sis.filter.DefaultObjectIdTest.class,
-    org.apache.sis.filter.FilterByIdentifierTest.class,
-    org.apache.sis.filter.ArithmeticFunctionTest.class,
-    org.apache.sis.filter.ComparisonFunctionTest.class,
-    org.apache.sis.filter.TemporalFunctionTest.class,
-    org.apache.sis.filter.SQLMMTest.class,
     org.apache.sis.internal.feature.AttributeConventionTest.class,
     org.apache.sis.internal.feature.j2d.ShapePropertiesTest.class,
     org.apache.sis.internal.feature.Java2DTest.class,
diff --git a/core/sis-metadata/pom.xml b/core/sis-metadata/pom.xml
index 8514939..c288e8f 100644
--- a/core/sis-metadata/pom.xml
+++ b/core/sis-metadata/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/Interim.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/Interim.java
new file mode 100644
index 0000000..5407115
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/Interim.java
@@ -0,0 +1,49 @@
+/*
+ * 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.geoapi.evolution;
+
+import java.lang.reflect.Method;
+import org.apache.sis.util.Static;
+
+
+/**
+ * Temporary methods used until a new major GeoAPI release provides the missing functionalities.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ */
+public final class Interim extends Static {
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private Interim() {
+    }
+
+    /**
+     * Returns the return type of the given method, or the interim type if the method is annotated
+     * with {@link InterimType}.
+     *
+     * @param  method  the method from which to get the return type.
+     * @return the return type or the interim type.
+     */
+    public static Class<?> getReturnType(final Method method) {
+        final InterimType an = method.getAnnotation(InterimType.class);
+        return (an != null) ? an.value() : method.getReturnType();
+    }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/InterimType.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/InterimType.java
new file mode 100644
index 0000000..ace7308
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/InterimType.java
@@ -0,0 +1,45 @@
+/*
+ * 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.geoapi.evolution;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+
+
+/**
+ * Identifies an interim class to use until an official GeoAPI class or interface is released.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since   0.8
+ * @module
+ *
+ * @see Interim#getReturnType(Method)
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InterimType {
+    /**
+     * The interim Apache SIS class to use until the GeoAPI class or interface is released.
+     *
+     * @return Apache SIS class to use in the interim.
+     */
+    Class<?> value();
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeList.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeList.java
new file mode 100644
index 0000000..09df869
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeList.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 desruisseaux.
+ *
+ * Licensed 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.geoapi.evolution;
+
+import java.util.List;
+import java.util.ArrayList;
+import org.opengis.annotation.UML;
+import org.opengis.util.CodeList;
+
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
+
+/**
+ * Placeholder for code list not yet available in GeoAPI.
+ * Currently defines constants mostly for {@code org.opengis.metadata.citation.TelephoneType},
+ * but constants for other code list can be constructed like below:
+ *
+ * {@preformat java
+ *   operation.getDistributedComputingPlatforms().add(UnsupportedCodeList.valueOf("SOAP"));
+ * }
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   0.5
+ * @module
+ */
+public final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 7205015191869240829L;
+
+    /**
+     * The list of constants defined in this code list.
+     */
+    private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>(3);
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="voice", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> VOICE = new UnsupportedCodeList("VOICE");
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="facsimile", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> FACSIMILE = new UnsupportedCodeList("FACSIMILE");
+
+    /**
+     * A frequently used code list element.
+     */
+    @UML(identifier="WebServices", obligation=CONDITIONAL, specification=ISO_19115)
+    public static final CodeList<?> WEB_SERVICES = new UnsupportedCodeList("WEB_SERVICES");
+
+    /**
+     * Constructor for new code list element.
+     *
+     * @param name The code list name.
+     */
+    private UnsupportedCodeList(String name) {
+        super(name, VALUES);
+    }
+
+    /**
+     * Returns the list of codes of the same kind than this code list element.
+     *
+     * @return All code values for this code list.
+     */
+    @Override
+    public UnsupportedCodeList[] family() {
+        synchronized (VALUES) {
+            return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+        }
+    }
+
+    /**
+     * Returns the telephone type that matches the given string, or returns a new one if none match it.
+     *
+     * @param code The name of the code to fetch or to create.
+     * @return A code matching the given name.
+     */
+    public static UnsupportedCodeList valueOf(String code) {
+        return valueOf(UnsupportedCodeList.class, code);
+    }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeListAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeListAdapter.java
new file mode 100644
index 0000000..459adf2
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/UnsupportedCodeListAdapter.java
@@ -0,0 +1,165 @@
+/*
+ * 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.geoapi.evolution;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.util.CodeList;
+import org.apache.sis.util.iso.Types;
+import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.jaxb.cat.CodeListUID;
+
+
+/**
+ * An adapter for {@link UnsupportedCodeList}, in order to implement the ISO 19115-3 standard.
+ * See {@link org.apache.sis.internal.jaxb.cat.CodeListAdapter} for more information.
+ *
+ * @param <ValueType> The subclass implementing this adapter.
+ *
+ * @author  Cédric Briançon (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 0.5
+ * @since   0.5
+ * @module
+ */
+public abstract class UnsupportedCodeListAdapter<ValueType extends UnsupportedCodeListAdapter<ValueType>>
+        extends XmlAdapter<ValueType,CodeList<?>>
+{
+    /**
+     * The value of the {@link CodeList}.
+     */
+    protected CodeListUID identifier;
+
+    /**
+     * Empty constructor for subclasses only.
+     */
+    protected UnsupportedCodeListAdapter() {
+    }
+
+    /**
+     * Creates a wrapper for a {@link CodeList}, in order to handle the format specified in ISO-19139.
+     *
+     * @param  value  the value of the {@link CodeList} to be marshalled.
+     */
+    protected UnsupportedCodeListAdapter(final CodeListUID value) {
+        identifier = value;
+    }
+
+    /**
+     * Wraps the code into an adapter.
+     * Most implementations will be like below:
+     *
+     * {@preformat java
+     *     return new ValueType(value);
+     * }
+     *
+     * @param  value  the value of {@link CodeList} to be marshalled.
+     * @return the wrapper for the code list value.
+     */
+    protected abstract ValueType wrap(CodeListUID value);
+
+    /**
+     * Returns the name of the code list class.
+     *
+     * @return the code list class name.
+     */
+    protected abstract String getCodeListName();
+
+    /**
+     * Substitutes the adapter value read from an XML stream by the object which will
+     * contains the value. JAXB calls automatically this method at unmarshalling time.
+     *
+     * @param  adapter  the adapter for this metadata value.
+     * @return a code list which represents the metadata value.
+     */
+    @Override
+    public final CodeList<?> unmarshal(final ValueType adapter) {
+        if (adapter == null) {
+            return null;
+        }
+        return Types.forCodeName(UnsupportedCodeList.class, adapter.identifier.toString(), true);
+    }
+
+    /**
+     * Substitutes the code list by the adapter to be marshalled into an XML file or stream.
+     * JAXB calls automatically this method at marshalling time.
+     *
+     * @param  value  the code list value.
+     * @return the adapter for the given code list.
+     */
+    @Override
+    public final ValueType marshal(final CodeList<?> value) {
+        if (value == null) {
+            return null;
+        }
+        final String name = value.name();
+        final int length = name.length();
+        final StringBuilder buffer = new StringBuilder(length);
+        final String codeListValue = toIdentifier(name, buffer, false);
+        buffer.setLength(0);
+        return wrap(new CodeListUID(Context.current(), getCodeListName(), codeListValue,
+                null, toIdentifier(name, buffer, true)));
+    }
+
+    /**
+     * Converts the given Java constant name to something hopefully close to the UML identifier,
+     * or close to the textual value to put in the XML. This method convert the Java constant name
+     * to camel case if {@code isValue} is {@code true}, or to lower cases with word separated by
+     * spaces if {@code isValue} is {@code true}.
+     *
+     * @param  name    The Java constant name (e.g. {@code WEB_SERVICES}).
+     * @param  buffer  An initially empty buffer to use for creating the identifier.
+     * @param  isValue {@code false} for the {@code codeListValue} attribute, or {@code true} for the XML value.
+     * @return The identifier (e.g. {@code "webServices"} or {@code "Web services"}).
+     */
+    protected String toIdentifier(final String name, final StringBuilder buffer, final boolean isValue) {
+        final int length = name.length();
+        boolean toUpper = isValue;
+        for (int i=0; i<length;) {
+            int c = name.codePointAt(i);
+            i += Character.charCount(c);
+            if (c == '_') {
+                if (isValue) {
+                    c = ' ';
+                } else {
+                    toUpper = true;
+                    continue;
+                }
+            }
+            if (toUpper) {
+                c = Character.toUpperCase(c);
+                toUpper = false;
+            } else {
+                c = Character.toLowerCase(c);
+            }
+            buffer.appendCodePoint(c);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Invoked by JAXB on marshalling. Subclasses must override this
+     * method with the appropriate {@code @XmlElement} annotation.
+     *
+     * @return The {@code CodeList} value to be marshalled.
+     */
+    public abstract CodeListUID getElement();
+
+    /*
+     * We do not define setter method (even abstract) since it seems to confuse JAXB.
+     * It is subclasses responsibility to define the setter method.
+     */
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/package-info.java
new file mode 100644
index 0000000..096014b
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides a transition path for new GeoAPI elements not yet published in a formal release.
+ * We try to avoid putting a copy of those new elements in Apache SIS, since it would break
+ * compatibility when they would be removed in favor of GeoAPI elements. The approach taken
+ * is rather to use in the new API the most immediate parent available in a GeoAPI release.
+ * For example for new code list classes, this is {@code CodeList<?>}. The Javadoc for such
+ * API shall contain a warning. See {@code warning-templates.txt} for some proposals.
+ *
+ * <p><STRONG>Do not use!</STRONG></p>
+ *
+ * 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)
+ * @version 0.5
+ * @since   0.5
+ * @module
+ */
+package org.apache.sis.internal.geoapi.evolution;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/warning-templates.txt b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/warning-templates.txt
new file mode 100644
index 0000000..b81aad9
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/geoapi/evolution/warning-templates.txt
@@ -0,0 +1,33 @@
+Suggestions for the Javadoc of methods having a CodeList<?> return type
+(replace "NewCodeList" and "3.1" by appropriate values):
+
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code NewCodeList} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
+     *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
index b33c925..9b1c94b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapEntry.java
@@ -21,6 +21,9 @@
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.metadata.iso.citation.Citations;
 
+// Branch-dependent imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * An entry in {@link org.apache.sis.xml.IdentifierMap}. This class implements both the {@link AbstractMap.Entry}
@@ -32,7 +35,7 @@
  * @since   0.3
  * @module
  */
-final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements Identifier {
+final class IdentifierMapEntry extends AbstractMap.SimpleEntry<Citation,String> implements ReferenceIdentifier {
     /**
      * For cross-version compatibility.
      */
@@ -74,15 +77,28 @@
     }
 
     /**
+     * Returns {@code null} since this class does not hold version information.
+     *
+     * @return {@code null}.
+     *
+     * @since 0.5
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Same than the above, but as an immutable entry. We use this implementation when the
      * entry has been created on-the-fly at iteration time rather than being stored in the
      * identifier collection.
      */
-    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements Identifier {
+    static final class Immutable extends AbstractMap.SimpleImmutableEntry<Citation,String> implements ReferenceIdentifier {
         private static final long serialVersionUID = -6857931598565368465L;
         Immutable(Citation authority, String code) {super(authority, code);}
         @Override public Citation            getAuthority()   {return getKey();}
         @Override public String              getCode()        {return getValue();}
         @Override public String              getCodeSpace()   {return Citations.toCodeSpace(getAuthority());}
+        @Override public String              getVersion()     {return null;}
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
index ecbcc44..b5e50f0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
@@ -31,7 +31,7 @@
 import org.apache.sis.metadata.iso.citation.Citations;
 
 // Branch-dependent imports
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -47,7 +47,7 @@
  * @since 0.3
  * @module
  */
-public final class SpecializedIdentifier<T> implements Identifier, Cloneable, Serializable {
+public final class SpecializedIdentifier<T> implements ReferenceIdentifier, Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -96,7 +96,7 @@
      *
      * @see IdentifierMapAdapter#put(Citation, String)
      */
-    static Identifier parse(final Citation authority, final String code) {
+    static ReferenceIdentifier parse(final Citation authority, final String code) {
         if (authority instanceof NonMarshalledAuthority) {
             final int ordinal = ((NonMarshalledAuthority) authority).ordinal;
             switch (ordinal) {
@@ -199,6 +199,18 @@
     }
 
     /**
+     * Returns {@code null} since this class does not hold version information.
+     *
+     * @return {@code null}.
+     *
+     * @since 0.5
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this identifier.
      */
     @Override
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
index 36444c9..51466c0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/CodeListUID.java
@@ -23,7 +23,6 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlValue;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.xml.Schemas;
@@ -145,7 +144,7 @@
      * @param context  the current (un)marshalling context, or {@code null} if none.
      * @param code     the code list to wrap.
      */
-    public CodeListUID(final Context context, final ControlledVocabulary code) {
+    public CodeListUID(final Context context, final CodeList<?> code) {
         final String classID = Types.getListName(code);
         final String fieldID = Types.getCodeName(code);
         codeList = schema(context, classID);
@@ -162,6 +161,9 @@
             try {
                 value = ResourceBundle.getBundle("org.opengis.metadata.CodeLists",
                         locale, CodeList.class.getClassLoader()).getString(key);
+                if ("Off line access".equals(value)) {
+                    value = "Offline access";               // For having the same value than GeoAPI 3.1.
+                }
             } catch (MissingResourceException e) {
                 Context.warningOccured(context, CodeListAdapter.class, "marshal", e, false);
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java
index 0158ec8..b0afc7d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/cat/EnumAdapter.java
@@ -17,10 +17,6 @@
 package org.apache.sis.internal.jaxb.cat;
 
 import javax.xml.bind.annotation.adapters.XmlAdapter;
-import org.apache.sis.util.iso.Types;
-
-// Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
 
 
 /**
@@ -87,7 +83,7 @@
      * @param  e  the enumeration constant.
      * @return the text to write in the XML element.
      */
-    protected static String value(final ControlledVocabulary e) {
-        return Types.getCodeName(e);
+    protected static String value(final Enum<?> e) {
+        return e.name();
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java
index 4cfc875..3ab423e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/CI_TelephoneTypeCode.java
@@ -17,14 +17,14 @@
 package org.apache.sis.internal.jaxb.code;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.citation.TelephoneType;
-import org.apache.sis.internal.jaxb.cat.CodeListAdapter;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeListAdapter;
+import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.cat.CodeListUID;
 import org.apache.sis.xml.Namespaces;
 
 
 /**
- * JAXB adapter for {@link TelephoneType}
+ * JAXB adapter for {@code TelephoneType}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
@@ -34,7 +34,7 @@
  * @since   1.0
  * @module
  */
-public class CI_TelephoneTypeCode extends CodeListAdapter<CI_TelephoneTypeCode, TelephoneType> {
+public class CI_TelephoneTypeCode extends UnsupportedCodeListAdapter<CI_TelephoneTypeCode> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -64,8 +64,8 @@
      * @return the code list class.
      */
     @Override
-    protected final Class<TelephoneType> getCodeListClass() {
-        return TelephoneType.class;
+    protected String getCodeListName() {
+        return "CI_TelephoneTypeCode";
     }
 
     /**
@@ -103,7 +103,7 @@
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
         @Override protected CI_TelephoneTypeCode wrap(final CodeListUID value) {
-            return accept2014() ? super.wrap(value) : null;
+            return FilterByVersion.CURRENT_METADATA.accept() ? super.wrap(value) : null;
         }
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java
index 6b97cc3..c4f4225 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/DCPList.java
@@ -17,14 +17,13 @@
 package org.apache.sis.internal.jaxb.code;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.apache.sis.internal.jaxb.cat.CodeListAdapter;
-import org.apache.sis.internal.jaxb.cat.CodeListUID;
 import org.apache.sis.xml.Namespaces;
+import org.apache.sis.internal.jaxb.cat.CodeListUID;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeListAdapter;
 
 
 /**
- * JAXB adapter for {@link DistributedComputingPlatform}
+ * JAXB adapter for {@code DistributedComputingPlatform}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
@@ -33,7 +32,7 @@
  * @since   0.5
  * @module
  */
-public final class DCPList extends CodeListAdapter<DCPList, DistributedComputingPlatform> {
+public final class DCPList extends UnsupportedCodeListAdapter<DCPList> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -63,8 +62,29 @@
      * @return the code list class.
      */
     @Override
-    protected Class<DistributedComputingPlatform> getCodeListClass() {
-        return DistributedComputingPlatform.class;
+    protected String getCodeListName() {
+        return "DCPList";
+    }
+
+    /**
+     * Converts the given Java constant name to something hopefully close to the UML identifier,
+     * or close to the textual value to put in the XML.
+     *
+     * @param  name    The Java constant name (e.g. {@code WEB_SERVICES}).
+     * @param  buffer  An initially empty buffer to use for creating the identifier.
+     * @param  isValue {@code false} for the {@code codeListValue} attribute, or {@code true} for the XML value.
+     * @return The identifier (e.g. {@code "WebServices"} or {@code "Web services"}).
+     */
+    @Override
+    protected String toIdentifier(final String name, final StringBuilder buffer, final boolean isValue) {
+        if (name.startsWith("WEB_")) {
+            super.toIdentifier(name, buffer, isValue);
+            buffer.setCharAt(0, 'W');
+            return buffer.toString();
+        } else {
+            // Other names are abbreviations (e.g. XML, SQL, FTP, etc.), so return unchanged.
+            return name;
+        }
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java
index 6962f03..6d35f4e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_CharacterSetCode.java
@@ -21,6 +21,7 @@
 import java.nio.charset.IllegalCharsetNameException;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.metadata.identification.CharacterSet;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.xml.ValueConverter;
 import org.apache.sis.internal.jaxb.Context;
@@ -105,4 +106,25 @@
     public void setElement(final CodeListUID value) {
         identifier = value;
     }
+
+    /**
+     * Converts the given Java Character Set to {@code CharacterSet}.
+     *
+     * @param  cs  the character set, or {@cod null}.
+     * @return a code list for the given character set, or {@code null} if the given {@code cs} was null.
+     */
+    public static CharacterSet fromCharset(final Charset cs) {
+        if (cs == null) {
+            return null;
+        }
+        final String name = cs.name();
+        for (final CharacterSet candidate : CharacterSet.values()) {
+            for (final String n : candidate.names()) {
+                if (name.equals(n)) {
+                    return candidate;
+                }
+            }
+        }
+        return CharacterSet.valueOf(name);
+    }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java
index 5379eda..c5442db 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/MD_RestrictionCode.java
@@ -94,8 +94,8 @@
      * @param  value  the unmarshalled value.
      */
     public void setElement(final CodeListUID value) {
-        if (value != null && "license".equalsIgnoreCase(value.codeListValue)) {
-            value.codeListValue = "licence";    // For matching current spelling (ISO 19115-3:2016).
+        if (value != null && "licence".equalsIgnoreCase(value.codeListValue)) {
+            value.codeListValue = "license";    // For matching legacy spelling (ISO 19139:2007).
         }
         identifier = value;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java
index cb973b1..5629c0c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_CouplingType.java
@@ -17,14 +17,13 @@
 package org.apache.sis.internal.jaxb.code;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.metadata.identification.CouplingType;
-import org.apache.sis.internal.jaxb.cat.CodeListAdapter;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeListAdapter;
 import org.apache.sis.internal.jaxb.cat.CodeListUID;
 import org.apache.sis.xml.Namespaces;
 
 
 /**
- * JAXB adapter for {@link CouplingType}
+ * JAXB adapter for {@code CouplingType}
  * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
  * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
  *
@@ -33,7 +32,7 @@
  * @since   0.5
  * @module
  */
-public final class SV_CouplingType extends CodeListAdapter<SV_CouplingType, CouplingType> {
+public final class SV_CouplingType extends UnsupportedCodeListAdapter<SV_CouplingType> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -63,8 +62,8 @@
      * @return the code list class.
      */
     @Override
-    protected Class<CouplingType> getCodeListClass() {
-        return CouplingType.class;
+    protected String getCodeListName() {
+        return "SV_CouplingType";
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_ParameterDirection.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_ParameterDirection.java
deleted file mode 100644
index 28ade2b..0000000
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/code/SV_ParameterDirection.java
+++ /dev/null
@@ -1,74 +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.internal.jaxb.code;
-
-import javax.xml.bind.annotation.XmlElement;
-import org.opengis.parameter.ParameterDirection;
-import org.apache.sis.internal.jaxb.cat.EnumAdapter;
-import org.apache.sis.xml.Namespaces;
-
-
-/**
- * JAXB adapter for {@link ParameterDirection}
- * in order to wrap the value in an XML element as specified by ISO 19115-3 standard.
- * See package documentation for more information about the handling of {@code CodeList} in ISO 19115-3.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   0.5
- * @module
- */
-public final class SV_ParameterDirection extends EnumAdapter<SV_ParameterDirection, ParameterDirection> {
-    /**
-     * The enumeration value.
-     */
-    @XmlElement(name = "SV_ParameterDirection", namespace = Namespaces.SRV)
-    private String value;
-
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public SV_ParameterDirection() {
-    }
-
-    /**
-     * Returns the wrapped value.
-     *
-     * @param  wrapper  the wrapper.
-     * @return the wrapped value.
-     */
-    @Override
-    public final ParameterDirection unmarshal(final SV_ParameterDirection wrapper) {
-        return ParameterDirection.valueOf(name(wrapper.value));
-    }
-
-    /**
-     * Wraps the given value.
-     *
-     * @param  e  the value to wrap.
-     * @return the wrapped value.
-     */
-    @Override
-    public final SV_ParameterDirection marshal(final ParameterDirection e) {
-        if (e == null) {
-            return null;
-        }
-        final SV_ParameterDirection wrapper = new SV_ParameterDirection();
-        wrapper.value = value(e);
-        return wrapper;
-    }
-}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
index 88759a8..2c6dc43 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
@@ -27,7 +27,6 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.w3c.dom.Element;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.gcx.Anchor;
@@ -259,7 +258,7 @@
         if (type != ENUM) {
             return null;
         }
-        final ControlledVocabulary code = Types.forCodeTitle(text);
+        final CodeList<?> code = Types.forCodeTitle(text);
         final String name = Types.getListName(code);
         /*
          * The namespace has have various value like CIT, SRV, MDQ, MRI, MSR, LAN, etc.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
index 9903cd0..546ced4 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TM_Primitive.java
@@ -18,8 +18,6 @@
 
 import java.util.Date;
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.Instant;
 import org.opengis.temporal.TemporalPrimitive;
 import org.apache.sis.internal.xml.XmlUtilities;
 import org.apache.sis.internal.jaxb.Context;
@@ -27,6 +25,10 @@
 import org.apache.sis.internal.util.TemporalUtilities;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.Instant;
+
 
 /**
  * JAXB adapter for {@link TemporalPrimitive}, in order to integrate the value in an element complying
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
index a18947b..8ab5b48 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
@@ -22,11 +22,13 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.datatype.XMLGregorianCalendar;
 import javax.xml.datatype.DatatypeConfigurationException;
-import org.opengis.temporal.Instant;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.internal.xml.XmlUtilities;
 
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.temporal.Instant;
+
 
 /**
  * Encapsulates a {@code gml:TimeInstant}. This element may be used alone, or included in a
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
index 71c4ff8..9c1b63a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
@@ -20,8 +20,8 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElements;
 import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.temporal.Period;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.geoapi.temporal.Period;
 import org.apache.sis.internal.util.Strings;
 
 import static org.apache.sis.internal.xml.LegacyNamespaces.VERSION_3_0;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java
index 6774f3c..3f915d1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriodBound.java
@@ -21,7 +21,9 @@
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.datatype.XMLGregorianCalendar;
-import org.opengis.temporal.Instant;
+
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.temporal.Instant;
 
 
 /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java
index ba2d4e7..e80f683 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_Duration.java
@@ -17,7 +17,7 @@
 package org.apache.sis.internal.jaxb.gts;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.temporal.Duration;
+import javax.xml.datatype.Duration;
 import org.opengis.temporal.PeriodDuration;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
@@ -99,6 +99,6 @@
      * @param  duration  the value to set.
      */
     public void setElement(final javax.xml.datatype.Duration duration) {
-        metadata = TM_PeriodDuration.toISO(duration);
+        metadata = /*TM_PeriodDuration.toISO*/(duration);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
index 4d561f8..4e297c8 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gts/TM_PeriodDuration.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.datatype.DatatypeConfigurationException;
 import org.opengis.temporal.PeriodDuration;
-import org.opengis.temporal.TemporalFactory;
+import org.apache.sis.internal.geoapi.temporal.TemporalFactory;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.xml.XmlUtilities;
@@ -96,8 +96,10 @@
     /**
      * Converts the given ISO 19108 duration into a Java XML duration.
      */
-    static Duration toXML(final PeriodDuration metadata) {
-        if (metadata != null) try {
+    static Duration toXML(final PeriodDuration duration) {
+        if (duration instanceof org.apache.sis.internal.geoapi.temporal.PeriodDuration) try {
+            final org.apache.sis.internal.geoapi.temporal.PeriodDuration metadata =
+                    (org.apache.sis.internal.geoapi.temporal.PeriodDuration) duration;
             /*
              * Get the DatatypeFactory first because if not available, then we don't need to parse
              * the calendar fields. This has the side effect of not validating the calendar fields
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java
index 43edd8f..a83766e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Party.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.citation.Party;
 import org.apache.sis.metadata.iso.citation.AbstractParty;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class CI_Party extends PropertyType<CI_Party, Party> {
+public final class CI_Party extends PropertyType<CI_Party, AbstractParty> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code Party.class}
      */
     @Override
-    protected Class<Party> getBoundType() {
-        return Party.class;
+    protected Class<AbstractParty> getBoundType() {
+        return AbstractParty.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CI_Party(final Party metadata) {
+    private CI_Party(final AbstractParty metadata) {
         super(metadata);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected CI_Party wrap(final Party metadata) {
+    protected CI_Party wrap(final AbstractParty metadata) {
         return new CI_Party(metadata);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public AbstractParty getElement() {
-        return AbstractParty.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java
index b767304..fb1f066 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/CI_Responsibility.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
@@ -35,7 +34,7 @@
  * @since   0.5
  * @module
  */
-public final class CI_Responsibility extends PropertyType<CI_Responsibility, Responsibility> {
+public final class CI_Responsibility extends PropertyType<CI_Responsibility, DefaultResponsibility> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -43,21 +42,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code Responsibility.class}
      */
     @Override
-    protected Class<Responsibility> getBoundType() {
-        return Responsibility.class;
+    protected Class<DefaultResponsibility> getBoundType() {
+        return DefaultResponsibility.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CI_Responsibility(final Responsibility metadata) {
+    private CI_Responsibility(final DefaultResponsibility metadata) {
         super(metadata);
     }
 
@@ -69,7 +66,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected CI_Responsibility wrap(final Responsibility metadata) {
+    protected CI_Responsibility wrap(final DefaultResponsibility metadata) {
         return new CI_Responsibility(metadata);
     }
 
@@ -88,9 +85,13 @@
                 // Need to build new DefaultResponsibility object here — simply casting doesn't work.
                 return new DefaultResponsibility(metadata);
             }
-            return DefaultResponsibility.castOrCopy(metadata);
+            return metadata;
         } else if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return DefaultResponsibleParty.castOrCopy(metadata);
+            if (metadata instanceof DefaultResponsibleParty) {
+                return metadata;
+            } else {
+                return new DefaultResponsibleParty(metadata);
+            }
         } else {
             return null;
         }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AssociatedResource.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AssociatedResource.java
deleted file mode 100644
index 172d73f..0000000
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AssociatedResource.java
+++ /dev/null
@@ -1,92 +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.internal.jaxb.metadata;
-
-import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.AssociatedResource;
-import org.apache.sis.metadata.iso.identification.DefaultAssociatedResource;
-import org.apache.sis.internal.jaxb.gco.PropertyType;
-
-
-/**
- * JAXB adapter mapping implementing class to the GeoAPI interface. See
- * package documentation for more information about JAXB and interface.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
- * @since   0.5
- * @module
- */
-public final class MD_AssociatedResource extends PropertyType<MD_AssociatedResource, AssociatedResource> {
-    /**
-     * Empty constructor for JAXB only.
-     */
-    public MD_AssociatedResource() {
-    }
-
-    /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
-     *
-     * @return {@code AssociatedResource.class}
-     */
-    @Override
-    protected Class<AssociatedResource> getBoundType() {
-        return AssociatedResource.class;
-    }
-
-    /**
-     * Constructor for the {@link #wrap} method only.
-     */
-    private MD_AssociatedResource(final AssociatedResource metadata) {
-        super(metadata);
-    }
-
-    /**
-     * Invoked by {@link PropertyType} at marshalling time for wrapping the given metadata value
-     * in a {@code <mri:MD_AssociatedResource>} XML element.
-     *
-     * @param  metadata  the metadata element to marshal.
-     * @return a {@code PropertyType} wrapping the given the metadata element.
-     */
-    @Override
-    protected MD_AssociatedResource wrap(final AssociatedResource metadata) {
-        return new MD_AssociatedResource(metadata);
-    }
-
-    /**
-     * Invoked by JAXB at marshalling time for getting the actual metadata to write
-     * inside the {@code <mri:MD_AssociatedResource>} XML element.
-     * This is the value or a copy of the value given in argument to the {@code wrap} method.
-     *
-     * @return the metadata to be marshalled.
-     */
-    @XmlElementRef
-    public DefaultAssociatedResource getElement() {
-        return DefaultAssociatedResource.castOrCopy(metadata);
-    }
-
-    /**
-     * Invoked by JAXB at unmarshalling time for storing the result temporarily.
-     *
-     * @param  metadata  the unmarshalled metadata.
-     */
-    public void setElement(final DefaultAssociatedResource metadata) {
-        this.metadata = metadata;
-    }
-}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java
index e0f510b..cabd87d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_AttributeGroup.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.content.AttributeGroup;
 import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class MD_AttributeGroup extends PropertyType<MD_AttributeGroup, AttributeGroup> {
+public final class MD_AttributeGroup extends PropertyType<MD_AttributeGroup, DefaultAttributeGroup> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code AttributeGroup.class}
      */
     @Override
-    protected Class<AttributeGroup> getBoundType() {
-        return AttributeGroup.class;
+    protected Class<DefaultAttributeGroup> getBoundType() {
+        return DefaultAttributeGroup.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_AttributeGroup(final AttributeGroup metadata) {
+    private MD_AttributeGroup(final DefaultAttributeGroup metadata) {
         super(metadata);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_AttributeGroup wrap(final AttributeGroup metadata) {
+    protected MD_AttributeGroup wrap(final DefaultAttributeGroup metadata) {
         return new MD_AttributeGroup(metadata);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultAttributeGroup getElement() {
-        return DefaultAttributeGroup.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java
index 86292dc..5e549b6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_FeatureTypeInfo.java
@@ -20,7 +20,6 @@
 
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
-import org.opengis.metadata.content.FeatureTypeInfo;
 
 
 /**
@@ -32,7 +31,7 @@
  * @version 1.0
  * @module
  */
-public final class MD_FeatureTypeInfo extends PropertyType<MD_FeatureTypeInfo, FeatureTypeInfo> {
+public final class MD_FeatureTypeInfo extends PropertyType<MD_FeatureTypeInfo, DefaultFeatureTypeInfo> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,14 +46,14 @@
      * @return {@code FeatureTypeInfo.class}
      */
     @Override
-    protected Class<FeatureTypeInfo> getBoundType() {
-        return FeatureTypeInfo.class;
+    protected Class<DefaultFeatureTypeInfo> getBoundType() {
+        return DefaultFeatureTypeInfo.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_FeatureTypeInfo(final FeatureTypeInfo metadata) {
+    private MD_FeatureTypeInfo(final DefaultFeatureTypeInfo metadata) {
         super(metadata);
     }
 
@@ -66,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_FeatureTypeInfo wrap(final FeatureTypeInfo metadata) {
+    protected MD_FeatureTypeInfo wrap(final DefaultFeatureTypeInfo metadata) {
         return new MD_FeatureTypeInfo(metadata);
     }
 
@@ -79,7 +78,7 @@
      */
     @XmlElementRef
     public DefaultFeatureTypeInfo getElement() {
-        return DefaultFeatureTypeInfo.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java
index 6188848..01bd5f9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Identifier.java
@@ -18,6 +18,7 @@
 
 import javax.xml.bind.annotation.XmlElementRef;
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
@@ -81,14 +82,15 @@
      */
     @XmlElementRef
     public final DefaultIdentifier getElement() {
-        if (FilterByVersion.LEGACY_METADATA.accept() && metadata != null) {
+        if (FilterByVersion.LEGACY_METADATA.accept() && metadata instanceof ReferenceIdentifier) {
             /*
              * In legacy specification, "code space" and "version" were not defined in <gmd:MD_Identifier> but were
              * defined in <gmd:RS_Identifier> subclass. In newer specification there is no longer such special case.
              * Note that "description" did not existed anywhere in legacy specification.
              */
-            if (metadata.getCodeSpace() != null || metadata.getVersion() != null) {
-                return RS_Identifier.wrap(metadata);
+            final ReferenceIdentifier id = (ReferenceIdentifier) metadata;
+            if (id.getCodeSpace() != null || id.getVersion() != null) {
+                return RS_Identifier.wrap(id);
             }
         }
         return DefaultIdentifier.castOrCopy(metadata);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java
index a9df9a7..07aa3c4 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_KeywordClass.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.KeywordClass;
 import org.apache.sis.metadata.iso.identification.DefaultKeywordClass;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public class MD_KeywordClass extends PropertyType<MD_KeywordClass, KeywordClass> {
+public class MD_KeywordClass extends PropertyType<MD_KeywordClass, DefaultKeywordClass> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,14 +45,14 @@
      * @return {@code KeywordClass.class}
      */
     @Override
-    protected final Class<KeywordClass> getBoundType() {
-        return KeywordClass.class;
+    protected final Class<DefaultKeywordClass> getBoundType() {
+        return DefaultKeywordClass.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_KeywordClass(final KeywordClass metadata) {
+    private MD_KeywordClass(final DefaultKeywordClass metadata) {
         super(metadata);
     }
 
@@ -65,7 +64,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_KeywordClass wrap(final KeywordClass metadata) {
+    protected MD_KeywordClass wrap(final DefaultKeywordClass metadata) {
         return new MD_KeywordClass(metadata);
     }
 
@@ -78,7 +77,7 @@
      */
     @XmlElementRef
     public final DefaultKeywordClass getElement() {
-        return DefaultKeywordClass.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -104,7 +103,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected MD_KeywordClass wrap(final KeywordClass value) {
+        @Override protected MD_KeywordClass wrap(final DefaultKeywordClass value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java
index 494ca1a..2f53adb 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_MetadataScope.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.MetadataScope;
 import org.apache.sis.metadata.iso.DefaultMetadataScope;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class MD_MetadataScope extends PropertyType<MD_MetadataScope, MetadataScope> {
+public final class MD_MetadataScope extends PropertyType<MD_MetadataScope, DefaultMetadataScope> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code MetadataScope.class}
      */
     @Override
-    protected Class<MetadataScope> getBoundType() {
-        return MetadataScope.class;
+    protected Class<DefaultMetadataScope> getBoundType() {
+        return DefaultMetadataScope.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_MetadataScope(final MetadataScope metadata) {
+    private MD_MetadataScope(final DefaultMetadataScope metadata) {
         super(metadata);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_MetadataScope wrap(final MetadataScope metadata) {
+    protected MD_MetadataScope wrap(final DefaultMetadataScope metadata) {
         return new MD_MetadataScope(metadata);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultMetadataScope getElement() {
-        return DefaultMetadataScope.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java
index d1d6504..9b05522 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Releasability.java
@@ -19,7 +19,6 @@
 import javax.xml.bind.annotation.XmlElementRef;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 import org.apache.sis.metadata.iso.constraint.DefaultReleasability;
-import org.opengis.metadata.constraint.Releasability;
 
 
 /**
@@ -32,7 +31,7 @@
  * @version 1.0
  * @module
  */
-public class MD_Releasability extends PropertyType<MD_Releasability, Releasability> {
+public class MD_Releasability extends PropertyType<MD_Releasability, DefaultReleasability> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,14 +46,14 @@
      * @return {@code Releasability.class}
      */
     @Override
-    protected final Class<Releasability> getBoundType() {
-        return Releasability.class;
+    protected final Class<DefaultReleasability> getBoundType() {
+        return DefaultReleasability.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private MD_Releasability(final Releasability metadata) {
+    private MD_Releasability(final DefaultReleasability metadata) {
         super(metadata);
     }
 
@@ -66,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected MD_Releasability wrap(final Releasability metadata) {
+    protected MD_Releasability wrap(final DefaultReleasability metadata) {
         return new MD_Releasability(metadata);
     }
 
@@ -79,7 +78,7 @@
      */
     @XmlElementRef
     public final DefaultReleasability getElement() {
-        return DefaultReleasability.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -105,7 +104,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected MD_Releasability wrap(final Releasability value) {
+        @Override protected MD_Releasability wrap(final DefaultReleasability value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java
index 0e8efd6..0500404 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/MD_Scope.java
@@ -17,7 +17,7 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java
index 2ad1b5b..9b69733 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_CoupledResource.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.CoupledResource;
 import org.apache.sis.metadata.iso.identification.DefaultCoupledResource;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class SV_CoupledResource extends PropertyType<SV_CoupledResource, CoupledResource> {
+public final class SV_CoupledResource extends PropertyType<SV_CoupledResource, DefaultCoupledResource> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code CoupledResource.class}
      */
     @Override
-    protected Class<CoupledResource> getBoundType() {
-        return CoupledResource.class;
+    protected Class<DefaultCoupledResource> getBoundType() {
+        return DefaultCoupledResource.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_CoupledResource(final CoupledResource metadata) {
+    private SV_CoupledResource(final DefaultCoupledResource metadata) {
         super(metadata);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_CoupledResource wrap(final CoupledResource metadata) {
+    protected SV_CoupledResource wrap(final DefaultCoupledResource metadata) {
         return new SV_CoupledResource(metadata);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultCoupledResource getElement() {
-        return DefaultCoupledResource.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java
index eb682a7..0e1fc4a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationChainMetadata.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.OperationChainMetadata;
 import org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public final class SV_OperationChainMetadata extends PropertyType<SV_OperationChainMetadata, OperationChainMetadata> {
+public final class SV_OperationChainMetadata extends PropertyType<SV_OperationChainMetadata, DefaultOperationChainMetadata> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code OperationChainMetadata.class}
      */
     @Override
-    protected Class<OperationChainMetadata> getBoundType() {
-        return OperationChainMetadata.class;
+    protected Class<DefaultOperationChainMetadata> getBoundType() {
+        return DefaultOperationChainMetadata.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_OperationChainMetadata(final OperationChainMetadata metadata) {
+    private SV_OperationChainMetadata(final DefaultOperationChainMetadata metadata) {
         super(metadata);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_OperationChainMetadata wrap(final OperationChainMetadata metadata) {
+    protected SV_OperationChainMetadata wrap(final DefaultOperationChainMetadata metadata) {
         return new SV_OperationChainMetadata(metadata);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public DefaultOperationChainMetadata getElement() {
-        return DefaultOperationChainMetadata.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java
index 5adb052..5dcce0c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/SV_OperationMetadata.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.metadata;
 
 import javax.xml.bind.annotation.XmlElementRef;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.metadata.iso.identification.DefaultOperationMetadata;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.5
  * @module
  */
-public class SV_OperationMetadata extends PropertyType<SV_OperationMetadata, OperationMetadata> {
+public class SV_OperationMetadata extends PropertyType<SV_OperationMetadata, DefaultOperationMetadata> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -39,21 +38,19 @@
     }
 
     /**
-     * Returns the GeoAPI interface which is bound by this adapter.
-     * This method is indirectly invoked by the private constructor
-     * below, so it shall not depend on the state of this object.
+     * Returns the type which is bound by this adapter.
      *
      * @return {@code OperationMetadata.class}
      */
     @Override
-    protected final Class<OperationMetadata> getBoundType() {
-        return OperationMetadata.class;
+    protected final Class<DefaultOperationMetadata> getBoundType() {
+        return DefaultOperationMetadata.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private SV_OperationMetadata(final OperationMetadata metadata) {
+    private SV_OperationMetadata(final DefaultOperationMetadata metadata) {
         super(metadata);
     }
 
@@ -65,7 +62,7 @@
      * @return a {@code PropertyType} wrapping the given the metadata element.
      */
     @Override
-    protected SV_OperationMetadata wrap(final OperationMetadata metadata) {
+    protected SV_OperationMetadata wrap(final DefaultOperationMetadata metadata) {
         return new SV_OperationMetadata(metadata);
     }
 
@@ -78,7 +75,7 @@
      */
     @XmlElementRef
     public final DefaultOperationMetadata getElement() {
-        return DefaultOperationMetadata.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
@@ -104,7 +101,7 @@
          *
          * @return a non-null value only if marshalling ISO 19115-3 or newer.
          */
-        @Override protected SV_OperationMetadata wrap(final OperationMetadata value) {
+        @Override protected SV_OperationMetadata wrap(final DefaultOperationMetadata value) {
             return accept2014() ? super.wrap(value) : null;
         }
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java
index 8b190ed..6d9d652 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameter.java
@@ -16,18 +16,19 @@
  */
 package org.apache.sis.internal.jaxb.metadata.replace;
 
+import java.util.Set;
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.measure.Unit;
 import org.opengis.util.TypeName;
 import org.opengis.util.MemberName;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValue;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.simple.SimpleIdentifiedObject;
@@ -42,7 +43,6 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 
-import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.internal.util.CollectionsExt.nonNull;
 
 
@@ -73,7 +73,6 @@
 @XmlType(name = "SV_Parameter_Type", namespace = Namespaces.SRV, propOrder = {
     "memberName",           // The  ISO 19115-3:2016 way to marshal name.
     "legacyName",           // Legacy ISO 19139:2007 way to marshal name.
-    "direction",
     "description",
     "optionality",
     "optionalityLabel",     // Legacy ISO 19139:2007 way to marshal optionality.
@@ -111,12 +110,6 @@
     MemberName memberName;
 
     /**
-     * Indication if the parameter is an input to the service, an output or both.
-     */
-    @XmlElement(required = true)
-    ParameterDirection direction;
-
-    /**
      * A narrative explanation of the role of the parameter.
      */
     @XmlElement
@@ -163,8 +156,6 @@
     private ServiceParameter(final ParameterDescriptor<?> parameter) {
         super(parameter);
         memberName    = getMemberName(parameter);
-        direction     = parameter.getDirection();
-        description   = parameter.getDescription();
         optionality   = parameter.getMinimumOccurs() > 0;
         repeatability = parameter.getMaximumOccurs() > 1;
     }
@@ -192,7 +183,7 @@
      */
     public static MemberName getMemberName(final ParameterDescriptor<?> parameter) {
         if (parameter != null) {
-            final Identifier id = parameter.getName();
+            final ReferenceIdentifier id = parameter.getName();
             if (id instanceof MemberName) {
                 return (MemberName) id;
             }
@@ -304,21 +295,10 @@
     }
 
     /**
-     * Returns an indication if the parameter is an input to the service, an output or both.
-     *
-     * @return indication if the parameter is an input or output to the service, or {@code null} if unspecified.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        return direction;
-    }
-
-    /**
      * Returns a narrative explanation of the role of the parameter.
      *
      * @return a narrative explanation of the role of the parameter, or {@code null} if none.
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
@@ -377,6 +357,16 @@
     }
 
     /**
+     * Optional properties.
+     * @return {@code null}.
+     */
+    @Override public Set<?>        getValidValues()  {return null;}     // Really null, not an empty set. See method contract.
+    @Override public Comparable<?> getMinimumValue() {return null;}
+    @Override public Comparable<?> getMaximumValue() {return null;}
+    @Override public Object        getDefaultValue() {return null;}
+    @Override public Unit<?>       getUnit()         {return null;}
+
+    /**
      * Creates a new instance of {@code ParameterValue}.
      * This method delegates the work to {@link org.apache.sis.parameter.DefaultParameterDescriptor}
      * since this {@code ServiceParameter} class is not a full-featured parameter descriptor implementation.
@@ -417,13 +407,11 @@
                     return Objects.equals(toString(getName()), toString(that.getName()));
                     // super.equals(…) already compared 'getName()' in others mode.
                 }
-                return deepEquals(that.getDescription(), getDescription(), mode) &&
-                                  that.getDirection()     == getDirection()     &&
-                                  that.getMinimumOccurs() == getMinimumOccurs() &&
-                                  that.getMaximumOccurs() == getMaximumOccurs() &&
-                                  that.getValidValues()   == null &&
-                                  that.getMinimumValue()  == null &&
-                                  that.getMaximumValue()  == null;
+                return that.getMinimumOccurs() == getMinimumOccurs() &&
+                       that.getMaximumOccurs() == getMaximumOccurs() &&
+                       that.getValidValues()   == null &&
+                       that.getMinimumValue()  == null &&
+                       that.getMaximumValue()  == null;
             }
         }
         return false;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
index 9e56492..9753a89 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/package-info.java
@@ -45,7 +45,6 @@
 @XmlJavaTypeAdapters({
     @XmlJavaTypeAdapter(GO_Boolean.class),
     @XmlJavaTypeAdapter(MD_Identifier.class),
-    @XmlJavaTypeAdapter(SV_ParameterDirection.class),
 
     // Java types, primitive types and basic OGC types handling
     @XmlJavaTypeAdapter(StringAdapter.class),
@@ -63,6 +62,5 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
-import org.apache.sis.internal.jaxb.code.SV_ParameterDirection;
 import org.apache.sis.internal.jaxb.gco.*;
 import org.apache.sis.xml.Namespaces;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java
index 1fb8556..165dbb5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/Identifiers.java
@@ -20,6 +20,7 @@
 import java.util.Iterator;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.CollectionsExt;
@@ -95,7 +96,7 @@
      * </ul>
      *
      * Use {@code toCodeSpace(…)} method when assigning values to be returned by methods like
-     * {@link Identifier#getCodeSpace()}, since those values are likely to be compared without special
+     * {@link ReferenceIdentifier#getCodeSpace()}, since those values are likely to be compared without special
      * care about ignorable identifier characters. But if the intent is to format a more complex string
      * like WKT or {@code toString()}, then we suggest to use {@code getIdentifier(citation, true)} instead,
      * which will produce the same result but preserving the ignorable characters, which can be useful
@@ -124,7 +125,10 @@
                          * Unicode identifiers. If a codespace exists, then the code does not need to begin
                          * with a "Unicode identifier start" (it may be a "Unicode identifier part").
                          */
-                        String cs = CharSequences.trimWhitespaces(id.getCodeSpace());
+                        String cs = null;
+                        if (id instanceof ReferenceIdentifier) {
+                            cs = CharSequences.trimWhitespaces(((ReferenceIdentifier) id).getCodeSpace());
+                        }
                         if (cs == null || cs.isEmpty()) {
                             cs = null;
                             isUnicode = CharSequences.isUnicodeIdentifier(candidate);
@@ -219,8 +223,8 @@
                 return Citations.identifierMatches(authority, other);
             }
         }
-        if (codeSpace != null) {
-            final String other = identifier.getCodeSpace();
+        if (codeSpace != null && identifier instanceof ReferenceIdentifier) {
+            final String other = ((ReferenceIdentifier) identifier).getCodeSpace();
             if (other != null) {
                 return CharSequences.equalsFiltered(codeSpace, other, Characters.Filter.UNICODE_IDENTIFIER, true);
             }
@@ -243,12 +247,12 @@
      * @return {@code TRUE} or {@code FALSE} on match or mismatch respectively, or {@code null} if this method
      *         can not determine if there is a match or mismatch.
      */
-    public static Boolean hasCommonIdentifier(final Iterable<? extends Identifier> id1,
-                                              final Iterable<? extends Identifier> id2)
+    public static Boolean hasCommonIdentifier(final Iterable<? extends ReferenceIdentifier> id1,
+                                              final Iterable<? extends ReferenceIdentifier> id2)
     {
         if (id1 != null && id2 != null) {
             boolean hasFound = false;
-            for (final Identifier identifier : id1) {
+            for (final ReferenceIdentifier identifier : id1) {
                 final Citation authority = identifier.getAuthority();
                 final String   codeSpace = identifier.getCodeSpace();
                 for (final Identifier other : id2) {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java
index cfccb35..f5dd53f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/NameToIdentifier.java
@@ -126,6 +126,14 @@
     }
 
     /**
+     * Returns {@code null} since names are not versioned.
+     */
+    @Override
+    public String getVersion() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this object.
      */
     @Override
@@ -199,7 +207,7 @@
      *
      * @since 0.8
      */
-    public static boolean isHeuristicMatchForIdentifier(final Iterable<? extends Identifier> identifiers, final String toSearch) {
+    public static boolean isHeuristicMatchForIdentifier(final Iterable<? extends ReferenceIdentifier> identifiers, final String toSearch) {
         if (toSearch != null && identifiers != null) {
             int s = toSearch.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR);
             if (s < 0) {
@@ -214,7 +222,7 @@
             do {
                 final String codespace = toSearch.substring(0, s).trim();
                 final String code = toSearch.substring(++s).trim();
-                for (final Identifier id : identifiers) {
+                for (final ReferenceIdentifier id : identifiers) {
                     if (codespace.equalsIgnoreCase(id.getCodeSpace()) && code.equalsIgnoreCase(id.getCode())) {
                         return true;
                     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
index 2cf4b6c..d25347d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ServicesForUtility.java
@@ -22,7 +22,7 @@
 import javax.sql.DataSource;
 import java.sql.SQLException;
 import java.util.function.Supplier;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.internal.util.MetadataServices;
 import org.apache.sis.internal.metadata.sql.Initializer;
@@ -35,7 +35,6 @@
 import org.apache.sis.util.resources.Errors;
 
 
-
 /**
  * Implements the metadata services needed by the {@code "sis-utility"} module.
  *
@@ -69,10 +68,10 @@
      * @param  locale  desired locale for the title.
      * @return the title.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)
+     * @see org.apache.sis.util.iso.Types#getCodeTitle(CodeList)
      */
     @Override
-    public String getCodeTitle(final ControlledVocabulary code, final Locale locale) {
+    public String getCodeTitle(final CodeList<?> code, final Locale locale) {
         return Types.getCodeTitle(code).toString(locale);
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
index e0bef39..e61db06 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
@@ -206,8 +206,6 @@
     @Override public Collection<PresentationForm>               getPresentationForms()       {return delegate().getPresentationForms();}
     @Override public Series                                     getSeries()                  {return delegate().getSeries();}
     @Override public InternationalString                        getOtherCitationDetails()    {return delegate().getOtherCitationDetails();}
-    @Override public Collection<? extends OnlineResource>       getOnlineResources()         {return delegate().getOnlineResources();}
-    @Override public Collection<? extends BrowseGraphic>        getGraphics()                {return delegate().getGraphics();}
     @Override public String                                     getISBN()                    {return delegate().getISBN();}
     @Override public String                                     getISSN()                    {return delegate().getISSN();}
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java
index 674e725..a3e83cd 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleAttributeType.java
@@ -18,8 +18,6 @@
 
 import java.io.Serializable;
 import org.opengis.util.Type;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.util.TypeName;
@@ -28,7 +26,7 @@
 /**
  * A simple attribute type containing only a name and a class of values.
  * Such simple type are suitable for use in ISO 19103 {@link org.opengis.util.RecordType}
- * in addition to ISO 19109 {@link org.opengis.feature.FeatureType}.
+ * in addition to ISO 19109 {@code org.opengis.feature.FeatureType}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.5
@@ -38,7 +36,7 @@
  * @since 0.5
  * @module
  */
-public final class SimpleAttributeType<V> implements AttributeType<V>, Type, Serializable {
+public final class SimpleAttributeType<V> implements Type, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -70,7 +68,6 @@
      *
      * @return the name of this attribute type.
      */
-    @Override
     public GenericName getName() {
         return name;
     }
@@ -90,7 +87,6 @@
      *
      * @return the class of value for attributes of this type.
      */
-    @Override
     public Class<V> getValueClass() {
         return valueClass;
     }
@@ -100,7 +96,6 @@
      *
      * @return always 1.
      */
-    @Override
     public int getMinimumOccurs() {
         return 1;
     }
@@ -110,7 +105,6 @@
      *
      * @return always 1.
      */
-    @Override
     public int getMaximumOccurs() {
         return 1;
     }
@@ -120,7 +114,6 @@
      *
      * @return always {@code null}.
      */
-    @Override
     public V getDefaultValue() {
         return null;
     }
@@ -130,17 +123,26 @@
      *
      * @return always {@code null}.
      */
-    @Override
     public InternationalString getDefinition() {
         return null;
     }
 
     /**
-     * Unsupported operation.
+     * Not used for this simple attribute type.
+     *
+     * @return always {@code null}.
      */
-    @Override
-    public Attribute<V> newInstance() {
-        throw new UnsupportedOperationException();
+    public InternationalString getDesignation() {
+        return null;
+    }
+
+    /**
+     * Not used for this simple attribute type.
+     *
+     * @return always {@code null}.
+     */
+    public InternationalString getDescription() {
+        return null;
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java
index 23af00d..5533ba5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleCitation.java
@@ -16,9 +16,17 @@
  */
 package org.apache.sis.internal.simple;
 
+import java.util.Date;
 import java.util.Objects;
+import java.util.Collection;
+import java.util.Collections;
 import java.io.Serializable;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.CitationDate;
+import org.opengis.metadata.citation.PresentationForm;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.citation.Series;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.internal.util.Strings;
@@ -69,6 +77,26 @@
     }
 
     /**
+     * Methods inherited from the {@link Citation} interface which are not of interest to this
+     * {@code SimpleCitation} implementation.
+     *
+     * @return an empty list.
+     */
+    @Override public Collection<? extends InternationalString>  getAlternateTitles()         {return Collections.emptyList();}
+    @Override public Collection<? extends CitationDate>         getDates()                   {return Collections.emptyList();}
+    @Override public InternationalString                        getEdition()                 {return null;}
+    @Override public Date                                       getEditionDate()             {return null;}
+    @Override public Collection<? extends Identifier>           getIdentifiers()             {return Collections.emptyList();}
+    @Override public Collection<? extends ResponsibleParty>     getCitedResponsibleParties() {return Collections.emptyList();}
+    @Override public Collection<PresentationForm>               getPresentationForms()       {return Collections.emptyList();}
+    @Override public Series                                     getSeries()                  {return null;}
+    @Override public InternationalString                        getOtherCitationDetails()    {return null;}
+    @Override public String                                     getISBN()                    {return null;}
+    @Override public String                                     getISSN()                    {return null;}
+    @Deprecated
+    @Override public InternationalString                        getCollectiveTitle()         {return null;}
+
+    /**
      * Compares the given object with this citation for equality.
      *
      * @param  obj  the object to compare with this citation.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java
index 909a3f5..d634a41 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleDuration.java
@@ -17,7 +17,7 @@
 package org.apache.sis.internal.simple;
 
 import org.apache.sis.internal.util.Numerics;
-import org.opengis.temporal.Duration;
+import org.apache.sis.internal.geoapi.temporal.Duration;
 
 
 /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java
index 7724694..34aad4f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleExtent.java
@@ -21,6 +21,7 @@
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.extent.TemporalExtent;
 import org.opengis.metadata.extent.VerticalExtent;
+import org.opengis.util.InternationalString;
 
 import static org.apache.sis.internal.util.CollectionsExt.singletonOrEmpty;
 
@@ -67,6 +68,7 @@
         this.temporalElements   = temporalElements;
     }
 
+    @Override public InternationalString          getDescription()        {return null;}
     @Override public Collection<GeographicExtent> getGeographicElements() {return singletonOrEmpty(geographicElements);}
     @Override public Collection<VerticalExtent>   getVerticalElements()   {return singletonOrEmpty(verticalElements);}
     @Override public Collection<TemporalExtent>   getTemporalElements()   {return singletonOrEmpty(temporalElements);}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
index 056611d..bfcff3d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleFormat.java
@@ -19,7 +19,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.distribution.Distributor;
 import org.opengis.metadata.distribution.Format;
 import org.apache.sis.internal.util.Strings;
 
@@ -68,13 +68,68 @@
     }
 
     /**
-     * Citation / URL of the specification format.
+     * @deprecated Replaced by {@link #getTitle()}
      *
-     * @return citation / URL of the specification format.
+     * @return name of a subset, profile, or product specification of the format, or {@code null}.
      */
     @Override
-    public Citation getFormatSpecificationCitation() {
-        return this;
+    @Deprecated
+    public InternationalString getSpecification() {
+        return getTitle();
+    }
+
+    /**
+     * @deprecated Replaced by {@link #getAlternateTitles()}
+     *
+     * @return name of the data transfer format(s).
+     */
+    @Override
+    @Deprecated
+    public InternationalString getName() {
+        return super.getTitle();
+    }
+
+    /**
+     * @deprecated Replaced by {@link #getEdition()}
+     *
+     * @return version of the format.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getVersion() {
+        return getEdition();
+    }
+
+    /**
+     * Amendment number of the format version.
+     *
+     * @return amendment number of the format version, or {@code null}.
+     */
+    @Override
+    public InternationalString getAmendmentNumber() {
+        return null;
+    }
+
+    /**
+     * Recommendations of algorithms or processes that can be applied to read
+     * or expand resources to which compression techniques have been applied.
+     *
+     * @return processes that can be applied to read resources to which compression techniques have been applied,
+     *         or {@code null}.
+     */
+    @Override
+    public InternationalString getFileDecompressionTechnique() {
+        return null;
+    }
+
+    /**
+     * Provides information about the distributor's format.
+     *
+     * @return information about the distributor's format.
+     */
+    @Override
+    public Collection<? extends Distributor> getFormatDistributors() {
+        return Collections.emptyList();
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java
index c2efe00..b302f5d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifiedObject.java
@@ -16,8 +16,12 @@
  */
 package org.apache.sis.internal.simple;
 
+import java.util.Set;
 import java.util.Objects;
+import java.util.Collection;
+import java.util.Collections;
 import java.io.Serializable;
+import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
@@ -91,6 +95,34 @@
     }
 
     /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns an empty set.
+     *
+     * <p>If a future version allows this method to returns a non-empty set,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the identifiers, or an empty set if none.
+     */
+    @Override
+    public final Set<ReferenceIdentifier> getIdentifiers() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns an empty set.
+     *
+     * <p>If a future version allows this method to returns a non-empty set,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the aliases, or an empty set if none.
+     */
+    @Override
+    public final Collection<GenericName> getAlias() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Method required by most {@link IdentifiedObject} sub-interfaces.
      * Current implementation returns {@code null}.
      *
@@ -117,6 +149,20 @@
     }
 
     /**
+     * Method required by the {@link IdentifiedObject} interface.
+     * Current implementation returns {@code null}.
+     *
+     * <p>If a future version allows this method to returns a non-null value,
+     * revisit {@link #equals(Object, ComparisonMode)}.</p>
+     *
+     * @return the remarks, or {@code null} if none.
+     */
+    @Override
+    public final InternationalString getRemarks() {
+        return null;
+    }
+
+    /**
      * Returns a hash code value for this object.
      */
     @Override
@@ -171,13 +217,24 @@
     }
 
     /**
+     * Throws an exception in all cases, since this object can't be formatted in a valid WKT.
+     *
+     * @return the Well Known Text.
+     * @throws UnsupportedOperationException always thrown.
+     */
+    @Override
+    public String toWKT() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns a pseudo-WKT representation for debugging purpose.
      */
     @Override
     public String toString() {
         final String code, codespace;
         final Citation authority;
-        final Identifier name = this.name;
+        final ReferenceIdentifier name = this.name;
         if (name != null) {
             code      = name.getCode();
             codespace = name.getCodeSpace();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java
index ae65239..7ffd8d1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleIdentifier.java
@@ -138,6 +138,17 @@
     }
 
     /**
+     * Returns a natural language description of the meaning of the code value.
+     *
+     * @return natural language description, or {@code null} if none.
+     *
+     * @since 0.5
+     */
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * An optional free text.
      *
      * @since 0.6
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
index 7f208c5..5178d85 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/SimpleMetadata.java
@@ -16,21 +16,33 @@
  */
 package org.apache.sis.internal.simple;
 
-import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Locale;
-import java.util.Map;
+import org.opengis.metadata.ApplicationSchemaInformation;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.MetadataScope;
+import org.opengis.metadata.MetadataExtensionInformation;
+import org.opengis.metadata.PortrayalCatalogueReference;
+import org.opengis.metadata.acquisition.AcquisitionInformation;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.CitationDate;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.citation.Series;
+import org.opengis.metadata.constraint.Constraints;
+import org.opengis.metadata.content.ContentInformation;
+import org.opengis.metadata.distribution.Distribution;
+import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.identification.*;
+import org.opengis.metadata.maintenance.MaintenanceInformation;
 import org.opengis.metadata.maintenance.ScopeCode;
+import org.opengis.metadata.quality.DataQuality;
+import org.opengis.metadata.spatial.SpatialRepresentation;
 import org.opengis.metadata.spatial.SpatialRepresentationType;
+import org.opengis.referencing.ReferenceSystem;
 import org.opengis.util.InternationalString;
 
 
@@ -43,8 +55,8 @@
  * <p>Unless specified otherwise, all methods in this class returns {@code null} or an empty collection by default.
  * The exceptions to this rules are the following methods:</p>
  * <ul>
- *   <li>{@link #getMetadataScopes()} returns {@code this}</li>
- *   <li>{@link #getResourceScope()} returns {@link ScopeCode#DATASET}</li>
+ *   <li>{@code  getMetadataScopes()} returns {@code this}</li>
+ *   <li>{@code  getResourceScope()} returns {@link ScopeCode#DATASET}</li>
  *   <li>{@link #getIdentificationInfo()} returns {@code this}</li>
  *   <li>{@link #getCitation()} returns {@code this}</li>
  *   <li>{@link #getSpatialRepresentationTypes()} returns {@link SpatialRepresentationType#VECTOR}</li>
@@ -62,11 +74,11 @@
  * </ul>
  *
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 0.8
  * @since   0.8
  * @module
  */
-public class SimpleMetadata implements Metadata, MetadataScope, DataIdentification, Citation {
+public class SimpleMetadata implements Metadata, DataIdentification, Citation {
     /**
      * Creates a new metadata object.
      */
@@ -74,34 +86,82 @@
     }
 
     /**
+     * Unique identifier for this metadata record.
+     */
+    @Override
+    public String getFileIdentifier() {
+        return null;
+    }
+
+    /**
      * Language(s) used for documenting metadata.
      * Also the language(s) used within the data.
      */
     @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
-        return Collections.emptyMap();
+    public Collection<Locale> getLanguages() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Locale' like a CodeList.
     }
 
     /**
-     * The scope or type of resource for which metadata is provided.
-     * This method returns {@code this} for allowing call to {@link #getResourceScope()}.
-     *
-     * @see #getResourceScope()
-     * @see #getName()
+     * Language(s) used for documenting metadata.
+     * Also the language(s) used within the data.
      */
     @Override
-    public Collection<MetadataScope> getMetadataScopes() {
-        return Collections.singleton(this);
+    public Locale getLanguage() {
+        return null;
     }
 
     /**
-     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default. This is part of the information
-     * provided by {@link #getMetadataScopes()}. The {@code DATASET} default value is consistent with the fact that
+     * Language(s) used for documenting metadata.
+     * Also the language(s) used within the data.
+     */
+    @Override
+    public Collection<Locale> getLocales() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Locale' like a CodeList.
+    }
+
+    /**
+     * The character coding standard used for the metadata set.
+     * Also the character coding standard(s) used for the dataset.
+     */
+    @Override
+    public Collection<CharacterSet> getCharacterSets() {
+        return Collections.emptySet();                  // We use 'Set' because we handle 'Charset' like a CodeList.
+    }
+
+    /**
+     * The character coding standard used for the metadata set.
+     * Also the character coding standard(s) used for the dataset.
+     */
+    @Override
+    public CharacterSet getCharacterSet() {
+        return null;
+    }
+
+    /**
+     * Identification of the parent metadata record.
+     */
+    @Override
+    public String getParentIdentifier() {
+        return null;
+    }
+
+    /**
+     * Code for the metadata scope, fixed to {@link ScopeCode#DATASET} by default.
+     * The {@code DATASET} default value is consistent with the fact that
      * {@code SimpleMetadata} implements {@link DataIdentification}.
      */
     @Override
-    public ScopeCode getResourceScope() {
-        return ScopeCode.DATASET;
+    public Collection<ScopeCode> getHierarchyLevels() {
+        return Collections.singleton(ScopeCode.DATASET);
+    }
+
+    /**
+     * Description of the metadata scope.
+     */
+    @Override
+    public Collection<String> getHierarchyLevelNames() {
+        return Collections.emptySet();
     }
 
     /**
@@ -116,7 +176,56 @@
      * Date(s) associated with the metadata.
      */
     @Override
-    public Collection<CitationDate> getDateInfo() {
+    public Date getDateStamp() {
+        return null;
+    }
+
+    /**
+     * Citation(s) for the standard(s) to which the metadata conform.
+     */
+    @Override
+    public String getMetadataStandardName() {
+        return null;
+    }
+
+    /**
+     * As of ISO 19115:2014, replaced by {@code getMetadataStandards()}
+     * followed by {@link Citation#getEdition()}.
+     */
+    @Override
+    public String getMetadataStandardVersion() {
+        return null;
+    }
+
+    /**
+     * Online location(s) where the metadata is available.
+     */
+    @Override
+    public String getDataSetUri() {
+        return null;
+    }
+
+    /**
+     * Digital representation of spatial information in the dataset.
+     */
+    @Override
+    public Collection<SpatialRepresentation> getSpatialRepresentationInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the spatial and temporal reference systems used in the dataset.
+     */
+    @Override
+    public Collection<ReferenceSystem> getReferenceSystemInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information describing metadata extensions.
+     */
+    @Override
+    public Collection<MetadataExtensionInformation> getMetadataExtensionInfo() {
         return Collections.emptyList();
     }
 
@@ -130,7 +239,6 @@
      * @see #getPointOfContacts()
      * @see #getSpatialRepresentationTypes()
      * @see #getSpatialResolutions()
-     * @see #getTemporalResolutions()
      * @see #getTopicCategories()
      * @see #getExtents()
      * @see #getResourceFormats()
@@ -141,6 +249,70 @@
         return Collections.singleton(this);
     }
 
+    /**
+     * Information about the feature and coverage characteristics.
+     */
+    @Override
+    public Collection<ContentInformation> getContentInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the distributor of and options for obtaining the resource(s).
+     */
+    @Override
+    public Distribution getDistributionInfo() {
+        return null;
+    }
+
+    /**
+     * Overall assessment of quality of a resource(s).
+     */
+    @Override
+    public Collection<DataQuality> getDataQualityInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the catalogue of rules defined for the portrayal of a resource(s).
+     */
+    @Override
+    public Collection<PortrayalCatalogueReference> getPortrayalCatalogueInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Restrictions on the access and use of metadata.
+     */
+    @Override
+    public Collection<Constraints> getMetadataConstraints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the conceptual schema of a dataset.
+     */
+    @Override
+    public Collection<ApplicationSchemaInformation> getApplicationSchemaInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the acquisition of the data.
+     */
+    @Override
+    public Collection<AcquisitionInformation> getAcquisitionInformation() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about the frequency of metadata updates, and the scope of those updates.
+     */
+    @Override
+    public MaintenanceInformation getMetadataMaintenance() {
+        return null;
+    }
+
 
     /* -------------------------------------------------------------------------------------------------
      * Implementation of the DataIdentification object returned by Metadata.getIdentificationInfo().
@@ -166,6 +338,42 @@
     }
 
     /**
+     * Summary of the intentions with which the resource was developed.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getPurpose() {
+        return null;
+    }
+
+    /**
+     * Recognition of those who contributed to the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<String> getCredits() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Status of the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Progress> getStatus() {
+        return Collections.emptySet();              // We use 'Set' because 'Progress' is a CodeList.
+    }
+
+    /**
+     * Identification of, and means of communication with, person(s) and organisations associated with the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<ResponsibleParty> getPointOfContacts() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Methods used to spatially represent geographic information.
      * This is part of the information returned by {@link #getIdentificationInfo()}.
      * Default implementation returns {@link SpatialRepresentationType#VECTOR}.
@@ -177,6 +385,15 @@
     }
 
     /**
+     * Factor which provides a general understanding of the density of spatial data in the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Resolution> getSpatialResolutions() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Main theme(s) of the resource.
      * This is part of the information returned by {@link #getIdentificationInfo()}.
      * Default implementation returns {@link TopicCategory#LOCATION}.
@@ -196,6 +413,89 @@
         return Collections.emptyList();
     }
 
+    /**
+     * Information about the frequency of resource updates, and the scope of those updates.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<MaintenanceInformation> getResourceMaintenances() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Graphic that illustrates the resource(s) (should include a legend for the graphic).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<BrowseGraphic> getGraphicOverviews() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the format of the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Format> getResourceFormats() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Category keywords, their type, and reference source.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Keywords> getDescriptiveKeywords() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Basic information about specific application(s) for which the resource(s)
+     * has/have been or is being used by different users.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Usage> getResourceSpecificUsages() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Information about constraints which apply to the resource(s).
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public Collection<Constraints> getResourceConstraints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * @deprecated As of ISO 19115:2014, replaced by {@code getAssociatedResources()}.
+     */
+    @Override
+    @Deprecated
+    public Collection<AggregateInformation> getAggregationInfo() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Description of the resource in the producer's processing environment, including items
+     * such as the software, the computer operating system, file name, and the dataset size.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getEnvironmentDescription() {
+        return null;
+    }
+
+    /**
+     * Any other descriptive information about the resource.
+     * This is part of the information returned by {@link #getIdentificationInfo()}.
+     */
+    @Override
+    public InternationalString getSupplementalInformation() {
+        return null;
+    }
+
 
     /* -------------------------------------------------------------------------------------------------
      * Implementation of the Citation object returned by DataIdentification.getCitation().
@@ -211,6 +511,61 @@
     }
 
     /**
+     * Short names or other language names by which the cited information is known.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<InternationalString> getAlternateTitles() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Reference dates for the cited resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<CitationDate> getDates() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Version of the cited resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public InternationalString getEdition() {
+        return null;
+    }
+
+    /**
+     * Date of the edition.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Date getEditionDate() {
+        return null;
+    }
+
+    /**
+     * Unique identifier for the resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<Identifier> getIdentifiers() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Role, name, contact and position information for individuals or organisations
+     * that are responsible for the resource.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Mode in which the resource is represented.
      * This is part of the information returned by {@link #getCitation()}.
      * Default implementation returns {@link PresentationForm#TABLE_DIGITAL}.
@@ -220,4 +575,49 @@
     public Collection<PresentationForm> getPresentationForms() {
         return Collections.singleton(PresentationForm.TABLE_DIGITAL);
     }
+
+    /**
+     * Information about the series, or aggregate dataset, of which the dataset is a part.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public Series getSeries() {
+        return null;
+    }
+
+    /**
+     * Other information required to complete the citation that is not recorded elsewhere.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public InternationalString getOtherCitationDetails() {
+        return null;
+    }
+
+    /**
+     * @deprecated Removed as of ISO 19115:2014.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getCollectiveTitle() {
+        return null;
+    }
+
+    /**
+     * International Standard Book Number.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public String getISBN() {
+        return null;
+    }
+
+    /**
+     * International Standard Serial Number.
+     * This is part of the information returned by {@link #getCitation()}.
+     */
+    @Override
+    public String getISSN() {
+        return null;
+    }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
index 4018fdb..60653e9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
@@ -31,6 +31,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.ExtendedElementInformation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.collection.TreeTable;
@@ -114,7 +115,7 @@
      * than GeoAPI, but have a slight performance cost at construction time. Performance
      * after construction should be the same.</p>
      */
-    static final boolean IMPLEMENTATION_CAN_ALTER_API = false;
+    static final boolean IMPLEMENTATION_CAN_ALTER_API = true;
 
     /**
      * Metadata instances defined in this class. The current implementation does not yet
@@ -137,15 +138,16 @@
 
     /**
      * An instance working on ISO 19123 standard as defined by GeoAPI interfaces
-     * in the {@link org.opengis.coverage} package and sub-packages.
+     * in the {@code org.opengis.coverage} package and sub-packages.
      */
     public static final MetadataStandard ISO_19123;
     static {
+        final String[] prefix = {"Default", "Abstract"};
         final String[] acronyms = {"CoordinateSystem", "CS", "CoordinateReferenceSystem", "CRS"};
 
         // If new StandardImplementation instances are added below, please update StandardImplementation.readResolve().
-        ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", null, null);
-        ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", acronyms, new MetadataStandard[] {ISO_19115});
+        ISO_19115 = new StandardImplementation("ISO 19115", "org.opengis.metadata.", "org.apache.sis.metadata.iso.", prefix, null, null);
+        ISO_19111 = new StandardImplementation("ISO 19111", "org.opengis.referencing.", "org.apache.sis.referencing.", prefix, acronyms, new MetadataStandard[] {ISO_19115});
         ISO_19123 = new MetadataStandard      ("ISO 19123", "org.opengis.coverage.", new MetadataStandard[] {ISO_19111});
         INSTANCES = new MetadataStandard[] {
             ISO_19111,
@@ -743,10 +745,10 @@
      * <p>In the particular case of Apache SIS implementation, all values in the information map
      * additionally implement the following interfaces:</p>
      * <ul>
-     *   <li>{@link Identifier} with the following properties:
+     *   <li>{@link ReferenceIdentifier} with the following properties:
      *     <ul>
      *       <li>The {@linkplain Identifier#getAuthority() authority} is this metadata standard {@linkplain #getCitation() citation}.</li>
-     *       <li>The {@linkplain Identifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li>
+     *       <li>The {@linkplain ReferenceIdentifier#getCodeSpace() codespace} is the standard name of the interface that contain the property.</li>
      *       <li>The {@linkplain Identifier#getCode() code} is the standard name of the property.</li>
      *     </ul>
      *   </li>
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
index 5440ba4..6a47b8d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyComparator.java
@@ -24,6 +24,7 @@
 
 import org.opengis.annotation.UML;
 import org.opengis.annotation.Obligation;
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -128,8 +129,14 @@
                      * the parent class, and we want the properties in the parent class to be sorted first.
                      * If duplicated properties are found, keep the first occurence (i.e. sort the property
                      * with the most specialized child that declared it).
+                     *
+                     * We make an exception for ResponsibleParty.role, which should be replaced by Party.role
+                     * but this replacement is not yet effective in GeoAPI 3.0.
                      */
-                    order.putIfAbsent(propOrder[i], order.size());
+                    final String prop = propOrder[i];
+                    if (!"role".equals(prop) || !ResponsibleParty.class.isAssignableFrom(implementation)) {
+                        order.putIfAbsent(prop, order.size());
+                    }
                 }
             }
             implementation = implementation.getSuperclass();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
index 54b2b50..16cfb63 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyInformation.java
@@ -181,6 +181,30 @@
     }
 
     /**
+     * Unconditionally returns {@code null}.
+     *
+     * @deprecated This property was defined in the 2003 edition of ISO 19115,
+     *             but has been removed in the 2014 edition.
+     */
+    @Override
+    @Deprecated
+    public String getShortName() {
+        return null;
+    }
+
+    /**
+     * Unconditionally returns {@code null}.
+     *
+     * @deprecated This property was defined in the 2003 edition of ISO 19115,
+     *             but has been removed in the 2014 edition.
+     */
+    @Override
+    @Deprecated
+    public Integer getDomainCode() {
+        return null;
+    }
+
+    /**
      * Returns the definition of this property, or {@code null} if none.
      */
     @Override
@@ -303,6 +327,22 @@
     }
 
     /**
+     * Unconditionally returns {@code null}.
+     */
+    public InternationalString getRationale() {
+        return null;
+    }
+
+    /**
+     * Unconditionally returns an empty list.
+     */
+    @Override
+    @Deprecated
+    public Collection<InternationalString> getRationales() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Returns the name of the person or organization creating the element.
      */
     @Override
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
index 4c14e62..64e6536 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
@@ -18,7 +18,7 @@
 
 import java.util.Iterator;
 import java.util.Collection;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.apache.sis.util.Emptiable;
 import org.apache.sis.internal.util.CollectionsExt;
 
@@ -135,7 +135,7 @@
                 } else if (!prune && element instanceof Emptiable) {
                     isEmptyElement = ((Emptiable) element).isEmpty();
                     // If 'prune' is true, we will rather test for Emptiable after our pruning attempt.
-                } else if (!(element instanceof ControlledVocabulary)) {
+                } else if (!(element instanceof Enum<?>) && !(element instanceof CodeList<?>)) {
                     final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
                     if (standard != null) {
                         /*
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
index 18db579..8aae148 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
@@ -20,8 +20,6 @@
 import java.util.IdentityHashMap;
 import java.io.ObjectStreamException;
 import org.opengis.annotation.UML;
-import org.opengis.annotation.Classifier;
-import org.opengis.annotation.Stereotype;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Modules;
@@ -48,6 +46,12 @@
     private final String implementationPackage;
 
     /**
+     * The prefixes that implementation classes may have.
+     * The most common prefixes should be first, since the prefixes will be tried in that order.
+     */
+    private final String[] prefix;
+
+    /**
      * The acronyms that implementation classes may have, or {@code null} if none. If non-null,
      * then this array shall contain (<var>full text</var>, <var>acronym</var>) pairs. The full
      * text shall appear to the end of the class name, otherwise it is not replaced. This is
@@ -75,30 +79,21 @@
      * @param citation               the title of the standard.
      * @param interfacePackage       the root package for metadata interfaces, with a trailing {@code '.'}.
      * @param implementationPackage  the root package for metadata implementations. with a trailing {@code '.'}.
+     * @param prefix                 the prefix of implementation class. This array is not cloned.
      * @param acronyms               an array of (full text, acronyms) pairs. This array is not cloned.
      * @param dependencies           the dependencies to other metadata standards, or {@code null} if none.
      */
     StandardImplementation(final String citation, final String interfacePackage, final String implementationPackage,
-            final String[] acronyms, final MetadataStandard[] dependencies)
+            final String[] prefix, final String[] acronyms, final MetadataStandard[] dependencies)
     {
         super(citation, interfacePackage, dependencies);
         this.implementationPackage = implementationPackage;
+        this.prefix                = prefix;
         this.acronyms              = acronyms;
         this.implementations       = new IdentityHashMap<>();
     }
 
     /**
-     * Returns {@code true} if the given type is conceptually abstract.
-     * The given type is usually an interface, so here "abstract" can not be in the Java sense.
-     * If this method can not find information about whether the given type is abstract,
-     * then this method conservatively returns {@code false}.
-     */
-    private static boolean isAbstract(final Class<?> type) {
-        final Classifier c = type.getAnnotation(Classifier.class);
-        return (c != null) && c.value() == Stereotype.ABSTRACT;
-    }
-
-    /**
      * Accepts Apache SIS implementation classes as "pseudo-interfaces" if they are annotated with {@link UML}.
      * We use this feature for example in the transition from ISO 19115:2003 to ISO 19115:2014, when new API is
      * defined in Apache SIS but not yet available in GeoAPI interfaces.
@@ -148,18 +143,22 @@
                         }
                     }
                     /*
-                     * Try to instantiate the implementation class.
+                     * Try to insert a prefix in front of the class name, until a match is found.
                      */
                     final int prefixPosition = buffer.lastIndexOf(".") + 1;
-                    buffer.insert(prefixPosition, isAbstract(type) ? "Abstract" : "Default");
-                    classname = buffer.toString();
-                    try {
-                        candidate = Class.forName(classname);
+                    int length = 0;
+                    for (final String p : prefix) {
+                        classname = buffer.replace(prefixPosition, prefixPosition + length, p).toString();
+                        try {
+                            candidate = Class.forName(classname);
+                        } catch (ClassNotFoundException e) {
+                            Logging.recoverableException(Logging.getLogger(Modules.METADATA),
+                                    MetadataStandard.class, "getImplementation", e);
+                            length = p.length();
+                            continue;
+                        }
                         implementations.put(type, candidate);
                         return candidate.asSubclass(type);
-                    } catch (ClassNotFoundException e) {
-                        Logging.recoverableException(Logging.getLogger(Modules.METADATA),
-                                MetadataStandard.class, "getImplementation", e);
                     }
                     implementations.put(type, Void.TYPE);                       // Marker for "class not found".
                 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
index 07679f1..9c573f1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
@@ -16,9 +16,7 @@
  */
 package org.apache.sis.metadata.iso;
 
-import java.util.AbstractSet;
 import java.util.Collection;
-import java.util.Iterator;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -41,6 +39,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * New metadata element, not found in ISO 19115, which is required to describe geographic data.
@@ -543,8 +546,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "rationale")
+    @UML(identifier="rationale", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getRationale() {
         return LegacyPropertyAdapter.getSingleton(rationales, InternationalString.class, null,
                 DefaultExtendedElementInformation.class, "getRationale");
@@ -570,29 +573,7 @@
     @Deprecated
     @Dependencies("getRationale")
     public Collection<InternationalString> getRationales() {
-        return new AbstractSet<InternationalString>() {
-            /** Returns 0 if empty, or 1 if a density has been specified. */
-            @Override public int size() {
-                return getRationale() != null ? 1 : 0;
-            }
-
-            /** Returns an iterator over 0 or 1 element. Current iterator implementation is unmodifiable. */
-            @Override public Iterator<InternationalString> iterator() {
-                return CollectionsExt.singletonOrEmpty(getRationale()).iterator();
-            }
-
-            /** Adds an element only if the set is empty. This method is invoked by JAXB at unmarshalling time. */
-            @Override public boolean add(final InternationalString newValue) {
-                if (isEmpty()) {
-                    setRationale(newValue);
-                    return true;
-                } else {
-                    LegacyPropertyAdapter.warnIgnoredExtraneous(InternationalString.class,
-                            DefaultExtendedElementInformation.class, "setRationales");
-                    return false;
-                }
-            }
-        };
+        return rationales = nonNullCollection(rationales, InternationalString.class);
     }
 
     /**
@@ -602,8 +583,7 @@
      */
     @Deprecated
     public void setRationales(final Collection<? extends InternationalString> newValues) {
-        setRationale(LegacyPropertyAdapter.getSingleton(newValues, InternationalString.class,
-                null, DefaultExtendedElementInformation.class, "setRationales"));
+        rationales = writeCollection(newValues, rationales, InternationalString.class);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
index 443556d..29f963f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultIdentifier.java
@@ -22,11 +22,17 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Value uniquely identifying an object within a namespace.
@@ -217,10 +223,17 @@
         super(object);
         if (object != null) {
             code        = object.getCode();
-            codeSpace   = object.getCodeSpace();
-            version     = object.getVersion();
-            description = object.getDescription();
             authority   = object.getAuthority();
+            if (object instanceof DefaultIdentifier) {
+                final DefaultIdentifier c = (DefaultIdentifier) object;
+                codeSpace   = c.getCodeSpace();
+                version     = c.getVersion();
+                description = c.getDescription();
+            } else if (object instanceof ReferenceIdentifier) {
+                final ReferenceIdentifier c = (ReferenceIdentifier) object;
+                codeSpace = c.getCodeSpace();
+                version   = c.getVersion();
+            }
         }
     }
 
@@ -310,8 +323,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "codeSpace")
+    @UML(identifier="codeSpace", obligation=OPTIONAL, specification=ISO_19115)
     public String getCodeSpace() {
         return codeSpace;
     }
@@ -337,8 +350,8 @@
      *
      * @return the version identifier for the namespace, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "version")
+    @UML(identifier="version", obligation=OPTIONAL, specification=ISO_19115)
     public String getVersion() {
         return version;
     }
@@ -362,8 +375,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index 3c87030..d37f1ea 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
@@ -36,7 +36,6 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.ApplicationSchemaInformation;
 import org.opengis.metadata.MetadataExtensionInformation;
 import org.opengis.metadata.PortrayalCatalogueReference;
@@ -83,6 +82,14 @@
 import org.apache.sis.internal.converter.SurjectiveConverter;
 import org.apache.sis.math.FunctionProperty;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.internal.jaxb.code.MD_CharacterSetCode;
+
 
 /**
  * Root entity which defines metadata about a resource or resources.
@@ -212,7 +219,7 @@
     /**
      * Scope to which the metadata applies.
      */
-    private Collection<MetadataScope> metadataScopes;
+    private Collection<DefaultMetadataScope> metadataScopes;
 
     /**
      * Parties responsible for the metadata information.
@@ -324,8 +331,8 @@
      * @param identificationInfo  basic information about the resource to which the metadata applies.
      */
     public DefaultMetadata(final ResponsibleParty contact,
-                           final Date           dateStamp,
-                           final Identification identificationInfo)
+                           final Date             dateStamp,
+                           final Identification   identificationInfo)
     {
         this.contacts  = singleton(contact, ResponsibleParty.class);
         this.identificationInfo = singleton(identificationInfo, Identification.class);
@@ -346,16 +353,7 @@
     public DefaultMetadata(final Metadata object) {
         super(object);
         if (object != null) {
-            identifiers                   = singleton(object.getMetadataIdentifier(), Identifier.class);
-            parentMetadata                = object.getParentMetadata();
-            locales                       = copyMap       (object.getLocalesAndCharsets(),            Locale.class);
-            metadataScopes                = copyCollection(object.getMetadataScopes(),                MetadataScope.class);
             contacts                      = copyCollection(object.getContacts(),                      ResponsibleParty.class);
-            dateInfo                      = copyCollection(object.getDateInfo(),                      CitationDate.class);
-            metadataStandards             = copyCollection(object.getMetadataStandards(),             Citation.class);
-            metadataProfiles              = copyCollection(object.getMetadataProfiles(),              Citation.class);
-            alternativeMetadataReferences = copyCollection(object.getAlternativeMetadataReferences(), Citation.class);
-            metadataLinkages              = copyCollection(object.getMetadataLinkages(),              OnlineResource.class);
             spatialRepresentationInfo     = copyCollection(object.getSpatialRepresentationInfo(),     SpatialRepresentation.class);
             referenceSystemInfo           = copyCollection(object.getReferenceSystemInfo(),           ReferenceSystem.class);
             metadataExtensionInfo         = copyCollection(object.getMetadataExtensionInfo(),         MetadataExtensionInformation.class);
@@ -368,7 +366,35 @@
             applicationSchemaInfo         = copyCollection(object.getApplicationSchemaInfo(),         ApplicationSchemaInformation.class);
             metadataMaintenance           = object.getMetadataMaintenance();
             acquisitionInformation        = copyCollection(object.getAcquisitionInformation(),        AcquisitionInformation.class);
-            resourceLineages              = copyCollection(object.getResourceLineages(),              Lineage.class);
+            if (object instanceof DefaultMetadata) {
+                final DefaultMetadata c = (DefaultMetadata) object;
+                identifiers                   = singleton(c.getMetadataIdentifier(), Identifier.class);
+                parentMetadata                = c.getParentMetadata();
+                locales                       = copyMap       (c.getLocalesAndCharsets(),            Locale.class);
+                metadataScopes                = copyCollection(c.getMetadataScopes(),                DefaultMetadataScope.class);
+                dateInfo                      = copyCollection(c.getDateInfo(),                      CitationDate.class);
+                metadataStandards             = copyCollection(c.getMetadataStandards(),             Citation.class);
+                metadataProfiles              = copyCollection(c.getMetadataProfiles(),              Citation.class);
+                alternativeMetadataReferences = copyCollection(c.getAlternativeMetadataReferences(), Citation.class);
+                metadataLinkages              = copyCollection(c.getMetadataLinkages(),              OnlineResource.class);
+                resourceLineages              = copyCollection(c.getResourceLineages(),              Lineage.class);
+            } else {
+                setFileIdentifier         (object.getFileIdentifier());
+                setParentIdentifier       (object.getParentIdentifier());
+                setLanguage               (object.getLanguage());
+                setLocales                (object.getLocales());
+                setCharacterSet           (object.getCharacterSet());
+                setHierarchyLevels        (object.getHierarchyLevels());
+                setHierarchyLevelNames    (object.getHierarchyLevelNames());
+                setDateStamp              (object.getDateStamp());
+                setMetadataStandardName   (object.getMetadataStandardName());
+                setMetadataStandardVersion(object.getMetadataStandardVersion());
+                try {
+                    setDataSetUri(object.getDataSetUri());
+                } catch (URISyntaxException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            }
         }
     }
 
@@ -417,9 +443,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "metadataIdentifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="metadataIdentifier", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getMetadataIdentifier() {
         return super.getIdentifier();
     }
@@ -496,8 +522,8 @@
      *
      * @since 1.0
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, specification=ISO_19115)
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
     }
@@ -614,6 +640,18 @@
     }
 
     /**
+     * Sets information about an alternatively used localized character string for a linguistic extension.
+     *
+     * @param  newValues  the new locales.
+     *
+     * @deprecated Replaced by putting keys in {@link #getLocalesAndCharsets()}.
+     */
+    @Deprecated
+    public void setLocales(final Collection<? extends Locale> newValues) {
+        getLocales().addAll(newValues);
+    }
+
+    /**
      * Converter from {@link PT_Locale} and {@link Locale}.
      */
     private static final class ToLocale extends SurjectiveConverter<PT_Locale,Locale> {
@@ -681,7 +719,7 @@
     @Dependencies("getLocalesAndCharsets")
     // @XmlElement at the end of this class.
     public CharacterSet getCharacterSet() {
-        return CharacterSet.fromCharset(LegacyPropertyAdapter.getSingleton(getCharacterSets(),
+        return MD_CharacterSetCode.fromCharset(LegacyPropertyAdapter.getSingleton(getCharacterSets(),
                 Charset.class, null, DefaultMetadata.class, "getCharacterSet"));
     }
 
@@ -705,9 +743,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "parentMetadata")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="parentMetadata", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getParentMetadata() {
         return parentMetadata;
     }
@@ -770,25 +808,35 @@
     /**
      * Returns the scope or type of resource for which metadata is provided.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code MetadataScope} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return scope or type of resource for which metadata is provided.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<MetadataScope> getMetadataScopes() {
-        return metadataScopes = nonNullCollection(metadataScopes, MetadataScope.class);
+    @UML(identifier="metadataScope", obligation=CONDITIONAL, specification=ISO_19115)
+    public Collection<DefaultMetadataScope> getMetadataScopes() {
+        return metadataScopes = nonNullCollection(metadataScopes, DefaultMetadataScope.class);
     }
 
     /**
      * Sets the scope or type of resource for which metadata is provided.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code MetadataScope} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new scope or type of resource.
      *
      * @since 0.5
      */
-    public void setMetadataScopes(final Collection<? extends MetadataScope> newValues) {
-        metadataScopes = writeCollection(newValues, metadataScopes, MetadataScope.class);
+    public void setMetadataScopes(final Collection<? extends DefaultMetadataScope> newValues) {
+        metadataScopes = writeCollection(newValues, metadataScopes, DefaultMetadataScope.class);
     }
 
     /**
@@ -807,22 +855,19 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
         return new MetadataScopeAdapter<ScopeCode>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected MetadataScope wrap(final ScopeCode value) {
+            @Override protected DefaultMetadataScope wrap(final ScopeCode value) {
                 return new DefaultMetadataScope(value, null);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected ScopeCode unwrap(final MetadataScope container) {
+            @Override protected ScopeCode unwrap(final DefaultMetadataScope container) {
                 return container.getResourceScope();
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final MetadataScope container, final ScopeCode value) {
-                if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setResourceScope(value);
-                    return true;
-                }
-                return false;
+            @Override protected boolean update(final DefaultMetadataScope container, final ScopeCode value) {
+                container.setResourceScope(value);
+                return true;
             }
         }.validOrNull();
     }
@@ -857,23 +902,20 @@
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
         return new MetadataScopeAdapter<String>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected MetadataScope wrap(final String value) {
+            @Override protected DefaultMetadataScope wrap(final String value) {
                 return new DefaultMetadataScope(null, value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected String unwrap(final MetadataScope container) {
+            @Override protected String unwrap(final DefaultMetadataScope container) {
                 final InternationalString name = container.getName();
                 return (name != null) ? name.toString() : null;
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final MetadataScope container, final String value) {
-                if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setName(value != null ? new SimpleInternationalString(value) : null);
-                    return true;
-                }
-                return false;
+            @Override protected boolean update(final DefaultMetadataScope container, final String value) {
+                container.setName(value != null ? new SimpleInternationalString(value) : null);
+                return true;
             }
         }.validOrNull();
     }
@@ -926,8 +968,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="dateInfo", obligation=MANDATORY, specification=ISO_19115)
     public Collection<CitationDate> getDateInfo() {
         return dateInfo = nonNullCollection(dateInfo, CitationDate.class);
     }
@@ -1018,8 +1060,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataStandard", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getMetadataStandards() {
         return metadataStandards = nonNullCollection(metadataStandards, Citation.class);
     }
@@ -1046,8 +1088,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataProfile", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getMetadataProfiles() {
         return metadataProfiles = nonNullCollection(metadataProfiles, Citation.class);
     }
@@ -1071,8 +1113,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="alternativeMetadataReference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAlternativeMetadataReferences() {
         return alternativeMetadataReferences = nonNullCollection(alternativeMetadataReferences, Citation.class);
     }
@@ -1198,8 +1240,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="metadataLinkage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getMetadataLinkages() {
         return metadataLinkages = nonNullCollection(metadataLinkages, OnlineResource.class);
     }
@@ -1233,8 +1275,8 @@
         if (FilterByVersion.LEGACY_METADATA.accept() && (info = getIdentificationInfo()) != null) {
             for (final Identification identification : info) {
                 final Citation citation = identification.getCitation();
-                if (citation != null) {
-                    final Collection<? extends OnlineResource> onlineResources = citation.getOnlineResources();
+                if (citation instanceof DefaultCitation) {
+                    final Collection<? extends OnlineResource> onlineResources = ((DefaultCitation) citation).getOnlineResources();
                     if (onlineResources != null) {
                         for (final OnlineResource link : onlineResources) {
                             final URI uri = link.getLinkage();
@@ -1561,8 +1603,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resourceLineage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Lineage> getResourceLineages() {
         return resourceLineages = nonNullCollection(resourceLineages, Lineage.class);
     }
@@ -1688,7 +1730,7 @@
     }
 
     @XmlElement(name = "metadataScope")
-    private Collection<MetadataScope> getMetadataScope() {
+    private Collection<DefaultMetadataScope> getMetadataScope() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getMetadataScopes() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
index 1e0a4dd..02b0455 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadataScope.java
@@ -20,10 +20,15 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the scope of the resource.
@@ -32,6 +37,14 @@
  * <div class="preformat">{@code MD_MetadataScope}
  * {@code   └─resourceScope……} Resource scope</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code MetadataScope} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -52,7 +65,8 @@
     "name"
 })
 @XmlRootElement(name = "MD_MetadataScope")
-public class DefaultMetadataScope extends ISOMetadata implements MetadataScope {
+@UML(identifier="MD_MetadataScope", specification=ISO_19115)
+public class DefaultMetadataScope extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -91,10 +105,8 @@
      * given object are not recursively copied.
      *
      * @param object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(MetadataScope)
      */
-    public DefaultMetadataScope(final MetadataScope object) {
+    public DefaultMetadataScope(final DefaultMetadataScope object) {
         super(object);
         if (object != null) {
             resourceScope = object.getResourceScope();
@@ -103,37 +115,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultMetadataScope}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultMetadataScope} instance is created using the
-     *       {@linkplain #DefaultMetadataScope(MetadataScope) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultMetadataScope castOrCopy(final MetadataScope object) {
-        if (object == null || object instanceof DefaultMetadataScope) {
-            return (DefaultMetadataScope) object;
-        }
-        return new DefaultMetadataScope(object);
-    }
-
-    /**
      * Returns the code for the scope.
      *
      * @return the code for the scope.
      */
-    @Override
     @XmlElement(name = "resourceScope", required = true)
+    @UML(identifier="resourceScope", obligation=MANDATORY, specification=ISO_19115)
     public ScopeCode getResourceScope() {
         return resourceScope;
     }
@@ -153,8 +140,8 @@
      *
      * @return description of the scope, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "name")
+    @UML(identifier="name", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index 4e498fa..38f99ed 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@ -21,7 +21,6 @@
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.ConcurrentModificationException;
-import org.opengis.metadata.MetadataScope;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 
 
@@ -35,25 +34,25 @@
  * @since   0.5
  * @module
  */
-abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataScope> {
+abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,DefaultMetadataScope> {
     /**
      * @param scopes Value of {@link DefaultMetadata#getMetadataScopes()}.
      */
-    MetadataScopeAdapter(final Collection<MetadataScope> scopes) {
+    MetadataScopeAdapter(final Collection<DefaultMetadataScope> scopes) {
         super(scopes);
     }
 
     /**
      * Invoked (indirectly) by JAXB when adding a new scope code or scope name. This implementation searches
-     * for an existing {@link MetadataScope} instance with a free slot for the new value before to create a
+     * for an existing {@code MetadataScope} instance with a free slot for the new value before to create a
      * new {@link DefaultMetadataScope} instance.
      */
     @Override
     public boolean add(final L newValue) {
         int n = 0;
-        final Iterator<MetadataScope> it = elements.iterator();
+        final Iterator<DefaultMetadataScope> it = elements.iterator();
         while (it.hasNext()) {
-            MetadataScope scope = it.next();
+            DefaultMetadataScope scope = it.next();
             if (unwrap(scope) != null) {
                 n++;
                 continue;
@@ -64,18 +63,16 @@
              * But if the metadata is not modifiable, then we will need to clone it and replaces the element in
              * the collection.
              */
-            if (!(scope instanceof DefaultMetadataScope) ||
-                    ((DefaultMetadataScope) scope).state() == DefaultMetadataScope.State.FINAL)
-            {
+            if (scope.state() == DefaultMetadataScope.State.FINAL) {
                 scope = new DefaultMetadataScope(scope);
                 if (elements instanceof List<?>) {
-                    ((List<MetadataScope>) elements).set(n, scope);
+                    ((List<DefaultMetadataScope>) elements).set(n, scope);
                 } else {
                     /*
                      * Not a list. Delete all the remaining parts, substitute the value
                      * and reinsert everything in the same order.
                      */
-                    final MetadataScope[] remaining = new MetadataScope[elements.size() - n];
+                    final DefaultMetadataScope[] remaining = new DefaultMetadataScope[elements.size() - n];
                     remaining[0] = scope;
                     n = 1;
                     it.remove();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
index e30e5f5..dbd681f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
@@ -69,7 +69,7 @@
     "types",
     "functions",
     "extents",
-    "objectiveOccurrences",
+    "objectiveOccurences",
     "pass",
     "sensingInstruments"
 })
@@ -78,7 +78,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 3312985886806161441L;
+    private static final long serialVersionUID = 8273806197892815938L;
 
     /**
      * Priority applied to the target.
@@ -104,7 +104,7 @@
     /**
      * Event or events associated with objective completion.
      */
-    private Collection<Event> objectiveOccurrences;
+    private Collection<Event> objectiveOccurences;
 
     /**
      * Pass of the platform over the objective.
@@ -139,7 +139,7 @@
             types                = copyCollection(object.getTypes(), ObjectiveType.class);
             functions            = copyCollection(object.getFunctions(), InternationalString.class);
             extents              = copyCollection(object.getExtents(), Extent.class);
-            objectiveOccurrences = copyCollection(object.getObjectiveOccurences(), Event.class);
+            objectiveOccurences  = copyCollection(object.getObjectiveOccurences(), Event.class);
             pass                 = copyCollection(object.getPass(), PlatformPass.class);
             sensingInstruments   = copyCollection(object.getSensingInstruments(), Instrument.class);
         }
@@ -289,45 +289,27 @@
     /**
      * Returns the event or events associated with objective completion.
      *
-     * @return events associated with objective completion.
-     *
-     * @since 1.0
-     */
-    @XmlElement(name = "objectiveOccurence", required = true)
-    public Collection<Event> getObjectiveOccurrences() {
-        return objectiveOccurrences = nonNullCollection(objectiveOccurrences, Event.class);
-    }
-
-    /**
-     * @deprecated Renamed {@link #getObjectiveOccurrences()}.
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is misspelled (missing "r"). Its name may be fixed in GeoAPI 4.0.</div>
      *
      * @return events associated with objective completion.
      */
     @Override
-    @Deprecated
+    @XmlElement(name = "objectiveOccurence", required = true)
     public Collection<Event> getObjectiveOccurences() {
-        return getObjectiveOccurrences();
+        return objectiveOccurences = nonNullCollection(objectiveOccurences, Event.class);
     }
 
     /**
      * Sets the event or events associated with objective completion.
      *
-     * @param  newValues  the new objective occurrences values.
-     *
-     * @since 1.0
-     */
-    public void setObjectiveOccurrences(final Collection<? extends Event> newValues) {
-        objectiveOccurrences = writeCollection(newValues, objectiveOccurrences, Event.class);
-    }
-
-    /**
-     * @deprecated Renamed {@link #setObjectiveOccurrences(Collection)}.
+     * <div class="warning"><b>Upcoming API change</b><br>
+     * This method is misspelled (missing "r"). Its name may be fixed in GeoAPI 4.0.</div>
      *
      * @param  newValues  the new objective occurrences values.
      */
-    @Deprecated
     public void setObjectiveOccurences(final Collection<? extends Event> newValues) {
-        setObjectiveOccurrences(newValues);
+        objectiveOccurences = writeCollection(newValues, objectiveOccurences, Event.class);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java
index 2bb73c7..c086f1b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/AbstractParty.java
@@ -23,13 +23,16 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
-import org.opengis.metadata.citation.Party;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the individual and / or organization of the party.
@@ -39,6 +42,14 @@
  * <div class="preformat">{@code CI_Party}
  * {@code   └─name……} Name of the party.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Party} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -64,7 +75,8 @@
     DefaultIndividual.class,
     DefaultOrganisation.class
 })
-public class AbstractParty extends ISOMetadata implements Party {
+@UML(identifier="CI_Party", specification=ISO_19115)
+public class AbstractParty extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -103,10 +115,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Party)
      */
-    public AbstractParty(final Party object) {
+    public AbstractParty(final AbstractParty object) {
         super(object);
         if (object != null) {
             name        = object.getName();
@@ -115,47 +125,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link Individual} or {@link Organisation},
-     *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
-     *       Note that if the given object implements more than one of the above-cited interfaces,
-     *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code AbstractParty}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code AbstractParty} instance is created using the
-     *       {@linkplain #AbstractParty(Party) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static AbstractParty castOrCopy(final Party object) {
-        if (object instanceof Individual) {
-            return DefaultIndividual.castOrCopy((Individual) object);
-        }
-        if (object instanceof Organisation) {
-            return DefaultOrganisation.castOrCopy((Organisation) object);
-        }
-        if (object == null || object instanceof AbstractParty) {
-            return (AbstractParty) object;
-        }
-        return new AbstractParty(object);
-    }
-
-    /**
      * Return the name of the party.
      *
      * @return name of the party.
      */
-    @Override
     @XmlElement(name = "name")
+    @UML(identifier="name", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
@@ -175,8 +150,8 @@
      *
      * @return contact information for the party.
      */
-    @Override
     @XmlElement(name = "contactInfo")
+    @UML(identifier="contactInfo", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Contact> getContactInfo() {
         return contactInfo = nonNullCollection(contactInfo, Contact.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
index e82ca78..7f7e6a9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/Citations.java
@@ -25,6 +25,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.CharSequences;
@@ -712,10 +713,10 @@
                      * Found a possible match. We will take the code space in account only if it is defined
                      * by both identifiers. If a code space is undefined, we consider that we have a match.
                      */
-                    if (identifier != null) {
-                        final String codeSpace = identifier.getCodeSpace();
-                        if (codeSpace != null) {
-                            final String cs = citId.getCodeSpace();
+                    if (identifier instanceof ReferenceIdentifier) {
+                        final String codeSpace = ((ReferenceIdentifier) identifier).getCodeSpace();
+                        if (codeSpace != null && citId instanceof ReferenceIdentifier) {
+                            final String cs = ((ReferenceIdentifier) citId).getCodeSpace();
                             if (cs != null && !equalsFiltered(codeSpace, cs)) {
                                 continue;       // Check other identifiers.
                             }
@@ -737,7 +738,8 @@
                     final CharSequence localPart = code.subSequence(++s, length);
                     for (it = citIds.iterator(); it.hasNext();) {
                         final Identifier id = it.next();
-                        if (equalsFiltered(codeSpace, id.getCodeSpace()) && equalsFiltered(localPart, id.getCode())) {
+                        final String cs = (id instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) id).getCodeSpace() : null;
+                        if (equalsFiltered(codeSpace, cs) && equalsFiltered(localPart, id.getCode())) {
                             return true;
                         }
                     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java
index 9355c3d..6288afd 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultAddress.java
@@ -57,7 +57,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -1709738216789373888L;
+    private static final long serialVersionUID = 1357443146723845129L;
 
     /**
      * State, province of the location.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
index f0795df..166ad72 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
@@ -39,6 +39,10 @@
 import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.xml.IdentifierMap;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
@@ -195,7 +199,6 @@
      *
      * @see #castOrCopy(Citation)
      */
-    @SuppressWarnings("deprecation")
     public DefaultCitation(final Citation object) {
         super(object);
         if (object != null) {
@@ -210,8 +213,11 @@
             series                  = object.getSeries();
             otherCitationDetails    = object.getOtherCitationDetails();
             collectiveTitle         = object.getCollectiveTitle();
-            onlineResources         = copyCollection(object.getOnlineResources(), OnlineResource.class);
-            graphics                = copyCollection(object.getGraphics(), BrowseGraphic.class);
+            if (object instanceof DefaultCitation) {
+                final DefaultCitation c = (DefaultCitation) object;
+                onlineResources = copyCollection(c.getOnlineResources(), OnlineResource.class);
+                graphics        = copyCollection(c.getGraphics(), BrowseGraphic.class);
+            }
             final String id1        = object.getISBN();
             final String id2        = object.getISSN();
             if (id1 != null || id2 != null) {
@@ -621,8 +627,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="onlineResource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getOnlineResources() {
         return onlineResources = nonNullCollection(onlineResources, OnlineResource.class);
     }
@@ -645,8 +651,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="graphic", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getGraphics() {
         return graphics = nonNullCollection(graphics, BrowseGraphic.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
index 412b7ef..b4da23d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultContact.java
@@ -28,17 +28,23 @@
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.Telephone;
 import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
 import org.apache.sis.internal.metadata.Dependencies;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.util.CollectionsExt;
 
+// Branch-specific imports
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information required to enable contact with the responsible person and/or organization.
@@ -77,7 +83,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2972124992825717056L;
+    private static final long serialVersionUID = -969735574940462381L;
 
     /**
      * Telephone numbers at which the organization or individual may be contacted.
@@ -137,12 +143,19 @@
     public DefaultContact(final Contact object) {
         super(object);
         if (object != null) {
-            phones              = copyCollection(object.getPhones(), Telephone.class);
-            addresses           = copyCollection(object.getAddresses(), Address.class);
-            onlineResources     = copyCollection(object.getOnlineResources(), OnlineResource.class);
             hoursOfService      = object.getHoursOfService();
             contactInstructions = object.getContactInstructions();
-            contactType         = object.getContactType();
+            if (object instanceof DefaultContact) {
+                final DefaultContact c = (DefaultContact) object;
+                phones          = copyCollection(c.getPhones(), Telephone.class);
+                addresses       = copyCollection(c.getAddresses(), Address.class);
+                onlineResources = copyCollection(c.getOnlineResources(), OnlineResource.class);
+                contactType     = c.getContactType();
+            } else {
+                phones          = singleton(object.getPhone(), Telephone.class);
+                addresses       = singleton(object.getAddress(), Address.class);
+                onlineResources = singleton(object.getOnlineResource(), OnlineResource.class);
+            }
         }
     }
 
@@ -178,8 +191,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="phone", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Telephone> getPhones() {
         return phones = nonNullCollection(phones, Telephone.class);
     }
@@ -218,8 +231,8 @@
 
     /**
      * Returns telephone numbers at which the organization or individual may be contacted.
-     * This method returns the first telephone number associated to {@link TelephoneType#VOICE}
-     * or {@link TelephoneType#FACSIMILE FACSIMILE}.
+     * This method returns the first telephone number associated to {@code TelephoneType.VOICE}
+     * or {@code TelephoneType.FACSIMILE FACSIMILE}.
      *
      * @return telephone numbers at which the organization or individual may be contacted, or {@code null}.
      *
@@ -233,25 +246,24 @@
         Telephone phone = null;
         if (FilterByVersion.LEGACY_METADATA.accept()) {
             final Collection<Telephone> phones = getPhones();
-            if (phones != null) {                                   // May be null on marshalling.
-                TelephoneType ignored = null;
+            if (phones != null) { // May be null on marshalling.
+                CodeList<?> ignored = null;
                 for (final Telephone c : phones) {
-                    final TelephoneType type = c.getNumberType();
-                    if (TelephoneType.VOICE.equals(type) || TelephoneType.FACSIMILE.equals(type)) {
-                        if (phone == null) {
-                            phone = c;
+                    if (c instanceof DefaultTelephone) {
+                        String name;
+                        final CodeList<?> type = ((DefaultTelephone) c).numberType;
+                        if (type != null && ("VOICE".equals(name = type.name()) || "FACSIMILE".equals(name))) {
+                            if (phone == null) {
+                                phone = c;
+                            }
+                        } else if (ignored == null) {
+                            ignored = type;
                         }
-                    } else if (ignored == null) {
-                        ignored = type;
                     }
                 }
                 if (ignored != null) {
-                    /*
-                     * Log a warning for ignored property using a call to 'ignored.toString()' instead than 'ignored'
-                     * because we want the property to appear as "TelephoneType[FOO]" instead than "FOO".
-                     */
                     Context.warningOccured(Context.current(), DefaultContact.class, "getPhone",
-                            Messages.class, Messages.Keys.IgnoredPropertyAssociatedTo_1, ignored.toString());
+                            Messages.class, Messages.Keys.IgnoredPropertyAssociatedTo_1, ignored);
                 }
             }
         }
@@ -275,10 +287,10 @@
             } else {
                 newValues = new ArrayList<>(4);
                 for (String number : newValue.getVoices()) {
-                    newValues.add(new DefaultTelephone(number, TelephoneType.VOICE));
+                    newValues.add(new DefaultTelephone(number, UnsupportedCodeList.VOICE));
                 }
                 for (String number : newValue.getFacsimiles()) {
-                    newValues.add(new DefaultTelephone(number, TelephoneType.FACSIMILE));
+                    newValues.add(new DefaultTelephone(number, UnsupportedCodeList.FACSIMILE));
                 }
             }
         }
@@ -292,8 +304,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="address", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Address> getAddresses() {
         return addresses = nonNullCollection(addresses, Address.class);
     }
@@ -348,8 +360,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="onlineResource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getOnlineResources() {
         return onlineResources = nonNullCollection(onlineResources, OnlineResource.class);
     }
@@ -457,9 +469,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "contactType")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="contactType", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getContactType() {
         return contactType;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
index 14449fe..bbe4498 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultIndividual.java
@@ -20,14 +20,26 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Individual;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the party if the party is an individual.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Individual} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -47,7 +59,8 @@
     "positionName"
 })
 @XmlRootElement(name = "CI_Individual")
-public class DefaultIndividual extends AbstractParty implements Individual {
+@UML(identifier="CI_Individual", specification=ISO_19115)
+public class DefaultIndividual extends AbstractParty {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -85,10 +98,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Individual)
      */
-    public DefaultIndividual(final Individual object) {
+    public DefaultIndividual(final DefaultIndividual object) {
         super(object);
         if (object != null) {
             positionName = object.getPositionName();
@@ -96,37 +107,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultIndividual}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultIndividual} instance is created using the
-     *       {@linkplain #DefaultIndividual(Individual) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultIndividual castOrCopy(final Individual object) {
-        if (object == null || object instanceof DefaultIndividual) {
-            return (DefaultIndividual) object;
-        }
-        return new DefaultIndividual(object);
-    }
-
-    /**
      * Returns position of the individual in an organization, or {@code null} if none.
      *
      * @return position of the individual in an organization, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "positionName")
+    @UML(identifier="positionName", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getPositionName() {
         return positionName;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
index 5c5da01..4c9d0e1 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
@@ -28,6 +28,11 @@
 import org.apache.sis.internal.jaxb.gco.URIAdapter;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about on-line sources from which the dataset, specification, or
@@ -68,7 +73,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2627991335953178610L;
+    private static final long serialVersionUID = 1413613911128890864L;
 
     /**
      * Location (address) for on-line access using a Uniform Resource Locator address or
@@ -141,7 +146,9 @@
             name               = object.getName();
             description        = object.getDescription();
             function           = object.getFunction();
-            protocolRequest    = object.getProtocolRequest();
+            if (object instanceof DefaultOnlineResource) {
+                protocolRequest = ((DefaultOnlineResource) object).getProtocolRequest();
+            }
         }
     }
 
@@ -330,9 +337,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "protocolRequest")
     @XmlJavaTypeAdapter(StringAdapter.Since2014.class)
+    @UML(identifier="protocolRequest", obligation=OPTIONAL, specification=ISO_19115)
     public String getProtocolRequest() {
         return protocolRequest;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
index 2fe2a73..0a70123 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
@@ -21,14 +21,26 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
 import org.opengis.metadata.identification.BrowseGraphic;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the party if the party is an organization.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Organisation} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -49,7 +61,8 @@
     "individual"
 })
 @XmlRootElement(name = "CI_Organisation")
-public class DefaultOrganisation extends AbstractParty implements Organisation {
+@UML(identifier="CI_Organisation", specification=ISO_19115)
+public class DefaultOrganisation extends AbstractParty {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -63,7 +76,7 @@
     /**
      * Individuals in the named organization.
      */
-    private Collection<Individual> individual;
+    private Collection<DefaultIndividual> individual;
 
     /**
      * Constructs an initially empty organization.
@@ -81,12 +94,12 @@
      */
     public DefaultOrganisation(final CharSequence name,
                                final BrowseGraphic logo,
-                               final Individual individual,
+                               final DefaultIndividual individual,
                                final Contact contactInfo)
     {
         super(name, contactInfo);
         this.logo       = singleton(logo, BrowseGraphic.class);
-        this.individual = singleton(individual, Individual.class);
+        this.individual = singleton(individual, DefaultIndividual.class);
     }
 
     /**
@@ -95,49 +108,22 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Organisation)
      */
-    public DefaultOrganisation(final Organisation object) {
+    public DefaultOrganisation(final DefaultOrganisation object) {
         super(object);
         if (object != null) {
             logo       = copyCollection(object.getLogo(), BrowseGraphic.class);
-            individual = copyCollection(object.getIndividual(), Individual.class);
+            individual = copyCollection(object.getIndividual(), DefaultIndividual.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOrganisation}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOrganisation} instance is created using the
-     *       {@linkplain #DefaultOrganisation(Organisation) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOrganisation castOrCopy(final Organisation object) {
-        if (object == null || object instanceof DefaultOrganisation) {
-            return (DefaultOrganisation) object;
-        }
-        return new DefaultOrganisation(object);
-    }
-
-    /**
      * Returns the graphics identifying organization.
      *
      * @return graphics identifying organization, or an empty collection if there is none.
      */
-    @Override
     @XmlElement(name = "logo")
+    @UML(identifier="logo", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getLogo() {
         return logo = nonNullCollection(logo, BrowseGraphic.class);
     }
@@ -154,20 +140,30 @@
     /**
      * Returns the individuals in the named organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Individual} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return individuals in the named organization, or an empty collection.
      */
-    @Override
     @XmlElement(name = "individual")
-    public Collection<Individual> getIndividual() {
-        return individual = nonNullCollection(individual, Individual.class);
+    @UML(identifier="individual", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultIndividual> getIndividual() {
+        return individual = nonNullCollection(individual, DefaultIndividual.class);
     }
 
     /**
      * Sets the individuals in the named organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Individual} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new individuals in the named organization.
      */
-    public void setIndividual(final Collection<? extends Individual> newValues) {
-        individual = writeCollection(newValues, individual, Individual.class);
+    public void setIndividual(final Collection<? extends DefaultIndividual> newValues) {
+        individual = writeCollection(newValues, individual, DefaultIndividual.class);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
index bb1fbf7..4aeccb2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
@@ -24,13 +24,16 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.code.CI_RoleCode;
 
 // Branch-specific imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -43,6 +46,14 @@
  * {@code   │   └─name……………} Name of the party.
  * {@code   └─role………………………} Function performed by the responsible party.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Responsibility} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -69,7 +80,8 @@
 @XmlSeeAlso({
     DefaultResponsibleParty.class
 })
-public class DefaultResponsibility extends ISOMetadata implements Responsibility {
+@UML(identifier="CI_Responsibility", specification=ISO_19115)
+public class DefaultResponsibility extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -88,7 +100,7 @@
     /**
      * Information about the parties.
      */
-    private Collection<Party> parties;
+    private Collection<AbstractParty> parties;
 
     /**
      * Constructs an initially empty responsible party.
@@ -103,10 +115,10 @@
      * @param extent  spatial or temporal extent of the role, or {@code null}.
      * @param party   information about the party, or {@code null}.
      */
-    public DefaultResponsibility(final Role role, final Extent extent, final Party party) {
+    public DefaultResponsibility(final Role role, final Extent extent, final AbstractParty party) {
         this.role    = role;
         this.extents = singleton(extent, Extent.class);
-        this.parties = singleton(party, Party.class);
+        this.parties = singleton(party, AbstractParty.class);
     }
 
     /**
@@ -115,41 +127,29 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Responsibility)
      */
-    public DefaultResponsibility(final Responsibility object) {
+    public DefaultResponsibility(final DefaultResponsibility object) {
         super(object);
         if (object != null) {
             this.role    = object.getRole();
             this.extents = copyCollection(object.getExtents(), Extent.class);
-            this.parties = copyCollection(object.getParties(), Party.class);
+            this.parties = copyCollection(object.getParties(), AbstractParty.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultResponsibility}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultResponsibility} instance is created using the
-     *       {@linkplain #DefaultResponsibility(Responsibility) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       Responsibility contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
+     * Bridge constructor for {@link DefaultResponsibleParty#DefaultResponsibleParty(ResponsibleParty)}.
      */
-    public static DefaultResponsibility castOrCopy(final Responsibility object) {
-        if (object == null || object instanceof DefaultResponsibility) {
-            return (DefaultResponsibility) object;
+    DefaultResponsibility(final ResponsibleParty object) {
+        super(object);
+        if (object != null) {
+            this.role = object.getRole();
+            if (object instanceof DefaultResponsibility) {
+                final DefaultResponsibility c = (DefaultResponsibility) object;
+                this.extents = copyCollection(c.getExtents(), Extent.class);
+                this.parties = copyCollection(c.getParties(), AbstractParty.class);
+            }
         }
-        return new DefaultResponsibility(object);
     }
 
     /**
@@ -157,9 +157,9 @@
      *
      * @return function performed by the responsible party.
      */
-    @Override
     @XmlElement(name = "role", required = true)
     @XmlJavaTypeAdapter(CI_RoleCode.Since2014.class)
+    @UML(identifier="role", obligation=MANDATORY, specification=ISO_19115)
     public Role getRole() {
         return role;
     }
@@ -179,8 +179,8 @@
      *
      * @return the spatial or temporal extents of the role.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="extent", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -197,21 +197,31 @@
     /**
      * Returns information about the parties.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Party} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the parties.
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<Party> getParties() {
-        return parties = nonNullCollection(parties, Party.class);
+    @UML(identifier="party", obligation=MANDATORY, specification=ISO_19115)
+    public Collection<AbstractParty> getParties() {
+        return parties = nonNullCollection(parties, AbstractParty.class);
     }
 
     /**
      * Sets information about the parties.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Party} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  new information about the parties.
      */
-    public void setParties(final Collection<? extends Party> newValues) {
-        parties = writeCollection(newValues, parties, Party.class);
+    public void setParties(final Collection<? extends AbstractParty> newValues) {
+        parties = writeCollection(newValues, parties, AbstractParty.class);
     }
 
 
@@ -239,7 +249,7 @@
     }
 
     @XmlElement(name = "party", required = true)
-    private Collection<Party> getParty() {
+    private Collection<AbstractParty> getParty() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getParties() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
index 5333744..7328e80 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibleParty.java
@@ -23,10 +23,6 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.citation.Contact;
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Individual;
-import org.opengis.metadata.citation.Organisation;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.Role;
 import org.opengis.util.InternationalString;
@@ -49,8 +45,11 @@
  * {@code   └─party…………………………} Information about the parties.
  * {@code       └─name…………………} Name of the party.</div>
  *
- * @deprecated As of ISO 19115:2014, the {@code ResponsibleParty} type has been replaced by {@code Responsibility}
- *             to allow more flexible associations of individuals, organizations, and roles.
+ * <div class="warning"><b>Upcoming API change — deprecation</b><br>
+ * As of ISO 19115:2014, the {@code ResponsibleParty} type has been replaced by {@code Responsibility}
+ * to allow more flexible associations of individuals, organisations, and roles.
+ * This {@code ResponsibleParty} interface may be deprecated in GeoAPI 4.0.
+ * </div>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Touraïvane (IRD)
@@ -59,7 +58,6 @@
  * @since   0.3
  * @module
  */
-@Deprecated
 @XmlType(name = "CI_ResponsibleParty_Type", namespace = LegacyNamespaces.GMD, propOrder = {
     "individualName",
     "organisationName",
@@ -95,14 +93,29 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Responsibility)
      */
-    public DefaultResponsibleParty(final Responsibility object) {
+    public DefaultResponsibleParty(final DefaultResponsibility object) {
         super(object);
     }
 
     /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
+     * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
+     * given object are not recursively copied.
+     *
+     * @param object The metadata to copy values from, or {@code null} if none.
+     *
+     * @see #castOrCopy(ResponsibleParty)
+     */
+    public DefaultResponsibleParty(final ResponsibleParty object) {
+        super(object);
+        if (object != null && !(object instanceof DefaultResponsibility)) {
+            setIndividualName(object.getIndividualName());
+            setOrganisationName(object.getOrganisationName());
+        }
+    }
+
+    /**
      * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
      * This method performs the first applicable action in the following choices:
      *
@@ -111,7 +124,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultResponsibleParty}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultResponsibleParty} instance is created using the
-     *       {@linkplain #DefaultResponsibleParty(Responsibility) copy constructor}
+     *       {@linkplain #DefaultResponsibleParty(ResponsibleParty) copy constructor}
      *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -120,7 +133,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultResponsibleParty castOrCopy(final Responsibility object) {
+    public static DefaultResponsibleParty castOrCopy(final ResponsibleParty object) {
         if (object == null || object instanceof DefaultResponsibleParty) {
             return (DefaultResponsibleParty) object;
         }
@@ -139,12 +152,12 @@
      * @see #getPositionName()
      */
     private InternationalString getIndividual(final boolean position) {
-        final Collection<Party> parties = getParties();
-        InternationalString name = getName(parties, Individual.class, position);
+        final Collection<AbstractParty> parties = getParties();
+        InternationalString name = getName(parties, DefaultIndividual.class, position);
         if (name == null && parties != null) {
-            for (final Party party : parties) {
-                if (party instanceof Organisation) {
-                    name = getName(((Organisation) party).getIndividual(), Individual.class, position);
+            for (final AbstractParty party : parties) {
+                if (party instanceof DefaultOrganisation) {
+                    name = getName(((DefaultOrganisation) party).getIndividual(), DefaultIndividual.class, position);
                     if (name != null) {
                         break;
                     }
@@ -164,20 +177,20 @@
      * @see #getIndividualName()
      * @see #getPositionName()
      */
-    private static InternationalString getName(final Collection<? extends Party> parties,
-            final Class<? extends Party> type, final boolean position)
+    private static InternationalString getName(final Collection<? extends AbstractParty> parties,
+            final Class<? extends AbstractParty> type, final boolean position)
     {
         InternationalString name = null;
         if (parties != null) {                              // May be null on marshalling.
-            for (final Party party : parties) {
+            for (final AbstractParty party : parties) {
                 if (type.isInstance(party)) {
                     if (name != null) {
                         LegacyPropertyAdapter.warnIgnoredExtraneous(type, DefaultResponsibleParty.class,
-                                position ? "getPositionName" : (type == Individual.class)
+                                position ? "getPositionName" : (type == DefaultIndividual.class)
                                          ? "getIndividualName" : "getOrganisationName");
                         break;
                     }
-                    name = position ? ((Individual) party).getPositionName() : party.getName();
+                    name = position ? ((DefaultIndividual) party).getPositionName() : party.getName();
                 }
             }
         }
@@ -189,18 +202,18 @@
      *
      * @return {@code true} if the name has been set, or {@code false} otherwise.
      */
-    private boolean setName(final Class<? extends Party> type, final boolean position, final InternationalString name) {
+    private boolean setName(final Class<? extends AbstractParty> type, final boolean position, final InternationalString name) {
         checkWritePermission(valueIfDefined(super.getParties()));
-        final Iterator<Party> it = getParties().iterator();
+        final Iterator<AbstractParty> it = getParties().iterator();
         while (it.hasNext()) {
-            final Party party = it.next();
-            if (party instanceof AbstractParty && type.isInstance(party)) {
+            final AbstractParty party = it.next();
+            if (type.isInstance(party)) {
                 if (position) {
                     ((DefaultIndividual) party).setPositionName(name);
                 } else {
-                    ((AbstractParty) party).setName(name);
+                    party.setName(name);
                 }
-                if (((AbstractParty) party).isEmpty()) {
+                if (party.isEmpty()) {
                     it.remove();
                 }
                 return true;
@@ -214,9 +227,9 @@
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the name of the first {@link Individual} found in the collection of
+     * <p>This implementation returns the name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
-     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
+     * on the first organisation member.</p>
      *
      * @return name, surname, given name and title of the responsible person, or {@code null}.
      *
@@ -236,7 +249,7 @@
      * Only one of {@code individualName}, {@link #getOrganisationName() organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation sets the name of the first {@link Individual} found in the collection of
+     * <p>This implementation sets the name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}, or create a new individual if no existing instance was found.</p>
      *
      * @param  newValue  the new individual name, or {@code null} if none.
@@ -245,7 +258,7 @@
      */
     @Deprecated
     public void setIndividualName(final String newValue) {
-        if (!setName(Individual.class, false, Types.toInternationalString(newValue))) {
+        if (!setName(DefaultIndividual.class, false, Types.toInternationalString(newValue))) {
             getParties().add(new DefaultIndividual(newValue, null, null));
         }
     }
@@ -255,7 +268,7 @@
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation returns the name of the first {@link Organisation}
+     * <p>This implementation returns the name of the first {@code Organisation}
      * found in the collection of {@linkplain #getParties() parties}.</p>
      *
      * @return name of the responsible organization, or {@code null}.
@@ -264,10 +277,10 @@
      */
     @Override
     @Deprecated
-    @Dependencies("getParties")
     @XmlElement(name = "organisationName")
+    @Dependencies("getParties")
     public InternationalString getOrganisationName() {
-        return getName(getParties(), Organisation.class, false);
+        return getName(getParties(), DefaultOrganisation.class, false);
     }
 
     /**
@@ -275,7 +288,7 @@
      * {@link #getIndividualName() individualName}, {@code organisationName}
      * and {@link #getPositionName() positionName} shall be provided.
      *
-     * <p>This implementation sets the name of the first {@link Organisation} found in the collection of
+     * <p>This implementation sets the name of the first {@code Organisation} found in the collection of
      * {@linkplain #getParties() parties}, or create a new organization if no existing instance was found.</p>
      *
      * @param  newValue  the new organization name, or {@code null} if none.
@@ -284,7 +297,7 @@
      */
     @Deprecated
     public void setOrganisationName(final InternationalString newValue) {
-        if (!setName(Organisation.class, false, Types.toInternationalString(newValue))) {
+        if (!setName(DefaultOrganisation.class, false, Types.toInternationalString(newValue))) {
             getParties().add(new DefaultOrganisation(newValue, null, null, null));
         }
     }
@@ -294,9 +307,9 @@
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation returns the position of the first {@link Individual} found in the collection of
+     * <p>This implementation returns the position of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}. If no individual is found in the parties, then this method fallbacks
-     * on the first {@linkplain Organisation#getIndividual() organisation member}.</p>
+     * on the first organisation member.</p>
      *
      * @return role or position of the responsible person, or {@code null}
      *
@@ -304,8 +317,8 @@
      */
     @Override
     @Deprecated
-    @Dependencies("getParties")
     @XmlElement(name = "positionName")
+    @Dependencies("getParties")
     public InternationalString getPositionName() {
         return getIndividual(true);
     }
@@ -315,7 +328,7 @@
      * {@link #getIndividualName() individualName}, {@link #getOrganisationName() organisationName}
      * and {@code positionName} shall be provided.
      *
-     * <p>This implementation sets the position name of the first {@link Individual} found in the collection of
+     * <p>This implementation sets the position name of the first {@code Individual} found in the collection of
      * {@linkplain #getParties() parties}, or create a new individual if no existing instance was found.</p>
      *
      * @param  newValue  the new position name, or {@code null} if none.
@@ -341,12 +354,12 @@
      */
     @Override
     @Deprecated
-    @Dependencies("getParties")
     @XmlElement(name = "contactInfo")
+    @Dependencies("getParties")
     public Contact getContactInfo() {
-        final Collection<Party> parties = getParties();
+        final Collection<AbstractParty> parties = getParties();
         if (parties != null) {                                          // May be null on marshalling.
-            for (final Party party : parties) {
+            for (final AbstractParty party : parties) {
                 final Collection<? extends Contact> contacts = party.getContactInfo();
                 if (contacts != null) {                                 // May be null on marshalling.
                     for (final Contact contact : contacts) {
@@ -373,16 +386,14 @@
     @Deprecated
     public void setContactInfo(final Contact newValue) {
         checkWritePermission(valueIfDefined(super.getParties()));
-        final Iterator<Party> it = getParties().iterator();
+        final Iterator<AbstractParty> it = getParties().iterator();
         while (it.hasNext()) {
-            final Party party = it.next();
-            if (party instanceof AbstractParty) {
-                ((AbstractParty) party).setContactInfo(newValue != null ? Collections.singleton(newValue) : null);
-                if (((AbstractParty) party).isEmpty()) {
-                    it.remove();
-                }
-                return;
+            final AbstractParty party = it.next();
+            party.setContactInfo(newValue != null ? Collections.singleton(newValue) : null);
+            if (party.isEmpty()) {
+                it.remove();
             }
+            return;
         }
         /*
          * If no existing AbstractParty were found, add a new one. However there is no way to know if
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
index 0cc6a40..4981fb0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
@@ -24,7 +24,6 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.citation.Telephone;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.jaxb.FilterByVersion;
@@ -33,6 +32,16 @@
 import org.apache.sis.internal.jaxb.code.CI_TelephoneTypeCode;
 import org.apache.sis.internal.metadata.Dependencies;
 
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
+// Branch-specific imports
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import org.apache.sis.internal.geoapi.evolution.InterimType;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
+
 
 /**
  * Telephone numbers for contacting the responsible individual or organization.
@@ -95,7 +104,7 @@
     /**
      * Type of telephone number.
      */
-    private TelephoneType numberType;
+    CodeList<?> numberType;
 
     /**
      * Constructs a default telephone.
@@ -111,7 +120,7 @@
      *
      * @since 0.5
      */
-    public DefaultTelephone(final String number, final TelephoneType numberType) {
+    DefaultTelephone(final String number, final CodeList<?> numberType) {
         this.number     = number;
         this.numberType = numberType;
     }
@@ -128,8 +137,13 @@
     public DefaultTelephone(final Telephone object) {
         super(object);
         if (object != null) {
-            number     = object.getNumber();
-            numberType = object.getNumberType();
+            if (object instanceof DefaultTelephone) {
+                number     = ((DefaultTelephone) object).getNumber();
+                numberType = ((DefaultTelephone) object).numberType;
+            } else {
+                setVoices(object.getVoices());
+                setFacsimiles(object.getFacsimiles());
+            }
         }
     }
 
@@ -165,9 +179,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "number", required = true)
     @XmlJavaTypeAdapter(StringAdapter.Since2014.class)
+    @UML(identifier="number", obligation=MANDATORY, specification=ISO_19115)
     public String getNumber() {
         return number;
     }
@@ -186,26 +200,63 @@
 
     /**
      * Returns the type of telephone number, or {@code null} if none.
+     * If non-null, the type can be {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The return type will be changed to the {@code TelephoneType} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
      *
      * @return type of telephone number, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
+    @InterimType(UnsupportedCodeList.class)
     @XmlElement(name = "numberType")
     @XmlJavaTypeAdapter(CI_TelephoneTypeCode.Since2014.class)
-    public TelephoneType getNumberType() {
+    @UML(identifier="numberType", obligation=OPTIONAL, specification=ISO_19115)
+    public CodeList<?> getNumberType() {
         return numberType;
     }
 
     /**
      * Sets the type of telephone number.
+     * If non-null, the type can only be {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code TelephoneType} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
      *
      * @param  newValue  the new type of telephone number.
      *
      * @since 0.5
      */
-    public void setNumberType(final TelephoneType newValue) {
+    public void setNumberType(final CodeList<?> newValue) {
         checkWritePermission(numberType);
         numberType = newValue;
     }
@@ -273,7 +324,7 @@
      * @return telephone numbers by which individuals can speak to the responsible organization or individual.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #getNumber() number}
-     *             with {@link TelephoneType#VOICE}.
+     *             with {@code TelephoneType.VOICE}.
      */
     @Override
     @Deprecated
@@ -281,9 +332,9 @@
     @XmlElement(name = "voice", namespace = LegacyNamespaces.GMD)
     public final Collection<String> getVoices() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return new LegacyTelephones(getOwner(), TelephoneType.VOICE);
+            return new LegacyTelephones(getOwner(), UnsupportedCodeList.VOICE);
         }
-        return null;                    // Marshalling newer ISO 19115-3 document.
+        return null;
     }
 
     /**
@@ -294,7 +345,7 @@
      * @param  newValues  the new telephone numbers, or {@code null} if none.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #setNumber(String) number}
-     *             with {@link TelephoneType#VOICE}.
+     *             code {@code TelephoneType.VOICE}.
      */
     @Deprecated
     public void setVoices(final Collection<? extends String> newValues) {
@@ -309,7 +360,7 @@
      * @return telephone numbers of a facsimile machine for the responsible organization or individual.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #getNumber() number}
-     *             with {@link TelephoneType#FACSIMILE}.
+     *             code {@code TelephoneType.FACSIMILE}.
      */
     @Override
     @Deprecated
@@ -317,7 +368,7 @@
     @XmlElement(name = "facsimile", namespace = LegacyNamespaces.GMD)
     public final Collection<String> getFacsimiles() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            return new LegacyTelephones(getOwner(), TelephoneType.FACSIMILE);
+            return new LegacyTelephones(getOwner(), UnsupportedCodeList.FACSIMILE);
         }
         return null;                    // Marshalling newer ISO 19115-3 document.
     }
@@ -330,7 +381,7 @@
      * @param  newValues  the new telephone number, or {@code null} if none.
      *
      * @deprecated As of ISO 19115:2014, replaced by a {@linkplain #setNumber(String) number}
-     *             with {@link TelephoneType#FACSIMILE}.
+     *             with {@code TelephoneType.FACSIMILE}.
      */
     @Deprecated
     public void setFacsimiles(final Collection<? extends String> newValues) {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
index 592a097..2a07bf7 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/LegacyTelephones.java
@@ -18,8 +18,8 @@
 
 import java.util.Collection;
 import java.util.Iterator;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.citation.Telephone;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 
 
@@ -36,14 +36,14 @@
 final class LegacyTelephones extends LegacyPropertyAdapter<String,Telephone> {
     /**
      * The type of telephone number.
-     * Either {@link TelephoneType#VOICE} or {@link TelephoneType#FACSIMILE}.
+     * Either {@code UnsupportedCodeList.VOICE} or {@code UnsupportedCodeList.FACSIMILE}.
      */
-    private final TelephoneType type;
+    private final CodeList<?> type;
 
     /**
      * Wraps the given telephone list for the given type.
      */
-    LegacyTelephones(final Collection<Telephone> telephones, final TelephoneType type) {
+    LegacyTelephones(final Collection<Telephone> telephones, final CodeList<?> type) {
         super(telephones);
         this.type = type;
     }
@@ -61,8 +61,13 @@
      */
     @Override
     protected String unwrap(final Telephone container) {
-        if (container != null && type.equals(container.getNumberType())) {
-            return container.getNumber();
+        if (container instanceof DefaultTelephone) {
+            final CodeList<?> ct = ((DefaultTelephone) container).numberType;
+            if (ct != null) {
+                if (type.name().equals(ct.name())) {
+                    return ((DefaultTelephone) container).getNumber();
+                }
+            }
         }
         return null;
     }
@@ -73,10 +78,10 @@
     @Override
     protected boolean update(final Telephone container, final String value) {
         if (container instanceof DefaultTelephone) {
-            final TelephoneType ct = container.getNumberType();
-            if (ct == null || ct.equals(type)) {
+            final CodeList<?> ct = ((DefaultTelephone) container).numberType;
+            if (ct == null || type.name().equals(ct.name())) {
                 if (ct == null) {
-                    ((DefaultTelephone) container).setNumberType(type);
+                    ((DefaultTelephone) container).numberType = type;
                 }
                 ((DefaultTelephone) container).setNumber(value);
                 return true;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
index 10fe07a..801203b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
@@ -24,19 +24,23 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.constraint.Releasability;
 import org.opengis.metadata.identification.BrowseGraphic;
 import org.opengis.metadata.constraint.Constraints;
 import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.SecurityConstraints;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.metadata.MD_Releasability;
 import org.apache.sis.internal.jaxb.metadata.MD_Scope;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Restrictions on the access and use of a resource or metadata.
@@ -102,12 +106,12 @@
     /**
      * Information concerning the parties to whom the resource can or cannot be released.
      */
-    private Releasability releasability;
+    private DefaultReleasability releasability;
 
     /**
      * Party responsible for the resource constraints.
      */
-    private Collection<Responsibility> responsibleParties;
+    private Collection<DefaultResponsibility> responsibleParties;
 
     /**
      * Constructs an initially empty constraints.
@@ -137,11 +141,14 @@
         super(object);
         if (object != null) {
             useLimitations             = copyCollection(object.getUseLimitations(), InternationalString.class);
-            constraintApplicationScope = object.getConstraintApplicationScope();
-            graphics                   = copyCollection(object.getGraphics(), BrowseGraphic.class);
-            references                 = copyCollection(object.getReferences(), Citation.class);
-            releasability              = object.getReleasability();
-            responsibleParties         = copyCollection(object.getResponsibleParties(), Responsibility.class);
+            if (object instanceof DefaultConstraints) {
+                final DefaultConstraints c = (DefaultConstraints) object;
+                constraintApplicationScope = c.getConstraintApplicationScope();
+                graphics                   = copyCollection(c.getGraphics(), BrowseGraphic.class);
+                references                 = copyCollection(c.getReferences(), Citation.class);
+                releasability              = c.getReleasability();
+                responsibleParties         = copyCollection(c.getResponsibleParties(), DefaultResponsibility.class);
+            }
         }
     }
 
@@ -212,9 +219,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "constraintApplicationScope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="constraintApplicationScope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getConstraintApplicationScope() {
         return constraintApplicationScope;
     }
@@ -238,8 +245,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="graphic", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<BrowseGraphic> getGraphics() {
         return graphics = nonNullCollection(graphics, BrowseGraphic.class);
     }
@@ -263,8 +270,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="reference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getReferences() {
         return references = nonNullCollection(references, Citation.class);
     }
@@ -283,25 +290,35 @@
     /**
      * Returns information concerning the parties to whom the resource can or cannot be released.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The return type will be changed to the {@code Releasability} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information concerning the parties to whom the resource can or cannot be released, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "releasability")
     @XmlJavaTypeAdapter(MD_Releasability.Since2014.class)
-    public Releasability getReleasability() {
+    @UML(identifier="releasability", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultReleasability getReleasability() {
         return releasability;
     }
 
     /**
      * Sets the information concerning the parties to whom the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code Releasability} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValue  the new information concerning the parties to whom the resource can or cannot be released.
      *
      * @since 0.5
      */
-    public void setReleasability(final Releasability newValue) {
+    public void setReleasability(final DefaultReleasability newValue) {
         checkWritePermission(releasability);
         releasability = newValue;
     }
@@ -309,25 +326,35 @@
     /**
      * Returns the parties responsible for the resource constraints.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return parties responsible for the resource constraints.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<Responsibility> getResponsibleParties() {
-        return responsibleParties = nonNullCollection(responsibleParties, Responsibility.class);
+    @UML(identifier="responsibleParty", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultResponsibility> getResponsibleParties() {
+        return responsibleParties = nonNullCollection(responsibleParties, DefaultResponsibility.class);
     }
 
     /**
      * Sets the parties responsible for the resource constraints.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new parties responsible for the resource constraints.
      *
      * @since 0.5
      */
-    public void setResponsibleParties(final Collection<? extends Responsibility> newValues) {
-        responsibleParties = writeCollection(newValues, responsibleParties, Responsibility.class);
+    public void setResponsibleParties(final Collection<? extends DefaultResponsibility> newValues) {
+        responsibleParties = writeCollection(newValues, responsibleParties, DefaultResponsibility.class);
     }
 
 
@@ -360,7 +387,7 @@
     }
 
     @XmlElement(name = "responsibleParty")
-    private Collection<Responsibility> getResponsibleParty() {
+    private Collection<DefaultResponsibility> getResponsibleParty() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getResponsibleParties() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
index ed7bbdd..b99616e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
@@ -21,15 +21,27 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Responsibility;
-import org.opengis.metadata.constraint.Releasability;
 import org.opengis.metadata.constraint.Restriction;
 import org.apache.sis.metadata.iso.ISOMetadata;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
  * Information about resource release constraints.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code Releasability} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -50,7 +62,8 @@
     "disseminationConstraints"
 })
 @XmlRootElement(name = "MD_Releasability")
-public class DefaultReleasability extends ISOMetadata implements Releasability {
+@UML(identifier="MD_Releasability", specification=ISO_19115)
+public class DefaultReleasability extends ISOMetadata {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -59,7 +72,7 @@
     /**
      * Party to which the release statement applies.
      */
-    private Collection<Responsibility> addressees;
+    private Collection<DefaultResponsibility> addressees;
 
     /**
      * Release statement.
@@ -83,61 +96,44 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(Releasability)
      */
-    public DefaultReleasability(final Releasability object) {
+    public DefaultReleasability(final DefaultReleasability object) {
         super(object);
         if (object != null) {
-            addressees                = copyCollection(object.getAddressees(), Responsibility.class);
+            addressees                = copyCollection(object.getAddressees(), DefaultResponsibility.class);
             statement                 = object.getStatement();
             disseminationConstraints  = copyCollection(object.getDisseminationConstraints(), Restriction.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultReleasability}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultReleasability} instance is created using the
-     *       {@linkplain #DefaultReleasability(Releasability) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultReleasability castOrCopy(final Releasability object) {
-        if (object == null || object instanceof DefaultReleasability) {
-            return (DefaultReleasability) object;
-        }
-        return new DefaultReleasability(object);
-    }
-
-    /**
      * Returns the parties to which the release statement applies.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return parties to which the release statement applies.
      */
-    @Override
     @XmlElement(name = "addressee")
-    public Collection<Responsibility> getAddressees() {
-        return addressees = nonNullCollection(addressees, Responsibility.class);
+    @UML(identifier="addressee", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultResponsibility> getAddressees() {
+        return addressees = nonNullCollection(addressees, DefaultResponsibility.class);
     }
 
     /**
      * Sets the parties to which the release statement applies.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code Responsibility} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new parties.
      */
-    public void getAddressees(final Collection<? extends Responsibility> newValues) {
-        addressees = writeCollection(newValues, addressees, Responsibility.class);
+    public void getAddressees(final Collection<? extends DefaultResponsibility> newValues) {
+        addressees = writeCollection(newValues, addressees, DefaultResponsibility.class);
     }
 
     /**
@@ -145,8 +141,8 @@
      *
      * @return release statement, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "statement")
+    @UML(identifier="statement", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getStatement() {
         return statement;
     }
@@ -166,8 +162,8 @@
      *
      * @return components in determining releasability.
      */
-    @Override
     @XmlElement(name = "disseminationConstraints")
+    @UML(identifier="disseminationConstraints", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Restriction> getDisseminationConstraints() {
         return disseminationConstraints = nonNullCollection(disseminationConstraints, Restriction.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
index 327c54d..665b5dc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultAttributeGroup.java
@@ -20,11 +20,16 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
-import org.opengis.metadata.content.AttributeGroup;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.RangeDimension;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about content type for groups of attributes for a specific
@@ -34,6 +39,14 @@
  * <div class="preformat">{@code MD_AttributeGroup}
  * {@code   └─contentType……} Content type</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code AttributeGroup} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -55,7 +68,8 @@
     "attributes"
 })
 @XmlRootElement(name = "MD_AttributeGroup")
-public class DefaultAttributeGroup extends ISOMetadata implements AttributeGroup {
+@UML(identifier="MD_AttributeGroup", specification=ISO_19115)
+public class DefaultAttributeGroup extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -94,10 +108,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AttributeGroup)
      */
-    public DefaultAttributeGroup(final AttributeGroup object) {
+    public DefaultAttributeGroup(final DefaultAttributeGroup object) {
         super(object);
         if (object != null) {
             contentTypes = copyCollection(object.getContentTypes(), CoverageContentType.class);
@@ -106,37 +118,12 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAttributeGroup}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAttributeGroup} instance is created using the
-     *       {@linkplain #DefaultAttributeGroup(AttributeGroup) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultAttributeGroup castOrCopy(final AttributeGroup object) {
-        if (object == null || object instanceof DefaultAttributeGroup) {
-            return (DefaultAttributeGroup) object;
-        }
-        return new DefaultAttributeGroup(object);
-    }
-
-    /**
      * Returns the types of information represented by the value(s).
      *
      * @return the types of information represented by the value(s).
      */
-    @Override
     @XmlElement(name = "contentType", required = true)
+    @UML(identifier="contentType", obligation=MANDATORY, specification=ISO_19115)
     public Collection<CoverageContentType> getContentTypes() {
         return contentTypes = nonNullCollection(contentTypes, CoverageContentType.class);
     }
@@ -155,8 +142,8 @@
      *
      * @return information on an attribute of the resource.
      */
-    @Override
     @XmlElement(name = "attribute")
+    @UML(identifier="attribute", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<RangeDimension> getAttributes() {
         return attributes = nonNullCollection(attributes, RangeDimension.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
index f81dc80..2b5b82d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
@@ -33,6 +33,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Range of wavelengths in the electromagnetic spectrum.
@@ -145,9 +150,12 @@
     public DefaultBand(final Band object) {
         super(object);
         if (object != null) {
-            boundMin                 = object.getBoundMin();
-            boundMax                 = object.getBoundMax();
-            boundUnits               = object.getBoundUnits();
+            if (object instanceof DefaultBand) {
+                final DefaultBand c = (DefaultBand) object;
+                boundMin   = c.getBoundMin();
+                boundMax   = c.getBoundMax();
+                boundUnits = c.getBoundUnits();
+            }
             peakResponse             = object.getPeakResponse();
             toneGradation            = object.getToneGradation();
             bandBoundaryDefinition   = object.getBandBoundaryDefinition();
@@ -190,10 +198,10 @@
      *
      * @since 0.5
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "boundMin")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="boundMin", obligation=OPTIONAL, specification=ISO_19115)
     public Double getBoundMin() {
         return boundMin;
     }
@@ -222,10 +230,10 @@
      *
      * @since 0.5
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "boundMax")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="boundMax", obligation=OPTIONAL, specification=ISO_19115)
     public Double getBoundMax() {
         return boundMax;
     }
@@ -254,9 +262,9 @@
      *
      * @see org.apache.sis.measure.Units#NANOMETRE
      */
-    @Override
     @XmlElement(name = "boundUnits")
     @XmlJavaTypeAdapter(UnitAdapter.Since2014.class)
+    @UML(identifier="boundUnits", obligation=OPTIONAL, specification=ISO_19115)
     public Unit<Length> getBoundUnits() {
         return boundUnits;
     }
@@ -295,6 +303,39 @@
     }
 
     /**
+     * Returns the units of data as a unit of length.
+     *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * As of ISO 19115:2014, the units of wavelength is rather {@code boundUnits}.
+     * The restriction for units of length in this {@code units} property may be relaxed in GeoAPI 4.0.
+     * </div>
+     *
+     * @return The units of data.
+     */
+    @Override
+    public Unit<Length> getUnits() {
+        final Unit<?> units = super.getUnits();
+        return (units != null) ? units.asType(Length.class) : null;
+    }
+
+    /**
+     * Sets the units of data as a unit of length.
+     *
+     * <div class="warning"><b>Upcoming precondition change — relaxation</b><br>
+     * The current implementation requires the unit to be an instance of {@code Unit<Length>},
+     * otherwise a {@link ClassCastException} is thrown. This is because the value returned by
+     * {@link #getUnits()} was restricted by ISO 19115:2003 to units of length.
+     * However this restriction may be relaxed in GeoAPI 4.0.
+     * </div>
+     *
+     * @param newValue The new units of data as an instance of {@code Unit<Length>}.
+     */
+    @Override
+    public void setUnits(final Unit<?> newValue) {
+        super.setUnits(newValue.asType(Length.class));
+    }
+
+    /**
      * Returns the wavelength at which the response is the highest.
      * The units of measurement is given by {@link #getBoundUnits()}.
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
index b11a593..bfa39d9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
@@ -24,7 +24,6 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.Identifier;
-import org.opengis.metadata.content.AttributeGroup;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.CoverageDescription;
 import org.opengis.metadata.content.ImageDescription;
@@ -38,6 +37,10 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -97,7 +100,7 @@
     /**
      * Information on attribute groups of the resource.
      */
-    private Collection<AttributeGroup> attributeGroups;
+    private Collection<DefaultAttributeGroup> attributeGroups;
 
     /**
      * Provides the description of the specific range elements of a coverage.
@@ -123,9 +126,11 @@
         super(object);
         if (object != null) {
             attributeDescription     = object.getAttributeDescription();
-            processingLevelCode      = object.getProcessingLevelCode();
-            attributeGroups          = copyCollection(object.getAttributeGroups(), AttributeGroup.class);
             rangeElementDescriptions = copyCollection(object.getRangeElementDescriptions(), RangeElementDescription.class);
+            if (object instanceof DefaultCoverageDescription) {
+                processingLevelCode  = ((DefaultCoverageDescription) object).getProcessingLevelCode();
+                attributeGroups      = copyCollection(((DefaultCoverageDescription) object).getAttributeGroups(), DefaultAttributeGroup.class);
+            }
         }
     }
 
@@ -190,9 +195,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "processingLevelCode")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="processingLevelCode", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getProcessingLevelCode() {
         return processingLevelCode;
     }
@@ -212,25 +217,35 @@
     /**
      * Returns information on attribute groups of the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AttributeGroup} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information on attribute groups of the resource.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<AttributeGroup> getAttributeGroups() {
-        return attributeGroups = nonNullCollection(attributeGroups, AttributeGroup.class);
+    @UML(identifier="attributeGroup", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultAttributeGroup> getAttributeGroups() {
+        return attributeGroups = nonNullCollection(attributeGroups, DefaultAttributeGroup.class);
     }
 
     /**
      * Sets information on attribute groups of the resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AttributeGroup} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information on attribute groups of the resource.
      *
      * @since 0.5
      */
-    public void setAttributeGroups(final Collection<? extends AttributeGroup> newValues) {
-        attributeGroups = writeCollection(newValues, attributeGroups, AttributeGroup.class);
+    public void setAttributeGroups(final Collection<? extends DefaultAttributeGroup> newValues) {
+        attributeGroups = writeCollection(newValues, attributeGroups, DefaultAttributeGroup.class);
     }
 
     /**
@@ -248,9 +263,9 @@
     public CoverageContentType getContentType() {
         CoverageContentType type = null;
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            final Collection<AttributeGroup> groups = getAttributeGroups();
+            final Collection<DefaultAttributeGroup> groups = getAttributeGroups();
             if (groups != null) {                                               // May be null on marshalling.
-                for (final AttributeGroup g : groups) {
+                for (final DefaultAttributeGroup g : groups) {
                     final Collection<? extends CoverageContentType> contentTypes = g.getContentTypes();
                     if (contentTypes != null) {                                 // May be null on marshalling.
                         for (final CoverageContentType t : contentTypes) {
@@ -281,13 +296,11 @@
     public void setContentType(final CoverageContentType newValue) {
         checkWritePermission(valueIfDefined(attributeGroups));
         final Collection<CoverageContentType> newValues = CollectionsExt.singletonOrEmpty(newValue);
-        Collection<AttributeGroup> groups = attributeGroups;
+        Collection<DefaultAttributeGroup> groups = attributeGroups;
         if (groups != null) {
-            for (final AttributeGroup group : groups) {
-                if (group instanceof DefaultAttributeGroup) {
-                    ((DefaultAttributeGroup) group).setContentTypes(newValues);
-                    return;
-                }
+            for (final DefaultAttributeGroup group : groups) {
+                group.setContentTypes(newValues);
+                return; // Actually stop at the first instance.
             }
         }
         final DefaultAttributeGroup group = new DefaultAttributeGroup();
@@ -295,7 +308,7 @@
         if (groups != null) {
             groups.add(group);
         } else {
-            groups = Collections.singleton(group);
+            groups = Collections.<DefaultAttributeGroup>singleton(group);
         }
         setAttributeGroups(groups);
     }
@@ -314,24 +327,24 @@
     @XmlElement(name = "dimension", namespace = LegacyNamespaces.GMD)
     public final Collection<RangeDimension> getDimensions() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<RangeDimension,AttributeGroup>(getAttributeGroups()) {
+        return new LegacyPropertyAdapter<RangeDimension,DefaultAttributeGroup>(getAttributeGroups()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected AttributeGroup wrap(final RangeDimension value) {
+            @Override protected DefaultAttributeGroup wrap(final RangeDimension value) {
                 final DefaultAttributeGroup container = new DefaultAttributeGroup();
                 container.setAttributes(CollectionsExt.singletonOrEmpty(value));
                 return container;
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected RangeDimension unwrap(final AttributeGroup container) {
+            @Override protected RangeDimension unwrap(final DefaultAttributeGroup container) {
                 return getSingleton(container.getAttributes(), RangeDimension.class,
                         this, DefaultCoverageDescription.class, "getDimensions");
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final AttributeGroup container, final RangeDimension value) {
+            @Override protected boolean update(final DefaultAttributeGroup container, final RangeDimension value) {
                 if (container instanceof DefaultAttributeGroup) {
-                    ((DefaultAttributeGroup) container).setAttributes(CollectionsExt.singletonOrEmpty(value));
+                    container.setAttributes(CollectionsExt.singletonOrEmpty(value));
                     return true;
                 }
                 return false;
@@ -393,7 +406,7 @@
      * If (and only if) marshalling an older standard version, we omit this attribute.
      */
     @XmlElement(name = "attributeGroup")
-    private Collection<AttributeGroup> getAttributeGroup() {
+    private Collection<DefaultAttributeGroup> getAttributeGroup() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAttributeGroups() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index 79e2382..709ecbb 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@ -26,7 +26,6 @@
 import org.opengis.util.GenericName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.content.FeatureCatalogueDescription;
-import org.opengis.metadata.content.FeatureTypeInfo;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.jaxb.lan.PT_Locale;
@@ -34,6 +33,11 @@
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
 import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -104,7 +108,7 @@
     /**
      * Subset of feature types from cited feature catalogue occurring in resource.
      */
-    private Collection<FeatureTypeInfo> featureTypes;
+    private Collection<DefaultFeatureTypeInfo> featureTypes;
 
     /**
      * Complete bibliographic reference to one or more external feature catalogues.
@@ -131,9 +135,14 @@
         if (object != null) {
             compliant                 = object.isCompliant();
             includedWithDataset       = object.isIncludedWithDataset();
-            locales                   = copyMap(object.getLocalesAndCharsets(), Locale.class);
-            featureTypes              = copyCollection(object.getFeatureTypeInfo(), FeatureTypeInfo.class);
             featureCatalogueCitations = copyCollection(object.getFeatureCatalogueCitations(), Citation.class);
+            if (object instanceof DefaultFeatureCatalogueDescription) {
+                locales = copyMap(((DefaultFeatureCatalogueDescription) object).getLocalesAndCharsets(), Locale.class);
+                featureTypes = copyCollection(((DefaultFeatureCatalogueDescription) object).getFeatureTypeInfo(), DefaultFeatureTypeInfo.class);
+            } else {
+                setLanguages(copyCollection(object.getLanguages(), Locale.class));
+                setFeatureTypes(object.getFeatureTypes());
+            }
         }
     }
 
@@ -190,7 +199,7 @@
      *
      * @since 1.0
      */
-    @Override
+    @UML(identifier="locale", obligation=CONDITIONAL, specification=ISO_19115)
     // @XmlElement at the end of this class.
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
@@ -259,25 +268,35 @@
     /**
      * Returns the subset of feature types from cited feature catalogue occurring in resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code FeatureTypeInfo} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return subset of feature types occurring in resource.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<FeatureTypeInfo> getFeatureTypeInfo() {
-        return featureTypes = nonNullCollection(featureTypes, FeatureTypeInfo.class);
+    @UML(identifier="featureTypes", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultFeatureTypeInfo> getFeatureTypeInfo() {
+        return featureTypes = nonNullCollection(featureTypes, DefaultFeatureTypeInfo.class);
     }
 
     /**
      * Sets the subset of feature types from cited feature catalogue occurring in resource.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code FeatureTypeInfo} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new feature types.
      *
      * @since 0.5
      */
-    public void setFeatureTypeInfo(final Collection<? extends FeatureTypeInfo> newValues) {
-        featureTypes = writeCollection(newValues, featureTypes, FeatureTypeInfo.class);
+    public void setFeatureTypeInfo(final Collection<? extends DefaultFeatureTypeInfo> newValues) {
+        featureTypes = writeCollection(newValues, featureTypes, DefaultFeatureTypeInfo.class);
     }
 
     /**
@@ -293,21 +312,21 @@
     @XmlElement(name = "featureTypes", namespace = LegacyNamespaces.GMD)
     public final Collection<GenericName> getFeatureTypes() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<GenericName,FeatureTypeInfo>(getFeatureTypeInfo()) {
+        return new LegacyPropertyAdapter<GenericName,DefaultFeatureTypeInfo>(getFeatureTypeInfo()) {
             /** Stores a legacy value into the new kind of value. */
-            @Override protected FeatureTypeInfo wrap(final GenericName value) {
+            @Override protected DefaultFeatureTypeInfo wrap(final GenericName value) {
                 return new DefaultFeatureTypeInfo(value);
             }
 
             /** Extracts the legacy value from the new kind of value. */
-            @Override protected GenericName unwrap(final FeatureTypeInfo container) {
+            @Override protected GenericName unwrap(final DefaultFeatureTypeInfo container) {
                 return container.getFeatureTypeName();
             }
 
             /** Updates the legacy value in an existing instance of the new kind of value. */
-            @Override protected boolean update(final FeatureTypeInfo container, final GenericName value) {
+            @Override protected boolean update(final DefaultFeatureTypeInfo container, final GenericName value) {
                 if (container instanceof DefaultFeatureTypeInfo) {
-                    ((DefaultFeatureTypeInfo) container).setFeatureTypeName(value);
+                    container.setFeatureTypeName(value);
                     return true;
                 }
                 return false;
@@ -368,7 +387,7 @@
      * If (and only if) marshalling an older standard version, we omit this attribute.
      */
     @XmlElement(name = "featureTypes")
-    private Collection<FeatureTypeInfo> getFeatureTypesInfo() {
+    private Collection<DefaultFeatureTypeInfo> getFeatureTypesInfo() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getFeatureTypeInfo() : null;
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
index 288f1bb..c32c2b7 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
@@ -20,17 +20,30 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.content.FeatureTypeInfo;
 import org.apache.sis.measure.ValueRange;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the occurring feature type.
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code FeatureTypeInfo} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -56,7 +69,8 @@
     "featureInstanceCount"
 })
 @XmlRootElement(name = "MD_FeatureTypeInfo")
-public class DefaultFeatureTypeInfo extends ISOMetadata implements FeatureTypeInfo {
+@UML(identifier="MD_FeatureTypeInfo", specification=ISO_19115)
+public class DefaultFeatureTypeInfo extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -100,10 +114,8 @@
      * </div>
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(FeatureTypeInfo)
      */
-    public DefaultFeatureTypeInfo(final FeatureTypeInfo object) {
+    public DefaultFeatureTypeInfo(final DefaultFeatureTypeInfo object) {
         super(object);
         if (object != null) {
             featureTypeName      = object.getFeatureTypeName();
@@ -112,39 +124,14 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultFeatureTypeInfo}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultFeatureTypeInfo} instance is created using the
-     *       {@linkplain #DefaultFeatureTypeInfo(FeatureTypeInfo) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultFeatureTypeInfo castOrCopy(final FeatureTypeInfo object) {
-        if (object == null || object instanceof DefaultFeatureTypeInfo) {
-            return (DefaultFeatureTypeInfo) object;
-        }
-        return new DefaultFeatureTypeInfo(object);
-    }
-
-    /**
      * Returns the name of the feature type.
      *
      * @return name of the feature type.
      *
      * @see org.apache.sis.feature.DefaultFeatureType#getName()
      */
-    @Override
     @XmlElement(name = "featureTypeName", required = true)
+    @UML(identifier="featureTypeName", obligation=MANDATORY, specification=ISO_19115)
     public GenericName getFeatureTypeName() {
         return featureTypeName;
     }
@@ -164,9 +151,9 @@
      *
      * @return the number of occurrence of feature instances for this feature types, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 1)
     @XmlElement(name = "featureInstanceCount")
+    @UML(identifier="featureInstanceCount", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getFeatureInstanceCount() {
         return featureInstanceCount;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
index 8a7c913..73e43bb 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultRangeDimension.java
@@ -26,7 +26,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.content.RangeDimension;
-import org.opengis.metadata.content.SampleDimension;
+import org.opengis.metadata.content.Band;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.internal.metadata.Dependencies;
@@ -34,6 +34,11 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information on the range of each dimension of a cell measurement value.
@@ -106,8 +111,10 @@
         super(object);
         if (object != null) {
             sequenceIdentifier = object.getSequenceIdentifier();
-            description        = object.getDescription();
-            names              = copyCollection(object.getNames(), Identifier.class);
+            description        = object.getDescriptor();
+            if (object instanceof DefaultRangeDimension) {
+                names = copyCollection(((DefaultRangeDimension) object).getNames(), Identifier.class);
+            }
         }
     }
 
@@ -117,7 +124,7 @@
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is an instance of {@link SampleDimension}, then this method
+     *   <li>Otherwise if the given object is an instance of {@code SampleDimension}, then this method
      *       delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.</li>
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultRangeDimension}, then it is returned unchanged.</li>
@@ -132,8 +139,8 @@
      *         given object itself), or {@code null} if the argument was null.
      */
     public static DefaultRangeDimension castOrCopy(final RangeDimension object) {
-        if (object instanceof SampleDimension) {
-            return DefaultSampleDimension.castOrCopy((SampleDimension) object);
+        if (object instanceof Band) {
+            return DefaultBand.castOrCopy((Band) object);
         }
         // Intentionally tested after the sub-interfaces.
         if (object == null || object instanceof DefaultRangeDimension) {
@@ -170,9 +177,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
@@ -226,8 +233,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="name", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Identifier> getNames() {
         return names = nonNullCollection(names, Identifier.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
index 9ee9902..ae63bdf 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
@@ -23,7 +23,6 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.content.Band;
-import org.opengis.metadata.content.SampleDimension;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.TransferFunctionType;
 import org.opengis.util.Record;
@@ -36,6 +35,12 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * The characteristic of each dimension (layer) included in the resource.
@@ -45,6 +50,14 @@
  * <div class="preformat">{@code MD_SampleDimension}
  * {@code   └─units………………………} Units of data in each dimension included in the resource.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code SampleDimension} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -78,7 +91,8 @@
 })
 @XmlRootElement(name = "MD_SampleDimension")
 @XmlSeeAlso({DefaultBand.class, DefaultRangeDimension.class})
-public class DefaultSampleDimension extends DefaultRangeDimension implements SampleDimension {
+@UML(identifier="MD_SampleDimension", specification=ISO_19115)
+public class DefaultSampleDimension extends DefaultRangeDimension {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -171,57 +185,50 @@
      * </div>
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(SampleDimension)
      */
-    public DefaultSampleDimension(final SampleDimension object) {
+    public DefaultSampleDimension(final DefaultSampleDimension object) {
         super(object);
         if (object != null) {
-            minValue                 = object.getMinValue();
-            maxValue                 = object.getMaxValue();
-            meanValue                = object.getMeanValue();
-            numberOfValues           = object.getNumberOfValues();
-            standardDeviation        = object.getStandardDeviation();
-            units                    = object.getUnits();
-            scaleFactor              = object.getScaleFactor();
-            offset                   = object.getOffset();
-            transferFunctionType     = object.getTransferFunctionType();
-            bitsPerValue             = object.getBitsPerValue();
-            nominalSpatialResolution = object.getNominalSpatialResolution();
-            otherPropertyType        = object.getOtherPropertyType();
-            otherProperty            = object.getOtherProperty();
+            init(object);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is is an instance of {@link Band}, then this
-     *       method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultSampleDimension}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultSampleDimension} instance is created using the
-     *       {@linkplain #DefaultSampleDimension(SampleDimension) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
+     * Initializes this sample dimension to the values of the given object.
      */
-    public static DefaultSampleDimension castOrCopy(final SampleDimension object) {
-        if (object instanceof Band) {
-            return DefaultBand.castOrCopy((Band) object);
+    private void init(final DefaultSampleDimension object) {
+        minValue                 = object.getMinValue();
+        maxValue                 = object.getMaxValue();
+        meanValue                = object.getMeanValue();
+        numberOfValues           = object.getNumberOfValues();
+        standardDeviation        = object.getStandardDeviation();
+        units                    = object.getUnits();
+        scaleFactor              = object.getScaleFactor();
+        offset                   = object.getOffset();
+        transferFunctionType     = object.getTransferFunctionType();
+        bitsPerValue             = object.getBitsPerValue();
+        nominalSpatialResolution = object.getNominalSpatialResolution();
+        otherPropertyType        = object.getOtherPropertyType();
+        otherProperty            = object.getOtherProperty();
+    }
+
+    /**
+     * Bridge constructor for {@link DefaultBand#DefaultBand(Band)}.
+     */
+    DefaultSampleDimension(final Band object) {
+        super(object);
+        if (object != null) {
+            if (object instanceof DefaultSampleDimension) {
+                init((DefaultSampleDimension) object);
+            } else {
+                maxValue     = object.getMaxValue();
+                minValue     = object.getMinValue();
+                units        = object.getUnits();
+                scaleFactor  = object.getScaleFactor();
+                offset       = object.getOffset();
+                bitsPerValue = object.getBitsPerValue();
+            }
         }
-        //-- Intentionally tested after the sub-interfaces.
-        if (object == null || object instanceof DefaultSampleDimension) {
-            return (DefaultSampleDimension) object;
-        }
-        return new DefaultSampleDimension(object);
     }
 
     /**
@@ -229,10 +236,10 @@
      *
      * @return the number of values used in a thematic classification resource, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 0)
     @XmlElement(name = "numberOfValues")
     @XmlJavaTypeAdapter(GO_Integer.Since2014.class)
+    @UML(identifier="numberOfValues", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getNumberOfValues() {
         return numberOfValues;
     }
@@ -255,8 +262,8 @@
      *
      * @return minimum value of data values in each dimension included in the resource, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "minValue")
+    @UML(identifier="minValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMinValue() {
         return minValue;
     }
@@ -276,8 +283,8 @@
      *
      * @return maximum value of data values in each dimension included in the resource, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "maxValue")
+    @UML(identifier="maxValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMaxValue() {
         return maxValue;
     }
@@ -297,9 +304,9 @@
      *
      * @return the mean value of data values in each dimension included in the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "meanValue")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="meanValue", obligation=OPTIONAL, specification=ISO_19115)
     public Double getMeanValue() {
         return meanValue;
     }
@@ -319,9 +326,9 @@
      *
      * @return standard deviation of data values in each dimension included in the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "standardDeviation")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="standardDeviation", obligation=OPTIONAL, specification=ISO_19115)
     public Double getStandardDeviation() {
         return standardDeviation;
     }
@@ -341,8 +348,8 @@
      *
      * @return the units of data in the dimension, or {@code null} if unspecified.
      */
-    @Override
     @XmlElement(name = "units")
+    @UML(identifier="units", obligation=CONDITIONAL, specification=ISO_19115)
     public Unit<?> getUnits() {
         return units;
     }
@@ -362,8 +369,8 @@
      *
      * @return scale factor which has been applied to the cell value, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "scaleFactor")
+    @UML(identifier="scaleFactor", obligation=OPTIONAL, specification=ISO_19115)
     public Double getScaleFactor() {
         return scaleFactor;
     }
@@ -383,8 +390,8 @@
      *
      * @return the physical value corresponding to a cell value of zero, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "offset")
+    @UML(identifier="offset", obligation=OPTIONAL, specification=ISO_19115)
     public Double getOffset() {
         return offset;
     }
@@ -410,7 +417,6 @@
      *
      * @return type of transfer function, or {@code null}.
      */
-    @Override
     public TransferFunctionType getTransferFunctionType() {
         return transferFunctionType;
     }
@@ -432,9 +438,9 @@
      * @return maximum number of significant bits in the uncompressed representation
      *         for the value in each band of each pixel, or {@code null} if none.
      */
-    @Override
     @ValueRange(minimum = 1)
     @XmlElement(name = "bitsPerValue")
+    @UML(identifier="bitsPerValue", obligation=OPTIONAL, specification=ISO_19115)
     public Integer getBitsPerValue() {
         return bitsPerValue;
     }
@@ -465,7 +471,6 @@
      *
      * @return smallest distance between which separate points can be distinguished, or {@code null}.
      */
-    @Override
     @ValueRange(minimum = 0, isMinIncluded = false)
     public Double getNominalSpatialResolution() {
         return nominalSpatialResolution;
@@ -490,9 +495,9 @@
      *
      * @return type of other attribute description, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "otherPropertyType")
     @XmlJavaTypeAdapter(GO_RecordType.Since2014.class)
+    @UML(identifier="otherPropertyType", obligation=OPTIONAL, specification=ISO_19115)
     public RecordType getOtherPropertyType() {
         return otherPropertyType;
     }
@@ -513,9 +518,9 @@
      *
      * @return instance of other/attributeType that defines attributes, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "otherProperty")
     @XmlJavaTypeAdapter(GO_Record.Since2014.class)
+    @UML(identifier="otherProperty", obligation=OPTIONAL, specification=ISO_19115)
     public Record getOtherProperty() {
         return otherProperty;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
index b8672f5..463299d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDataFile.java
@@ -127,11 +127,14 @@
     public DefaultDataFile(final DataFile object) {
         super(object);
         if (object != null) {
-            fileName        = object.getFileName();
-            fileDescription = object.getFileDescription();
-            fileType        = object.getFileType();
-            featureTypes    = copyCollection(object.getFeatureTypes(), LocalName.class);
-            fileFormat      = object.getFileFormat();
+            if (object instanceof DefaultDataFile) {
+                DefaultDataFile df = (DefaultDataFile) object;
+                fileName        = df.getFileName();
+                fileDescription = df.getFileDescription();
+                fileType        = df.getFileType();
+            }
+            featureTypes = copyCollection(object.getFeatureTypes(), LocalName.class);
+            fileFormat   = object.getFileFormat();
         }
     }
 
@@ -168,7 +171,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileName()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileName", required = true)
     public URI getFileName() {
         return fileName;
@@ -194,7 +196,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileDescription()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileDescription", required = true)
     public InternationalString getFileDescription() {
         return fileDescription;
@@ -220,7 +221,6 @@
      * @see org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic#getFileType()
      * @since 1.0
      */
-    @Override
     @XmlElement(name = "fileType", required = true)
     @XmlJavaTypeAdapter(MimeFileTypeAdapter.class)
     public String getFileType() {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
index a0dc36a..b0e82cc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
@@ -37,6 +37,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Technical means and media by which a resource is obtained from the distributor.
@@ -132,9 +137,14 @@
             unitsOfDistribution = object.getUnitsOfDistribution();
             transferSize        = object.getTransferSize();
             onLines             = copyCollection(object.getOnLines(), OnlineResource.class);
-            offLines            = copyCollection(object.getOffLines(), Medium.class);
-            transferFrequency   = object.getTransferFrequency();
-            distributionFormats = copyCollection(object.getDistributionFormats(), Format.class);
+            if (object instanceof DefaultDigitalTransferOptions) {
+                final DefaultDigitalTransferOptions c = (DefaultDigitalTransferOptions) object;
+                offLines            = copyCollection(c.getOffLines(), Medium.class);
+                transferFrequency   = c.getTransferFrequency();
+                distributionFormats = copyCollection(c.getDistributionFormats(), Format.class);
+            } else {
+                offLines = singleton(object.getOffLine(), Medium.class);
+            }
         }
     }
 
@@ -238,8 +248,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "offLine")
+    @UML(identifier="offLine", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Medium> getOffLines() {
         Collection<Medium> c = offLines = nonNullCollection(offLines, Medium.class);
         if (c != null && c.size() > 1 && FilterByVersion.LEGACY_METADATA.accept()) {
@@ -293,9 +303,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "transferFrequency")
     @XmlJavaTypeAdapter(TM_PeriodDuration.Since2014.class)
+    @UML(identifier="transferFrequency", obligation=OPTIONAL, specification=ISO_19115)
     public PeriodDuration getTransferFrequency() {
         return transferFrequency;
     }
@@ -319,8 +329,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="distributionFormat", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Format> getDistributionFormats() {
         return distributionFormats = nonNullCollection(distributionFormats, Format.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
index 70af366..e016136 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDistribution.java
@@ -30,6 +30,11 @@
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.internal.jaxb.gco.InternationalStringAdapter;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the distributor of and options for obtaining the resource.
@@ -112,10 +117,12 @@
     public DefaultDistribution(final Distribution object) {
         super(object);
         if (object != null) {
-            description         = object.getDescription();
             distributionFormats = copyCollection(object.getDistributionFormats(), Format.class);
             distributors        = copyCollection(object.getDistributors(), Distributor.class);
             transferOptions     = copyCollection(object.getTransferOptions(), DigitalTransferOptions.class);
+            if (object instanceof DefaultDistribution) {
+                description = ((DefaultDistribution) object).getDescription();
+            }
         }
     }
 
@@ -151,9 +158,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "description")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
index be5a51a..df56d7c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultFormat.java
@@ -37,6 +37,12 @@
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Description of the computer language construct that specifies the representation
@@ -150,11 +156,17 @@
     public DefaultFormat(final Format object) {
         super(object);
         if (object != null) {
-            formatSpecificationCitation = object.getFormatSpecificationCitation();
             amendmentNumber             = object.getAmendmentNumber();
             fileDecompressionTechnique  = object.getFileDecompressionTechnique();
-            media                       = copyCollection(object.getMedia(), Medium.class);
             formatDistributors          = copyCollection(object.getFormatDistributors(), Distributor.class);
+            if (object instanceof DefaultFormat) {
+                formatSpecificationCitation = ((DefaultFormat) object).getFormatSpecificationCitation();
+                media = copyCollection(((DefaultFormat) object).getMedia(), Medium.class);
+            } else {
+                setSpecification(object.getSpecification());
+                setVersion(object.getVersion());
+                setName(object.getName());
+            }
         }
     }
 
@@ -190,9 +202,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "formatSpecificationCitation", required = true)
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="formatSpecificationCitation", obligation=MANDATORY, specification=ISO_19115)
     public Citation getFormatSpecificationCitation() {
         return formatSpecificationCitation;
     }
@@ -397,9 +409,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "medium")
     @XmlJavaTypeAdapter(MD_Medium.Since2014.class)
+    @UML(identifier="medium", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Medium> getMedia() {
         return media = nonNullCollection(media, Medium.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
index a3b3317..d4857ae 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
@@ -16,9 +16,7 @@
  */
 package org.apache.sis.metadata.iso.distribution;
 
-import java.util.AbstractSet;
 import java.util.Collection;
-import java.util.Iterator;
 import javax.measure.Unit;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
@@ -44,6 +42,12 @@
 import org.apache.sis.internal.util.CodeLists;
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 
 
 /**
@@ -87,7 +91,7 @@
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 2657393801067168091L;
+    private static final long serialVersionUID = 7751002701087451894L;
 
     /**
      * Name of the medium on which the resource can be received.
@@ -98,7 +102,7 @@
      * Density at which the data is recorded.
      * If non-null, then the number shall be greater than zero.
      */
-    private Double density;
+    private Collection<Double> densities;
 
     /**
      * Units of measure for the recording density.
@@ -146,12 +150,14 @@
         super(object);
         if (object != null) {
             name          = object.getName();
-            density       = object.getDensity();
+            densities     = copyCollection(object.getDensities(), Double.class);
             densityUnits  = object.getDensityUnits();
             volumes       = object.getVolumes();
             mediumFormats = copyCollection(object.getMediumFormats(), MediumFormat.class);
             mediumNote    = object.getMediumNote();
-            identifiers   = singleton(object.getIdentifier(), Identifier.class);
+            if (object instanceof DefaultMedium) {
+                identifiers = singleton(((DefaultMedium) object).getIdentifier(), Identifier.class);
+            }
         }
     }
 
@@ -220,12 +226,12 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "density")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
     @ValueRange(minimum = 0, isMinIncluded = false)
+    @UML(identifier="density", obligation=OPTIONAL, specification=ISO_19115)
     public Double getDensity() {
-        return density;
+        return LegacyPropertyAdapter.getSingleton(densities, Double.class, null, DefaultMedium.class, "getDensity");
     }
 
     /**
@@ -238,9 +244,9 @@
      * @since 0.5
      */
     public void setDensity(final Double newValue) {
-        checkWritePermission(density);
+        checkWritePermission(valueIfDefined(densities));
         if (ensurePositive(DefaultMedium.class, "density", true, newValue)) {
-            density = newValue;
+            densities = writeCollection(CollectionsExt.singletonOrEmpty(newValue), densities, Double.class);
         }
     }
 
@@ -255,28 +261,7 @@
     @XmlElement(name = "density", namespace = LegacyNamespaces.GMD)
     public Collection<Double> getDensities() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new AbstractSet<Double>() {
-            /** Returns 0 if empty, or 1 if a density has been specified. */
-            @Override public int size() {
-                return getDensity() != null ? 1 : 0;
-            }
-
-            /** Returns an iterator over 0 or 1 element. Current iterator implementation is unmodifiable. */
-            @Override public Iterator<Double> iterator() {
-                return CollectionsExt.singletonOrEmpty(getDensity()).iterator();
-            }
-
-            /** Adds an element only if the set is empty. This method is invoked by JAXB at unmarshalling time. */
-            @Override public boolean add(final Double newValue) {
-                if (isEmpty()) {
-                    setDensity(newValue);
-                    return true;
-                } else {
-                    LegacyPropertyAdapter.warnIgnoredExtraneous(Double.class, DefaultMedium.class, "setDensities");
-                    return false;
-                }
-            }
-        };
+        return densities = nonNullCollection(densities, Double.class);
     }
 
     /**
@@ -286,7 +271,7 @@
      */
     @Deprecated
     public void setDensities(final Collection<? extends Double> newValues) {
-        setDensity(LegacyPropertyAdapter.getSingleton(newValues, Double.class, null, DefaultMedium.class, "setDensities"));
+        densities = writeCollection(newValues, densities, Double.class);
     }
 
     /**
@@ -383,9 +368,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "identifier")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="identifier", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getIdentifier() {
         return super.getIdentifier();
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
index bbb05bb..963642a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
@@ -30,6 +30,10 @@
 import org.apache.sis.internal.jaxb.gco.GO_Record;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
 
@@ -129,8 +133,10 @@
             plannedAvailableDateTime = toMilliseconds(object.getPlannedAvailableDateTime());
             orderingInstructions     = object.getOrderingInstructions();
             turnaround               = object.getTurnaround();
-            orderOptionsType         = object.getOrderOptionsType();
-            orderOptions             = object.getOrderOptions();
+            if (object instanceof DefaultStandardOrderProcess) {
+                orderOptionsType = ((DefaultStandardOrderProcess) object).getOrderOptionsType();
+                orderOptions     = ((DefaultStandardOrderProcess) object).getOrderOptions();
+            }
         }
     }
 
@@ -202,7 +208,6 @@
      *
      * @see #getFees()
      */
-    @Override
     public Currency getCurrency() {
         return currency;
     }
@@ -295,9 +300,9 @@
      *
      * @see org.apache.sis.util.iso.DefaultRecord#getRecordType()
      */
-    @Override
     @XmlElement(name = "orderOptionsType")
     @XmlJavaTypeAdapter(GO_RecordType.Since2014.class)
+    @UML(identifier="orderOptionsType", obligation=OPTIONAL, specification=ISO_19115)
     public RecordType getOrderOptionsType() {
         return orderOptionsType;
     }
@@ -309,7 +314,7 @@
      *
      * @since 0.5
      */
-    @Deprecated
+//  @Deprecated - omitted for allowing APIVerifier to pass.
     public RecordType getOrderOptionType() {
         return getOrderOptionsType();
     }
@@ -349,9 +354,9 @@
      *       when he ordered the resource. We presume that this is not a record to be filled by the user for new
      *       orders, otherwise this method would need to be a factory rather than a getter.
      */
-    @Override
     @XmlElement(name = "orderOptions")
     @XmlJavaTypeAdapter(GO_Record.Since2014.class)
+    @UML(identifier="orderOptions", obligation=OPTIONAL, specification=ISO_19115)
     public Record getOrderOptions() {
         return orderOptions;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
index cde0054..48d2a71 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
@@ -29,6 +29,10 @@
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.metadata.ReferencingServices;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -113,7 +117,9 @@
         super(object);
         if (object != null) {
             spatialExtent  = copyCollection(object.getSpatialExtent(), GeographicExtent.class);
-            verticalExtent = object.getVerticalExtent();
+            if (object instanceof DefaultSpatialTemporalExtent) {
+                verticalExtent = ((DefaultSpatialTemporalExtent) object).getVerticalExtent();
+            }
         }
     }
 
@@ -169,8 +175,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "verticalExtent")
+    @UML(identifier="verticalExtent", obligation=OPTIONAL, specification=ISO_19115)
     public VerticalExtent getVerticalExtent() {
         return verticalExtent;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
index d169d0a..826fd82 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
@@ -22,8 +22,8 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.geometry.Envelope;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.Instant;
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.Instant;
 import org.opengis.temporal.TemporalPrimitive;
 import org.opengis.metadata.extent.TemporalExtent;
 import org.opengis.metadata.extent.SpatialTemporalExtent;
@@ -202,7 +202,7 @@
      * primitive for the given dates, then invokes {@link #setExtent(TemporalPrimitive)}.
      *
      * <p><b>Note:</b> this method is available only if the {@code sis-temporal} module is available on the classpath,
-     * or any other module providing an implementation of the {@link org.opengis.temporal.TemporalFactory} interface.</p>
+     * or any other module providing an implementation of the {@code TemporalFactory} interface.</p>
      *
      * @param  startTime  the start date and time for the content of the dataset, or {@code null} if none.
      * @param  endTime    the end date and time for the content of the dataset, or {@code null} if none.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
index a2fe0f3..c915b5e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultVerticalExtent.java
@@ -25,7 +25,6 @@
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.VerticalExtent;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.jaxb.gco.GO_Real;
@@ -272,14 +271,14 @@
      * bounds, then the corresponding bounds of the intersection result will also be NaN.</p>
      *
      * @param  other  the vertical extent to intersect with this extent.
-     * @throws MismatchedReferenceSystemException if the two extents do not use the same datum, ignoring metadata.
+     * @throws IllegalArgumentException if the two extents do not use the same datum, ignoring metadata.
      *
      * @see Extents#intersection(VerticalExtent, VerticalExtent)
      * @see org.apache.sis.geometry.GeneralEnvelope#intersect(Envelope)
      *
      * @since 0.8
      */
-    public void intersect(final VerticalExtent other) throws MismatchedReferenceSystemException {
+    public void intersect(final VerticalExtent other) throws IllegalArgumentException {
         checkWritePermission(value());
         ArgumentChecks.ensureNonNull("other", other);
         Double min = other.getMinimumValue();
@@ -307,7 +306,7 @@
                 }
             }
         } catch (UnsupportedOperationException | FactoryException | ClassCastException | TransformException e) {
-            throw new MismatchedReferenceSystemException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, "verticalCRS"), e);
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatiblePropertyValue_1, "verticalCRS"), e);
         }
         if (minimumValue != null && maximumValue != null && minimumValue > maximumValue) {
             minimumValue = maximumValue = NilReason.MISSING.createNilObject(Double.class);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
index 3ac8015..119af52 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
@@ -30,7 +30,6 @@
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.GeographicDescription;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -53,9 +52,6 @@
 import static java.lang.Math.*;
 import static org.apache.sis.internal.metadata.ReferencingServices.AUTHALIC_RADIUS;
 
-// Branch-dependent imports
-import org.opengis.geometry.Geometry;
-
 
 /**
  * Convenience static methods for extracting information from {@link Extent} objects.
@@ -127,7 +123,6 @@
     public static GeographicBoundingBox getGeographicBoundingBox(final Extent extent) {
         GeographicBoundingBox bounds = null;
         if (extent != null) {
-            boolean useOnlyGeographicEnvelopes = false;
             DefaultGeographicBoundingBox modifiable = null;
             final List<Envelope> fallbacks = new ArrayList<>();
             for (final GeographicExtent element : extent.getGeographicElements()) {
@@ -151,33 +146,6 @@
                         }
                         modifiable.add(item);
                     }
-                } else if (bounds == null && element instanceof BoundingPolygon) {
-                    /*
-                     * If no GeographicBoundingBox has been found so far but we found a BoundingPolygon, remember
-                     * its Envelope but do not transform it yet. We will transform envelopes later only if needed.
-                     *
-                     * No need for DefaultGeographicBoundingBox.getInclusion(Boolean) below because we do not perform
-                     * any processing (apart just ignoring the element) for cases where the inclusion value is false.
-                     */
-                    if (!Boolean.FALSE.equals(element.getInclusion())) {
-                        for (final Geometry geometry : ((BoundingPolygon) extent).getPolygons()) {
-                            final Envelope envelope = geometry.getEnvelope();
-                            if (envelope != null) {
-                                final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
-                                if (crs != null) {
-                                    if (crs instanceof GeographicCRS) {
-                                        if (!useOnlyGeographicEnvelopes) {
-                                            useOnlyGeographicEnvelopes = true;
-                                            fallbacks.clear();
-                                        }
-                                    } else if (useOnlyGeographicEnvelopes) {
-                                        continue;
-                                    }
-                                    fallbacks.add(envelope);
-                                }
-                            }
-                        }
-                    }
                 }
             }
             /*
@@ -536,7 +504,7 @@
      * @param  e2  the second extent, or {@code null}.
      * @return the intersection (may be any of the {@code e1} or {@code e2} argument if unchanged),
      *         or {@code null} if the two given extents are null.
-     * @throws MismatchedReferenceSystemException if the two extents do not use the same datum, ignoring metadata.
+     * @throws IllegalArgumentException if the two extents do not use the same datum, ignoring metadata.
      *
      * @see DefaultVerticalExtent#intersect(VerticalExtent)
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
index 9a24099..81b8017 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.metadata.iso.identification;
 
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Collection;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlSeeAlso;
@@ -29,7 +31,6 @@
 import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.identification.AggregateInformation;
-import org.opengis.metadata.identification.AssociatedResource;
 import org.opengis.metadata.identification.Identification;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.identification.BrowseGraphic;
@@ -41,7 +42,6 @@
 import org.opengis.metadata.identification.ServiceIdentification;
 import org.opengis.metadata.maintenance.MaintenanceInformation;
 import org.opengis.metadata.spatial.SpatialRepresentationType;
-import org.opengis.temporal.Duration;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.metadata.Dependencies;
 import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
@@ -51,6 +51,12 @@
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Basic information required to uniquely identify a resource or resources.
@@ -95,7 +101,6 @@
     "pointOfContacts",
     "spatialRepresentationTypes",       // Here in ISO 19115:2014 (was after 'aggregationInfo' in ISO 19115:2003)
     "spatialResolutions",               // Shall be kept next to 'spatialRepresentationTypes'
-    "temporalResolution",               // ISO 19115-3 only
     "topicCategories",                  // Here in ISO 19115:2014 (was in subclasses in ISO 19115:2003)
     "extents",                          // Here in ISO 19115:2014 (was in subclasses in ISO 19115:2003)
     "additionalDocumentation",          // ISO 19115:2014 only
@@ -124,7 +129,7 @@
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = -1132210324047663554L;
+    private static final long serialVersionUID = 157053637951213015L;
 
     /**
      * Citation for the resource(s).
@@ -168,11 +173,6 @@
     private Collection<Resolution> spatialResolutions;
 
     /**
-     * Smallest resolvable temporal period in a resource.
-     */
-    private Collection<Duration> temporalResolutions;
-
-    /**
      * Main theme(s) of the resource.
      */
     private Collection<TopicCategory> topicCategories;
@@ -226,7 +226,7 @@
     /**
      * Provides aggregate dataset information.
      */
-    private Collection<AssociatedResource> associatedResources;
+    private Collection<DefaultAssociatedResource> associatedResources;
 
     /**
      * Constructs an initially empty identification.
@@ -263,20 +263,24 @@
             credits                    = copyCollection(object.getCredits(), String.class);
             status                     = copyCollection(object.getStatus(), Progress.class);
             pointOfContacts            = copyCollection(object.getPointOfContacts(), ResponsibleParty.class);
-            spatialRepresentationTypes = copyCollection(object.getSpatialRepresentationTypes(), SpatialRepresentationType.class);
-            spatialResolutions         = copyCollection(object.getSpatialResolutions(), Resolution.class);
-            temporalResolutions        = copyCollection(object.getTemporalResolutions(), Duration.class);
-            topicCategories            = copyCollection(object.getTopicCategories(), TopicCategory.class);
-            extents                    = copyCollection(object.getExtents(), Extent.class);
-            additionalDocumentations   = copyCollection(object.getAdditionalDocumentations(), Citation.class);
-            processingLevel            = object.getProcessingLevel();
             resourceMaintenances       = copyCollection(object.getResourceMaintenances(), MaintenanceInformation.class);
             graphicOverviews           = copyCollection(object.getGraphicOverviews(), BrowseGraphic.class);
             resourceFormats            = copyCollection(object.getResourceFormats(), Format.class);
             descriptiveKeywords        = copyCollection(object.getDescriptiveKeywords(), Keywords.class);
             resourceSpecificUsages     = copyCollection(object.getResourceSpecificUsages(), Usage.class);
             resourceConstraints        = copyCollection(object.getResourceConstraints(), Constraints.class);
-            associatedResources        = copyCollection(object.getAssociatedResources(), AssociatedResource.class);
+            if (object instanceof AbstractIdentification) {
+                final AbstractIdentification c = (AbstractIdentification) object;
+                spatialRepresentationTypes = copyCollection(c.getSpatialRepresentationTypes(), SpatialRepresentationType.class);
+                spatialResolutions         = copyCollection(c.getSpatialResolutions(), Resolution.class);
+                topicCategories            = copyCollection(c.getTopicCategories(), TopicCategory.class);
+                extents                    = copyCollection(c.getExtents(), Extent.class);
+                additionalDocumentations   = copyCollection(c.getAdditionalDocumentations(), Citation.class);
+                processingLevel            = c.getProcessingLevel();
+                associatedResources        = copyCollection(c.getAssociatedResources(), DefaultAssociatedResource.class);
+            } else {
+                setAggregationInfo(object.getAggregationInfo());
+            }
         }
     }
 
@@ -468,8 +472,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "spatialRepresentationType")
+    @UML(identifier="spatialRepresentationType", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() {
         return spatialRepresentationTypes = nonNullCollection(spatialRepresentationTypes, SpatialRepresentationType.class);
     }
@@ -493,8 +497,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "spatialResolution")
+    @UML(identifier="spatialResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Resolution> getSpatialResolutions() {
         return spatialResolutions = nonNullCollection(spatialResolutions, Resolution.class);
     }
@@ -511,38 +515,14 @@
     }
 
     /**
-     * Returns the smallest resolvable temporal period in a resource.
-     *
-     * @return smallest resolvable temporal period in a resource.
-     *
-     * @since 0.5
-     */
-    @Override
-    // @XmlElement at the end of this class.
-    public Collection<Duration> getTemporalResolutions() {
-        return temporalResolutions = nonNullCollection(temporalResolutions, Duration.class);
-    }
-
-    /**
-     * Sets the smallest resolvable temporal period in a resource.
-     *
-     * @param  newValues  the new temporal resolutions.
-     *
-     * @since 0.5
-     */
-    public void setTemporalResolutions(final Collection<? extends Duration> newValues) {
-        temporalResolutions = writeCollection(newValues, temporalResolutions, Duration.class);
-    }
-
-    /**
      * Returns the main theme(s) of the resource.
      *
      * @return main theme(s).
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "topicCategory")
+    @UML(identifier="topicCategory", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<TopicCategory> getTopicCategories()  {
         return topicCategories = nonNullCollection(topicCategories, TopicCategory.class);
     }
@@ -565,8 +545,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "extent")
+    @UML(identifier="extent", obligation=CONDITIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -589,8 +569,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentations() {
         return additionalDocumentations = nonNullCollection(additionalDocumentations, Citation.class);
     }
@@ -613,9 +593,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "processingLevel")
     @XmlJavaTypeAdapter(MD_Identifier.Since2014.class)
+    @UML(identifier="processingLevel", obligation=OPTIONAL, specification=ISO_19115)
     public Identifier getProcessingLevel() {
         return processingLevel;
     }
@@ -761,25 +741,35 @@
     /**
      * Provides associated resource information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AssociatedResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return associated resource information.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<AssociatedResource> getAssociatedResources() {
-        return associatedResources = nonNullCollection(associatedResources, AssociatedResource.class);
+    @UML(identifier="associatedResource", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultAssociatedResource> getAssociatedResources() {
+        return associatedResources = nonNullCollection(associatedResources, DefaultAssociatedResource.class);
     }
 
     /**
      * Sets associated resource information.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code AssociatedResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new associated resources.
      *
      * @since 0.5
      */
-    public void setAssociatedResources(final Collection<? extends AssociatedResource> newValues) {
-        associatedResources = writeCollection(newValues, associatedResources, AssociatedResource.class);
+    public void setAssociatedResources(final Collection<? extends DefaultAssociatedResource> newValues) {
+        associatedResources = writeCollection(newValues, associatedResources, DefaultAssociatedResource.class);
     }
 
     /**
@@ -795,16 +785,20 @@
     @XmlElement(name = "aggregationInfo", namespace = LegacyNamespaces.GMD)
     public Collection<AggregateInformation> getAggregationInfo() {
         if (!FilterByVersion.LEGACY_METADATA.accept()) return null;
-        return new LegacyPropertyAdapter<AggregateInformation,AssociatedResource>(getAssociatedResources()) {
-            @Override protected AssociatedResource wrap(final AggregateInformation value) {
-                return value;
+        return new LegacyPropertyAdapter<AggregateInformation,DefaultAssociatedResource>(getAssociatedResources()) {
+            @Override protected DefaultAssociatedResource wrap(final AggregateInformation value) {
+                return DefaultAssociatedResource.castOrCopy(value);
             }
 
-            @Override protected AggregateInformation unwrap(final AssociatedResource container) {
-                return DefaultAggregateInformation.castOrCopy(container);
+            @Override protected AggregateInformation unwrap(final DefaultAssociatedResource container) {
+                if (container instanceof AggregateInformation) {
+                    return (AggregateInformation) container;
+                } else {
+                    return new DefaultAggregateInformation(container);
+                }
             }
 
-            @Override protected boolean update(final AssociatedResource container, final AggregateInformation value) {
+            @Override protected boolean update(final DefaultAssociatedResource container, final AggregateInformation value) {
                 return container == value;
             }
         }.validOrNull();
@@ -819,7 +813,20 @@
      */
     @Deprecated
     public void setAggregationInfo(final Collection<? extends AggregateInformation> newValues) {
-        setAssociatedResources(newValues);
+        checkWritePermission(associatedResources);
+        /*
+         * We can not invoke getAggregationInfo().setValues(newValues) because this method
+         * is invoked by the constructor, which is itself invoked at JAXB marshalling time,
+         * in which case getAggregationInfo() may return null.
+         */
+        List<DefaultAssociatedResource> r = null;
+        if (newValues != null) {
+            r = new ArrayList<DefaultAssociatedResource>(newValues.size());
+            for (final AggregateInformation value : newValues) {
+                r.add(DefaultAssociatedResource.castOrCopy(value));
+            }
+        }
+        setAssociatedResources(r);
     }
 
 
@@ -840,22 +847,14 @@
      * Invoked by JAXB at both marshalling and unmarshalling time.
      * This attribute has been added by ISO 19115:2014 standard.
      * If (and only if) marshalling an older standard version, we omit this attribute.
-     *
-     * @todo Currently, the {@code XmlJavaTypeAdapter} used here just internally converts {@code Duration} objects
-     *       into {@code PeriodDuration} objects. Need to add support for {@code IntervalLength} in the future.
      */
-    @XmlElement(name = "temporalResolution")
-    private Collection<Duration> getTemporalResolution() {
-        return FilterByVersion.CURRENT_METADATA.accept() ? getTemporalResolutions() : null;
-    }
-
     @XmlElement(name = "additionalDocumentation")
     private Collection<Citation> getAdditionalDocumentation() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAdditionalDocumentations() : null;
     }
 
     @XmlElement(name = "associatedResource")
-    private Collection<AssociatedResource> getAssociatedResource() {
+    private Collection<DefaultAssociatedResource> getAssociatedResource() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getAssociatedResources() : null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
index e0fa384..0c5314d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAggregateInformation.java
@@ -26,7 +26,6 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.AggregateInformation;
-import org.opengis.metadata.identification.AssociatedResource;
 import org.opengis.metadata.identification.AssociationType;
 import org.opengis.metadata.identification.InitiativeType;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
@@ -37,7 +36,7 @@
 
 
 /**
- * Aggregate dataset information.
+ * Associated resource information.
  * The following properties are mandatory or conditional (i.e. mandatory under some circumstances)
  * in a well-formed metadata according ISO 19115:
  *
@@ -48,8 +47,14 @@
  * {@code   │   └─date……………………………} Reference date for the cited resource.
  * {@code   └─name………………………………………} Citation information about the associated resource.</div>
  *
- * According ISO 19115, at least one of {@linkplain #getAggregateDataSetName() aggregate dataset name}
- * and {@linkplain #getAggregateDataSetIdentifier() aggregate dataset identifier} shall be provided.
+ * According ISO 19115, at least one of {@linkplain #getName() name} and
+ * {@linkplain #getMetadataReference() metadata reference} shall be provided.
+ *
+ * <div class="warning"><b>Upcoming API change — renaming</b><br>
+ * As of ISO 19115:2014, {@code AggregateInformation} has been renamed {@code AssociatedResource}.
+ * This class will be replaced by {@link DefaultAssociatedResource} when GeoAPI will provide the
+ * {@code AssociatedResource} interface (tentatively in GeoAPI 3.1 or 4.0).
+ * </div>
  *
  * <h2>Limitations</h2>
  * <ul>
@@ -66,10 +71,7 @@
  * @version 1.0
  * @since   0.3
  * @module
- *
- * @deprecated As of ISO 19115:2014, replaced by {@link DefaultAssociatedResource}.
  */
-@Deprecated
 @XmlType(name = "MD_AggregateInformation_Type", namespace = LegacyNamespaces.GMD, propOrder = {
     "aggregateDataSetName",
     "aggregateDataSetIdentifier",
@@ -91,15 +93,26 @@
 
     /**
      * Constructs a new instance initialized with the values from the specified metadata object.
+     *
+     * @param object The metadata to copy values from.
+     */
+    DefaultAggregateInformation(final DefaultAssociatedResource object) {
+        super(object);
+    }
+
+    /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AssociatedResource)
      */
-    public DefaultAggregateInformation(final AssociatedResource object) {
+    public DefaultAggregateInformation(final AggregateInformation object) {
         super(object);
+        if (object != null && !(object instanceof DefaultAssociatedResource)) {
+            setAggregateDataSetName(object.getAggregateDataSetName());
+            setAggregateDataSetIdentifier(object.getAggregateDataSetIdentifier());
+        }
     }
 
     /**
@@ -111,7 +124,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code DefaultAggregateInformation}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code DefaultAggregateInformation} instance is created using the
-     *       {@linkplain #DefaultAggregateInformation(AssociatedResource) copy constructor}
+     *       copy constructor
      *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -120,7 +133,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultAggregateInformation castOrCopy(final AssociatedResource object) {
+    public static DefaultAggregateInformation castOrCopy(final AggregateInformation object) {
         if (object == null || object instanceof DefaultAggregateInformation) {
             return (DefaultAggregateInformation) object;
         }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
index e045595..0131685 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultAssociatedResource.java
@@ -21,7 +21,7 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.identification.AssociatedResource;
+import org.opengis.metadata.identification.AggregateInformation;
 import org.opengis.metadata.identification.AssociationType;
 import org.opengis.metadata.identification.InitiativeType;
 import org.apache.sis.internal.jaxb.metadata.CI_Citation;
@@ -29,6 +29,13 @@
 import org.apache.sis.internal.jaxb.code.DS_InitiativeTypeCode;
 import org.apache.sis.metadata.iso.ISOMetadata;
 
+// Branch-specific imports.
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Associated resource information.
@@ -68,7 +75,8 @@
     "metadataReference"
 })
 @XmlRootElement(name = "MD_AssociatedResource")
-public class DefaultAssociatedResource extends ISOMetadata implements AssociatedResource {
+@UML(identifier="MD_AssociatedResource", specification=ISO_19115)
+public class DefaultAssociatedResource extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -113,41 +121,43 @@
 
     /**
      * Constructs a new instance initialized with the values from the specified metadata object.
+     * This is a constructor for {@link DefaultAggregateInformation} constructor only.
+     *
+     * @param object The metadata to copy values from.
+     */
+    DefaultAssociatedResource(final DefaultAssociatedResource object) {
+        this.associationType   = object.associationType;
+        this.initiativeType    = object.initiativeType;
+        this.name              = object.name;
+        this.metadataReference = object.metadataReference;
+    }
+
+    /**
+     * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(AssociatedResource)
      */
-    public DefaultAssociatedResource(final AssociatedResource object) {
+    DefaultAssociatedResource(final AggregateInformation object) {
         if (object != null) {
-            this.name              = object.getName();
             this.associationType   = object.getAssociationType();
             this.initiativeType    = object.getInitiativeType();
-            this.metadataReference = object.getMetadataReference();
+            if (object instanceof DefaultAssociatedResource) {
+                this.name              = ((DefaultAssociatedResource) object).getName();
+                this.metadataReference = ((DefaultAssociatedResource) object).getMetadataReference();
+            }
         }
     }
 
     /**
      * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultAssociatedResource}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultAssociatedResource} instance is created using the
-     *       {@linkplain #DefaultAssociatedResource(AssociatedResource) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
      *
      * @param  object  the object to get as a SIS implementation, or {@code null} if none.
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static DefaultAssociatedResource castOrCopy(final AssociatedResource object) {
+    static DefaultAssociatedResource castOrCopy(final AggregateInformation object) {
         if (object == null || object instanceof DefaultAssociatedResource) {
             return (DefaultAssociatedResource) object;
         }
@@ -159,9 +169,9 @@
      *
      * @return Citation information about the associated resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "name")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="name", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getName() {
         return name;
     }
@@ -181,9 +191,9 @@
      *
      * @return type of relation between the resources.
      */
-    @Override
     @XmlElement(name = "associationType", required = true)
     @XmlJavaTypeAdapter(DS_AssociationTypeCode.Since2014.class)
+    @UML(identifier="associationType", obligation=MANDATORY, specification=ISO_19115)
     public AssociationType getAssociationType() {
         return associationType;
     }
@@ -203,9 +213,9 @@
      *
      * @return the type of initiative under which the associated resource was produced, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "initiativeType")
     @XmlJavaTypeAdapter(DS_InitiativeTypeCode.Since2014.class)
+    @UML(identifier="initiativeType", obligation=OPTIONAL, specification=ISO_19115)
     public InitiativeType getInitiativeType() {
         return initiativeType;
     }
@@ -225,9 +235,9 @@
      *
      * @return reference to the metadata of the associated resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "metadataReference")
     @XmlJavaTypeAdapter(CI_Citation.Since2014.class)
+    @UML(identifier="metadataReference", obligation=CONDITIONAL, specification=ISO_19115)
     public Citation getMetadataReference() {
         return metadataReference;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
index b9e50ee..94439e3 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultBrowseGraphic.java
@@ -31,6 +31,11 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Graphic that provides an illustration of the dataset (should include a legend for the graphic).
@@ -127,8 +132,10 @@
             fileName         = object.getFileName();
             fileDescription  = object.getFileDescription();
             fileType         = object.getFileType();
-            imageConstraints = copyCollection(object.getImageConstraints(), Constraints.class);
-            linkages         = copyCollection(object.getLinkages(), OnlineResource.class);
+            if (object instanceof DefaultBrowseGraphic) {
+                imageConstraints = copyCollection(((DefaultBrowseGraphic) object).getImageConstraints(), Constraints.class);
+                linkages         = copyCollection(((DefaultBrowseGraphic) object).getLinkages(), OnlineResource.class);
+            }
         }
     }
 
@@ -234,8 +241,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="imageContraints", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Constraints> getImageConstraints() {
         return imageConstraints = nonNullCollection(imageConstraints, Constraints.class);
     }
@@ -258,8 +265,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="linkage", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<OnlineResource> getLinkages() {
         return linkages = nonNullCollection(linkages, OnlineResource.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
index 58c9404..f80411f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
@@ -25,8 +25,6 @@
 import org.opengis.util.ScopedName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.DataIdentification;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.internal.jaxb.metadata.SV_OperationMetadata;
 import org.apache.sis.internal.jaxb.FilterByVersion;
@@ -36,10 +34,23 @@
 import org.apache.sis.util.iso.Names;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Links a given operation name with a resource identified by an "identifier".
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code CoupledResource} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -66,7 +77,8 @@
     "legacyName"                // Legacy ISO 19139:2007 way to write scoped name
 })
 @XmlRootElement(name = "SV_CoupledResource", namespace = Namespaces.SRV)
-public class DefaultCoupledResource extends ISOMetadata implements CoupledResource {
+@UML(identifier="SV_CoupledResource", specification=ISO_19115)
+public class DefaultCoupledResource extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -90,7 +102,7 @@
     /**
      * The service operation.
      */
-    private OperationMetadata operation;
+    private DefaultOperationMetadata operation;
 
     /**
      * Constructs an initially empty coupled resource.
@@ -109,7 +121,7 @@
     public DefaultCoupledResource(final ScopedName name,
                                   final Citation reference,
                                   final DataIdentification resource,
-                                  final OperationMetadata operation)
+                                  final DefaultOperationMetadata operation)
     {
         this.scopedName         = name;
         this.resourceReferences = singleton(reference, Citation.class);
@@ -123,10 +135,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(CoupledResource)
      */
-    public DefaultCoupledResource(final CoupledResource object) {
+    public DefaultCoupledResource(final DefaultCoupledResource object) {
         super(object);
         if (object != null) {
             this.scopedName         = object.getScopedName();
@@ -137,38 +147,13 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultCoupledResource}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultCoupledResource} instance is created using the
-     *       {@linkplain #DefaultCoupledResource(CoupledResource) copy constructor} and returned.
-     *       Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultCoupledResource castOrCopy(final CoupledResource object) {
-        if (object == null || object instanceof DefaultCoupledResource) {
-            return (DefaultCoupledResource) object;
-        }
-        return new DefaultCoupledResource(object);
-    }
-
-    /**
      * Returns scoped identifier of the resource in the context of the given service instance.
      *
      * @return identifier of the resource, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "scopedName")
     @XmlJavaTypeAdapter(GO_GenericName.Since2014.class)
+    @UML(identifier="scopedName", obligation=OPTIONAL, specification=ISO_19115)
     public ScopedName getScopedName() {
         return scopedName;
     }
@@ -188,8 +173,8 @@
      *
      * @return references to the resource on which the services operates.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resourceReference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getResourceReferences() {
         return resourceReferences = nonNullCollection(resourceReferences, Citation.class);
     }
@@ -208,8 +193,8 @@
      *
      * @return tightly coupled resources.
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="resource", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<DataIdentification> getResources() {
         return resources = nonNullCollection(resources, DataIdentification.class);
     }
@@ -226,23 +211,33 @@
     /**
      * Returns the service operation.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The return type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return the service operation, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "operation")
     @XmlJavaTypeAdapter(SV_OperationMetadata.Since2014.class)
-    public OperationMetadata getOperation() {
+    @UML(identifier="operation", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultOperationMetadata getOperation() {
         return operation;
     }
 
     /**
      * Sets a new service operation.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValue  the new service operation.
      */
-    public void setOperation(final OperationMetadata newValue) {
+    public void setOperation(final DefaultOperationMetadata newValue) {
         checkWritePermission(operation);
-        operation = newValue;
+        this.operation = newValue;
     }
 
 
@@ -265,7 +260,7 @@
     @XmlElement(name = "operationName", namespace = LegacyNamespaces.SRV)
     private String getOperationName() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
-            final OperationMetadata operation = getOperation();
+            final DefaultOperationMetadata operation = getOperation();
             if (operation != null) {
                 return operation.getOperationName();
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
index cbb8e54..919d663 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultDataIdentification.java
@@ -37,6 +37,12 @@
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.metadata.Dependencies;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+import org.apache.sis.internal.jaxb.code.MD_CharacterSetCode;
+
 
 /**
  * Information required to identify a dataset.
@@ -152,7 +158,11 @@
     public DefaultDataIdentification(final DataIdentification object) {
         super(object);
         if (object != null) {
-            locales                 = copyMap(object.getLocalesAndCharsets(), Locale.class);
+            if (object instanceof DefaultDataIdentification) {
+                locales = copyMap(((DefaultDataIdentification) object).getLocalesAndCharsets(), Locale.class);
+            } else {
+                setLanguages(object.getLanguages());
+            }
             environmentDescription  = object.getEnvironmentDescription();
             supplementalInformation = object.getSupplementalInformation();
         }
@@ -192,7 +202,7 @@
      *
      * @since 1.0
      */
-    @Override
+    @UML(identifier="defaultLocale+otherLocale", obligation=CONDITIONAL, specification=ISO_19115)
     // @XmlElement at the end of this class.
     public Map<Locale,Charset> getLocalesAndCharsets() {
         return locales = nonNullMap(locales, Locale.class);
@@ -260,7 +270,7 @@
     @Dependencies("getLocalesAndCharsets")
     // @XmlElement at the end of this class.
     public Collection<CharacterSet> getCharacterSets() {
-        return getLocalesAndCharsets().values().stream().map(CharacterSet::fromCharset).collect(Collectors.toSet());
+        return getLocalesAndCharsets().values().stream().map(MD_CharacterSetCode::fromCharset).collect(Collectors.toSet());
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
index 79dde2c..880438a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywordClass.java
@@ -22,7 +22,6 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.identification.KeywordClass;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
@@ -39,6 +38,14 @@
  * {@code       ├─title………………………} Name by which the cited resource is known.
  * {@code       └─date…………………………} Reference date for the cited resource.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code KeywordClass} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -60,7 +67,7 @@
     "ontology"
 })
 @XmlRootElement(name = "MD_KeywordClass")
-public class DefaultKeywordClass extends ISOMetadata implements KeywordClass {
+public class DefaultKeywordClass extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -105,10 +112,8 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(KeywordClass)
      */
-    public DefaultKeywordClass(final KeywordClass object) {
+    public DefaultKeywordClass(final DefaultKeywordClass object) {
         super(object);
         if (object != null) {
             className         = object.getClassName();
@@ -118,36 +123,10 @@
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultKeywordClass}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultKeywordClass} instance is created using the
-     *       {@linkplain #DefaultKeywordClass(KeywordClass) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultKeywordClass castOrCopy(final KeywordClass object) {
-        if (object == null || object instanceof DefaultKeywordClass) {
-            return (DefaultKeywordClass) object;
-        }
-        return new DefaultKeywordClass(object);
-    }
-
-    /**
      * Returns a label for the keyword category in natural language.
      *
      * @return the keyword category in natural language.
      */
-    @Override
     @XmlElement(name = "className", required = true)
     public InternationalString getClassName() {
         return className;
@@ -168,7 +147,6 @@
      *
      * @return URI of concept in the ontology, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "conceptIdentifier")
     public URI getConceptIdentifier() {
         return conceptIdentifier;
@@ -189,7 +167,6 @@
      *
      * @return a reference that binds the keyword class to a formal conceptualization.
      */
-    @Override
     @XmlElement(name = "ontology", required = true)
     public Citation getOntology() {
         return ontology;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
index a09af15..6e5d315 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
@@ -25,11 +25,15 @@
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.Keywords;
 import org.opengis.metadata.identification.KeywordType;
-import org.opengis.metadata.identification.KeywordClass;
 import org.apache.sis.internal.jaxb.metadata.MD_KeywordClass;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Keywords, their type and reference source.
@@ -87,7 +91,7 @@
      * User-defined categorization of groups of keywords that extend or are orthogonal
      * to the standardized {@linkplain #getType() keyword type} codes.
      */
-    private KeywordClass keywordClass;
+    private DefaultKeywordClass keywordClass;
 
     /**
      * Constructs an initially empty keywords.
@@ -224,25 +228,34 @@
      * Returns the user-defined categorization of groups of keywords that extend or
      * are orthogonal to the standardized {@linkplain #getType() keyword type} codes.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code KeywordClass} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return user-defined categorization of groups of keywords, or {@code null} if none.
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "keywordClass")
     @XmlJavaTypeAdapter(MD_KeywordClass.Since2014.class)
-    public KeywordClass getKeywordClass() {
+    @UML(identifier="keywordClass", obligation=OPTIONAL, specification=ISO_19115)
+    public DefaultKeywordClass getKeywordClass() {
         return keywordClass;
     }
 
     /**
      * Sets the user-defined categorization of groups of keywords.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The argument type will be changed to the {@code KeywordClass} interface when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1).</div>
+     *
      * @param newValue  new user-defined categorization of groups of keywords.
      *
      * @since 0.5
      */
-    public void setKeywordClass(final KeywordClass newValue) {
+    public void setKeywordClass(final DefaultKeywordClass newValue) {
         checkWritePermission(keywordClass);
         keywordClass = newValue;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
index fe61784..58726a6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
@@ -20,14 +20,18 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.metadata.identification.OperationChainMetadata;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Operation chain information.
@@ -41,6 +45,14 @@
  * {@code       └─connectPoint………………………………………………} Handle for accessing the service interface.
  * {@code           └─linkage…………………………………………………} Location for on-line access using a URL address or similar addressing scheme.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code OperationChainMetadata} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -63,11 +75,12 @@
     "operations"
 })
 @XmlRootElement(name = "SV_OperationChainMetadata", namespace = Namespaces.SRV)
-public class DefaultOperationChainMetadata extends ISOMetadata implements OperationChainMetadata {
+@UML(identifier="SV_OperationChainMetadata", specification=ISO_19115)
+public class DefaultOperationChainMetadata extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = 4132508877114835287L;
+    private static final long serialVersionUID = 4132508877114835286L;
 
     /**
      * The name as used by the service for this chain.
@@ -82,7 +95,7 @@
     /**
      * Information about the operations applied by the chain.
      */
-    private List<OperationMetadata> operations;
+    private List<DefaultOperationMetadata> operations;
 
     /**
      * Constructs an initially empty operation chain metadata.
@@ -105,50 +118,23 @@
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(OperationChainMetadata)
      */
-    public DefaultOperationChainMetadata(final OperationChainMetadata object) {
+    public DefaultOperationChainMetadata(final DefaultOperationChainMetadata object) {
         super(object);
         if (object != null) {
             this.name        = object.getName();
             this.description = object.getDescription();
-            this.operations  = copyList(object.getOperations(), OperationMetadata.class);
+            this.operations  = copyList(object.getOperations(), DefaultOperationMetadata.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOperationChainMetadata}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOperationChainMetadata} instance is created using the
-     *       {@linkplain #DefaultOperationChainMetadata(OperationChainMetadata) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOperationChainMetadata castOrCopy(final OperationChainMetadata object) {
-        if (object == null || object instanceof DefaultOperationChainMetadata) {
-            return (DefaultOperationChainMetadata) object;
-        }
-        return new DefaultOperationChainMetadata(object);
-    }
-
-    /**
      * Returns the name as used by the service for this chain.
      *
      * @return name as used by the service for this chain.
      */
-    @Override
     @XmlElement(name = "name", namespace = Namespaces.SRV, required = true)
+    @UML(identifier="name", obligation=MANDATORY, specification=ISO_19115)
     public InternationalString getName() {
         return name;
     }
@@ -168,8 +154,8 @@
      *
      * @return narrative explanation of the services in the chain and resulting output, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "description", namespace = Namespaces.SRV)
+    @UML(identifier="description", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDescription() {
         return description;
     }
@@ -187,20 +173,30 @@
     /**
      * Returns information about the operations applied by the chain.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the operations applied by the chain.
      */
-    @Override
     @XmlElement(name = "operation", namespace = Namespaces.SRV, required = true)
-    public List<OperationMetadata> getOperations() {
-        return operations = nonNullList(operations, OperationMetadata.class);
+    @UML(identifier="operation", obligation=MANDATORY, specification=ISO_19115)
+    public List<DefaultOperationMetadata> getOperations() {
+        return operations = nonNullList(operations, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets the information about the operations applied by the chain.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information about the operations applied by the chain.
      */
-    public void setOperations(final List<? extends OperationMetadata> newValues) {
-        operations = writeList(newValues, operations, OperationMetadata.class);
+    public void setOperations(final List<? extends DefaultOperationMetadata> newValues) {
+        operations = writeList(newValues, operations, DefaultOperationMetadata.class);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
index 67439a6..1adbd29 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
@@ -21,15 +21,22 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.util.CodeList;
+import org.opengis.annotation.UML;
+import org.apache.sis.internal.jaxb.code.DCPList;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Parameter information.
@@ -41,6 +48,14 @@
  * {@code   └─connectPoint………………………………………………} Handle for accessing the service interface.
  * {@code       └─linkage…………………………………………………} Location for on-line access using a URL address or similar addressing scheme.</div>
  *
+ * <div class="warning"><b>Note on International Standard versions</b><br>
+ * This class is derived from a new type defined in the ISO 19115 international standard published in 2014,
+ * while GeoAPI 3.0 is based on the version published in 2003. Consequently this implementation class does
+ * not yet implement a GeoAPI interface, but is expected to do so after the next GeoAPI releases.
+ * When the interface will become available, all references to this implementation class in Apache SIS will
+ * be replaced be references to the {@code OperationMetadata} interface.
+ * </div>
+ *
  * <p><b>Limitations:</b></p>
  * <ul>
  *   <li>Instances of this class are not synchronized for multi-threading.
@@ -68,11 +83,12 @@
     "dependsOn"
 })
 @XmlRootElement(name = "SV_OperationMetadata", namespace = Namespaces.SRV)
-public class DefaultOperationMetadata extends ISOMetadata implements OperationMetadata {
+@UML(identifier="SV_OperationMetadata", specification=ISO_19115)
+public class DefaultOperationMetadata extends ISOMetadata {
     /**
      * Serial number for compatibility with different versions.
      */
-    private static final long serialVersionUID = -6120853428175790473L;
+    private static final long serialVersionUID = -3513177609655567627L;
 
     /**
      * An unique identifier for this interface.
@@ -82,7 +98,7 @@
     /**
      * Distributed computing platforms on which the operation has been implemented.
      */
-    private Collection<DistributedComputingPlatform> distributedComputingPlatforms;
+    private Collection<CodeList<?>> distributedComputingPlatforms;
 
     /**
      * Free text description of the intent of the operation and the results of the operation.
@@ -107,7 +123,7 @@
     /**
      * List of operation that must be completed immediately.
      */
-    private List<OperationMetadata> dependsOn;
+    private List<DefaultOperationMetadata> dependsOn;
 
     /**
      * Constructs an initially empty operation metadata.
@@ -116,76 +132,33 @@
     }
 
     /**
-     * Constructs a new operation metadata initialized to the specified values.
-     *
-     * @param operationName  an unique identifier for this interface.
-     * @param platform       distributed computing platforms on which the operation has been implemented.
-     * @param connectPoint   handle for accessing the service interface.
-     */
-    public DefaultOperationMetadata(final String operationName,
-                                    final DistributedComputingPlatform platform,
-                                    final OnlineResource connectPoint)
-    {
-        this.operationName                 = operationName;
-        this.distributedComputingPlatforms = singleton(platform, DistributedComputingPlatform.class);
-        this.connectPoints                 = singleton(connectPoint, OnlineResource.class);
-    }
-
-    /**
      * Constructs a new instance initialized with the values from the specified metadata object.
      * This is a <cite>shallow</cite> copy constructor, since the other metadata contained in the
      * given object are not recursively copied.
      *
      * @param  object  the metadata to copy values from, or {@code null} if none.
-     *
-     * @see #castOrCopy(OperationMetadata)
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public DefaultOperationMetadata(final OperationMetadata object) {
+    public DefaultOperationMetadata(final DefaultOperationMetadata object) {
         super(object);
         if (object != null) {
             this.operationName                 = object.getOperationName();
-            this.distributedComputingPlatforms = copyCollection(object.getDistributedComputingPlatforms(), DistributedComputingPlatform.class);
+            this.distributedComputingPlatforms = copyCollection(object.getDistributedComputingPlatforms(), (Class) CodeList.class);
             this.operationDescription          = object.getOperationDescription();
             this.invocationName                = object.getInvocationName();
             this.connectPoints                 = copyCollection(object.getConnectPoints(), OnlineResource.class);
             this.parameters                    = copySet(object.getParameters(), (Class) ParameterDescriptor.class);
-            this.dependsOn                     = copyList(object.getDependsOn(), OperationMetadata.class);
+            this.dependsOn                     = copyList(object.getDependsOn(), DefaultOperationMetadata.class);
         }
     }
 
     /**
-     * Returns a SIS metadata implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable action in the following choices:
-     *
-     * <ul>
-     *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given object is already an instance of
-     *       {@code DefaultOperationMetadata}, then it is returned unchanged.</li>
-     *   <li>Otherwise a new {@code DefaultOperationMetadata} instance is created using the
-     *       {@linkplain #DefaultOperationMetadata(OperationMetadata) copy constructor}
-     *       and returned. Note that this is a <cite>shallow</cite> copy operation, since the other
-     *       metadata contained in the given object are not recursively copied.</li>
-     * </ul>
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultOperationMetadata castOrCopy(final OperationMetadata object) {
-        if (object == null || object instanceof DefaultOperationMetadata) {
-            return (DefaultOperationMetadata) object;
-        }
-        return new DefaultOperationMetadata(object);
-    }
-
-    /**
      * Returns an unique identifier for this interface.
      *
      * @return an unique identifier for this interface.
      */
-    @Override
     @XmlElement(name = "operationName", required = true)
+    @UML(identifier="operationName", obligation=MANDATORY, specification=ISO_19115)
     public String getOperationName() {
         return operationName;
     }
@@ -203,21 +176,56 @@
     /**
      * Returns the distributed computing platforms (DCPs) on which the operation has been implemented.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The element type will be changed to the {@code DistributedComputingPlatform} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return distributed computing platforms on which the operation has been implemented.
      */
-    @Override
+    @XmlJavaTypeAdapter(DCPList.class)
     @XmlElement(name = "distributedComputingPlatform", required = true)
-    public Collection<DistributedComputingPlatform> getDistributedComputingPlatforms() {
-        return distributedComputingPlatforms = nonNullCollection(distributedComputingPlatforms, DistributedComputingPlatform.class);
+    @UML(identifier="distributedComputingPlatform", obligation=MANDATORY, specification=ISO_19115)
+    public Collection<CodeList<?>> getDistributedComputingPlatforms() {
+        return distributedComputingPlatforms = nonNullCollection(distributedComputingPlatforms, (Class) CodeList.class);
     }
 
     /**
      * Sets the distributed computing platforms on which the operation has been implemented.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The element type will be changed to the {@code DistributedComputingPlatform} code list when GeoAPI will provide
+     * it (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
+     *
      * @param  newValues  the new distributed computing platforms on which the operation has been implemented.
      */
-    public void setDistributedComputingPlatforms(final Collection<? extends DistributedComputingPlatform> newValues) {
-        distributedComputingPlatforms = writeCollection(newValues, distributedComputingPlatforms, DistributedComputingPlatform.class);
+    public void setDistributedComputingPlatforms(final Collection<? extends CodeList<?>> newValues) {
+        distributedComputingPlatforms = writeCollection(newValues, distributedComputingPlatforms, (Class) CodeList.class);
     }
 
     /**
@@ -225,8 +233,8 @@
      *
      * @return free text description of the intent of the operation and the results of the operation, or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "operationDescription")
+    @UML(identifier="operationDescription", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getOperationDescription() {
         return operationDescription;
     }
@@ -247,8 +255,8 @@
      * @return the name used to invoke this interface within the context of the distributed computing platforms,
      *         or {@code null} if none.
      */
-    @Override
     @XmlElement(name = "invocationName")
+    @UML(identifier="invocationName", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getInvocationName() {
         return invocationName;
     }
@@ -268,8 +276,8 @@
      *
      * @return handle for accessing the service interface.
      */
-    @Override
     @XmlElement(name = "connectPoint", required = true)
+    @UML(identifier="connectPoint", obligation=MANDATORY, specification=ISO_19115)
     public Collection<OnlineResource> getConnectPoints() {
         return connectPoints = nonNullCollection(connectPoints, OnlineResource.class);
     }
@@ -288,9 +296,9 @@
      *
      * @return the parameters that are required for this interface, or an empty collection if none.
      */
-    @Override
     @XmlElement(name = "parameter")
     @SuppressWarnings({"unchecked", "rawtypes"})
+    @UML(identifier="parameters", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<ParameterDescriptor<?>> getParameters() {
         return parameters = nonNullCollection(parameters, (Class) ParameterDescriptor.class);
     }
@@ -308,20 +316,30 @@
     /**
      * Returns the list of operation that must be completed immediately before current operation is invoked.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return list of operation that must be completed immediately, or an empty list if none.
      */
-    @Override
     @XmlElement(name = "dependsOn")
-    public List<OperationMetadata> getDependsOn() {
-        return dependsOn = nonNullList(dependsOn, OperationMetadata.class);
+    @UML(identifier="dependsOn", obligation=OPTIONAL, specification=ISO_19115)
+    public List<DefaultOperationMetadata> getDependsOn() {
+        return dependsOn = nonNullList(dependsOn, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets the list of operation that must be completed before current operation is invoked.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new list of operation.
      */
-    public void setDependsOn(final List<? extends OperationMetadata> newValues) {
-        dependsOn = writeList(newValues, dependsOn, OperationMetadata.class);
+    public void setDependsOn(final List<? extends DefaultOperationMetadata> newValues) {
+        dependsOn = writeList(newValues, dependsOn, DefaultOperationMetadata.class);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
index 64e55b0..f128582 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
@@ -33,6 +33,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Level of detail expressed as a scale factor or a ground distance.
@@ -168,13 +173,13 @@
         super(object);
         if (object != null) {
             for (byte p=SCALE; p<=TEXT; p++) {
-                final Object c;
+                Object c = null;
                 switch (p) {
                     case SCALE:    c = object.getEquivalentScale(); break;
-                    case DISTANCE: c = object.getDistance();        break;
-                    case VERTICAL: c = object.getVertical();        break;
-                    case ANGULAR:  c = object.getAngularDistance(); break;
-                    case TEXT:     c = object.getLevelOfDetail();   break;
+                    case DISTANCE: c = object.getDistance(); break;
+                    case VERTICAL: if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getVertical(); break;
+                    case ANGULAR:  if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getAngularDistance(); break;
+                    case TEXT:     if (c instanceof DefaultResolution) c = ((DefaultResolution) object).getLevelOfDetail(); break;
                     default:       throw new AssertionError(p);
                 }
                 if (c != null) {
@@ -291,9 +296,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "vertical")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="vertical", obligation=CONDITIONAL, specification=ISO_19115)
     @ValueRange(minimum=0, isMinIncluded=false)
     public Double getVertical() {
         return (property == VERTICAL) ? (Double) value : null;
@@ -324,9 +329,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "angularDistance")
     @XmlJavaTypeAdapter(GO_Real.Since2014.class)
+    @UML(identifier="angularDistance", obligation=CONDITIONAL, specification=ISO_19115)
     @ValueRange(minimum=0, isMinIncluded=false)
     public Double getAngularDistance() {
         return (property == ANGULAR) ? (Double) value : null;
@@ -357,9 +362,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "levelOfDetail")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="levelOfDetail", obligation=CONDITIONAL, specification=ISO_19115)
     public InternationalString getLevelOfDetail() {
         return (property == TEXT) ? (InternationalString) value : null;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
index 0a74b2b..9da1ba2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
@@ -21,18 +21,24 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.opengis.util.CodeList;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.distribution.StandardOrderProcess;
 import org.opengis.metadata.identification.ServiceIdentification;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.CouplingType;
-import org.opengis.metadata.identification.OperationChainMetadata;
-import org.opengis.metadata.identification.OperationMetadata;
+import org.apache.sis.internal.jaxb.code.SV_CouplingType;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.MANDATORY;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Identification of capabilities which a service provider makes available to a service user
@@ -111,12 +117,12 @@
     /**
      * Type of coupling between service and associated data (if exist).
      */
-    private CouplingType couplingType;
+    private CodeList<?> couplingType;
 
     /**
      * Further description of the data coupling in the case of tightly coupled services.
      */
-    private Collection<CoupledResource> coupledResources;
+    private Collection<DefaultCoupledResource> coupledResources;
 
     /**
      * References to the resource on which the service operates.
@@ -136,7 +142,7 @@
     /**
      * Information about the operations that comprise the service.
      */
-    private Collection<OperationMetadata> containsOperations;
+    private Collection<DefaultOperationMetadata> containsOperations;
 
     /**
      * Information on the resources that the service operates on.
@@ -146,7 +152,7 @@
     /**
      * Information about the chain applied by the service.
      */
-    private Collection<OperationChainMetadata> containsChain;
+    private Collection<DefaultOperationChainMetadata> containsChain;
 
     /**
      * Constructs an initially empty service identification.
@@ -180,18 +186,19 @@
      */
     public DefaultServiceIdentification(final ServiceIdentification object) {
         super(object);
-        if (object != null) {
-            serviceType         = object.getServiceType();
-            serviceTypeVersions = copyCollection(object.getServiceTypeVersions(), String.class);
-            accessProperties    = object.getAccessProperties();
-            couplingType        = object.getCouplingType();
-            coupledResources    = copyCollection(object.getCoupledResources(), CoupledResource.class);
-            operatedDatasets    = copyCollection(object.getOperatedDatasets(), Citation.class);
-            profiles            = copyCollection(object.getProfiles(), Citation.class);
-            serviceStandards    = copyCollection(object.getServiceStandards(), Citation.class);
-            containsOperations  = copyCollection(object.getContainsOperations(), OperationMetadata.class);
-            operatesOn          = copyCollection(object.getOperatesOn(), DataIdentification.class);
-            containsChain       = copyCollection(object.getContainsChain(), OperationChainMetadata.class);
+        if (object instanceof DefaultServiceIdentification) {
+            final DefaultServiceIdentification c = (DefaultServiceIdentification) object;
+            serviceType         = c.getServiceType();
+            serviceTypeVersions = copyCollection(c.getServiceTypeVersions(), String.class);
+            accessProperties    = c.getAccessProperties();
+            couplingType        = c.getCouplingType();
+            coupledResources    = copyCollection(c.getCoupledResources(), DefaultCoupledResource.class);
+            operatedDatasets    = copyCollection(c.getOperatedDatasets(), Citation.class);
+            profiles            = copyCollection(c.getProfiles(), Citation.class);
+            serviceStandards    = copyCollection(c.getServiceStandards(), Citation.class);
+            containsOperations  = copyCollection(c.getContainsOperations(), DefaultOperationMetadata.class);
+            operatesOn          = copyCollection(c.getOperatesOn(), DataIdentification.class);
+            containsChain       = copyCollection(c.getContainsChain(), DefaultOperationChainMetadata.class);
         }
     }
 
@@ -227,8 +234,8 @@
      *
      * @return a service type name.
      */
-    @Override
     @XmlElement(name = "serviceType", required = true)
+    @UML(identifier="serviceType", obligation=MANDATORY, specification=ISO_19115)
     public GenericName getServiceType() {
         return serviceType;
     }
@@ -248,8 +255,8 @@
      *
      * @return the versions of the service.
      */
-    @Override
     @XmlElement(name = "serviceTypeVersion")
+    @UML(identifier="serviceTypeVersion", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<String> getServiceTypeVersions() {
         return serviceTypeVersions = nonNullCollection(serviceTypeVersions, String.class);
     }
@@ -270,8 +277,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "accessProperties")
+    @UML(identifier="accessProperties", obligation=OPTIONAL, specification=ISO_19115)
     public StandardOrderProcess getAccessProperties() {
         return accessProperties;
 
@@ -292,20 +299,55 @@
     /**
      * Returns type of coupling between service and associated data (if exist).
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The return type will be changed to the {@code CouplingType} code list
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return type of coupling between service and associated data, or {@code null} if none.
      */
-    @Override
+    @XmlJavaTypeAdapter(SV_CouplingType.class)
     @XmlElement(name = "couplingType")
-    public CouplingType getCouplingType() {
+    @UML(identifier="couplingType", obligation=CONDITIONAL, specification=ISO_19115)
+    public CodeList<?> getCouplingType() {
         return couplingType;
     }
 
     /**
      * Sets the type of coupling between service and associated data.
      *
+     * <div class="warning"><b>Upcoming API change — specialization</b><br>
+     * The argument type will be changed to the {@code CouplingType} code list when GeoAPI will provide it
+     * (tentatively in GeoAPI 3.1). In the meantime, users can define their own code list class as below:
+     *
+     * {@preformat java
+     *   final class UnsupportedCodeList extends CodeList<UnsupportedCodeList> {
+     *       private static final List<UnsupportedCodeList> VALUES = new ArrayList<UnsupportedCodeList>();
+     *
+     *       // Need to declare at least one code list element.
+     *       public static final UnsupportedCodeList MY_CODE_LIST = new UnsupportedCodeList("MY_CODE_LIST");
+     *
+     *       private UnsupportedCodeList(String name) {
+     *           super(name, VALUES);
+     *       }
+     *
+     *       public static UnsupportedCodeList valueOf(String code) {
+     *           return valueOf(UnsupportedCodeList.class, code);
+     *       }
+     *
+     *       &#64;Override
+     *       public UnsupportedCodeList[] family() {
+     *           synchronized (VALUES) {
+     *               return VALUES.toArray(new UnsupportedCodeList[VALUES.size()]);
+     *           }
+     *       }
+     *   }
+     * }
+     * </div>
+     *
      * @param  newValue  the new type of coupling between service and associated data.
      */
-    public void setCouplingType(final CouplingType newValue) {
+    public void setCouplingType(final CodeList<?> newValue) {
         checkWritePermission(couplingType);
         couplingType = newValue;
     }
@@ -313,21 +355,31 @@
     /**
      * Returns further description(s) of the data coupling in the case of tightly coupled services.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code CoupledResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return further description(s) of the data coupling in the case of tightly coupled services.
      */
-    @Override
     @XmlElement(name = "coupledResource")
-    public Collection<CoupledResource> getCoupledResources() {
-        return coupledResources = nonNullCollection(coupledResources, CoupledResource.class);
+    @UML(identifier="coupledResource", obligation=CONDITIONAL, specification=ISO_19115)
+    public Collection<DefaultCoupledResource> getCoupledResources() {
+        return coupledResources = nonNullCollection(coupledResources, DefaultCoupledResource.class);
     }
 
     /**
      * Sets further description(s) of the data coupling in the case of tightly coupled services.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code CoupledResource} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new further description(s) of the data coupling.
      */
-    public void setCoupledResources(final Collection<? extends CoupledResource> newValues) {
-        coupledResources = writeCollection(newValues, coupledResources, CoupledResource.class);
+    public void setCoupledResources(final Collection<? extends DefaultCoupledResource> newValues) {
+        coupledResources = writeCollection(newValues, coupledResources, DefaultCoupledResource.class);
     }
 
     /**
@@ -337,8 +389,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="operatedDataset", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getOperatedDatasets() {
         return operatedDatasets = nonNullCollection(operatedDatasets, Citation.class);
     }
@@ -361,8 +413,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="profile", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getProfiles() {
         return profiles = nonNullCollection(profiles, Citation.class);
     }
@@ -383,8 +435,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="serviceStandard", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getServiceStandards() {
         return serviceStandards = nonNullCollection(serviceStandards, Citation.class);
     }
@@ -403,21 +455,31 @@
     /**
      * Provides information about the operations that comprise the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the operations that comprise the service.
      */
-    @Override
     @XmlElement(name = "containsOperations")
-    public Collection<OperationMetadata> getContainsOperations() {
-        return containsOperations = nonNullCollection(containsOperations, OperationMetadata.class);
+    @UML(identifier="containsOperations", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultOperationMetadata> getContainsOperations() {
+        return containsOperations = nonNullCollection(containsOperations, DefaultOperationMetadata.class);
     }
 
     /**
      * Sets information(s) about the operations that comprise the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information(s) about the operations that comprise the service.
      */
-    public void setContainsOperations(final Collection<? extends OperationMetadata> newValues) {
-        containsOperations = writeCollection(newValues, containsOperations, OperationMetadata.class);
+    public void setContainsOperations(final Collection<? extends DefaultOperationMetadata> newValues) {
+        containsOperations = writeCollection(newValues, containsOperations, DefaultOperationMetadata.class);
     }
 
     /**
@@ -425,8 +487,8 @@
      *
      * @return information on the resources that the service operates on.
      */
-    @Override
     @XmlElement(name = "operatesOn")
+    @UML(identifier="operatesOn", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<DataIdentification> getOperatesOn() {
         return operatesOn = nonNullCollection(operatesOn, DataIdentification.class);
     }
@@ -443,25 +505,35 @@
     /**
      * Provides information about the chain applied by the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationChainMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @return information about the chain applied by the service.
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
-    public Collection<OperationChainMetadata> getContainsChain() {
-        return containsChain = nonNullCollection(containsChain, OperationChainMetadata.class);
+    @UML(identifier="containsChain", obligation=OPTIONAL, specification=ISO_19115)
+    public Collection<DefaultOperationChainMetadata> getContainsChain() {
+        return containsChain = nonNullCollection(containsChain, DefaultOperationChainMetadata.class);
     }
 
     /**
      * Sets the information about the chain applied by the service.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * The element type will be changed to the {@code OperationChainMetadata} interface
+     * when GeoAPI will provide it (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param  newValues  the new information about the chain applied by the service.
      *
      * @since 0.5
      */
-    public void setContainsChain(final Collection<? extends OperationChainMetadata>  newValues) {
-        containsChain = writeCollection(newValues, containsChain, OperationChainMetadata.class);
+    public void setContainsChain(final Collection<? extends DefaultOperationChainMetadata>  newValues) {
+        containsChain = writeCollection(newValues, containsChain, DefaultOperationChainMetadata.class);
     }
 
 
@@ -499,7 +571,7 @@
     }
 
     @XmlElement(name = "containsChain")
-    private Collection<OperationChainMetadata> getOperationChain() {
+    private Collection<DefaultOperationChainMetadata> getOperationChain() {
         return FilterByVersion.CURRENT_METADATA.accept() ? getContainsChain() : null;
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
index 01574e0..2a64b62 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
@@ -30,6 +30,10 @@
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
 import static org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
 
@@ -155,9 +159,12 @@
             usageDate                 = toMilliseconds(object.getUsageDate());
             userDeterminedLimitations = object.getUserDeterminedLimitations();
             userContactInfo           = copyCollection(object.getUserContactInfo(), ResponsibleParty.class);
-            responses                 = copyCollection(object.getResponses(), InternationalString.class);
-            additionalDocumentation   = copyCollection(object.getAdditionalDocumentation(), Citation.class);
-            identifiedIssues          = copyCollection(object.getIdentifiedIssues(), Citation.class);
+            if (object instanceof DefaultUsage) {
+                final DefaultUsage c = (DefaultUsage) object;
+                responses                 = copyCollection(c.getResponses(), InternationalString.class);
+                additionalDocumentation   = copyCollection(c.getAdditionalDocumentation(), Citation.class);
+                identifiedIssues          = copyCollection(c.getIdentifiedIssues(), Citation.class);
+            }
         }
     }
 
@@ -286,8 +293,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="response", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<InternationalString> getResponses() {
         return responses = nonNullCollection(responses, InternationalString.class);
     }
@@ -310,8 +317,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentation() {
         return additionalDocumentation = nonNullCollection(additionalDocumentation, Citation.class);
     }
@@ -335,8 +342,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="identifiedIssues", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getIdentifiedIssues() {
         return identifiedIssues = nonNullCollection(identifiedIssues, Citation.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java
index a7e7356..0bcb09f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/OperationName.java
@@ -19,17 +19,11 @@
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Collection;
-import java.util.Collections;
-import java.io.Serializable;
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
-import org.opengis.metadata.identification.OperationMetadata;
 import org.apache.sis.internal.util.Strings;
 
 
 /**
- * An {@code OperationMetadata} placeholder to be replaced later by a reference to an other {@link OperationMetadata}.
+ * An {@code OperationMetadata} placeholder to be replaced later by a reference to an other {@code OperationMetadata}.
  * This temporary place holder is used when the operation name is unmarshalled before the actual operation definition.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -37,37 +31,25 @@
  * @since   0.5
  * @module
  */
-final class OperationName implements OperationMetadata, Serializable {
+final class OperationName extends DefaultOperationMetadata {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -7958898214063034276L;
-
-    /**
-     * The operation name.
-     */
-    private final String operationName;
+    private static final long serialVersionUID = 7221543581387125873L;
 
     /**
      * Creates a new placeholder for the operation of the given name.
      */
     OperationName(final String operationName) {
-        this.operationName = operationName;
+        setOperationName(operationName);
     }
 
     /**
-     * Returns the operation name.
-     */
-    @Override public String                                   getOperationName()                 {return operationName;}
-    @Override public Collection<DistributedComputingPlatform> getDistributedComputingPlatforms() {return Collections.emptySet();}
-    @Override public Collection<OnlineResource>               getConnectPoints()                 {return Collections.emptySet();}
-
-    /**
      * Returns a string representation of this placeholder.
      */
     @Override
     public String toString() {
-        return Strings.bracket("OperationMetadata", operationName);
+        return Strings.bracket("OperationMetadata", getOperationName());
     }
 
     /**
@@ -80,25 +62,23 @@
      * <p>This method is invoked at unmarshalling time for resolving the {@code OperationMetadata} instance which
      * were identified only by a name in a {@code <srv:operationName>} element.</p>
      */
-    static void resolve(final Collection<OperationMetadata> containsOperations, final Collection<CoupledResource> coupledResources) {
-        final Map<String,OperationMetadata> byName = new HashMap<>();
-        for (final OperationMetadata operation : containsOperations) {
+    static void resolve(final Collection<DefaultOperationMetadata> containsOperations, final Collection<DefaultCoupledResource> coupledResources) {
+        final Map<String,DefaultOperationMetadata> byName = new HashMap<>();
+        for (final DefaultOperationMetadata operation : containsOperations) {
             add(byName, operation.getOperationName(), operation);
         }
-        for (final CoupledResource resource : coupledResources) {
-            if (resource instanceof DefaultCoupledResource) {
-                OperationMetadata operation = resource.getOperation();
-                if (operation instanceof OperationName) {
-                    final String name = operation.getOperationName();
+        for (final DefaultCoupledResource resource : coupledResources) {
+            DefaultOperationMetadata operation = resource.getOperation();
+            if (operation instanceof OperationName) {
+                final String name = operation.getOperationName();
+                operation = byName.get(name);
+                if (operation == null) {
                     operation = byName.get(name);
                     if (operation == null) {
-                        operation = byName.get(name);
-                        if (operation == null) {
-                            continue;
-                        }
+                        continue;
                     }
-                    ((DefaultCoupledResource) resource).setOperation(operation);
                 }
+                resource.setOperation(operation);
             }
         }
     }
@@ -107,9 +87,9 @@
      * Adds the given operation in the given map under the given name. If an entry already exists for the given name,
      * then this method sets the value to {@code null} for meaning that we have duplicated values for that name.
      */
-    private static void add(final Map<String,OperationMetadata> byName, final String name, final OperationMetadata operation) {
+    private static void add(final Map<String,DefaultOperationMetadata> byName, final String name, final DefaultOperationMetadata operation) {
         final boolean exists = byName.containsKey(name);
-        final OperationMetadata previous = byName.put(name, operation);
+        final DefaultOperationMetadata previous = byName.put(name, operation);
         if (previous != operation && (previous != null || exists)) {
             byName.put(name, null);                                         // Mark the entry as duplicated.
         }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
index daa8d62..d5b31fd 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/package-info.java
@@ -47,8 +47,8 @@
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.AssociationType Association type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.InitiativeType  Initiative type}<br>
  * {@code  ├─} {@linkplain org.opengis.metadata.identification.TopicCategory   Topic category}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.identification.CouplingType    Coupling type}<br>
- * {@code  └─} {@linkplain org.opengis.metadata.identification.DistributedComputingPlatform Distributed computing platform}<br>
+ * {@code  ├─} Coupling type}<br>
+ * {@code  └─} Distributed computing platform}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                 {@linkplain org.apache.sis.metadata.iso.identification.AbstractIdentification        Identification} «abstract»<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultResolution             Resolution}<br>
@@ -63,12 +63,12 @@
  * {@code      └─} {@linkplain org.opengis.metadata.identification.InitiativeType                       Initiative type} «code list»<br>
  *                 {@linkplain org.apache.sis.metadata.iso.identification.DefaultDataIdentification     Data identification}<br>
  *                 {@linkplain org.apache.sis.metadata.iso.identification.DefaultServiceIdentification  Service identification}<br>
- * {@code  ├─}     {@linkplain org.opengis.metadata.identification.CouplingType                         Coupling type} «code list»<br>
+ * {@code  ├─}     Coupling type} «code list»<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultCoupledResource        Coupled resource}<br>
  * {@code  ├─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultOperationMetadata      Operation metadata}<br>
- * {@code  │   ├─} {@linkplain org.opengis.metadata.identification.DistributedComputingPlatform         Distributed computing platform} «code list»<br>
+ * {@code  │   ├─} Distributed computing platform} «code list»<br>
  * {@code  │   └─} {@linkplain org.apache.sis.parameter.DefaultParameterDescriptor                      Parameter descriptor}<br>
- * {@code  │       └─} {@linkplain org.opengis.parameter.ParameterDirection                             Parameter direction} «enum»<br>
+ * {@code  │       └─} Parameter direction} «enum»<br>
  * {@code  └─}     {@linkplain org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata Operation chain metadata}<br>
  * </td></tr></table>
  *
@@ -116,12 +116,10 @@
     @XmlJavaTypeAdapter(CI_Citation.class),
     @XmlJavaTypeAdapter(CI_OnlineResource.class),
     @XmlJavaTypeAdapter(CI_ResponsibleParty.class),
-    @XmlJavaTypeAdapter(DCPList.class),
     @XmlJavaTypeAdapter(EX_Extent.class),
     @XmlJavaTypeAdapter(GO_DateTime.class),
     @XmlJavaTypeAdapter(GO_GenericName.class),
     @XmlJavaTypeAdapter(MD_AggregateInformation.class),
-    @XmlJavaTypeAdapter(MD_AssociatedResource.class),
     @XmlJavaTypeAdapter(MD_BrowseGraphic.class),
     @XmlJavaTypeAdapter(MD_CharacterSetCode.class),
     @XmlJavaTypeAdapter(MD_Constraints.class),
@@ -139,7 +137,6 @@
     @XmlJavaTypeAdapter(MD_TopicCategoryCode.class),
     @XmlJavaTypeAdapter(MD_Usage.class),
     @XmlJavaTypeAdapter(SV_CoupledResource.class),
-    @XmlJavaTypeAdapter(SV_CouplingType.class),
     @XmlJavaTypeAdapter(SV_OperationMetadata.class),
     @XmlJavaTypeAdapter(SV_OperationChainMetadata.class),
     @XmlJavaTypeAdapter(SV_Parameter.class),
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
index 490471e..daf9d47 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultLineage.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.lineage.Source;
 import org.opengis.metadata.lineage.Lineage;
 import org.opengis.metadata.lineage.ProcessStep;
@@ -33,6 +33,11 @@
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.jaxb.metadata.MD_Scope;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the events or source data used in constructing the data specified by
@@ -138,10 +143,12 @@
         super(object);
         if (object != null) {
             statement               = object.getStatement();
-            scope                   = object.getScope();
-            additionalDocumentation = copyCollection(object.getAdditionalDocumentation(), Citation.class);
             processSteps            = copyCollection(object.getProcessSteps(), ProcessStep.class);
             sources                 = copyCollection(object.getSources(), Source.class);
+            if (object instanceof DefaultLineage) {
+                scope                   = ((DefaultLineage) object).getScope();
+                additionalDocumentation = copyCollection(((DefaultLineage) object).getAdditionalDocumentation(), Citation.class);
+            }
         }
     }
 
@@ -200,9 +207,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
@@ -226,8 +233,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="additionalDocumentation", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getAdditionalDocumentation() {
         return additionalDocumentation = nonNullCollection(additionalDocumentation, Citation.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
index fe49546..8cb4e4b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java
@@ -27,7 +27,7 @@
 import org.opengis.temporal.TemporalPrimitive;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.ResponsibleParty;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.lineage.Source;
 import org.opengis.metadata.lineage.Processing;
 import org.opengis.metadata.lineage.ProcessStep;
@@ -41,6 +41,11 @@
 import org.apache.sis.internal.jaxb.metadata.MD_Scope;
 import org.apache.sis.internal.util.TemporalUtilities;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about an event or transformation in the life of a resource.
@@ -174,12 +179,14 @@
             rationale             = object.getRationale();
             stepDateTime          = TemporalUtilities.createInstant(object.getDate());
             processors            = copyCollection(object.getProcessors(), ResponsibleParty.class);
-            references            = copyCollection(object.getReferences(), Citation.class);
             sources               = copyCollection(object.getSources(), Source.class);
-            scope                 = object.getScope();
             outputs               = copyCollection(object.getOutputs(), Source.class);
             processingInformation = object.getProcessingInformation();
             reports               = copyCollection(object.getReports(), ProcessStepReport.class);
+            if (object instanceof DefaultProcessStep) {
+                references = copyCollection(((DefaultProcessStep) object).getReferences(), Citation.class);
+                scope      = ((DefaultProcessStep) object).getScope();
+            }
         }
     }
 
@@ -340,8 +347,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="reference", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getReferences() {
         return references = nonNullCollection(references, Citation.class);
     }
@@ -364,9 +371,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=OPTIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
index 092242f..0c13ea5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
@@ -32,7 +32,7 @@
 import org.opengis.metadata.lineage.ProcessStep;
 import org.opengis.metadata.identification.Resolution;
 import org.opengis.metadata.identification.RepresentativeFraction;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.referencing.ReferenceSystem;
 import org.apache.sis.metadata.TitleProperty;
 import org.apache.sis.metadata.iso.ISOMetadata;
@@ -46,6 +46,12 @@
 import org.apache.sis.internal.metadata.Dependencies;
 import org.apache.sis.util.iso.Types;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Obligation.CONDITIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Information about the source data used in creating the data specified by the scope.
@@ -180,14 +186,19 @@
         super(object);
         if (object != null) {
             description             = object.getDescription();
-            sourceSpatialResolution = object.getSourceSpatialResolution();
             sourceReferenceSystem   = object.getSourceReferenceSystem();
             sourceCitation          = object.getSourceCitation();
-            sourceMetadata          = copyCollection(object.getSourceMetadata(), Citation.class);
-            scope                   = object.getScope();
             sourceSteps             = copyCollection(object.getSourceSteps(), ProcessStep.class);
             processedLevel          = object.getProcessedLevel();
             resolution              = object.getResolution();
+            if (object instanceof DefaultSource) {
+                sourceSpatialResolution = ((DefaultSource) object).getSourceSpatialResolution();
+                sourceMetadata          = copyCollection(((DefaultSource) object).getSourceMetadata(), Citation.class);
+                scope                   = ((DefaultSource) object).getScope();
+            } else {
+                setScaleDenominator(object.getScaleDenominator());
+                setSourceExtents(object.getSourceExtents());
+            }
         }
     }
 
@@ -244,9 +255,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "sourceSpatialResolution")
     @XmlJavaTypeAdapter(MD_Resolution.Since2014.class)
+    @UML(identifier="sourceSpatialResolution", obligation=OPTIONAL, specification=ISO_19115)
     public Resolution getSourceSpatialResolution() {
         return sourceSpatialResolution;
     }
@@ -366,8 +377,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="sourceMetadata", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Citation> getSourceMetadata() {
         return sourceMetadata = nonNullCollection(sourceMetadata, Citation.class);
     }
@@ -391,9 +402,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "scope")
     @XmlJavaTypeAdapter(MD_Scope.Since2014.class)
+    @UML(identifier="scope", obligation=CONDITIONAL, specification=ISO_19115)
     public Scope getScope() {
         return scope;
     }
@@ -431,7 +442,7 @@
                         scope = new DefaultScope(scope);
                         this.scope = scope;
                     } else {
-                        return Collections.unmodifiableCollection(scope.getExtents());
+                        return Collections.singleton(scope.getExtent());
                     }
                 }
                 return ((DefaultScope) scope).getExtents();
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
index 7214950..b615f6b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
@@ -30,7 +30,7 @@
 import org.opengis.metadata.maintenance.MaintenanceInformation;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.maintenance.ScopeDescription;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.temporal.PeriodDuration;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.ISOMetadata;
@@ -41,6 +41,10 @@
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.util.CollectionsExt;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
 import static org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
 
 
@@ -89,6 +93,11 @@
     private static final long serialVersionUID = -8736825706141936429L;
 
     /**
+     * New code list item defined in ISO 19115:2014.
+     */
+    private static final DateType NEXT_UPDATE = DateType.valueOf("NEXT_UPDATE");
+
+    /**
      * Frequency with which changes and additions are made to the resource after the
      * initial resource is completed.
      */
@@ -149,11 +158,18 @@
         super(object);
         if (object != null) {
             maintenanceAndUpdateFrequency   = object.getMaintenanceAndUpdateFrequency();
-            maintenanceDates                = copyCollection(object.getMaintenanceDates(), CitationDate.class);
             userDefinedMaintenanceFrequency = object.getUserDefinedMaintenanceFrequency();
-            maintenanceScopes               = copyCollection(object.getMaintenanceScopes(), Scope.class);
             maintenanceNotes                = copyCollection(object.getMaintenanceNotes(), InternationalString.class);
-            contacts                        = copyCollection(object.getContacts(), ResponsibleParty.class);
+            if (object instanceof DefaultMaintenanceInformation) {
+                final DefaultMaintenanceInformation c = (DefaultMaintenanceInformation) object;
+                maintenanceDates                = copyCollection(c.getMaintenanceDates(), CitationDate.class);
+                maintenanceScopes               = copyCollection(c.getMaintenanceScopes(), Scope.class);
+                contacts                        = copyCollection(c.getContacts(), ResponsibleParty.class);
+            } else {
+                setDateOfNextUpdate(object.getDateOfNextUpdate());
+                setUpdateScopes(object.getUpdateScopes());
+                setUpdateScopeDescriptions(object.getUpdateScopeDescriptions());
+            }
         }
     }
 
@@ -212,8 +228,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="maintenanceDate", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<CitationDate> getMaintenanceDates() {
         return maintenanceDates = nonNullCollection(maintenanceDates, CitationDate.class);
     }
@@ -236,8 +252,8 @@
      * @return scheduled revision date, or {@code null}.
      *
      * @deprecated As of ISO 19115:2014, replaced by {@link #getMaintenanceDates()} in order to enable inclusion
-     *             of a {@link DateType} to describe the type of the date. Note that {@link DateType#NEXT_UPDATE}
-     *             was added to that code list.
+     *             of a {@link DateType} to describe the type of the date. The associated date type is
+     *             {@code DateType.valueOf("NEXT_UPDATE")}.
      */
     @Override
     @Deprecated
@@ -246,9 +262,9 @@
     public Date getDateOfNextUpdate() {
         if (FilterByVersion.LEGACY_METADATA.accept()) {
             final Collection<CitationDate> dates = getMaintenanceDates();
-            if (dates != null) {                                                    // May be null on XML marshalling.
+            if (dates != null) {                                                // May be null on XML marshalling.
                 for (final CitationDate date : dates) {
-                    if (DateType.NEXT_UPDATE.equals(date.getDateType())) {
+                    if (NEXT_UPDATE.equals(date.getDateType())) {
                         return date.getDate();
                     }
                 }
@@ -271,7 +287,7 @@
             final Iterator<CitationDate> it = dates.iterator();
             while (it.hasNext()) {
                 final CitationDate date = it.next();
-                if (DateType.NEXT_UPDATE.equals(date.getDateType())) {
+                if (NEXT_UPDATE.equals(date.getDateType())) {
                     if (newValue == null) {
                         it.remove();
                         return;
@@ -283,7 +299,7 @@
             }
         }
         if (newValue != null) {
-            final CitationDate date = new DefaultCitationDate(newValue, DateType.NEXT_UPDATE);
+            final CitationDate date = new DefaultCitationDate(newValue, NEXT_UPDATE);
             if (dates != null) {
                 dates.add(date);
             } else {
@@ -321,8 +337,8 @@
      *
      * @since 0.5
      */
-    @Override
     // @XmlElement at the end of this class.
+    @UML(identifier="maintenanceScope", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Scope> getMaintenanceScopes() {
         return maintenanceScopes = nonNullCollection(maintenanceScopes, Scope.class);
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
index 7aee4f8..df4a6a9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScope.java
@@ -21,12 +21,20 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.maintenance.Scope;
+import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.maintenance.ScopeDescription;
+import org.apache.sis.internal.metadata.Dependencies;
+import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.metadata.iso.ISOMetadata;
 import org.apache.sis.xml.Namespaces;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * The target resource and physical extent for which information is reported.
@@ -114,8 +122,12 @@
         super(object);
         if (object != null) {
             level            = object.getLevel();
-            extents          = copyCollection(object.getExtents(), Extent.class);
             levelDescription = copyCollection(object.getLevelDescription(), ScopeDescription.class);
+            if (object instanceof DefaultScope) {
+                extents = copyCollection(((DefaultScope) object).getExtents(), Extent.class);
+            } else {
+                extents = singleton(object.getExtent(), Extent.class);
+            }
         }
     }
 
@@ -172,8 +184,8 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "extent")
+    @UML(identifier="extent", obligation=OPTIONAL, specification=ISO_19115)
     public Collection<Extent> getExtents() {
         return extents = nonNullCollection(extents, Extent.class);
     }
@@ -190,6 +202,34 @@
     }
 
     /**
+     * Information about the spatial, vertical and temporal extent of the data specified by the scope.
+     * This method fetches the value from the {@linkplain #getExtents() extents} collection.
+     *
+     * @return Information about the extent of the data, or {@code null}.
+     *
+     * @deprecated As of ISO 19115:2014, replaced by {@link #getExtents()}.
+     */
+    @Override
+    @Deprecated
+    @Dependencies("getExtents")
+    public Extent getExtent() {
+        return LegacyPropertyAdapter.getSingleton(getExtents(), Extent.class, null, DefaultScope.class, "getExtent");
+    }
+
+    /**
+     * Sets information about the spatial, vertical and temporal extent of the data specified by the scope.
+     * This method stores the value in the {@linkplain #setExtents(Collection) extents} collection.
+     *
+     * @param newValue The new extent.
+     *
+     * @deprecated As of ISO 19115:2014, replaced by {@link #setExtents(Collection)}.
+     */
+    @Deprecated
+    public void setExtent(final Extent newValue) {
+        setExtents(CollectionsExt.singletonOrEmpty(newValue));
+    }
+
+    /**
      * Returns detailed descriptions about the level of the data specified by the scope.
      *
      * @return detailed description about the level of the data.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
index 0ce33fe..2534760 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/package-info.java
@@ -41,8 +41,8 @@
  * {@code  ├─} {@linkplain org.apache.sis.metadata.iso.DefaultExtendedElementInformation   Extended element information}<br>
  * {@code  └─} {@linkplain org.apache.sis.metadata.iso.DefaultIdentifier                   Identifier}<br>
  * {@linkplain org.opengis.util.CodeList Code list}<br>
- * {@code  ├─} {@linkplain org.opengis.metadata.Datatype     Data type}<br>
- * {@code  └─} {@linkplain org.opengis.annotation.Obligation Obligation}<br>
+ * {@code  ├─} {@linkplain org.opengis.metadata.Datatype   Data type}<br>
+ * {@code  └─} {@linkplain org.opengis.metadata.Obligation Obligation}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
  *                     {@linkplain org.apache.sis.metadata.iso.DefaultMetadata                     Metadata}<br>
  * {@code  ├─}         {@linkplain org.apache.sis.metadata.iso.DefaultMetadataScope                Metadata scope}<br>
@@ -51,7 +51,7 @@
  * {@code  ├─}         {@linkplain org.apache.sis.metadata.iso.DefaultMetadataExtensionInformation Metadata extension information}<br>
  * {@code  │   └─}     {@linkplain org.apache.sis.metadata.iso.DefaultExtendedElementInformation   Extended element information}<br>
  * {@code  │       ├─} {@linkplain org.opengis.metadata.Datatype                                   Data type} «code list»<br>
- * {@code  │       └─} {@linkplain org.opengis.annotation.Obligation                               Obligation} «code list»<br>
+ * {@code  │       └─} {@linkplain org.opengis.metadata.Obligation                                 Obligation} «code list»<br>
  * {@code  └─}         {@linkplain org.apache.sis.metadata.iso.DefaultIdentifier                   Identifier}<br>
  * </td></tr></table>
  *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
index 7709409..9cb894d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultDataQuality.java
@@ -26,6 +26,7 @@
 import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.metadata.iso.ISOMetadata;
+import org.apache.sis.metadata.iso.maintenance.DefaultScope;
 import org.apache.sis.internal.jaxb.FilterByVersion;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java
index 0be9e86..c84c26e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultScope.java
@@ -16,14 +16,9 @@
  */
 package org.apache.sis.metadata.iso.quality;
 
-import java.util.Collection;
 import javax.xml.bind.annotation.XmlTransient;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.quality.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.apache.sis.internal.metadata.Dependencies;
-import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
-import org.apache.sis.internal.util.CollectionsExt;
 
 
 /**
@@ -107,32 +102,4 @@
         }
         return new DefaultScope(object);
     }
-
-    /**
-     * Information about the spatial, vertical and temporal extent of the data specified by the scope.
-     * This method fetches the value from the {@linkplain #getExtents() extents} collection.
-     *
-     * @return information about the extent of the data, or {@code null}.
-     *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #getExtents()}.
-     */
-    @Override
-    @Deprecated
-    @Dependencies("getExtents")
-    public Extent getExtent() {
-        return LegacyPropertyAdapter.getSingleton(getExtents(), Extent.class, null, DefaultScope.class, "getExtent");
-    }
-
-    /**
-     * Sets information about the spatial, vertical and temporal extent of the data specified by the scope.
-     * This method stores the value in the {@linkplain #setExtents(Collection) extents} collection.
-     *
-     * @param  newValue  the new extent.
-     *
-     * @deprecated As of ISO 19115:2014, replaced by {@link #setExtents(Collection)}.
-     */
-    @Deprecated
-    public void setExtent(final Extent newValue) {
-        setExtents(CollectionsExt.singletonOrEmpty(newValue));
-    }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
index 4d344a0..55eadfc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
@@ -32,6 +32,11 @@
 
 import static org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
 
+// Branch-specific imports
+import org.opengis.annotation.UML;
+import static org.opengis.annotation.Obligation.OPTIONAL;
+import static org.opengis.annotation.Specification.ISO_19115;
+
 
 /**
  * Axis properties.
@@ -142,8 +147,10 @@
             dimensionName        = object.getDimensionName();
             dimensionSize        = object.getDimensionSize();
             resolution           = object.getResolution();
-            dimensionTitle       = object.getDimensionTitle();
-            dimensionDescription = object.getDimensionDescription();
+            if (object instanceof DefaultDimension) {
+                dimensionTitle       = ((DefaultDimension) object).getDimensionTitle();
+                dimensionDescription = ((DefaultDimension) object).getDimensionDescription();
+            }
         }
     }
 
@@ -254,9 +261,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "dimensionTitle")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="dimensionTitle", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDimensionTitle() {
         return dimensionTitle;
     }
@@ -280,9 +287,9 @@
      *
      * @since 0.5
      */
-    @Override
     @XmlElement(name = "dimensionDescription")
     @XmlJavaTypeAdapter(InternationalStringAdapter.Since2014.class)
+    @UML(identifier="dimensionDescription", obligation=OPTIONAL, specification=ISO_19115)
     public InternationalString getDimensionDescription() {
         return dimensionDescription;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
index 68933fa..4ce3bba 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
@@ -30,7 +30,7 @@
  * defined in the {@link org.opengis.metadata} package and sub-packages. That standard is identified in SIS by the
  * {@link org.apache.sis.metadata.MetadataStandard#ISO_19115} constant. Other standards are defined as well,
  * for example the {@link org.apache.sis.metadata.MetadataStandard#ISO_19123} constant stands for the standards
- * defined by the interfaces in the {@link org.opengis.coverage} package and sub-packages.
+ * defined by the interfaces in the {@code org.opengis.coverage} package and sub-packages.
  *
  * <p>For each interface, the collection of declared getter methods defines its <cite>properties</cite>
  * (or <cite>attributes</cite>). If a {@link org.opengis.annotation.UML} annotation is attached to the getter method,
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
index d00febf..c9d5fe6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
@@ -34,8 +34,8 @@
 import org.apache.sis.internal.util.Numerics;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
@@ -161,8 +161,13 @@
                  */
                 Object value;
                 try {
-                    method = supercede(method);
+                    final long nb = nullValues;
                     value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                    if (value == null) {
+                        nullValues = nb;
+                        method = supercede(method);
+                        value = fetchValue(source.getLookupInfo(method.getDeclaringClass()), method);
+                    }
                 } catch (ReflectiveOperationException | SQLException | MetadataStoreException e) {
                     throw new BackingStoreException(error(method), e);
                 }
@@ -332,7 +337,7 @@
      */
     private static Method supercede(Method method) throws NoSuchMethodException {
         if (method.getDeclaringClass() == ResponsibleParty.class && "getRole".equals(method.getName())) {
-            method = Responsibility.class.getMethod("getRole");
+            method = DefaultResponsibility.class.getMethod("getRole");
         }
         return method;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
index be835d8..8d35a87 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataFallback.java
@@ -16,7 +16,7 @@
  */
 package org.apache.sis.metadata.sql;
 
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.PresentationForm;
@@ -81,7 +81,7 @@
         ArgumentChecks.ensureNonNull("type", type);
         ArgumentChecks.ensureNonEmpty("identifier", identifier);
         Object value;
-        if (ControlledVocabulary.class.isAssignableFrom(type)) {
+        if (CodeList.class.isAssignableFrom(type)) {
             value = getCodeList(type, identifier);
         } else {
             value = null;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
index 17dad47..11341f2 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java
@@ -46,7 +46,6 @@
 import java.sql.PreparedStatement;
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.FactoryException;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.KeyNamePolicy;
@@ -73,6 +72,9 @@
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.iso.Types;
 
+// Branch-dependent imports
+import org.apache.sis.internal.geoapi.evolution.Interim;
+
 
 /**
  * A connection to a metadata database in read-only mode. It can be either the database
@@ -653,8 +655,8 @@
              * be present in the database in order to ensure foreigner key constraints, but
              * those tables are not used in any way by the org.apache.sis.metadata.sql package.
              */
-            if (metadata instanceof ControlledVocabulary) {
-                identifier = Types.getCodeName((ControlledVocabulary) metadata);
+            if (metadata instanceof CodeList<?>) {
+                identifier = Types.getCodeName((CodeList<?>) metadata);
             } else if (metadata instanceof Enum<?>) {
                 identifier = ((Enum<?>) metadata).name();
             } else {
@@ -720,8 +722,8 @@
              * Note that if a metadata dependency is not found, we can stop the whole process immediately.
              */
             if (value != null) {
-                if (value instanceof ControlledVocabulary) {
-                    value = Types.getCodeName((ControlledVocabulary) value);
+                if (value instanceof CodeList<?>) {
+                    value = Types.getCodeName((CodeList<?>) value);
                 } else if (value instanceof Enum<?>) {
                     value = ((Enum<?>) value).name();
                 } else {
@@ -819,7 +821,7 @@
      *
      * @param  <T>         the parameterized type of the {@code type} argument.
      * @param  type        the interface to implement (e.g. {@link org.opengis.metadata.citation.Citation}),
-     *                     or the {@link ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
+     *                     or the {@code ControlledVocabulary} type ({@link CodeList} or some {@link Enum}).
      * @param  identifier  the identifier of the record for the metadata entity to be created.
      *                     This is usually the primary key of the record to search for.
      * @return an implementation of the required interface, or the code list element.
@@ -829,7 +831,7 @@
         ArgumentChecks.ensureNonNull("type", type);
         ArgumentChecks.ensureNonEmpty("identifier", identifier);
         Object value;
-        if (ControlledVocabulary.class.isAssignableFrom(type)) {
+        if (CodeList.class.isAssignableFrom(type)) {
             value = getCodeList(type, identifier);
         } else {
             final CacheKey key = new CacheKey(type, identifier);
@@ -911,7 +913,7 @@
          * the name between bracket is a subtype of the given 'type' argument.
          */
         final Class<?> type           = TableHierarchy.subType(info.getMetadataType(), toSearch.identifier);
-        final Class<?> returnType     = method.getReturnType();
+        final Class<?> returnType     = Interim.getReturnType(method);
         final boolean  wantCollection = Collection.class.isAssignableFrom(returnType);
         final Class<?> elementType    = wantCollection ? Classes.boundOfParameterizedProperty(method) : returnType;
         final boolean  isMetadata     = standard.isMetadata(elementType);
@@ -1002,12 +1004,8 @@
      * message when the actual class is unknown (it must have been checked dynamically by the caller however).
      */
     @SuppressWarnings("unchecked")
-    static ControlledVocabulary getCodeList(final Class<?> type, final String name) {
-        if (type.isEnum()) {
-            return (ControlledVocabulary) Types.forEnumName(type.asSubclass(Enum.class), name);
-        } else {
-            return Types.forCodeName(type.asSubclass(CodeList.class), name, true);
-        }
+    static CodeList<?> getCodeList(final Class<?> type, final String name) {
+        return Types.forCodeName(type.asSubclass(CodeList.class), name, true);
     }
 
     /**
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
index 9cf1602..dfe230f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataWriter.java
@@ -33,6 +33,7 @@
 import javax.sql.DataSource;
 import java.lang.reflect.Modifier;
 
+import org.opengis.util.CodeList;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.util.FactoryException;
@@ -55,7 +56,7 @@
 import org.apache.sis.xml.IdentifiedObject;
 
 // Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -194,8 +195,8 @@
                 boolean success = false;
                 try {
                     try (Statement stmt = connection.createStatement()) {
-                        if (metadata instanceof ControlledVocabulary) {
-                            identifier = addCode(stmt, (ControlledVocabulary) metadata);
+                        if (metadata instanceof CodeList<?>) {
+                            identifier = addCode(stmt, (CodeList<?>) metadata);
                         } else {
                             identifier = add(stmt, metadata, new IdentityHashMap<>(), null);
                         }
@@ -319,7 +320,7 @@
                  */
                 int maxLength = maximumValueLength;
                 Class<?> rt = colTypes.get(column);
-                final boolean isCodeList = ControlledVocabulary.class.isAssignableFrom(rt);     // Accept also some enums.
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
                 if (isCodeList || standard.isMetadata(rt)) {
                     /*
                      * Found a reference to an other metadata. Remind that column for creating a foreign key
@@ -409,8 +410,8 @@
         for (final Map.Entry<String,Object> entry : asSingletons.entrySet()) {
             Object value = entry.getValue();
             final Class<?> type = value.getClass();
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
-                value = addCode(stmt, (ControlledVocabulary) value);
+            if (CodeList.class.isAssignableFrom(type)) {
+                value = addCode(stmt, (CodeList<?>) value);
             } else if (type.isEnum()) {
                 value = ((Enum<?>) value).name();
             } else if (standard.isMetadata(type)) {
@@ -487,7 +488,7 @@
             for (final Map.Entry<String,FKey> entry : foreigners.entrySet()) {
                 final FKey fkey = entry.getValue();
                 Class<?> rt = fkey.tableType;
-                final boolean isCodeList = ControlledVocabulary.class.isAssignableFrom(rt);
+                final boolean isCodeList = CodeList.class.isAssignableFrom(rt);
                 final String primaryKey;
                 if (isCodeList) {
                     primaryKey = CODE_COLUMN;
@@ -647,11 +648,9 @@
     /**
      * Adds a code list if it is not already present. This is used only in order to enforce
      * foreigner key constraints in the database. The value of CodeList tables are not used
-     * at parsing time. Enumerations are handled as if they were CodeLists; we do not use
-     * the native SQL {@code ENUM} type for making easier to add new values when a standard
-     * is updated.
+     * at parsing time.
      */
-    private String addCode(final Statement stmt, final ControlledVocabulary code) throws SQLException, FactoryException {
+    private String addCode(final Statement stmt, final CodeList<?> code) throws SQLException, FactoryException {
         assert Thread.holdsLock(this);
         final String table = getTableName(code.getClass());
         final Set<String> columns = getExistingColumns(table);
@@ -706,9 +705,11 @@
         for (final Identifier id : identifiers) {
             identifier = nonEmpty(id.getCode());
             if (identifier != null) {
-                final String cs = nonEmpty(id.getCodeSpace());
-                if (cs != null) {
-                    identifier = cs + Constants.DEFAULT_SEPARATOR + identifier;
+                if (id instanceof ReferenceIdentifier) {
+                    final String cs = nonEmpty(((ReferenceIdentifier) id).getCodeSpace());
+                    if (cs != null) {
+                        identifier = cs + Constants.DEFAULT_SEPARATOR + identifier;
+                    }
                 }
                 break;
             }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
index 1fb3f6c..e92ab89 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
@@ -226,7 +226,6 @@
      *
      * @see Names#createMemberName(CharSequence, String, CharSequence, Class)
      */
-    @Override
     public MemberName createMemberName(final NameSpace scope, final CharSequence name, final TypeName attributeType) {
         return pool.unique(new DefaultMemberName(scope, name, attributeType));
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
index 149f2a8..fb30330 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordSchema.java
@@ -29,7 +29,6 @@
 import org.opengis.util.NameSpace;
 import org.opengis.util.RecordSchema;
 import org.opengis.util.RecordType;
-import org.opengis.feature.AttributeType;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ObjectConverter;
 import org.apache.sis.util.ObjectConverters;
@@ -83,14 +82,13 @@
     /**
      * The factory to use for creating names.
      * This is the factory given at construction time.
+     *
+     * <div class="warning"><b>Upcoming API change</b> — generalization<br>
+     * This field type will be changed to the {@link NameFactory} interface when that interface
+     * will provide a {@code createMemberName(…)} method (tentatively in GeoAPI 3.1).
+     * </div>
      */
-    protected final NameFactory nameFactory;
-
-    /**
-     * The helper class to use for mapping Java classes to {@code TypeName} instances, or {@code null} if not needed.
-     * This helper class is needed only if {@link #nameFactory} is not an instance of {@link DefaultNameFactory}.
-     */
-    private final TypeNames typeFactory;
+    protected final DefaultNameFactory nameFactory;
 
     /**
      * The namespace of {@link RecordType} to be created by this class.
@@ -122,17 +120,21 @@
     /**
      * Creates a new schema of the given name.
      *
+     * <div class="warning"><b>Upcoming API change</b> — generalization<br>
+     * This type of the first argument will be changed to the {@link NameFactory} interface when
+     * that interface will provide a {@code createMemberName(…)} method (tentatively in GeoAPI 3.1).
+     * </div>
+     *
      * @param nameFactory  the factory to use for creating names, or {@code null} for the default factory.
      * @param parent       the parent namespace, or {@code null} if none.
      * @param schemaName   the name of the new schema.
      */
-    public DefaultRecordSchema(NameFactory nameFactory, final NameSpace parent, final CharSequence schemaName) {
+    public DefaultRecordSchema(DefaultNameFactory nameFactory, final NameSpace parent, final CharSequence schemaName) {
         ArgumentChecks.ensureNonNull("schemaName", schemaName);
         if (nameFactory == null) {
-            nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+            nameFactory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
         }
         this.nameFactory    = nameFactory;
-        this.typeFactory    = (nameFactory instanceof DefaultNameFactory) ? null : new TypeNames(nameFactory);
         this.namespace      = nameFactory.createNameSpace(nameFactory.createLocalName(parent, schemaName), null);
         this.description    = new WeakValueHashMap<>(TypeName.class);
         this.attributeTypes = new ConcurrentHashMap<>();
@@ -188,7 +190,7 @@
             if (!e2.getKey().tip().toString().equals(e1.toString())) {
                 break;                                                  // Member names differ.
             }
-            if (!((AttributeType) e2.getValue()).getValueClass().equals(e1.getValue())) {
+            if (!((SimpleAttributeType) e2.getValue()).getValueClass().equals(e1.getValue())) {
                 break;                                                  // Value classes differ.
             }
         }
@@ -212,12 +214,7 @@
             if (valueClass == Void.TYPE) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "valueClass", "void"));
             }
-            final TypeName name;
-            if (nameFactory instanceof DefaultNameFactory) {
-                name = ((DefaultNameFactory) nameFactory).toTypeName(valueClass);
-            } else {
-                name = typeFactory.toTypeName(nameFactory, valueClass);
-            }
+            final TypeName name = nameFactory.toTypeName(valueClass);
             type = new SimpleAttributeType<>(name, valueClass);
             final Type old = attributeTypes.putIfAbsent(valueClass, type);
             if (old != null) {      // May happen if the type has been computed concurrently.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
index d107128..c5ffcc6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultRecordType.java
@@ -34,7 +34,6 @@
 import org.opengis.util.MemberName;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameSpace;
-import org.opengis.util.NameFactory;
 import org.opengis.util.Record;
 import org.opengis.util.RecordType;
 import org.opengis.util.RecordSchema;
@@ -195,7 +194,7 @@
      * @param nameFactory  the factory to use for instantiating {@link MemberName}.
      */
     DefaultRecordType(final TypeName typeName, final RecordSchema container,
-            final Map<? extends CharSequence, ? extends Type> members, final NameFactory nameFactory)
+            final Map<? extends CharSequence, ? extends Type> members, final DefaultNameFactory nameFactory)
     {
         this.typeName  = typeName;
         this.container = container;
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
index cad4682..6262a0b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/RecordDefinition.java
@@ -26,12 +26,12 @@
 import org.opengis.util.Type;
 import org.opengis.util.RecordType;
 import org.opengis.util.MemberName;
-import org.opengis.feature.AttributeType;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.internal.simple.SimpleAttributeType;
 
 
 /**
@@ -152,8 +152,8 @@
         int i = 0;
         for (final Map.Entry<? extends MemberName, ? extends Type> entry : memberTypes.entrySet()) {
             final Type type = entry.getValue();
-            if (type instanceof AttributeType) {
-                final Class<?> c = ((AttributeType) type).getValueClass();
+            if (type instanceof SimpleAttributeType) {
+                final Class<?> c = ((SimpleAttributeType) type).getValueClass();
                 if (c != Object.class) {
                     if (valueClasses == null) {
                         valueClasses = new Class<?>[size];
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java
index fda82dc..5b3bf4b 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Types.java
@@ -29,7 +29,6 @@
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
 import org.opengis.util.InternationalString;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.Locales;
 import org.apache.sis.util.CharSequences;
@@ -48,14 +47,14 @@
  *
  * <ul>
  *   <li>Methods for fetching the ISO name or description of a code list:<ul>
- *     <li>{@link #getStandardName(Class)}            for ISO name</li>
- *     <li>{@link #getListName(ControlledVocabulary)} for ISO name</li>
- *     <li>{@link #getDescription(Class)}             for a description</li>
+ *     <li>{@link #getStandardName(Class)}   for ISO name</li>
+ *     <li>{@link #getListName(CodeList)}    for ISO name</li>
+ *     <li>{@link #getDescription(Class)}    for a description</li>
  *   </ul></li>
  *   <li>Methods for fetching the ISO name or description of a code value:<ul>
- *     <li>{@link #getCodeName(ControlledVocabulary)}    for ISO name,</li>
- *     <li>{@link #getCodeTitle(ControlledVocabulary)}   for a label or title</li>
- *     <li>{@link #getDescription(ControlledVocabulary)} for a more verbose description</li>
+ *     <li>{@link #getCodeName(CodeList)}    for ISO name,</li>
+ *     <li>{@link #getCodeTitle(CodeList)}   for a label or title</li>
+ *     <li>{@link #getDescription(CodeList)} for a more verbose description</li>
  *   </ul></li>
  *   <li>Methods for fetching an instance from a name (converse of above {@code get} methods):<ul>
  *     <li>{@link #forCodeName(Class, String, boolean)}</li>
@@ -85,7 +84,7 @@
  * Such substitution can be done with:
  *
  * <ul>
- *   <li>{@link #getCodeTitle(ControlledVocabulary)} for getting the {@link InternationalString} instance
+ *   <li>{@link #getCodeTitle(CodeList)} for getting the {@link InternationalString} instance
  *       to store in a metadata property.</li>
  *   <li>{@link #forCodeTitle(CharSequence)} for retrieving the {@link CodeList} previously stored as an
  *       {@code InternationalString}.</li>
@@ -176,11 +175,11 @@
      * @param  code  the code for which to get the class name, or {@code null}.
      * @return the ISO (preferred) or Java (fallback) class name, or {@code null} if the given code is null.
      */
-    public static String getListName(final ControlledVocabulary code) {
+    public static String getListName(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
-        final Class<?> type = (code instanceof Enum<?>) ? ((Enum<?>) code).getDeclaringClass() : code.getClass();
+        final Class<?> type = code.getClass();
         final String id = getStandardName(type);
         return (id != null) ? id : type.getSimpleName();
     }
@@ -201,12 +200,12 @@
      * @param  code  the code for which to get the name, or {@code null}.
      * @return the UML identifiers or programmatic name for the given code, or {@code null} if the given code is null.
      *
-     * @see #getCodeLabel(ControlledVocabulary)
-     * @see #getCodeTitle(ControlledVocabulary)
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getCodeLabel(CodeList)
+     * @see #getCodeTitle(CodeList)
+     * @see #getDescription(CodeList)
      * @see #forCodeName(Class, String, boolean)
      */
-    public static String getCodeName(final ControlledVocabulary code) {
+    public static String getCodeName(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
@@ -218,7 +217,7 @@
      * Returns a unlocalized title for the given enumeration or code list value.
      * This method builds a title using heuristics rules, which should give reasonable
      * results without the need of resource bundles. For better results, consider using
-     * {@link #getCodeTitle(ControlledVocabulary)} instead.
+     * {@link #getCodeTitle(CodeList)} instead.
      *
      * <p>The current heuristic implementation iterates over {@linkplain CodeList#names() all code names},
      * selects the longest one excluding the {@linkplain CodeList#name() field name} if possible, then
@@ -235,11 +234,11 @@
      * @param  code  the code from which to get a title, or {@code null}.
      * @return a unlocalized title for the given code, or {@code null} if the given code is null.
      *
-     * @see #getCodeName(ControlledVocabulary)
-     * @see #getCodeTitle(ControlledVocabulary)
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getCodeName(CodeList)
+     * @see #getCodeTitle(CodeList)
+     * @see #getDescription(CodeList)
      */
-    public static String getCodeLabel(final ControlledVocabulary code) {
+    public static String getCodeLabel(final CodeList<?> code) {
         if (code == null) {
             return null;
         }
@@ -258,7 +257,7 @@
 
     /**
      * Returns the title of the given enumeration or code list value. Title are usually much shorter than descriptions.
-     * English titles are often the same than the {@linkplain #getCodeLabel(ControlledVocabulary) code labels}.
+     * English titles are often the same than the {@linkplain #getCodeLabel(CodeList) code labels}.
      *
      * <p>The code or enumeration value given in argument to this method can be retrieved from the returned title
      * with the {@link #forCodeTitle(CharSequence)} method. See <cite>Substituting a free text by a code list</cite>
@@ -267,10 +266,10 @@
      * @param  code  the code for which to get the title, or {@code null}.
      * @return the title, or {@code null} if the given code is null.
      *
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getDescription(CodeList)
      * @see #forCodeTitle(CharSequence)
      */
-    public static InternationalString getCodeTitle(final ControlledVocabulary code) {
+    public static InternationalString getCodeTitle(final CodeList<?> code) {
         return (code != null) ? new CodeTitle(code) : null;
     }
 
@@ -282,10 +281,10 @@
      * @param  code  the code for which to get the localized description, or {@code null}.
      * @return the description, or {@code null} if none or if the given code is null.
      *
-     * @see #getCodeTitle(ControlledVocabulary)
+     * @see #getCodeTitle(CodeList)
      * @see #getDescription(Class)
      */
-    public static InternationalString getDescription(final ControlledVocabulary code) {
+    public static InternationalString getDescription(final CodeList<?> code) {
         if (code != null) {
             final String resources = getResources(code.getClass().getName());
             if (resources != null) {
@@ -302,7 +301,7 @@
      * @param  type  the GeoAPI interface or code list from which to get the description, or {@code null}.
      * @return the description, or {@code null} if none or if the given type is {@code null}.
      *
-     * @see #getDescription(ControlledVocabulary)
+     * @see #getDescription(CodeList)
      */
     public static InternationalString getDescription(final Class<?> type) {
         final String name = getStandardName(type);
@@ -401,7 +400,7 @@
         /**
          * Returns the resource key for the given code list.
          */
-        static String resourceKey(final ControlledVocabulary code) {
+        static String resourceKey(final CodeList<?> code) {
             String key = getCodeName(code);
             if (key.indexOf(SEPARATOR) < 0) {
                 key = getListName(code) + SEPARATOR + key;
@@ -431,14 +430,14 @@
         /**
          * The code list for which to create a title.
          */
-        final ControlledVocabulary code;
+        final CodeList<?> code;
 
         /**
          * Creates a new international string for the given code list element.
          *
          * @param  code  the code list for which to create a title.
          */
-        CodeTitle(final ControlledVocabulary code) {
+        CodeTitle(final CodeList<?> code) {
             super(CodeLists.RESOURCES, resourceKey(code));
             this.code = code;
         }
@@ -468,21 +467,17 @@
     }
 
     /**
-     * Returns all known values for the given type of code list or enumeration.
+     * Returns all known values for the given type of code list.
      * Note that the size of the returned array may growth between different invocations of this method,
      * since users can add their own codes to an existing list.
      *
-     * <div class="note"><b>Note:</b>
-     * This method works with both {@link Enum} and {@link CodeList}. However if the type is known to be an
-     * {@code Enum}, then the standard {@link Class#getEnumConstants()} method is more efficient.</div>
-     *
      * @param  <T>       the compile-time type given as the {@code codeType} parameter.
-     * @param  codeType  the type of code list or enumeration.
-     * @return the list of values for the given code list or enumeration, or an empty array if none.
+     * @param  codeType  the type of code list.
+     * @return the list of values for the given code list, or an empty array if none.
      *
      * @see Class#getEnumConstants()
      */
-    public static <T extends ControlledVocabulary> T[] getCodeValues(final Class<T> codeType) {
+    public static <T extends CodeList<?>> T[] getCodeValues(final Class<T> codeType) {
         return CodeLists.values(codeType);
     }
 
@@ -523,7 +518,7 @@
             return null;
         }
         if (typeForNames == null) {
-            final Class<UML> c = UML.class;
+            final Class<Types> c = Types.class;
             final InputStream in = c.getResourceAsStream("class-index.properties");
             if (in == null) {
                 throw new MissingResourceException("class-index.properties", c.getName(), identifier);
@@ -620,7 +615,7 @@
      * @return a code matching the given name, or {@code null} if the name is null
      *         or if no matching code is found and {@code canCreate} is {@code false}.
      *
-     * @see #getCodeName(ControlledVocabulary)
+     * @see #getCodeName(CodeList)
      * @see CodeList#valueOf(Class, String)
      */
     public static <T extends CodeList<T>> T forCodeName(final Class<T> codeType, String name, final boolean canCreate) {
@@ -632,7 +627,7 @@
      * The current implementation performs the following choice:
      *
      * <ul>
-     *   <li>If the given title is a value returned by a previous call to {@link #getCodeTitle(ControlledVocabulary)},
+     *   <li>If the given title is a value returned by a previous call to {@link #getCodeTitle(CodeList)},
      *       returns the code or enumeration value used for creating that title.</li>
      *   <li>Otherwise returns {@code null}.</li>
      * </ul>
@@ -640,11 +635,11 @@
      * @param  title  the title for which to get a code or enumeration value, or {@code null}.
      * @return the code or enumeration value associated with the given title, or {@code null}.
      *
-     * @see #getCodeTitle(ControlledVocabulary)
+     * @see #getCodeTitle(CodeList)
      *
      * @since 0.7
      */
-    public static ControlledVocabulary forCodeTitle(final CharSequence title) {
+    public static CodeList<?> forCodeTitle(final CharSequence title) {
         return (title instanceof CodeTitle) ? ((CodeTitle) title).code : null;
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/package-info.java
index d2b676e..2b4ab8e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/package-info.java
@@ -26,7 +26,7 @@
  *       <li>{@link org.apache.sis.util.iso.SimpleInternationalString}   for wrapping a single {@link java.lang.String};</li>
  *       <li>{@link org.apache.sis.util.iso.DefaultInternationalString}  for providing many localizations in a {@link java.util.Map};</li>
  *       <li>{@link org.apache.sis.util.iso.ResourceInternationalString} for providing localizations from a {@link java.util.ResourceBundle}.</li>
- *       <li>{@link org.apache.sis.util.iso.Types#getCodeTitle Types.getCodeTitle(ControlledVocabulary)} for wrapping a {@link org.opengis.util.CodeList} value.</li>
+ *       <li>{@link org.apache.sis.util.iso.Types#getCodeTitle Types.getCodeTitle(CodeList)} for wrapping a {@link org.opengis.util.CodeList} value.</li>
  *     </ul>
  *   </li>
  *   <li>Implementations of {@link org.opengis.util.GenericName} (derived from ISO 19103):
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java
index 5ba2dc4..65f4f7e 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/LegacyCodes.java
@@ -19,13 +19,8 @@
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Locale;
-import java.util.Properties;
-import java.io.InputStream;
-import java.io.IOException;
-import org.opengis.metadata.Metadata;
-import org.apache.sis.internal.system.Loggers;
+import org.opengis.metadata.identification.CharacterSet;
 import org.apache.sis.util.collection.Containers;
-import org.apache.sis.util.logging.Logging;
 
 
 /**
@@ -50,18 +45,15 @@
      */
     static final Map<String,String> IANA_TO_LEGACY, LEGACY_TO_IANA;
     static {
-        final Properties codes = new Properties();
-        try (InputStream in = Metadata.class.getResourceAsStream("2003/charset-codes.properties")) {
-            codes.load(in);
-        } catch (IOException e) {
-            Logging.unexpectedException(Logging.getLogger(Loggers.XML), ValueConverter.class, "toCharset[Code]", e);
-        }
-        final int capacity = Containers.hashMapCapacity(codes.size());
+        final CharacterSet[] codes = CharacterSet.values();
+        final int capacity = Containers.hashMapCapacity(codes.length);
         IANA_TO_LEGACY = new HashMap<>(capacity);
         LEGACY_TO_IANA = new HashMap<>(capacity);
-        for (final Map.Entry<Object,Object> entry : codes.entrySet()) {
-            final String legacy = ((String) entry.getKey()).intern();
-            final String name   = ((String) entry.getValue()).intern();
+        for (final CharacterSet code : codes) {
+            final String   legacy = code.identifier().intern();
+            final String[] names  = code.names();
+            String name = names[names.length - 1];
+            if (name.equals("ebcdic")) name = "EBCDIC"; // Missing IANA name in GeoAPI CharacterSet.
             IANA_TO_LEGACY.put(name  .toUpperCase(Locale.US), legacy); // IANA names are restricted to US-ASCII.
             LEGACY_TO_IANA.put(legacy.toLowerCase(Locale.US), name);
             IANA_TO_LEGACY.put(name, legacy);
diff --git a/core/sis-metadata/src/main/resources/org/apache/sis/util/iso/class-index.properties b/core/sis-metadata/src/main/resources/org/apache/sis/util/iso/class-index.properties
new file mode 100644
index 0000000..e44a58b
--- /dev/null
+++ b/core/sis-metadata/src/main/resources/org/apache/sis/util/iso/class-index.properties
@@ -0,0 +1,229 @@
+#
+# This is an automatically generated file. The same content is provided by GeoAPI 3.1
+# in the org.opengis.annotation package. Since this file does not exist in GeoAPI 3.0,
+# the Apache SIS branch for GeoAPI 3.0 maintains this copy (provided by the original
+# author).
+#
+CC_ConcatenatedOperation=org.opengis.referencing.operation.ConcatenatedOperation
+CC_Conversion=org.opengis.referencing.operation.Conversion
+CC_CoordinateOperation=org.opengis.referencing.operation.CoordinateOperation
+CC_Formula=org.opengis.referencing.operation.Formula
+CC_GeneralOperationParameter=org.opengis.parameter.GeneralParameterDescriptor
+CC_GeneralParameterValue=org.opengis.parameter.GeneralParameterValue
+CC_OperationMethod=org.opengis.referencing.operation.OperationMethod
+CC_OperationParameter=org.opengis.parameter.ParameterDescriptor
+CC_OperationParameterGroup=org.opengis.parameter.ParameterDescriptorGroup
+CC_ParameterValue=org.opengis.parameter.ParameterValue
+CC_ParameterValueGroup=org.opengis.parameter.ParameterValueGroup
+CC_PassThroughOperation=org.opengis.referencing.operation.PassThroughOperation
+CC_SingleOperation=org.opengis.referencing.operation.SingleOperation
+CC_Transformation=org.opengis.referencing.operation.Transformation
+CD_Datum=org.opengis.referencing.datum.Datum
+CD_Ellipsoid=org.opengis.referencing.datum.Ellipsoid
+CD_EngineeringDatum=org.opengis.referencing.datum.EngineeringDatum
+CD_GeodeticDatum=org.opengis.referencing.datum.GeodeticDatum
+CD_ImageDatum=org.opengis.referencing.datum.ImageDatum
+CD_PixelInCell=org.opengis.referencing.datum.PixelInCell
+CD_PrimeMeridian=org.opengis.referencing.datum.PrimeMeridian
+CD_TemporalDatum=org.opengis.referencing.datum.TemporalDatum
+CD_VerticalDatum=org.opengis.referencing.datum.VerticalDatum
+CD_VerticalDatumType=org.opengis.referencing.datum.VerticalDatumType
+CI_Address=org.opengis.metadata.citation.Address
+CI_Citation=org.opengis.metadata.citation.Citation
+CI_Contact=org.opengis.metadata.citation.Contact
+CI_Date=org.opengis.metadata.citation.CitationDate
+CI_DateTypeCode=org.opengis.metadata.citation.DateType
+CI_OnLineFunctionCode=org.opengis.metadata.citation.OnLineFunction
+CI_OnlineResource=org.opengis.metadata.citation.OnlineResource
+CI_PresentationFormCode=org.opengis.metadata.citation.PresentationForm
+CI_ResponsibleParty=org.opengis.metadata.citation.ResponsibleParty
+CI_RoleCode=org.opengis.metadata.citation.Role
+CI_Series=org.opengis.metadata.citation.Series
+CI_Telephone=org.opengis.metadata.citation.Telephone
+CS_AffineCS=org.opengis.referencing.cs.AffineCS
+CS_AxisDirection=org.opengis.referencing.cs.AxisDirection
+CS_CartesianCS=org.opengis.referencing.cs.CartesianCS
+CS_CoordinateSystem=org.opengis.referencing.cs.CoordinateSystem
+CS_CoordinateSystemAxis=org.opengis.referencing.cs.CoordinateSystemAxis
+CS_CylindricalCS=org.opengis.referencing.cs.CylindricalCS
+CS_EllipsoidalCS=org.opengis.referencing.cs.EllipsoidalCS
+CS_LinearCS=org.opengis.referencing.cs.LinearCS
+CS_PolarCS=org.opengis.referencing.cs.PolarCS
+CS_RangeMeaning=org.opengis.referencing.cs.RangeMeaning
+CS_SphericalCS=org.opengis.referencing.cs.SphericalCS
+CS_TimeCS=org.opengis.referencing.cs.TimeCS
+CS_UserDefinedCS=org.opengis.referencing.cs.UserDefinedCS
+CS_VerticalCS=org.opengis.referencing.cs.VerticalCS
+DQ_AbsoluteExternalPositionalAccuracy=org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy
+DQ_AccuracyOfATimeMeasurement=org.opengis.metadata.quality.AccuracyOfATimeMeasurement
+DQ_Completeness=org.opengis.metadata.quality.Completeness
+DQ_CompletenessCommission=org.opengis.metadata.quality.CompletenessCommission
+DQ_CompletenessOmission=org.opengis.metadata.quality.CompletenessOmission
+DQ_ConceptualConsistency=org.opengis.metadata.quality.ConceptualConsistency
+DQ_ConformanceResult=org.opengis.metadata.quality.ConformanceResult
+DQ_DataQuality=org.opengis.metadata.quality.DataQuality
+DQ_DomainConsistency=org.opengis.metadata.quality.DomainConsistency
+DQ_Element=org.opengis.metadata.quality.Element
+DQ_EvaluationMethodTypeCode=org.opengis.metadata.quality.EvaluationMethodType
+DQ_FormatConsistency=org.opengis.metadata.quality.FormatConsistency
+DQ_GriddedDataPositionalAccuracy=org.opengis.metadata.quality.GriddedDataPositionalAccuracy
+DQ_LogicalConsistency=org.opengis.metadata.quality.LogicalConsistency
+DQ_NonQuantitativeAttributeAccuracy=org.opengis.metadata.quality.NonQuantitativeAttributeAccuracy
+DQ_PositionalAccuracy=org.opengis.metadata.quality.PositionalAccuracy
+DQ_QuantitativeAttributeAccuracy=org.opengis.metadata.quality.QuantitativeAttributeAccuracy
+DQ_QuantitativeResult=org.opengis.metadata.quality.QuantitativeResult
+DQ_RelativeInternalPositionalAccuracy=org.opengis.metadata.quality.RelativeInternalPositionalAccuracy
+DQ_Result=org.opengis.metadata.quality.Result
+DQ_Scope=org.opengis.metadata.quality.Scope
+DQ_TemporalAccuracy=org.opengis.metadata.quality.TemporalAccuracy
+DQ_TemporalConsistency=org.opengis.metadata.quality.TemporalConsistency
+DQ_TemporalValidity=org.opengis.metadata.quality.TemporalValidity
+DQ_ThematicAccuracy=org.opengis.metadata.quality.ThematicAccuracy
+DQ_ThematicClassificationCorrectness=org.opengis.metadata.quality.ThematicClassificationCorrectness
+DQ_TopologicalConsistency=org.opengis.metadata.quality.TopologicalConsistency
+DS_AssociationTypeCode=org.opengis.metadata.identification.AssociationType
+DS_InitiativeTypeCode=org.opengis.metadata.identification.InitiativeType
+EX_BoundingPolygon=org.opengis.metadata.extent.BoundingPolygon
+EX_Extent=org.opengis.metadata.extent.Extent
+EX_GeographicBoundingBox=org.opengis.metadata.extent.GeographicBoundingBox
+EX_GeographicDescription=org.opengis.metadata.extent.GeographicDescription
+EX_GeographicExtent=org.opengis.metadata.extent.GeographicExtent
+EX_SpatialTemporalExtent=org.opengis.metadata.extent.SpatialTemporalExtent
+EX_TemporalExtent=org.opengis.metadata.extent.TemporalExtent
+EX_VerticalExtent=org.opengis.metadata.extent.VerticalExtent
+IO_IdentifiedObject=org.opengis.referencing.IdentifiedObject
+LE_Algorithm=org.opengis.metadata.lineage.Algorithm
+LE_NominalResolution=org.opengis.metadata.lineage.NominalResolution
+LE_ProcessStep=org.opengis.metadata.lineage.ProcessStep
+LE_ProcessStepReport=org.opengis.metadata.lineage.ProcessStepReport
+LE_Processing=org.opengis.metadata.lineage.Processing
+LE_Source=org.opengis.metadata.lineage.Source
+LI_Lineage=org.opengis.metadata.lineage.Lineage
+LI_ProcessStep=org.opengis.metadata.lineage.ProcessStep
+LI_Source=org.opengis.metadata.lineage.Source
+MD_AggregateInformation=org.opengis.metadata.identification.AggregateInformation
+MD_ApplicationSchemaInformation=org.opengis.metadata.ApplicationSchemaInformation
+MD_Band=org.opengis.metadata.content.Band
+MD_BrowseGraphic=org.opengis.metadata.identification.BrowseGraphic
+MD_CellGeometryCode=org.opengis.metadata.spatial.CellGeometry
+MD_CharacterSetCode=org.opengis.metadata.identification.CharacterSet
+MD_ClassificationCode=org.opengis.metadata.constraint.Classification
+MD_Constraints=org.opengis.metadata.constraint.Constraints
+MD_ContentInformation=org.opengis.metadata.content.ContentInformation
+MD_CoverageContentTypeCode=org.opengis.metadata.content.CoverageContentType
+MD_CoverageDescription=org.opengis.metadata.content.CoverageDescription
+MD_DataIdentification=org.opengis.metadata.identification.DataIdentification
+MD_DatatypeCode=org.opengis.metadata.Datatype
+MD_DigitalTransferOptions=org.opengis.metadata.distribution.DigitalTransferOptions
+MD_Dimension=org.opengis.metadata.spatial.Dimension
+MD_DimensionNameTypeCode=org.opengis.metadata.spatial.DimensionNameType
+MD_Distribution=org.opengis.metadata.distribution.Distribution
+MD_Distributor=org.opengis.metadata.distribution.Distributor
+MD_ExtendedElementInformation=org.opengis.metadata.ExtendedElementInformation
+MD_FeatureCatalogueDescription=org.opengis.metadata.content.FeatureCatalogueDescription
+MD_FeatureTypeList=org.opengis.metadata.FeatureTypeList
+MD_Format=org.opengis.metadata.distribution.Format
+MD_GeometricObjectTypeCode=org.opengis.metadata.spatial.GeometricObjectType
+MD_GeometricObjects=org.opengis.metadata.spatial.GeometricObjects
+MD_Georectified=org.opengis.metadata.spatial.Georectified
+MD_Georeferenceable=org.opengis.metadata.spatial.Georeferenceable
+MD_GridSpatialRepresentation=org.opengis.metadata.spatial.GridSpatialRepresentation
+MD_Identification=org.opengis.metadata.identification.Identification
+MD_Identifier=org.opengis.metadata.Identifier
+MD_ImageDescription=org.opengis.metadata.content.ImageDescription
+MD_ImagingConditionCode=org.opengis.metadata.content.ImagingCondition
+MD_KeywordTypeCode=org.opengis.metadata.identification.KeywordType
+MD_Keywords=org.opengis.metadata.identification.Keywords
+MD_LegalConstraints=org.opengis.metadata.constraint.LegalConstraints
+MD_MaintenanceFrequencyCode=org.opengis.metadata.maintenance.MaintenanceFrequency
+MD_MaintenanceInformation=org.opengis.metadata.maintenance.MaintenanceInformation
+MD_Medium=org.opengis.metadata.distribution.Medium
+MD_MediumFormatCode=org.opengis.metadata.distribution.MediumFormat
+MD_MediumNameCode=org.opengis.metadata.distribution.MediumName
+MD_Metadata=org.opengis.metadata.Metadata
+MD_MetadataExtensionInformation=org.opengis.metadata.MetadataExtensionInformation
+MD_ObligationCode=org.opengis.metadata.Obligation
+MD_PixelOrientationCode=org.opengis.metadata.spatial.PixelOrientation
+MD_PortrayalCatalogueReference=org.opengis.metadata.PortrayalCatalogueReference
+MD_ProgressCode=org.opengis.metadata.identification.Progress
+MD_RangeDimension=org.opengis.metadata.content.RangeDimension
+MD_RepresentativeFraction=org.opengis.metadata.identification.RepresentativeFraction
+MD_Resolution=org.opengis.metadata.identification.Resolution
+MD_RestrictionCode=org.opengis.metadata.constraint.Restriction
+MD_ScopeCode=org.opengis.metadata.maintenance.ScopeCode
+MD_ScopeDescription=org.opengis.metadata.maintenance.ScopeDescription
+MD_SecurityConstraints=org.opengis.metadata.constraint.SecurityConstraints
+MD_SpatialRepresentation=org.opengis.metadata.spatial.SpatialRepresentation
+MD_SpatialRepresentationTypeCode=org.opengis.metadata.spatial.SpatialRepresentationType
+MD_StandardOrderProcess=org.opengis.metadata.distribution.StandardOrderProcess
+MD_TopicCategoryCode=org.opengis.metadata.identification.TopicCategory
+MD_TopologyLevelCode=org.opengis.metadata.spatial.TopologyLevel
+MD_Usage=org.opengis.metadata.identification.Usage
+MD_VectorSpatialRepresentation=org.opengis.metadata.spatial.VectorSpatialRepresentation
+MI_AcquisitionInformation=org.opengis.metadata.acquisition.AcquisitionInformation
+MI_Band=org.opengis.metadata.content.Band
+MI_BandDefinition=org.opengis.metadata.content.BandDefinition
+MI_ContextCode=org.opengis.metadata.acquisition.Context
+MI_CoverageDescription=org.opengis.metadata.content.CoverageDescription
+MI_EnvironmentalRecord=org.opengis.metadata.acquisition.EnvironmentalRecord
+MI_Event=org.opengis.metadata.acquisition.Event
+MI_GCP=org.opengis.metadata.spatial.GCP
+MI_GCPCollection=org.opengis.metadata.spatial.GCPCollection
+MI_GeolocationInformation=org.opengis.metadata.spatial.GeolocationInformation
+MI_GeometryTypeCode=org.opengis.metadata.acquisition.GeometryType
+MI_Georectified=org.opengis.metadata.spatial.Georectified
+MI_Georeferenceable=org.opengis.metadata.spatial.Georeferenceable
+MI_ImageDescription=org.opengis.metadata.content.ImageDescription
+MI_Instrument=org.opengis.metadata.acquisition.Instrument
+MI_Metadata=org.opengis.metadata.Metadata
+MI_Objective=org.opengis.metadata.acquisition.Objective
+MI_ObjectiveTypeCode=org.opengis.metadata.acquisition.ObjectiveType
+MI_Operation=org.opengis.metadata.acquisition.Operation
+MI_OperationTypeCode=org.opengis.metadata.acquisition.OperationType
+MI_Plan=org.opengis.metadata.acquisition.Plan
+MI_Platform=org.opengis.metadata.acquisition.Platform
+MI_PlatformPass=org.opengis.metadata.acquisition.PlatformPass
+MI_PolarizationOrientationCode=org.opengis.metadata.content.PolarizationOrientation
+MI_PriorityCode=org.opengis.metadata.acquisition.Priority
+MI_RangeElementDescription=org.opengis.metadata.content.RangeElementDescription
+MI_RequestedDate=org.opengis.metadata.acquisition.RequestedDate
+MI_Requirement=org.opengis.metadata.acquisition.Requirement
+MI_SequenceCode=org.opengis.metadata.acquisition.Sequence
+MI_TransferFunctionTypeCode=org.opengis.metadata.content.TransferFunctionType
+MI_TriggerCode=org.opengis.metadata.acquisition.Trigger
+QE_CoverageResult=org.opengis.metadata.quality.CoverageResult
+QE_Usability=org.opengis.metadata.quality.Usability
+RS_Identifier=org.opengis.referencing.ReferenceIdentifier
+RS_ReferenceSystem=org.opengis.referencing.ReferenceSystem
+SC_CRS=org.opengis.referencing.crs.CoordinateReferenceSystem
+SC_CompoundCRS=org.opengis.referencing.crs.CompoundCRS
+SC_DerivedCRS=org.opengis.referencing.crs.DerivedCRS
+SC_EngineeringCRS=org.opengis.referencing.crs.EngineeringCRS
+SC_GeneralDerivedCRS=org.opengis.referencing.crs.GeneralDerivedCRS
+SC_GeocentricCRS=org.opengis.referencing.crs.GeocentricCRS
+SC_GeodeticCRS=org.opengis.referencing.crs.GeodeticCRS
+SC_GeographicCRS=org.opengis.referencing.crs.GeographicCRS
+SC_ImageCRS=org.opengis.referencing.crs.ImageCRS
+SC_ProjectedCRS=org.opengis.referencing.crs.ProjectedCRS
+SC_SingleCRS=org.opengis.referencing.crs.SingleCRS
+SC_TemporalCRS=org.opengis.referencing.crs.TemporalCRS
+SC_VerticalCRS=org.opengis.referencing.crs.VerticalCRS
+SV_ServiceIdentification=org.opengis.metadata.identification.ServiceIdentification
+
+#
+# Additional types not yet defined in GeoAPI.
+#
+CI_Party=org.apache.sis.metadata.iso.citation.AbstractParty
+CI_Individual=org.apache.sis.metadata.iso.citation.DefaultIndividual
+CI_Organisation=org.apache.sis.metadata.iso.citation.DefaultOrganisation
+CI_Responsibility=org.apache.sis.metadata.iso.citation.DefaultResponsibility
+CI_TelephoneTypeCode=org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList
+MD_AssociatedResource=org.apache.sis.metadata.iso.identification.DefaultAssociatedResource
+MD_AttributeGroup=org.apache.sis.metadata.iso.content.DefaultAttributeGroup
+MD_FeatureTypeInfo=org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo
+MD_Releasability=org.apache.sis.metadata.iso.constraint.DefaultReleasability
+MD_SampleDimension=org.apache.sis.metadata.iso.content.DefaultSampleDimension
+MD_MetadataScope=org.apache.sis.metadata.iso.DefaultMetadataScope
+SV_CoupledResource=org.apache.sis.metadata.iso.identification.DefaultCoupledResource
+SV_OperationChainMetadata=org.apache.sis.metadata.iso.identification.DefaultOperationChainMetadata
+SV_OperationMetadata=org.apache.sis.metadata.iso.identification.DefaultOperationMetadata
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
index 28b529f..6aaa36a 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/cat/CodeListMarshallingTest.java
@@ -24,7 +24,7 @@
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.citation.CitationDate;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.PresentationForm;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.internal.xml.LegacyNamespaces;
@@ -102,7 +102,7 @@
     @Test
     public void testDefaultURL() throws JAXBException {
         final String expected = getResponsiblePartyXML(Schemas.METADATA_ROOT_LEGACY);
-        final Responsibility rp = unmarshal(Responsibility.class, expected);
+        final ResponsibleParty rp = unmarshal(ResponsibleParty.class, expected);
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, rp.getRole());
         /*
          * Use the convenience method in order to avoid the effort of creating
@@ -122,7 +122,7 @@
     @Test
     public void testLegacyISO_URL() throws JAXBException {
         final String expected = getResponsiblePartyXML("http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_19139_Schemas/");
-        final Responsibility rp = unmarshal(Responsibility.class, expected);
+        final ResponsibleParty rp = unmarshal(ResponsibleParty.class, expected);
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, rp.getRole());
 
         final MarshallerPool pool = getMarshallerPool();
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/DummyInstant.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/DummyInstant.java
index 12a8693..a682eeb 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/DummyInstant.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/gml/DummyInstant.java
@@ -17,12 +17,7 @@
 package org.apache.sis.internal.jaxb.gml;
 
 import java.util.Date;
-import org.opengis.temporal.Instant;
-import org.opengis.temporal.Duration;
-import org.opengis.temporal.RelativePosition;
-import org.opengis.temporal.TemporalPosition;
-import org.opengis.temporal.TemporalPrimitive;
-import org.opengis.temporal.TemporalGeometricPrimitive;
+import org.apache.sis.internal.geoapi.temporal.Instant;
 import org.apache.sis.internal.simple.SimpleIdentifiedObject;
 
 
@@ -56,12 +51,4 @@
     public Date getDate() {
         return new Date(time);
     }
-
-    /**
-     * Unsupported operations.
-     */
-    @Override public Duration         length()                                   {throw new UnsupportedOperationException();}
-    @Override public RelativePosition relativePosition(TemporalPrimitive  other) {throw new UnsupportedOperationException();}
-    @Override public Duration         distance(TemporalGeometricPrimitive other) {throw new UnsupportedOperationException();}
-    @Override public TemporalPosition getTemporalPosition()                      {throw new UnsupportedOperationException();}
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
index d55cd18..e3b912b 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/LanguageCodeTest.java
@@ -36,7 +36,6 @@
 import org.junit.Test;
 
 import static org.apache.sis.test.MetadataAssert.*;
-import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.internal.util.StandardDateFormat.UTC;
 
 
@@ -167,7 +166,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(LANGUAGE_CODE);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
     }
 
     /**
@@ -190,7 +189,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(LANGUAGE_CODE_WITHOUT_ATTRIBUTE);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
         pool.recycle(unmarshaller);
     }
 
@@ -232,7 +231,7 @@
         final Unmarshaller unmarshaller = pool.acquireUnmarshaller();
         final String xml = getMetadataXML(CHARACTER_STRING);
         final Metadata metadata = (Metadata) unmarshal(unmarshaller, xml);
-        assertEquals(Locale.JAPANESE, getSingleton(metadata.getLocalesAndCharsets().keySet()));
+        assertEquals(Locale.JAPANESE, metadata.getLanguage());
         pool.recycle(unmarshaller);
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java
index 0159972..d4e0633 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/metadata/replace/ServiceParameterTest.java
@@ -18,7 +18,6 @@
 
 import javax.xml.bind.JAXBException;
 import org.opengis.util.MemberName;
-import org.opengis.parameter.ParameterDirection;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.util.iso.Names;
 import org.apache.sis.test.xml.TestCase;
@@ -48,7 +47,6 @@
         param.memberName    = paramName;
         param.optionality   = true;
         param.repeatability = false;
-        param.direction     = ParameterDirection.IN;
         return param;
     }
 
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java
index 38dfb93..2f43a2a 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/IdentifiersTest.java
@@ -20,6 +20,7 @@
 import java.util.Arrays;
 import java.util.ArrayList;
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.test.TestCase;
@@ -49,8 +50,15 @@
     /**
      * Creates an identifier with a code space.
      */
-    private static Identifier identifier(final String codeSpace, final String code) {
-        return new DefaultIdentifier(codeSpace, code, null);
+    private static ReferenceIdentifier identifier(final String codeSpace, final String code) {
+        return new Id(codeSpace, code);
+    }
+
+    @SuppressWarnings("serial")
+    private static final class Id extends DefaultIdentifier implements ReferenceIdentifier {
+        Id(String codeSpace, String code) {
+            super(codeSpace, code, null);
+        }
     }
 
     /**
@@ -58,8 +66,8 @@
      */
     @Test
     public void testHasCommonIdentifier() {
-        final List<Identifier> id1 = new ArrayList<>(3);
-        final List<Identifier> id2 = new ArrayList<>(2);
+        final List<ReferenceIdentifier> id1 = new ArrayList<>(3);
+        final List<ReferenceIdentifier> id2 = new ArrayList<>(2);
         assertNull(Identifiers.hasCommonIdentifier(id1, id2));
         /*
          * Add codes for two Operation Methods which are implemented in Apache SIS by the same class:
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
index e3ef4bd..e1ffeea 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/internal/metadata/MergerTest.java
@@ -23,7 +23,6 @@
 import java.nio.charset.StandardCharsets;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.content.ContentInformation;
-import org.opengis.metadata.content.CoverageDescription;
 import org.opengis.metadata.content.FeatureCatalogueDescription;
 import org.opengis.metadata.content.ImagingCondition;
 import org.opengis.metadata.content.ImageDescription;
@@ -109,7 +108,7 @@
         final Iterator<ContentInformation> it       = target.getContentInfo().iterator();
         final ImageDescription             image    = (ImageDescription)            it.next();
         final FeatureCatalogueDescription  features = (FeatureCatalogueDescription) it.next();
-        final CoverageDescription          coverage = (CoverageDescription)         it.next();
+        final DefaultCoverageDescription   coverage = (DefaultCoverageDescription)  it.next();
         assertFalse(it.hasNext());
 
         assertEquals("imagingCondition",     ImagingCondition.CLOUD, image   .getImagingCondition());
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
index 6e49ec9..a85ef32 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/HashCodeTest.java
@@ -21,7 +21,6 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Individual;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.acquisition.Instrument;
 import org.opengis.metadata.acquisition.Platform;
@@ -96,7 +95,7 @@
         /*
          * Individual hash code is the sum of all its properties, none of them being a collection.
          */
-        int expected = Individual.class.hashCode() + person.hashCode();
+        int expected = DefaultIndividual.class.hashCode() + person.hashCode();
         assertEquals("Individual", Integer.valueOf(expected), hash(party));
         /*
          * The +31 below come from java.util.List contract, since above Individual is a list member.
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
index 3990f1b..3adc5d6 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
@@ -18,14 +18,12 @@
 
 import java.util.Set;
 import java.util.Map;
-import java.util.List;
 import java.util.HashSet;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.quality.Completeness;
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.coverage.grid.RectifiedGrid;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
 import org.apache.sis.metadata.iso.acquisition.DefaultPlatform;
@@ -94,7 +92,7 @@
         assertFalse("isMetadata(IdentifiedObject)",       isMetadata(IdentifiedObject.class));
         assertFalse("isMetadata(SimpleIdentifiedObject)", isMetadata(SimpleIdentifiedObject.class));
         assertFalse("isMetadata(GeographicCRS)",          isMetadata(GeographicCRS.class));
-        assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
+//      assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
         assertFalse("isMetadata(Double)",                 isMetadata(Double.class));
         assertFalse("isMetadata(double)",                 isMetadata(Double.TYPE));
 
@@ -105,7 +103,7 @@
         assertTrue ("isMetadata(IdentifiedObject)",       isMetadata(IdentifiedObject.class));
         assertTrue ("isMetadata(SimpleIdentifiedObject)", isMetadata(SimpleIdentifiedObject.class));
         assertTrue ("isMetadata(GeographicCRS)",          isMetadata(GeographicCRS.class));
-        assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
+//      assertFalse("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
 
         standard = MetadataStandard.ISO_19123;
         assertFalse("isMetadata(String)",                 isMetadata(String.class));
@@ -114,7 +112,7 @@
         assertTrue ("isMetadata(IdentifiedObject)",       isMetadata(IdentifiedObject.class));       // Dependency
         assertTrue ("isMetadata(SimpleIdentifiedObject)", isMetadata(SimpleIdentifiedObject.class)); // Dependency
         assertTrue ("isMetadata(GeographicCRS)",          isMetadata(GeographicCRS.class));          // Dependency
-        assertTrue ("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
+//      assertTrue ("isMetadata(RectifiedGrid)",          isMetadata(RectifiedGrid.class));
     }
 
     /**
@@ -325,39 +323,6 @@
     }
 
     /**
-     * Tests the {@link MetadataStandard#ISO_19123} constant. Getters shall
-     * be accessible even if there is no implementation on the classpath.
-     */
-    @Test
-    @DependsOnMethod("testGetAccessor")
-    public void testWithoutImplementation() {
-        standard = MetadataStandard.ISO_19123;
-        assertFalse("isMetadata(String)",          isMetadata(String.class));
-        assertTrue ("isMetadata(Citation)",        isMetadata(Citation.class));         // Transitive dependency
-        assertTrue ("isMetadata(DefaultCitation)", isMetadata(DefaultCitation.class));  // Transitive dependency
-        assertTrue ("isMetadata(RectifiedGrid)",   isMetadata(RectifiedGrid.class));
-        /*
-         * Ensure that the getters have been found.
-         */
-        final Map<String,String> names = standard.asNameMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, KeyNamePolicy.JAVABEANS_PROPERTY);
-        assertFalse("Getters should have been found even if there is no implementation.", names.isEmpty());
-        assertEquals("dimension", names.get("dimension"));
-        assertEquals("cells", names.get("cell"));
-        /*
-         * Ensure that the type are recognized, especially RectifiedGrid.getOffsetVectors()
-         * which is of type List<double[]>.
-         */
-        Map<String,Class<?>> types;
-        types = standard.asTypeMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.PROPERTY_TYPE);
-        assertEquals("The return type is the int primitive type.", Integer.TYPE, types.get("dimension"));
-        assertEquals("The offset vectors are stored in a List.",   List.class,   types.get("offsetVectors"));
-
-        types = standard.asTypeMap(RectifiedGrid.class, KeyNamePolicy.UML_IDENTIFIER, TypeValuePolicy.ELEMENT_TYPE);
-        assertEquals("As elements in a list of dimensions.",       Integer.class,  types.get("dimension"));
-        assertEquals("As elements in the list of offset vectors.", double[].class, types.get("offsetVectors"));
-    }
-
-    /**
      * Tests serialization of pre-defined constants.
      */
     @Test
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
index eea35a7..29da42e 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
@@ -35,7 +35,6 @@
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.constraint.Constraints;
-import org.opengis.metadata.content.AttributeGroup;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.CoverageDescription;
 import org.opengis.metadata.identification.*;                       // Really using almost everything.
@@ -50,13 +49,15 @@
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.util.InternationalString;
 import org.opengis.util.GenericName;
-import org.opengis.temporal.Duration;
 
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
+import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.metadata.iso.content.DefaultCoverageDescription;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
+import org.apache.sis.metadata.iso.identification.DefaultAssociatedResource;
 import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -186,8 +187,8 @@
 //          Citation.class, "getCollectiveTitle",         "collectiveTitle",         "collectiveTitle",       "Collective title",           InternationalString.class,   -- deprecated as of ISO 19115:2014
             Citation.class, "getISBN",                    "ISBN",                    "ISBN",                  "ISBN",                       String.class,
             Citation.class, "getISSN",                    "ISSN",                    "ISSN",                  "ISSN",                       String.class,
-            Citation.class, "getOnlineResources",         "onlineResources",         "onlineResource",        "Online resources",           OnlineResource[].class,
-            Citation.class, "getGraphics",                "graphics",                "graphic",               "Graphics",                   BrowseGraphic[].class);
+     DefaultCitation.class, "getOnlineResources",         "onlineResources",         "onlineResource",        "Online resources",           OnlineResource[].class,
+     DefaultCitation.class, "getGraphics",                "graphics",                "graphic",               "Graphics",                   BrowseGraphic[].class);
     }
 
     /**
@@ -210,23 +211,23 @@
             Identification.class, "getCredits",                    "credits",                    "credit",                    "Credits",                      String[].class,
             Identification.class, "getStatus",                     "status",                     "status",                    "Status",                       Progress[].class,
             Identification.class, "getPointOfContacts",            "pointOfContacts",            "pointOfContact",            "Point of contacts",            ResponsibleParty[].class,
-            Identification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
-            Identification.class, "getSpatialResolutions",         "spatialResolutions",         "spatialResolution",         "Spatial resolutions",          Resolution[].class,
-            Identification.class, "getTemporalResolutions",        "temporalResolutions",        "temporalResolution",        "Temporal resolutions",         Duration[].class,
-            Identification.class, "getTopicCategories",            "topicCategories",            "topicCategory",             "Topic categories",             TopicCategory[].class,
-            Identification.class, "getExtents",                    "extents",                    "extent",                    "Extents",                      Extent[].class,
-            Identification.class, "getAdditionalDocumentations",   "additionalDocumentations",   "additionalDocumentation",   "Additional documentations",    Citation[].class,
-            Identification.class, "getProcessingLevel",            "processingLevel",            "processingLevel",           "Processing level",             Identifier.class,
+        DataIdentification.class, "getSpatialRepresentationTypes", "spatialRepresentationTypes", "spatialRepresentationType", "Spatial representation types", SpatialRepresentationType[].class,
+        DataIdentification.class, "getSpatialResolutions",         "spatialResolutions",         "spatialResolution",         "Spatial resolutions",          Resolution[].class,
+//          Identification.class, "getTemporalResolutions",        "temporalResolutions",        "temporalResolution",        "Temporal resolutions",         Duration[].class,
+        DataIdentification.class, "getTopicCategories",            "topicCategories",            "topicCategory",             "Topic categories",             TopicCategory[].class,
+        DataIdentification.class, "getExtents",                    "extents",                    "extent",                    "Extents",                      Extent[].class,
+    AbstractIdentification.class, "getAdditionalDocumentations",   "additionalDocumentations",   "additionalDocumentation",   "Additional documentations",    Citation[].class,
+    AbstractIdentification.class, "getProcessingLevel",            "processingLevel",            "processingLevel",           "Processing level",             Identifier.class,
             Identification.class, "getResourceMaintenances",       "resourceMaintenances",       "resourceMaintenance",       "Resource maintenances",        MaintenanceInformation[].class,
             Identification.class, "getGraphicOverviews",           "graphicOverviews",           "graphicOverview",           "Graphic overviews",            BrowseGraphic[].class,
             Identification.class, "getResourceFormats",            "resourceFormats",            "resourceFormat",            "Resource formats",             Format[].class,
             Identification.class, "getDescriptiveKeywords",        "descriptiveKeywords",        "descriptiveKeywords",       "Descriptive keywords",         Keywords[].class,
             Identification.class, "getResourceSpecificUsages",     "resourceSpecificUsages",     "resourceSpecificUsage",     "Resource specific usages",     Usage[].class,
             Identification.class, "getResourceConstraints",        "resourceConstraints",        "resourceConstraints",       "Resource constraints",         Constraints[].class,
-            Identification.class, "getAssociatedResources",        "associatedResources",        "associatedResource",        "Associated resources",         AssociatedResource[].class,
+    AbstractIdentification.class, "getAssociatedResources",        "associatedResources",        "associatedResource",        "Associated resources",         DefaultAssociatedResource[].class,
         DataIdentification.class, "getEnvironmentDescription",     "environmentDescription",     "environmentDescription",    "Environment description",      InternationalString.class,
         DataIdentification.class, "getSupplementalInformation",    "supplementalInformation",    "supplementalInformation",   "Supplemental information",     InternationalString.class,
-        DataIdentification.class, "getLocalesAndCharsets",         "localesAndCharsets",         "defaultLocale+otherLocale", "Locales and charsets",         Map.class);
+ DefaultDataIdentification.class, "getLocalesAndCharsets",         "localesAndCharsets",         "defaultLocale+otherLocale", "Locales and charsets",         Map.class);
     }
 
     /**
@@ -382,7 +383,7 @@
         /*
          * Compares with the non-deprecated property.
          */
-        final Collection<AttributeGroup> groups = instance.getAttributeGroups();
+        final Collection<DefaultAttributeGroup> groups = instance.getAttributeGroups();
         assertSame(groups, accessor.get(indexOfReplacement, instance));
         assertEquals(CoverageContentType.IMAGE, getSingleton(getSingleton(groups).getContentTypes()));
         /*
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
index 1fd7067..36b8be3 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyConsistencyCheck.java
@@ -23,7 +23,6 @@
 import java.util.Map;
 import java.lang.reflect.Method;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.CheckedContainer;
@@ -132,11 +131,11 @@
         if (Date.class.isAssignableFrom(type)) {
             return new Date(random.nextInt() * 1000L);
         }
-        if (ControlledVocabulary.class.isAssignableFrom(type)) try {
+        if (CodeList.class.isAssignableFrom(type)) try {
             if (type == CodeList.class) {
                 return null;
             }
-            final ControlledVocabulary[] codes = (ControlledVocabulary[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
+            final CodeList<?>[] codes = (CodeList<?>[]) type.getMethod("values", (Class[]) null).invoke(null, (Object[]) null);
             return codes[random.nextInt(codes.length)];
         } catch (ReflectiveOperationException e) {
             fail(e.toString());
@@ -191,7 +190,7 @@
     public void testPropertyValues() {
         random = TestUtilities.createRandomNumberGenerator();
         for (final Class<?> type : types) {
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
                     assertTrue("Not an implementation of expected interface.", type.isAssignableFrom(impl));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
index 79db6a9..ad31dae 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeNodeTest.java
@@ -21,7 +21,6 @@
 import org.opengis.metadata.citation.Address;
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.Role;
@@ -190,8 +189,8 @@
     private void assertCitationContentEquals(final int offset, final TableColumn<?> column, final Object... expected) {
         if (valuePolicy == ValueExistencePolicy.COMPACT) {
             assertEquals(19, expected.length);
-            System.arraycopy(expected, 11+offset, expected, 10+offset,  8-offset);    // Compact the "Individual" element.
-            System.arraycopy(expected,  7+offset, expected,  6+offset, 11-offset);    // Compact the "Organisation" element.
+            System.arraycopy(expected, 12+offset, expected, 11+offset,  7-offset);    // Compact the "Individual" element.
+            System.arraycopy(expected,  8+offset, expected,  7+offset, 10-offset);    // Compact the "Organisation" element.
             System.arraycopy(expected,  1+offset, expected,    offset, 16-offset);    // Compact the "Title" element.
             Arrays.fill(expected, 16, 19, null);
         }
@@ -211,16 +210,16 @@
               "Alternate title (2 of 2)",
               "Edition",
               "Cited responsible party (1 of 2)",
-                "Organisation",
-                  "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
                 "Role",
+                "Party",
+                  "Name",                               // In COMPACT mode, this value is associated to "Organisation" node.
               "Cited responsible party (2 of 2)",
-                "Individual",
+                "Role",
+                "Party",
                   "Name",                               // In COMPACT mode, this value is associated to "Individual" node.
                   "Contact info",
                     "Address",
                       "Electronic mail address",
-                "Role",
               "Presentation form (1 of 2)",
               "Presentation form (2 of 2)",
               "Other citation details");
@@ -242,16 +241,16 @@
               "alternateTitle",
               "edition",
               "citedResponsibleParty",
+                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
-                "role",
               "citedResponsibleParty",
+                "role",
                 "party",
                   "name",                               // In COMPACT mode, this value is associated to "party" node.
                   "contactInfo",
                     "address",
                       "electronicMailAddress",
-                "role",
               "presentationForm",
               "presentationForm",
               "otherCitationDetails");
@@ -273,16 +272,16 @@
               ONE,          // alternateTitle
               null,         // edition
               ZERO,         // citedResponsibleParty
+                null,       // role
                 ZERO,       // party (organisation)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
-                null,       // role
               ONE,          // citedResponsibleParty
+                null,       // role
                 ZERO,       // party (individual)
                   null,     // name                         — in COMPACT mode, this value is associated to "party" node.
                   ZERO,     // contactInfo
                     ZERO,   // address
                       ZERO, // electronicMailAddress
-                null,       // role
               ZERO,         // presentationForm
               ONE,          // presentationForm
               null);        // otherCitationDetails
@@ -301,16 +300,16 @@
               InternationalString.class,
               InternationalString.class,
               ResponsibleParty.class,
-                Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
-                  InternationalString.class,            // Name
                 Role.class,
+                AbstractParty.class,                    // In COMPACT mode, value with be the one of "name" node instead.
+                  InternationalString.class,            // Name
               ResponsibleParty.class,
-                Party.class,                            // In COMPACT mode, value with be the one of "name" node instead.
+                Role.class,
+                AbstractParty.class,                    // In COMPACT mode, value with be the one of "name" node instead.
                   InternationalString.class,            // Name
                   Contact.class,
                     Address.class,
                       String.class,
-                Role.class,
               PresentationForm.class,
               PresentationForm.class,
               InternationalString.class);
@@ -329,16 +328,16 @@
               "Second alternate title",
               "Some edition",
               null,                             // ResponsibleParty
+                Role.DISTRIBUTOR,
                 null,                           // Party (organisation)
                   "Some organisation",
-                Role.DISTRIBUTOR,
               null,                             // ResponsibleParty
+                Role.POINT_OF_CONTACT,
                 null,                           // Party (individual)
                   "Some person of contact",
                   null,                         // Contact
                     null,                       // Address
                       "Some email",
-                Role.POINT_OF_CONTACT,
               PresentationForm.MAP_DIGITAL,
               PresentationForm.MAP_HARDCOPY,
               "Some other details");
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
index afda86b..ce84eef 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
@@ -87,9 +87,10 @@
             "Citation……………………………………………………………………………… Undercurrent\n" +
             "  ├─Alternate title………………………………………………… Andākarento\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Individual…………………………………………………… Testsuya Toyoda\n" +
-            "  │   └─Role…………………………………………………………………… Author\n" +
+            "  │   ├─Role…………………………………………………………………… Author\n" +
+            "  │   └─Party………………………………………………………………… Testsuya Toyoda\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
+            "  │   ├─Role…………………………………………………………………… EDITOR\n" +
             "  │   ├─Extent……………………………………………………………… World\n" +
             "  │   │   └─Geographic element\n" +
             "  │   │       ├─West bound longitude…… 180°W\n" +
@@ -97,8 +98,7 @@
             "  │   │       ├─South bound latitude…… 90°S\n" +
             "  │   │       ├─North bound latitude…… 90°N\n" +
             "  │   │       └─Extent type code……………… true\n" +
-            "  │   ├─Organisation……………………………………………… Kōdansha\n" +
-            "  │   └─Role…………………………………………………………………… Editor\n" +
+            "  │   └─Party………………………………………………………………… Kōdansha\n" +
             "  ├─Presentation form (1 of 2)…………………… Document digital\n" +
             "  ├─Presentation form (2 of 2)…………………… Document hardcopy\n" +
             "  └─ISBN……………………………………………………………………………… 9782505004509\n", text);
@@ -177,7 +177,7 @@
             "  ├─Alternate title (2 of 3)………… Orange\n" +
             "  ├─Alternate title (3 of 3)………… Kiwi\n" +
             "  ├─Presentation form (1 of 3)…… Image digital\n" +
-            "  ├─Presentation form (2 of 3)…… Audio digital\n" +
+            "  ├─Presentation form (2 of 3)…… AUDIO-DIGITAL\n" + // Missing localization resource for that one.
             "  └─Presentation form (3 of 3)…… Test\n",
             text);
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
index 72cf351..090f719 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableViewTest.java
@@ -60,14 +60,14 @@
             "  ├─Alternate title (2 of 2)…………………………………………… Second alternate title\n" +
             "  ├─Edition………………………………………………………………………………………… Some edition\n" +
             "  ├─Cited responsible party (1 of 2)\n" +
-            "  │   ├─Organisation………………………………………………………………… Some organisation\n" +
-            "  │   └─Role……………………………………………………………………………………… Distributor\n" +
+            "  │   ├─Role……………………………………………………………………………………… Distributor\n" +
+            "  │   └─Party…………………………………………………………………………………… Some organisation\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
-            "  │   ├─Individual……………………………………………………………………… Some person of contact\n" +
-            "  │   │   └─Contact info\n" +
-            "  │   │       └─Address\n" +
-            "  │   │           └─Electronic mail address…… Some email\n" +
-            "  │   └─Role……………………………………………………………………………………… Point of contact\n" +
+            "  │   ├─Role……………………………………………………………………………………… Point of contact\n" +
+            "  │   └─Party…………………………………………………………………………………… Some person of contact\n" +
+            "  │       └─Contact info\n" +
+            "  │           └─Address\n" +
+            "  │               └─Electronic mail address…… Some email\n" +
             "  ├─Presentation form (1 of 2)……………………………………… Map digital\n" +
             "  ├─Presentation form (2 of 2)……………………………………… Map hardcopy\n" +
             "  └─Other citation details………………………………………………… Some other details\n";
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
index 464cdf2a..99dbd13 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
@@ -18,7 +18,7 @@
 
 import java.lang.reflect.Modifier;
 import org.opengis.annotation.UML;
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 import org.apache.sis.internal.jaxb.Context;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.PropertyConsistencyCheck;
@@ -51,14 +51,12 @@
     @SuppressWarnings("deprecation")
     public AllMetadataTest() {
         super(MetadataStandard.ISO_19115,
-            org.opengis.annotation.Obligation.class,
             org.opengis.metadata.ApplicationSchemaInformation.class,
             org.opengis.metadata.Datatype.class,
             org.opengis.metadata.ExtendedElementInformation.class,
             org.opengis.metadata.Identifier.class,
             org.opengis.metadata.Metadata.class,
             org.opengis.metadata.MetadataExtensionInformation.class,
-            org.opengis.metadata.MetadataScope.class,
             org.opengis.metadata.PortrayalCatalogueReference.class,
             org.opengis.metadata.acquisition.AcquisitionInformation.class,
             org.opengis.metadata.acquisition.Context.class,
@@ -85,9 +83,7 @@
             org.opengis.metadata.citation.DateType.class,
             org.opengis.metadata.citation.OnLineFunction.class,
             org.opengis.metadata.citation.OnlineResource.class,
-            org.opengis.metadata.citation.Party.class,
             org.opengis.metadata.citation.PresentationForm.class,
-            org.opengis.metadata.citation.Responsibility.class,
             org.opengis.metadata.citation.ResponsibleParty.class,
             org.opengis.metadata.citation.Role.class,
             org.opengis.metadata.citation.Series.class,
@@ -97,7 +93,6 @@
             org.opengis.metadata.constraint.LegalConstraints.class,
             org.opengis.metadata.constraint.Restriction.class,
             org.opengis.metadata.constraint.SecurityConstraints.class,
-            org.opengis.metadata.content.AttributeGroup.class,
             org.opengis.metadata.content.Band.class,
             org.opengis.metadata.content.BandDefinition.class,
             org.opengis.metadata.content.ContentInformation.class,
@@ -109,7 +104,6 @@
             org.opengis.metadata.content.PolarizationOrientation.class,
             org.opengis.metadata.content.RangeDimension.class,
             org.opengis.metadata.content.RangeElementDescription.class,
-            org.opengis.metadata.content.SampleDimension.class,
             org.opengis.metadata.content.TransferFunctionType.class,
             org.opengis.metadata.distribution.DataFile.class,
             org.opengis.metadata.distribution.DigitalTransferOptions.class,
@@ -129,20 +123,15 @@
             org.opengis.metadata.extent.TemporalExtent.class,
             org.opengis.metadata.extent.VerticalExtent.class,
             org.opengis.metadata.identification.AggregateInformation.class,
-            org.opengis.metadata.identification.AssociatedResource.class,
             org.opengis.metadata.identification.AssociationType.class,
             org.opengis.metadata.identification.BrowseGraphic.class,
             org.opengis.metadata.identification.CharacterSet.class,
-            org.opengis.metadata.identification.CoupledResource.class,
             org.opengis.metadata.identification.DataIdentification.class,
             org.opengis.metadata.identification.Identification.class,
             org.opengis.metadata.identification.InitiativeType.class,
             org.opengis.metadata.identification.Keywords.class,
-            org.opengis.metadata.identification.KeywordClass.class,
             org.opengis.metadata.identification.KeywordType.class,
             org.opengis.metadata.identification.Progress.class,
-            org.opengis.metadata.identification.OperationChainMetadata.class,
-            org.opengis.metadata.identification.OperationMetadata.class,
             org.opengis.metadata.identification.RepresentativeFraction.class,
             org.opengis.metadata.identification.Resolution.class,
             org.opengis.metadata.identification.ServiceIdentification.class,
@@ -157,7 +146,6 @@
             org.opengis.metadata.lineage.Source.class,
             org.opengis.metadata.maintenance.MaintenanceFrequency.class,
             org.opengis.metadata.maintenance.MaintenanceInformation.class,
-            org.opengis.metadata.maintenance.Scope.class,
             org.opengis.metadata.maintenance.ScopeCode.class,
             org.opengis.metadata.maintenance.ScopeDescription.class,
             org.opengis.metadata.quality.AbsoluteExternalPositionalAccuracy.class,
@@ -239,9 +227,13 @@
              */
             return null;
         }
+        String identifier = type.getAnnotation(UML.class).identifier();
+        if (identifier.equals("MI_PolarizationOrientationCode")) {
+            identifier = "MI_PolarisationOrientationCode";
+            // https://issues.apache.org/jira/browse/SIS-398
+        }
         final String classname = "org.apache.sis.internal.jaxb." +
-                (ControlledVocabulary.class.isAssignableFrom(type) ? "code" : "metadata") +
-                '.' + type.getAnnotation(UML.class).identifier();
+                (CodeList.class.isAssignableFrom(type) ? "code" : "metadata") + '.' + identifier;
         final Class<?> wrapper = Class.forName(classname);
         Class<?>[] expectedFinalClasses = wrapper.getClasses();   // "Since2014" internal class.
         if (expectedFinalClasses.length == 0) {
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
index c1f20b6..b7fa6d5 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/CustomMetadataTest.java
@@ -27,7 +27,12 @@
 import org.opengis.util.NameFactory;
 import org.opengis.metadata.identification.*;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.opengis.metadata.constraint.Constraints;
+import org.opengis.metadata.distribution.Format;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.metadata.maintenance.MaintenanceInformation;
+import org.opengis.metadata.spatial.SpatialRepresentationType;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
@@ -105,9 +110,25 @@
                 return factory.createInternationalString(names);
             }
 
-            @Override public Citation                  getCitation()           {return null;}
-            @Override public Collection<TopicCategory> getTopicCategories()    {return null;}
-            @Override public Collection<Extent>        getExtents()            {return null;}
+            @Override public InternationalString                   getSupplementalInformation()    {return null;}
+            @Override public Citation                              getCitation()                   {return null;}
+            @Override public InternationalString                   getPurpose()                    {return null;}
+            @Override public Collection<SpatialRepresentationType> getSpatialRepresentationTypes() {return null;}
+            @Override public Collection<Resolution>                getSpatialResolutions()         {return null;}
+            @Override public Collection<Locale>                    getLanguages()                  {return null;}
+            @Override public Collection<CharacterSet>              getCharacterSets()              {return null;}
+            @Override public Collection<TopicCategory>             getTopicCategories()            {return null;}
+            @Override public Collection<Extent>                    getExtents()                    {return null;}
+            @Override public Collection<String>                    getCredits()                    {return null;}
+            @Override public Collection<Progress>                  getStatus()                     {return null;}
+            @Override public Collection<ResponsibleParty>          getPointOfContacts()            {return null;}
+            @Override public Collection<MaintenanceInformation>    getResourceMaintenances()       {return null;}
+            @Override public Collection<BrowseGraphic>             getGraphicOverviews()           {return null;}
+            @Override public Collection<Format>                    getResourceFormats()            {return null;}
+            @Override public Collection<Keywords>                  getDescriptiveKeywords()        {return null;}
+            @Override public Collection<Usage>                     getResourceSpecificUsages()     {return null;}
+            @Override public Collection<Constraints>               getResourceConstraints()        {return null;}
+@Deprecated @Override public Collection<AggregateInformation>      getAggregationInfo()            {return null;}
         };
         final DefaultMetadata data = new DefaultMetadata();
         data.setIdentificationInfo(singleton(identification));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
index d26da70..c3ddd2f 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
@@ -23,7 +23,6 @@
 import java.util.Collection;
 import java.net.URISyntaxException;
 import javax.xml.bind.JAXBException;
-import org.opengis.metadata.MetadataScope;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.maintenance.ScopeCode;
@@ -183,9 +182,9 @@
         /*
          * The above deprecated methods shall have created MetadataScope object. Verify that.
          */
-        final Collection<MetadataScope> scopes = metadata.getMetadataScopes();
-        final Iterator<MetadataScope> it = scopes.iterator();
-        MetadataScope scope = it.next();
+        final Collection<DefaultMetadataScope> scopes = metadata.getMetadataScopes();
+        final Iterator<DefaultMetadataScope> it = scopes.iterator();
+        DefaultMetadataScope scope = it.next();
         assertEquals("metadataScopes[0].name", "Bridges", scope.getName().toString());
         assertEquals("metadataScopes[0].resourceScope", ScopeCode.FEATURE_TYPE, scope.getResourceScope());
         scope = it.next();
@@ -229,7 +228,7 @@
          */
         Date creation = date("2014-10-07 00:00:00");
         final DefaultCitationDate[] dates = new DefaultCitationDate[] {
-                new DefaultCitationDate(date("2014-10-09 00:00:00"), DateType.LAST_UPDATE),
+                new DefaultCitationDate(date("2014-10-09 00:00:00"), DateType.valueOf("LAST_UPDATE")),
                 new DefaultCitationDate(creation, DateType.CREATION)
         };
         metadata.setDateInfo(Arrays.asList(dates));
@@ -278,7 +277,5 @@
         final DefaultMetadata metadata = new DefaultMetadata();
         metadata.setDataSetUri("file:/tmp/myfile.txt");
         assertEquals("file:/tmp/myfile.txt", metadata.getDataSetUri());
-        assertEquals("file:/tmp/myfile.txt", getSingleton(getSingleton(metadata.getIdentificationInfo())
-                .getCitation().getOnlineResources()).getLinkage().toString());
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
index 513862c..9e31c3c 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/CitationsTest.java
@@ -26,6 +26,7 @@
 import java.lang.reflect.Field;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.internal.simple.CitationConstant;
 import org.apache.sis.internal.simple.SimpleCitation;
@@ -120,21 +121,21 @@
      */
     @Test
     public void testGetIdentifier() {
-        assertEquals("Apache:SIS",           getIdentifier(SIS));
+        assertEquals("SIS",                  getIdentifier(SIS));
         assertEquals("OGC",                  getIdentifier(OGC));
         assertEquals("IOGP",                 getIdentifier(IOGP));
         assertEquals("EPSG",                 getIdentifier(EPSG));
-        assertEquals("ESRI:ArcGIS",          getIdentifier(ESRI));
+        assertEquals("ArcGIS",               getIdentifier(ESRI));
         assertEquals("NetCDF",               getIdentifier(NETCDF));
         assertEquals("GeoTIFF",              getIdentifier(GEOTIFF));
-        assertEquals("Pitney Bowes:MapInfo", getIdentifier(MAP_INFO));
+        assertEquals("MapInfo",              getIdentifier(MAP_INFO));
         assertEquals("ISBN",                 getIdentifier(ISBN));
         assertEquals("ISSN",                 getIdentifier(ISSN));
-        assertEquals("OSGeo:Proj4",          getIdentifier(PROJ4));              // Not a valid Unicode identifier.
-        assertEquals("IHO:S-57",             getIdentifier(S57));                // Not a valid Unicode identifier.
-        assertEquals("ISO:19115-1",          getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
-        assertEquals("ISO:19115-2",          getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
-        assertEquals("OGC:WMS",              getIdentifier(WMS));
+        assertEquals("Proj4",                getIdentifier(PROJ4));              // Not a valid Unicode identifier.
+        assertEquals("S-57",                 getIdentifier(S57));                // Not a valid Unicode identifier.
+        assertEquals("19115-1",              getIdentifier(ISO_19115.get(0)));   // The ':' separator is not usual in ISO references
+        assertEquals("19115-2",              getIdentifier(ISO_19115.get(1)));   // and could be changed in future SIS versions.
+        assertEquals("WMS",                  getIdentifier(WMS));
     }
 
     /**
@@ -220,6 +221,7 @@
      * Tests {@code getCitedResponsibleParties()} on some {@code Citation} constants.
      */
     @Test
+    @org.junit.Ignore("Requires GeoAPI 3.1.")
     public void testGetCitedResponsibleParty() {
         assertEquals("Open Geospatial Consortium",                       getCitedResponsibleParty(OGC));
         assertEquals("International Organization for Standardization",   getCitedResponsibleParty(ISO_19115.get(0)));
@@ -232,7 +234,7 @@
      * Returns the responsible party for the given constant.
      */
     private static String getCitedResponsibleParty(final Citation citation) {
-        return getSingleton(getSingleton(citation.getCitedResponsibleParties()).getParties()).getName().toString(Locale.US);
+        return getSingleton(citation.getCitedResponsibleParties()).getOrganisationName().toString(Locale.US);
     }
 
     /**
@@ -246,7 +248,7 @@
     public void testEPSG() {
         final Identifier identifier = getSingleton(EPSG.getIdentifiers());
         assertEquals("EPSG", toCodeSpace(EPSG));
-        assertEquals("IOGP", identifier.getCodeSpace());
+//      assertEquals("IOGP", identifier.getCodeSpace());
         assertEquals("EPSG", identifier.getCode());
     }
 
@@ -286,17 +288,24 @@
      */
     @Test
     public void testIdentifierMatches() {
-        final Identifier ogc = new DefaultIdentifier("OGC", "06-042", null);
-        final Identifier iso = new DefaultIdentifier("ISO", "19128", null);
+        final Identifier ogc = new Id("OGC", "06-042");
+        final Identifier iso = new Id("ISO", "19128");
         final DefaultCitation citation = new DefaultCitation("Web Map Server");
         citation.setIdentifiers(Arrays.asList(ogc, iso, new DefaultIdentifier("Foo", "06-042", null)));
         assertTrue ("With full identifier",  Citations.identifierMatches(citation, ogc, ogc.getCode()));
         assertTrue ("With full identifier",  Citations.identifierMatches(citation, iso, iso.getCode()));
-        assertFalse("With wrong code",       Citations.identifierMatches(citation, new DefaultIdentifier("ISO", "19115", null), "19115"));
-        assertFalse("With wrong code space", Citations.identifierMatches(citation, new DefaultIdentifier("Foo", "19128", null), "19128"));
+        assertFalse("With wrong code",       Citations.identifierMatches(citation, new Id("ISO", "19115"), "19115"));
+        assertFalse("With wrong code space", Citations.identifierMatches(citation, new Id("Foo", "19128"), "19128"));
         assertFalse("With wrong code",       Citations.identifierMatches(citation, "Foo"));
         assertTrue ("Without identifier",    Citations.identifierMatches(citation, "19128"));
         assertTrue ("With parsing",          Citations.identifierMatches(citation, "ISO:19128"));
         assertFalse("With wrong code space", Citations.identifierMatches(citation, "Foo:19128"));
     }
+
+    @SuppressWarnings("serial")
+    private static final class Id extends DefaultIdentifier implements ReferenceIdentifier {
+        Id(String codeSpace, String code) {
+            super(codeSpace, code, null);
+        }
+    }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
index 05238b8..519f713 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
@@ -27,9 +27,7 @@
 import org.opengis.metadata.citation.CitationDate;
 import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.DateType;
-import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.citation.Role;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.citation.OnLineFunction;
 import org.opengis.metadata.citation.OnlineResource;
@@ -92,7 +90,7 @@
         final DefaultResponsibleParty author = new DefaultResponsibleParty(Role.AUTHOR);
         author.setParties(Collections.singleton(new DefaultIndividual("Testsuya Toyoda", null, null)));
 
-        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.EDITOR);
+        final DefaultResponsibleParty editor = new DefaultResponsibleParty(Role.valueOf("EDITOR"));
         editor.setParties(Collections.singleton(new DefaultOrganisation("Kōdansha", null, null, null)));
         editor.setExtents(Collections.singleton(Extents.WORLD));
 
@@ -189,12 +187,12 @@
         /*
          * Verify the author metadata.
          */
-        final Responsibility re = CollectionsExt.first(original.getCitedResponsibleParties());
-        final Responsibility ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
+        final ResponsibleParty re = CollectionsExt.first(original.getCitedResponsibleParties());
+        final ResponsibleParty ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
         assertNotSame("citedResponsibleParty", re, ra);
         assertSame("role", re.getRole(), ra.getRole());
-        assertSame("name", getSingleton(re.getParties()).getName(),
-                           getSingleton(ra.getParties()).getName());
+        assertSame("name", re.getIndividualName(),
+                           ra.getIndividualName());
     }
 
     /**
@@ -255,12 +253,12 @@
         contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "ip-protocol");
         final DefaultCitation c = new DefaultCitation("Fight against poverty");
         final DefaultResponsibleParty r1 = new DefaultResponsibleParty(Role.ORIGINATOR);
-        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.FUNDER);
+        final DefaultResponsibleParty r2 = new DefaultResponsibleParty(Role.valueOf("funder"));
         r1.setParties(Collections.singleton(new DefaultIndividual("Maid Marian", null, contact)));
         r2.setParties(Collections.singleton(new DefaultIndividual("Robin Hood",  null, contact)));
         c.setCitedResponsibleParties(Arrays.asList(r1, r2));
-        c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.ADOPTED));
-        c.getPresentationForms().add(PresentationForm.PHYSICAL_OBJECT);
+        c.getDates().add(new DefaultCitationDate(TestUtilities.date("2015-10-17 00:00:00"), DateType.valueOf("adopted")));
+        c.getPresentationForms().add(PresentationForm.valueOf("physicalObject"));
         /*
          * Check that XML file built by the marshaller is the same as the example file.
          */
@@ -306,31 +304,31 @@
 
         final CitationDate date = getSingleton(c.getDates());
         assertEquals("date", date.getDate(), TestUtilities.date("2015-10-17 00:00:00"));
-        assertEquals("dateType", DateType.ADOPTED, date.getDateType());
-        assertEquals("presentationForm", PresentationForm.PHYSICAL_OBJECT, getSingleton(c.getPresentationForms()));
+        assertEquals("dateType", DateType.valueOf("adopted"), date.getDateType());
+        assertEquals("presentationForm", PresentationForm.valueOf("physicalObject"), getSingleton(c.getPresentationForms()));
 
         final Iterator<ResponsibleParty> it = c.getCitedResponsibleParties().iterator();
         final Contact contact = assertResponsibilityEquals(Role.ORIGINATOR, "Maid Marian", it.next());
         assertEquals("Contact instruction", "Send carrier pigeon.", String.valueOf(contact.getContactInstructions()));
 
-        final OnlineResource resource = TestUtilities.getSingleton(contact.getOnlineResources());
+        final OnlineResource resource = contact.getOnlineResource();
         assertEquals("Resource name", "IP over Avian Carriers", String.valueOf(resource.getName()));
         assertEquals("Resource description", "High delay, low throughput, and low altitude service.", String.valueOf(resource.getDescription()));
         assertEquals("Resource linkage", "https://tools.ietf.org/html/rfc1149", String.valueOf(resource.getLinkage()));
         assertEquals("Resource function", OnLineFunction.OFFLINE_ACCESS, resource.getFunction());
 
         // Thanks to xlink:href, the Contact shall be the same instance as above.
-        assertSame("contact", contact, assertResponsibilityEquals(Role.FUNDER, "Robin Hood", it.next()));
+        assertSame("contact", contact, assertResponsibilityEquals(Role.valueOf("funder"), "Robin Hood", it.next()));
         assertFalse(it.hasNext());
     }
 
     /**
      * Asserts that the given responsibility has the expected properties, then returns its contact info.
      */
-    private static Contact assertResponsibilityEquals(final Role role, final String name, final Responsibility actual) {
+    private static Contact assertResponsibilityEquals(final Role role, final String name, final ResponsibleParty actual) {
         assertEquals("role", role, actual.getRole());
-        final Party p = getSingleton(actual.getParties());
-        assertEquals("name", name, String.valueOf(p.getName()));
+        final AbstractParty p = getSingleton(((DefaultResponsibleParty) actual).getParties());
+        assertEquals("name", name, p.getName().toString());
         return getSingleton(p.getContactInfo());
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
index e4c0dbe..0261fae 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
@@ -21,8 +21,8 @@
 import java.util.logging.Filter;
 import java.util.logging.LogRecord;
 import org.opengis.metadata.citation.Telephone;
-import org.opengis.metadata.citation.TelephoneType;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.xml.TestCase;
 import org.junit.Test;
@@ -82,10 +82,10 @@
     @SuppressWarnings("deprecation")
     public void testSetPhones() {
         init();
-        final DefaultTelephone   tel1 = new DefaultTelephone("00.01", TelephoneType.SMS);
-        final DefaultTelephone   tel2 = new DefaultTelephone("00.02", TelephoneType.VOICE);
-        final DefaultTelephone   tel3 = new DefaultTelephone("00.03", TelephoneType.FACSIMILE);
-        final DefaultTelephone   tel4 = new DefaultTelephone("00.04", TelephoneType.VOICE);
+        final DefaultTelephone   tel1 = new DefaultTelephone("00.01", UnsupportedCodeList.valueOf("SMS"));
+        final DefaultTelephone   tel2 = new DefaultTelephone("00.02", UnsupportedCodeList.VOICE);
+        final DefaultTelephone   tel3 = new DefaultTelephone("00.03", UnsupportedCodeList.FACSIMILE);
+        final DefaultTelephone   tel4 = new DefaultTelephone("00.04", UnsupportedCodeList.VOICE);
         final DefaultTelephone[] tels = new DefaultTelephone[] {tel1, tel2, tel3, tel4};
         final DefaultContact  contact = new DefaultContact();
         contact.setPhones(Arrays.asList(tel1, tel2, tel3, tel4));
@@ -96,7 +96,7 @@
          */
         assertSame("getPhone", tel2, contact.getPhone()); // Shall ignore the TelephoneType.SMS.
         assertEquals("warningOccured", "IgnoredPropertyAssociatedTo_1", resourceKey);
-        assertArrayEquals("warningOccured", new String[] {"TelephoneType[SMS]"}, parameters);
+        assertArrayEquals("warningOccured", new String[] {"SMS"}, parameters);
         verifyLegacyLists(tels);
     }
 
@@ -154,8 +154,6 @@
         final Telephone view;
         if (hideSIS) {
             view = new Telephone() {
-                @Override public String             getNumber()     {return tel.getNumber();}
-                @Override public TelephoneType      getNumberType() {return tel.getNumberType();}
                 @Override public Collection<String> getVoices()     {return tel.getVoices();}
                 @Override public Collection<String> getFacsimiles() {return tel.getFacsimiles();}
             };
@@ -170,9 +168,9 @@
         contact.setPhone(view);
         verifyLegacyLists(view);
         assertArrayEquals("getPhones", new DefaultTelephone[] {
-                new DefaultTelephone("00.02", TelephoneType.VOICE),
-                new DefaultTelephone("00.04", TelephoneType.VOICE),
-                new DefaultTelephone("00.03", TelephoneType.FACSIMILE)
+                new DefaultTelephone("00.02", UnsupportedCodeList.VOICE),
+                new DefaultTelephone("00.04", UnsupportedCodeList.VOICE),
+                new DefaultTelephone("00.03", UnsupportedCodeList.FACSIMILE)
             }, contact.getPhones().toArray());
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
index d0646e2..97881c0 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultResponsibilityTest.java
@@ -57,7 +57,7 @@
                 "        <gco:CharacterString>An author</gco:CharacterString>\n" +
                 "      </gmd:individualName>\n" +
                 "      <gmd:role>\n" +
-                "        <gmd:CI_RoleCode codeList=\"http://schemas.opengis.net/iso/19139/20070417/resources/Codelist/gmxCodelists.xml#CI_RoleCode\" codeListValue=\"author\" codeSpace=\"eng\">Author</gmd:CI_RoleCode>\n" +
+                "        <gmd:CI_RoleCode codeList=\"http://schemas.opengis.net/iso/19139/20070417/resources/Codelist/gmxCodelists.xml#CI_RoleCode\" codeListValue=\"author\">Author</gmd:CI_RoleCode>\n" +
                 "      </gmd:role>\n" +
                 "    </gmd:CI_ResponsibleParty>\n" +
                 "  </gmd:citedResponsibleParty>\n" +
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
index f4c6b6e..bd29b5e 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/constraint/DefaultLegalConstraintsTest.java
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Tests (un)marshalling of a XML fragment containing the {@link Restriction#LICENCE} code.
+     * Tests (un)marshalling of a XML fragment containing the {@link Restriction#LICENSE} code.
      * The spelling changed between ISO 19115:2003 and 19115:2014, from "license" to "licence".
      * We need to ensure that XML marshalling use the old spelling, until the XML schema is updated.
      *
@@ -78,16 +78,15 @@
                 "  <mco:useConstraints>\n" +
                 "    <mco:MD_RestrictionCode"
                         + " codeList=\"http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#MD_RestrictionCode\""
-                        + " codeListValue=\"licence\""
-                        + " codeSpace=\"eng\">Licence</mco:MD_RestrictionCode>\n" +
+                        + " codeListValue=\"licence\">License</mco:MD_RestrictionCode>\n" +
                 "  </mco:useConstraints>\n" +
                 "</mco:MD_LegalConstraints>\n";
 
         final DefaultLegalConstraints c = new DefaultLegalConstraints();
-        c.setUseConstraints(singleton(Restriction.LICENCE));
+        c.setUseConstraints(singleton(Restriction.LICENSE));
         assertXmlEquals(xml, marshal(c), "xmlns:*");
         DefaultLegalConstraints actual = unmarshal(DefaultLegalConstraints.class, xml);
-        assertSame(Restriction.LICENCE, getSingleton(actual.getUseConstraints()));
+        assertSame(Restriction.LICENSE, getSingleton(actual.getUseConstraints()));
         assertEquals(c, actual);
         /*
          * Above code tested ISO 19115-3 (un)marshalling. Code below test legacy ISO 19139:2007 (un)marshalling.
@@ -98,14 +97,13 @@
                 "  <gmd:useConstraints>\n" +
                 "    <gmd:MD_RestrictionCode"
                         + " codeList=\"http://schemas.opengis.net/iso/19139/20070417/resources/Codelist/gmxCodelists.xml#MD_RestrictionCode\""
-                        + " codeListValue=\"license\""                              // Note the "s" - from old ISO 19115:2013 spelling.
-                        + " codeSpace=\"eng\">Licence</gmd:MD_RestrictionCode>\n" + // Note the "c" - this one come from resource file.
+                        + " codeListValue=\"license\">License</gmd:MD_RestrictionCode>\n" +
                 "  </gmd:useConstraints>\n" +
                 "</gmd:MD_LegalConstraints>\n";
 
         assertXmlEquals(xml, marshal(c, VERSION_2007), "xmlns:*");
         actual = unmarshal(DefaultLegalConstraints.class, xml);
-        assertSame(Restriction.LICENCE, getSingleton(actual.getUseConstraints()));
+        assertSame(Restriction.LICENSE, getSingleton(actual.getUseConstraints()));
         assertEquals(c, actual);
     }
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
index 638402f..6ebf23e 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResourceTest.java
@@ -20,8 +20,7 @@
 import org.opengis.util.NameFactory;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.OperationMetadata;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.internal.jaxb.metadata.replace.ServiceParameterTest;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.xml.NilReason;
@@ -47,8 +46,9 @@
      * Creates the resource to use for testing purpose.
      */
     static DefaultCoupledResource create(final NameFactory factory) {
-        final DefaultOperationMetadata operation = new DefaultOperationMetadata("Get Map",
-                DistributedComputingPlatform.WEB_SERVICES, null);
+        final DefaultOperationMetadata operation = new DefaultOperationMetadata();
+        operation.setOperationName("Get Map");
+        operation.setDistributedComputingPlatforms(singleton(UnsupportedCodeList.valueOf("WEB_SERVICES")));
         operation.setParameters(singleton((ParameterDescriptor<?>) ServiceParameterTest.create()));
         operation.setConnectPoints(singleton(NilReason.MISSING.createNilObject(OnlineResource.class)));
 
@@ -64,7 +64,7 @@
     @Test
     public void testOperationNameResolve() {
         final DefaultCoupledResource resource  = create(DefaultFactories.forBuildin(NameFactory.class));
-        final OperationMetadata      operation = resource.getOperation();
+        final DefaultOperationMetadata operation = resource.getOperation();
         /*
          * Test OperationName replacement when the name matches.
          */
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
index 9ee0684..b0c6b8c 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentificationTest.java
@@ -18,14 +18,9 @@
 
 import javax.xml.bind.JAXBException;
 import org.opengis.util.NameFactory;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.identification.CouplingType;
-import org.opengis.metadata.identification.CoupledResource;
-import org.opengis.metadata.identification.OperationMetadata;
-import org.opengis.metadata.identification.ServiceIdentification;
-import org.opengis.metadata.identification.DistributedComputingPlatform;
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.metadata.xml.TestUsingFile;
@@ -70,7 +65,7 @@
                 "A dummy service for testing purpose.");                // abstract
         id.setServiceTypeVersions(singleton("1.0"));
         id.setCoupledResources(singleton(resource));
-        id.setCouplingType(CouplingType.LOOSE);
+        id.setCouplingType(UnsupportedCodeList.valueOf("LOOSE"));
         id.setContainsOperations(singleton(resource.getOperation()));
         return id;
     }
@@ -79,28 +74,28 @@
      * Compare values of the given service identifications against the value expected for the
      * instance created by {@link #create()} method.
      */
-    private static void verify(final ServiceIdentification id) {
+    private static void verify(final DefaultServiceIdentification id) {
         assertEquals("serviceTypeVersion", "1.0",                                  getSingleton(id.getServiceTypeVersions()));
         assertEquals("serviceType",        "Web Map Server",                       String.valueOf(id.getServiceType()));
         assertEquals("abstract",           "A dummy service for testing purpose.", String.valueOf(id.getAbstract()));
         assertEquals("citation",           NilReason.MISSING,                      NilReason.forObject(id.getCitation()));
-        assertEquals("couplingType",       CouplingType.LOOSE,                     id.getCouplingType());
+        assertEquals("couplingType",       UnsupportedCodeList.valueOf("loose"),   id.getCouplingType());
 
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
 //      assertEquals("scopedName",        "mySpace:ABC-123",   …)  skipped because not present in new ISO 19115-3:2016.
 //      assertEquals("resourceReference", "WMS specification", …)  skipped because not present in legacy ISO 19139:2007.
 
-        final OperationMetadata op = resource.getOperation();
+        final DefaultOperationMetadata op = resource.getOperation();
         assertNotNull("operation", op);
         assertEquals("operationName", "Get Map", op.getOperationName());
-        assertEquals("distributedComputingPlatform", DistributedComputingPlatform.WEB_SERVICES, getSingleton(op.getDistributedComputingPlatforms()));
+        assertEquals("distributedComputingPlatform", UnsupportedCodeList.valueOf("WEB_SERVICES"), getSingleton(op.getDistributedComputingPlatforms()));
         assertEquals("connectPoints", NilReason.MISSING, NilReason.forObject(getSingleton(op.getConnectPoints())));
 
         final ParameterDescriptor<?> param = getSingleton(op.getParameters());
         assertEquals("name",          "Version",             String.valueOf(param.getName()));
         assertEquals("minimumOccurs", 0,                     param.getMinimumOccurs());
         assertEquals("maximumOccurs", 1,                     param.getMaximumOccurs());
-        assertEquals("direction",     ParameterDirection.IN, param.getDirection());
+//      assertEquals("direction",     ParameterDirection.IN, param.getDirection());
     }
 
     /**
@@ -110,9 +105,9 @@
      */
     @Test
     public void testUnmarshal() throws JAXBException {
-        final ServiceIdentification id = unmarshalFile(ServiceIdentification.class, XML2016+FILENAME);
+        final DefaultServiceIdentification id = unmarshalFile(DefaultServiceIdentification.class, XML2016+FILENAME);
         verify(id);
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
         assertTitleEquals("resourceReference", "WMS specification", getSingleton(resource.getResourceReferences()));
     }
 
@@ -123,9 +118,9 @@
      */
     @Test
     public void testUnmarshalLegacy() throws JAXBException {
-        final ServiceIdentification id = unmarshalFile(ServiceIdentification.class, XML2007+FILENAME);
+        final DefaultServiceIdentification id = unmarshalFile(DefaultServiceIdentification.class, XML2007+FILENAME);
         verify(id);
-        final CoupledResource resource = getSingleton(id.getCoupledResources());
+        final DefaultCoupledResource resource = getSingleton(id.getCoupledResources());
         assertEquals("scopedName", "mySpace:ABC-123", String.valueOf(resource.getScopedName()));
     }
 
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
index 4161629..8546863 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataFallbackVerifier.java
@@ -21,9 +21,8 @@
 import java.util.HashSet;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Party;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.internal.simple.CitationConstant;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.MetadataStandard;
@@ -118,22 +117,22 @@
             assertNull(name, actualID);
         } else if (actualID != null) {
             assertEquals(name, expectedID.getCode(),      actualID.getCode());
-            assertEquals(name, expectedID.getCodeSpace(), actualID.getCodeSpace());
-            assertEquals(name, expectedID.getVersion(),   actualID.getVersion());
+//          assertEquals(name, expectedID.getCodeSpace(), actualID.getCodeSpace());
+//          assertEquals(name, expectedID.getVersion(),   actualID.getVersion());
         }
         /*
          * The fallback may not declare all responsible parties.
          * If it declares a party, the name and role shall be equal.
          */
-        final Responsibility expectedResp = first(fromDB.getCitedResponsibleParties());
-        final Responsibility actualResp   = first(fromFB.getCitedResponsibleParties());
+        final ResponsibleParty expectedResp = first(fromDB.getCitedResponsibleParties());
+        final ResponsibleParty actualResp   = first(fromFB.getCitedResponsibleParties());
         if (expectedResp == null) {
             assertNull(name, actualResp);
         } else if (actualResp != null) {
             assertEquals(name, expectedResp.getRole(), actualResp.getRole());
-            final Party expectedParty = first(expectedResp.getParties());
-            final Party actualParty = first(actualResp.getParties());
-            assertEquals(name, expectedParty.getName(), actualParty.getName());
+//          final Party expectedParty = first(expectedResp.getParties());
+//          final Party actualParty = first(actualResp.getParties());
+//          assertEquals(name, expectedParty.getName(), actualParty.getName());
         }
         assertEquals(name, first(fromDB.getPresentationForms()),
                            first(fromFB.getPresentationForms()));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
index de424ce..9f551a4 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataSourceTest.java
@@ -32,7 +32,6 @@
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static org.apache.sis.test.TestUtilities.getSingleton;
 
 
 /**
@@ -110,10 +109,8 @@
      * @param title         the expected format title.
      */
     private static void verify(final Format format, final String abbreviation, final String title) {
-        final Citation spec = format.getFormatSpecificationCitation();
-        assertNotNull("formatSpecificationCitation", spec);
-        assertEquals("abbreviation", abbreviation, String.valueOf(getSingleton(spec.getAlternateTitles())));
-        assertEquals("title", title, String.valueOf(spec.getTitle()));
+        assertEquals("abbreviation", abbreviation, String.valueOf(format.getName()));
+        assertEquals("title", title, String.valueOf(format.getSpecification()));
     }
 
     /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
index 8b4d6a2..34bcecb 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/sql/MetadataWriterTest.java
@@ -17,7 +17,6 @@
 package org.apache.sis.metadata.sql;
 
 import java.util.Collections;
-import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.OnLineFunction;
@@ -36,8 +35,7 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 
 
 /**
@@ -85,6 +83,7 @@
      * @throws Exception if an error occurred while writing or reading the database.
      */
     @Test
+    @org.junit.Ignore("Requires GeoAPI 3.1.")
     public void testPostgreSQL() throws Exception {
         try (final TestDatabase db = TestDatabase.createOnPostgreSQL("MetadataWriter", true)) {
             source = new MetadataWriter(MetadataStandard.ISO_19115, db.source, "MetadataWriter", null);
@@ -121,7 +120,7 @@
         assertEquals("EPSG",      source.search(HardCodedCitations.EPSG));
         assertEquals("SIS",       source.search(HardCodedCitations.SIS));
         assertNull  ("ISO 19111", source.search(HardCodedCitations.ISO_19111));
-        assertEquals("{rp}EPSG",  source.search(TestUtilities.getSingleton(
+        assertEquals("EPSG",      source.search(TestUtilities.getSingleton(
                 HardCodedCitations.EPSG.getCitedResponsibleParties())));
     }
 
@@ -154,20 +153,12 @@
         /*
          * Ask for dependencies that are known to exist.
          */
-        final Responsibility responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
+        final ResponsibleParty responsible = TestUtilities.getSingleton(c.getCitedResponsibleParties());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, responsible.getRole());
 
-        final Party party = TestUtilities.getSingleton(responsible.getParties());
-        assertEquals("International Association of Oil & Gas Producers", party.getName().toString());
-        final Contact contact = TestUtilities.getSingleton(party.getContactInfo());
-        /*
-         * Invoke for the deprecated 'getOnlineResource()' method (singular form) before the non-deprecated
-         * 'getOnlineResources()' (plural form) replacement. They shall give the same result no matter which
-         * form were stored in the database.
-         */
-        @SuppressWarnings("deprecation")
-        final OnlineResource resource = contact.getOnlineResource();
-        assertSame(resource, TestUtilities.getSingleton(contact.getOnlineResources()));
+        assertEquals("International Association of Oil & Gas Producers", responsible.getOrganisationName().toString());
+
+        OnlineResource resource = responsible.getContactInfo().getOnlineResource();
         assertEquals("http://www.epsg.org", resource.getLinkage().toString());
         assertEquals(OnLineFunction.INFORMATION, resource.getFunction());
         /*
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/xml/SchemaComplianceTest.java b/core/sis-metadata/src/test/java/org/apache/sis/metadata/xml/SchemaComplianceTest.java
deleted file mode 100644
index be775fc..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/xml/SchemaComplianceTest.java
+++ /dev/null
@@ -1,79 +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.metadata.xml;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.Files;
-import org.apache.sis.metadata.iso.ISOMetadata;
-import org.apache.sis.internal.system.DataDirectory;
-import org.apache.sis.test.xml.SchemaCompliance;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.junit.Assume.*;
-import static org.junit.Assert.*;
-
-
-/**
- * Tests conformance of JAXB annotations with XML schemas if those schemas are available.
- * This tests requires the {@code $SIS_DATA/Schemas/iso/19115/-3} directory to exists.
- * Those files must be installed manually; they are not distributed with Apache SIS for licensing reasons.
- * Content can be downloaded as ZIP files from <a href="https://standards.iso.org/iso/19115/">ISO portal</a>.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class SchemaComplianceTest extends TestCase {
-    /**
-     * Verifies compliance with metadata schemas.
-     *
-     * @throws Exception if an error occurred while checking the schema.
-     *
-     * @see <a href="https://standards.iso.org/iso/19115/-3/">ISO schemas for metadata</a>
-     */
-    @Test
-    public void verifyMetadata() throws Exception {
-        Path directory = DataDirectory.SCHEMAS.getDirectory();
-        assumeNotNull(directory);
-        directory = directory.resolve("iso");
-        assumeTrue(Files.isDirectory(directory.resolve("19115")));
-        /*
-         * Locate the root of metadata class directory. In a Maven build:
-         * "core/sis-metadata/target/classes/org/apache/sis/metadata/iso"
-         */
-        final Path mdp = Paths.get(ISOMetadata.class.getResource("ISOMetadata.class").toURI()).getParent();
-        final Path cp = getParent(mdp, "org", "apache", "sis", "metadata", "iso");
-        final SchemaCompliance checker = new SchemaCompliance(cp, directory);
-        checker.loadDefaultSchemas();
-        checker.verify(mdp);
-    }
-
-    /**
-     * Returns the parent directory. The expected names of skipped directories are given in argument;
-     * they will be verified.
-     */
-    private static Path getParent(Path cp, final String... parents) {
-        for (int i=parents.length; --i >= 0;) {
-            assertTrue(cp.endsWith(parents[i]));
-            cp = cp.getParent();
-        }
-        return cp;
-    }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
index e7b78d4..f817af8 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/MetadataAssert.java
@@ -23,9 +23,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.lineage.Source;
-import org.opengis.metadata.maintenance.Scope;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.metadata.content.FeatureTypeInfo;
 import org.opengis.metadata.content.FeatureCatalogueDescription;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.test.xml.DocumentComparator;
@@ -35,7 +33,13 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-specific imports
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.feature.type.FeatureType;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+import org.apache.sis.metadata.iso.content.DefaultFeatureCatalogueDescription;
+import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
+import org.apache.sis.metadata.iso.lineage.DefaultSource;
+import org.apache.sis.metadata.iso.quality.DefaultScope;
 
 
 /**
@@ -82,9 +86,9 @@
      *
      * @since 0.8
      */
-    public static void assertPartyNameEquals(final String message, final String expected, final Citation citation) {
+    public static void assertPartyNameEquals(final String message, final String expected, final DefaultCitation citation) {
         assertNotNull(message, citation);
-        final Responsibility r = getSingleton(citation.getCitedResponsibleParties());
+        final DefaultResponsibility r = (DefaultResponsibility) getSingleton(citation.getCitedResponsibleParties());
         final InternationalString name = getSingleton(r.getParties()).getName();
         assertNotNull(message, name);
         assertEquals(message, expected, name.toString(Locale.US));
@@ -101,7 +105,7 @@
      * @since 1.0
      */
     public static void assertContentInfoEquals(final String name, final Integer count, final FeatureCatalogueDescription catalog) {
-        final FeatureTypeInfo info = getSingleton(catalog.getFeatureTypeInfo());
+        final DefaultFeatureTypeInfo info = getSingleton(((DefaultFeatureCatalogueDescription) catalog).getFeatureTypeInfo());
         assertEquals("metadata.contentInfo.featureType", name, String.valueOf(info.getFeatureTypeName()));
         assertEquals("metadata.contentInfo.featureInstanceCount", count, info.getFeatureInstanceCount());
     }
@@ -118,10 +122,10 @@
      */
     public static void assertFeatureSourceEquals(final String name, final String[] features, final Source source) {
         assertEquals("metadata.lineage.source.sourceCitation.title", name, String.valueOf(source.getSourceCitation().getTitle()));
-        final Scope scope = source.getScope();
+        final DefaultScope scope = (DefaultScope) ((DefaultSource) source).getScope();
         assertNotNull("metadata.lineage.source.scope", scope);
         assertEquals("metadata.lineage.source.scope.level", ScopeCode.FEATURE_TYPE, scope.getLevel());
-        final CharSequence[] actual = CollectionsExt.toArray(getSingleton(scope.getLevelDescription()).getFeatures(), CharSequence.class);
+        final Object[] actual = CollectionsExt.toArray(getSingleton(scope.getLevelDescription()).getFeatures(), FeatureType.class);
         for (int i=0; i<actual.length; i++) {
             actual[i] = actual[i].toString();
         }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
index 2d88290..e270d19 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/CoordinateSystemAxisMock.java
@@ -64,6 +64,8 @@
     @Override public int                  getDimension()         {return 1;}
     @Override public CoordinateSystemAxis getAxis(int dimension) {return this;}
     @Override public AxisDirection        getDirection()         {return null;}
+    @Override public double               getMinimumValue()      {return Double.NEGATIVE_INFINITY;}
+    @Override public double               getMaximumValue()      {return Double.POSITIVE_INFINITY;}
     @Override public RangeMeaning         getRangeMeaning()      {return RangeMeaning.EXACT;}
     @Override public Unit<?>              getUnit()              {return null;}
 }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java
index 42e620b..de13618 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/IdentifiedObjectMock.java
@@ -17,12 +17,15 @@
 package org.apache.sis.test.mock;
 
 import java.util.Arrays;
+import java.util.Set;
 import java.util.Collection;
 import java.io.Serializable;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.GenericName;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.util.Strings;
@@ -125,6 +128,26 @@
     }
 
     /**
+     * Returns the namespace version ({@code null} for now).
+     *
+     * @return the namespace version.
+     */
+    @Override
+    public final String getVersion() {
+        return null;
+    }
+
+    /**
+     * Returns the authority that define the object ({@code null} for now).
+     *
+     * @return the defining authority.
+     */
+    @Override
+    public final Citation getAuthority() {
+        return null;
+    }
+
+    /**
      * Returns {@link #alias} in an unmodifiable collection, or an empty collection if the alias is null.
      *
      * @return {@link #alias} singleton or an empty collection.
@@ -135,6 +158,37 @@
     }
 
     /**
+     * Returns the identifiers (currently null).
+     *
+     * @return the identifiers of this object.
+     */
+    @Override
+    public final Set<ReferenceIdentifier> getIdentifiers() {
+        return null;
+    }
+
+    /**
+     * Returns the remarks (currently null).
+     *
+     * @return the remarks associated to this object.
+     */
+    @Override
+    public final InternationalString getRemarks() {
+        return null;
+    }
+
+    /**
+     * Returns the WKT representation (currently none).
+     *
+     * @return the WKT representation of this object.
+     * @throws UnsupportedOperationException if there is no WKT representation.
+     */
+    @Override
+    public final String toWKT() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Returns a string representation for debugging purpose.
      */
     @Override
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
index 634f1e2..5479bc2 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/MetadataMock.java
@@ -16,11 +16,9 @@
  */
 package org.apache.sis.test.mock;
 
-import java.util.Map;
+import java.util.Locale;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Locale;
-import java.nio.charset.Charset;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -69,22 +67,11 @@
     }
 
     /**
-     * Returns {@link #language} in a singleton map or an empty map.
-     *
-     * @return {@link #language}
-     */
-    @Override
-    public Map<Locale,Charset> getLocalesAndCharsets() {
-        return (language != null) ? Collections.singletonMap(language, null) : Collections.emptyMap();
-    }
-
-    /**
      * Returns {@link #language} in a singleton set or an empty set.
      *
      * @return {@link #language}
      */
     @Override
-    @Deprecated
     public Collection<Locale> getLanguages() {
         return (language != null) ? Collections.singleton(language) : Collections.emptySet();
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
index 203fd55..c74d5aa 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/mock/VerticalCRSMock.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.test.mock;
 
+import java.util.Date;
 import javax.measure.Unit;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -121,6 +122,8 @@
 
     @Override public String               getAbbreviation()      {return up ? "h" : "d";}
     @Override public InternationalString  getScope()             {return null;}
+    @Override public InternationalString  getAnchorPoint()       {return null;}
+    @Override public Date                 getRealizationEpoch()  {return null;}
     @Override public Extent               getDomainOfValidity()  {return null;}
     @Override public VerticalDatumType    getVerticalDatumType() {return type;}
     @Override public VerticalDatum        getDatum()             {return this;}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java b/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
index 9053846..991fd98 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
@@ -133,8 +133,7 @@
     org.apache.sis.metadata.sql.IdentifierGeneratorTest.class,
     org.apache.sis.metadata.sql.MetadataSourceTest.class,
     org.apache.sis.metadata.sql.MetadataWriterTest.class,
-    org.apache.sis.metadata.iso.citation.CitationsTest.class,
-    org.apache.sis.metadata.xml.SchemaComplianceTest.class
+    org.apache.sis.metadata.iso.citation.CitationsTest.class
 })
 public final strictfp class MetadataTestSuite extends TestSuite {
     /**
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
index 76c1032..8d6f333 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/AnnotationConsistencyCheck.java
@@ -31,12 +31,9 @@
 import javax.xml.bind.annotation.XmlElementRefs;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.annotation.UML;
-import org.opengis.annotation.Classifier;
-import org.opengis.annotation.Stereotype;
 import org.opengis.annotation.Obligation;
 import org.opengis.annotation.Specification;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.internal.xml.Schemas;
@@ -68,7 +65,6 @@
  * </ul>
  *
  * This class does not verify JAXB annotations against a XSD file.
- * For such verification, see {@link SchemaCompliance}.
  *
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -227,13 +223,12 @@
      * <p>The default implementation recognizes the
      * {@linkplain Specification#ISO_19115   ISO 19115},
      * {@linkplain Specification#ISO_19115_2 ISO 19115-2},
-     * {@linkplain Specification#ISO_19115_3 ISO 19115-3},
      * {@linkplain Specification#ISO_19139   ISO 19139} and
      * {@linkplain Specification#ISO_19108   ISO 19108} specifications,
      * with a hard-coded list of exceptions to the general rule.
      * Subclasses shall override this method if they need to support more namespaces.</p>
      *
-     * <p>Note that a more complete verification is done by {@link SchemaCompliance}.
+     * <p>Note that a more complete verification is done by {@code SchemaCompliance}.
      * But the test done in this {@link AnnotationConsistencyCheck} class can be run without network access.</p>
      *
      * <p>The prefix for the given namespace will be fetched by
@@ -243,8 +238,10 @@
      * @param  uml   the UML associated to the class or the method.
      * @return the expected namespace.
      * @throws IllegalArgumentException if the given UML is unknown to this method.
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    @SuppressWarnings("deprecation")
+    @Deprecated
     protected String getExpectedNamespaceStart(final Class<?> impl, final UML uml) {
         final String identifier = uml.identifier();
         switch (identifier) {
@@ -253,13 +250,11 @@
             case "SV_OperationChainMetadata":
             case "SV_ServiceIdentification": {              // Historical reasons (other standard integrated into ISO 19115)
                 assertEquals("Unexpected @Specification value.", Specification.ISO_19115, uml.specification());
-                assertEquals("Specification version should be latest ISO 19115.", (short) 0, uml.version());
                 return Namespaces.SRV;
             }
             case "DQ_TemporalAccuracy":                     // Renamed DQ_TemporalQuality
             case "DQ_NonQuantitativeAttributeAccuracy": {   // Renamed DQ_NonQuantitativeAttributeCorrectness
                 assertEquals("Unexpected @Specification value.", Specification.ISO_19115, uml.specification());
-                assertEquals("Specification version should be legacy ISO 19115.", (short) 2003, uml.version());
                 return LegacyNamespaces.GMD;
             }
             case "role": {
@@ -311,12 +306,11 @@
          */
         if (identifier.startsWith("DQ_")) {
             assertEquals("Unexpected @Specification value.", Specification.ISO_19115, uml.specification());
-            assertEquals("Specification version should be legacy ISO 19115.", (short) 2003, uml.version());
             return Namespaces.MDQ;
         }
         if (identifier.startsWith("QE_")) {
             assertEquals("Unexpected @Specification value.", Specification.ISO_19115_2, uml.specification());
-            switch (uml.version()) {
+            switch (/*uml.version()*/ 0) {
                 case 0:    return Namespaces.MDQ;
                 case 2009: return LegacyNamespaces.GMI;
                 default: fail("Unexpected version number in " + uml);
@@ -332,7 +326,7 @@
          * General cases (after we processed all the special cases)
          * based on which standard defines the type or property.
          */
-        if (uml.version() != 0) {
+        if (/*uml.version()*/ 0 != 0) {
             switch (uml.specification()) {
                 case ISO_19115:   return LegacyNamespaces.GMD;
                 case ISO_19115_2: return LegacyNamespaces.GMI;
@@ -340,8 +334,7 @@
         }
         switch (uml.specification()) {
             case ISO_19115:
-            case ISO_19115_2:
-            case ISO_19115_3: return Schemas.METADATA_ROOT;
+            case ISO_19115_2: return Schemas.METADATA_ROOT;
             case ISO_19139:   return LegacyNamespaces.GMX;
             case ISO_19108:   return LegacyNamespaces.GMD;
             default: throw new IllegalArgumentException(uml.toString());
@@ -355,18 +348,17 @@
      * and unconditionally with {@code "_Type"} appended.
      * Subclasses shall override this method when mismatches are known to exist between the UML and XML type names.
      *
-     * @param  stereotype  the stereotype of the interface, or {@code null} if none.
-     * @param  uml         the UML of the interface for which to get the corresponding XML type name.
+     * @param  uml  the UML of the interface for which to get the corresponding XML type name.
      * @return the name of the XML type for the given element, or {@code null} if none.
      *
      * @see #testImplementationAnnotations()
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    protected String getExpectedXmlTypeName(final Stereotype stereotype, final UML uml) {
+    @Deprecated
+    protected String getExpectedXmlTypeName(final UML uml) {
         final String rootName = uml.identifier();
         final StringBuilder buffer = new StringBuilder(rootName.length() + 13);
-        if (Stereotype.ABSTRACT.equals(stereotype)) {
-            buffer.append("Abstract");
-        }
         return buffer.append(rootName).append("_Type").toString();
     }
 
@@ -375,16 +367,19 @@
      * The default implementation returns {@link UML#identifier()}, possibly with {@code "Abstract"} prepended.
      * Subclasses shall override this method when mismatches are known to exist between the UML and XML element names.
      *
-     * @param  stereotype  the stereotype of the interface, or {@code null} if none.
-     * @param  uml         the UML of the interface for which to get the corresponding XML root element name.
+     * @param  uml  the UML of the interface for which to get the corresponding XML root element name.
      * @return the name of the XML root element for the given UML.
      *
      * @see #testImplementationAnnotations()
+     *
+     * @deprecated the complete function is available only on development branch because it depends on GeoAPI 3.1.
      */
-    protected String getExpectedXmlRootElementName(final Stereotype stereotype, final UML uml) {
+    @Deprecated
+    protected String getExpectedXmlRootElementName(final UML uml) {
         String name = uml.identifier();
-        if (Stereotype.ABSTRACT.equals(stereotype)) {
-            name = "Abstract".concat(name);
+        switch (name) {
+            // This case can be removed if https://issues.apache.org/jira/browse/SIS-398 is fixed.
+            case "MI_PolarizationOrientationCode": name = "MI_PolarisationOrientationCode"; break;
         }
         return name;
     }
@@ -424,6 +419,24 @@
                 }
                 break;
             }
+            case "detectedPolarization": {
+                if (org.opengis.metadata.content.Band.class.isAssignableFrom(enclosing)) {
+                    name = "detectedPolarisation";          // Spelling change in XSD files
+                }
+                break;
+            }
+            case "transmittedPolarization": {
+                if (org.opengis.metadata.content.Band.class.isAssignableFrom(enclosing)) {
+                    name = "transmittedPolarisation";       // Spelling change in XSD files
+                }
+                break;
+            }
+            case "featureType": {
+                if (org.opengis.metadata.distribution.DataFile.class.isAssignableFrom(enclosing)) {
+                    name = "featureTypes";                  // Spelling change in XSD files
+                }
+                break;
+            }
             case "valueType": {
                 if (org.opengis.metadata.quality.Result.class.isAssignableFrom(enclosing)) {
                     return "valueRecordType";
@@ -591,7 +604,7 @@
             case "getNominalSpatialResolution":
             case "getTransferFunctionType": {
                 final Class<?> dc = method.getDeclaringClass();
-                return org.opengis.metadata.content.SampleDimension.class.isAssignableFrom(dc)
+                return org.apache.sis.metadata.iso.content.DefaultSampleDimension.class.isAssignableFrom(dc)
                         && !org.opengis.metadata.content.Band.class.isAssignableFrom(dc);
             }
             /*
@@ -622,7 +635,7 @@
             testingClass = type.getCanonicalName();
             UML uml = type.getAnnotation(UML.class);
             assertNotNull("Missing @UML annotation.", uml);
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 for (final Method method : type.getDeclaredMethods()) {
                     if (isPublic(method)) {
                         testingMethod = method.getName();
@@ -651,7 +664,7 @@
     public void testPackageAnnotations() {
         final Set<Package> packages = new HashSet<>();
         for (final Class<?> type : types) {
-            if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (!CodeList.class.isAssignableFrom(type)) {
                 testingClass = type.getCanonicalName();
                 final Class<?> impl = getImplementation(type);
                 if (impl != null) {
@@ -692,7 +705,7 @@
     @DependsOnMethod("testInterfaceAnnotations")
     public void testImplementationAnnotations() {
         for (final Class<?> type : types) {
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (CodeList.class.isAssignableFrom(type)) {
                 // Skip code lists, since they are not the purpose of this test.
                 continue;
             }
@@ -715,14 +728,7 @@
             final XmlRootElement root = impl.getAnnotation(XmlRootElement.class);
             assertNotNull("Missing @XmlRootElement annotation.", root);
             final UML uml = type.getAnnotation(UML.class);
-            Stereotype stereotype = null;
-            if (uml != null) {
-                final Classifier c = type.getAnnotation(Classifier.class);
-                if (c != null) {
-                    stereotype = c.value();
-                }
-                assertEquals("Wrong @XmlRootElement.name().", getExpectedXmlRootElementName(stereotype, uml), root.name());
-            }
+            // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
             /*
              * Check that the namespace is the expected one (according subclass)
              * and is not redundant with the package @XmlSchema annotation.
@@ -733,11 +739,7 @@
              */
             final XmlType xmlType = impl.getAnnotation(XmlType.class);
             assertNotNull("Missing @XmlType annotation.", xmlType);
-            String expected = getExpectedXmlTypeName(stereotype, uml);
-            if (expected == null) {
-                expected = DEFAULT;
-            }
-            assertEquals("Wrong @XmlType.name().", expected, xmlType.name());
+            // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
         }
     }
 
@@ -755,7 +757,7 @@
     @DependsOnMethod("testImplementationAnnotations")
     public void testMethodAnnotations() {
         for (final Class<?> type : types) {
-            if (ControlledVocabulary.class.isAssignableFrom(type)) {
+            if (CodeList.class.isAssignableFrom(type)) {
                 // Skip code lists, since they are not the purpose of this test.
                 continue;
             }
@@ -829,9 +831,7 @@
                  */
                 if (uml != null) {
                     assertEquals("Wrong @XmlElement.name().", getExpectedXmlElementName(type, uml), element.name());
-                    if (!method.isAnnotationPresent(Deprecated.class) && uml.version() == 0) {
-                        assertEquals("Wrong @XmlElement.required().", uml.obligation() == Obligation.MANDATORY, element.required());
-                    }
+                    // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
                 }
                 /*
                  * Check that the namespace is the expected one (according subclass)
@@ -917,10 +917,10 @@
                 assertFalse("Expected @XmlElementRef.", wrapper.isInherited);
                 final UML uml = type.getAnnotation(UML.class);
                 if (uml != null) {                  // 'assertNotNull' is 'testInterfaceAnnotations()' job.
-                    assertEquals("Wrong @XmlElement.", getExpectedXmlRootElementName(null, uml), element.name());
+                    assertEquals("Wrong @XmlElement.", getExpectedXmlRootElementName(uml), element.name());
                 }
                 final String namespace = assertExpectedNamespace(element.namespace(), wrapper.type, uml);
-                if (!ControlledVocabulary.class.isAssignableFrom(type)) {
+                if (!CodeList.class.isAssignableFrom(type)) {
                     final String expected = getNamespace(getImplementation(type));
                     if (expected != null) {         // 'assertNotNull' is 'testImplementationAnnotations()' job.
                         assertEquals("Inconsistent @XmlRootElement namespace.", expected, namespace);
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
deleted file mode 100644
index 461120b..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/PackageVerifier.java
+++ /dev/null
@@ -1,510 +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.test.xml;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.HashMap;
-import java.util.Collection;
-import java.util.Collections;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.ParameterizedType;
-import javax.xml.bind.annotation.XmlNs;
-import javax.xml.bind.annotation.XmlType;
-import javax.xml.bind.annotation.XmlSchema;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
-import javax.xml.parsers.ParserConfigurationException;
-import org.xml.sax.SAXException;
-import org.opengis.util.CodeList;
-import org.opengis.annotation.UML;
-import org.opengis.geoapi.SchemaException;
-import org.opengis.geoapi.SchemaInformation;
-import org.apache.sis.util.Classes;
-import org.apache.sis.internal.system.Modules;
-import org.apache.sis.internal.xml.LegacyNamespaces;
-import org.apache.sis.xml.Namespaces;
-
-
-/**
- * Verify JAXB annotations in a single package. A new instance of this class is created by
- * {@link SchemaCompliance#verify(java.nio.file.Path)} for each Java package to be verified.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-final strictfp class PackageVerifier {
-    /**
-     * Sentinel value used in {@link #LEGACY_NAMESPACES} for meaning "all properties in that namespace".
-     */
-    @SuppressWarnings("unchecked")
-    private static final Set<String> ALL = InfiniteSet.INSTANCE;
-
-    /**
-     * Classes or properties having a JAXB annotation in this namespace should be deprecated.
-     * Deprecated namespaces are enumerated as keys. If the associated value is {@link #ALL},
-     * the whole namespace is deprecated. If the value is not ALL, then only the enumerated
-     * properties are deprecated.
-     *
-     * <p>Non-ALL values are rare. They happen in a few cases where a property is legacy despite its namespace.
-     * Those "properties" are errors in the legacy ISO 19139:2007 schema; they were types without their property
-     * wrappers. For example in {@code SV_CoupledResource}, {@code <gco:ScopedName>} was marshalled without its
-     * {@code <srv:scopedName>} wrapper — note the upper and lower-case "s". Because {@code ScopedName} is a type,
-     * we had to keep the namespace declared in {@link org.apache.sis.util.iso.DefaultScopedName}
-     * (the replacement is performed by {@code org.apache.sis.xml.TransformingWriter}).</p>
-     */
-    private static final Map<String, Set<String>> LEGACY_NAMESPACES;
-    static {
-        final Map<String, Set<String>> m = new HashMap<>(8);
-        m.put(LegacyNamespaces.GMD, ALL);
-        m.put(LegacyNamespaces.GMI, ALL);
-        m.put(LegacyNamespaces.GMX, ALL);
-        m.put(LegacyNamespaces.SRV, ALL);
-        m.put(Namespaces.GCO, Collections.singleton("ScopedName"));     // Not to be confused with standard <srv:scopedName>
-        LEGACY_NAMESPACES = Collections.unmodifiableMap(m);
-    }
-
-    /**
-     * Types declared in JAXB annotations to be considered as equivalent to types in XML schemas.
-     */
-    private static final Map<String,String> TYPE_EQUIVALENCES;
-    static {
-        final Map<String,String> m = new HashMap<>();
-        m.put("PT_FreeText",             "CharacterString");
-        m.put("Abstract_Citation",       "CI_Citation");
-        m.put("AbstractCI_Party",        "CI_Party");
-        m.put("Abstract_Responsibility", "CI_Responsibility");
-        m.put("Abstract_Extent",         "EX_Extent");
-        TYPE_EQUIVALENCES = Collections.unmodifiableMap(m);
-    }
-
-    /**
-     * The schemas to compare with the JAXB annotations.
-     * Additional schemas will be loaded as needed.
-     */
-    private final SchemaCompliance schemas;
-
-    /**
-     * The package name, for reporting error.
-     */
-    private final String packageName;
-
-    /**
-     * The default namespace to use if a class does not define explicitly a namespace.
-     */
-    private final String packageNS;
-
-    /**
-     * The namespace of the class under examination.
-     * This field must be updated for every class found in a package.
-     */
-    private String classNS;
-
-    /**
-     * The class under examination, used in error messages.
-     * This field must be updated for every class found in a package.
-     */
-    private Class<?> currentClass;
-
-    /**
-     * Whether the class under examination is defined in a legacy namespace.
-     * In such case, some checks may be skipped because we didn't loaded schemas for legacy properties.
-     */
-    private boolean isDeprecatedClass;
-
-    /**
-     * The schema definition for the class under examination.
-     *
-     * @see SchemaCompliance#getTypeDefinition(String)
-     */
-    private Map<String, SchemaCompliance.Element> properties;
-
-    /**
-     * Whether a namespace is actually used of not.
-     * We use this map for identifying unnecessary prefix declarations.
-     */
-    private final Map<String,Boolean> namespaceIsUsed;
-
-    /**
-     * Whether adapters declared in {@code package-info.java} are used or not.
-     */
-    private final Map<Class<?>,Boolean> adapterIsUsed;
-
-    /**
-     * Creates a new verifier for the given package.
-     */
-    PackageVerifier(final SchemaCompliance schemas, final Package pkg)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        this.schemas = schemas;
-        namespaceIsUsed = new HashMap<>();
-        adapterIsUsed = new HashMap<>();
-        String name = "?", namespace = "";
-        if (pkg != null) {
-            name = pkg.getName();
-            final XmlSchema schema = pkg.getAnnotation(XmlSchema.class);
-            if (schema != null) {
-                namespace = schema.namespace();
-                String location = schema.location();
-                if (!XmlSchema.NO_LOCATION.equals(location)) {
-                    if (!location.startsWith(schema.namespace())) {
-                        throw new SchemaException("XML schema location inconsistent with namespace in package " + name);
-                    }
-                    schemas.loadSchema(location);
-                }
-                for (final XmlNs xmlns : schema.xmlns()) {
-                    final String pr = xmlns.prefix();
-                    final String ns = xmlns.namespaceURI();
-                    final String cr = schemas.allXmlNS.put(pr, ns);
-                    if (cr != null && !cr.equals(ns)) {
-                        throw new SchemaException(String.format("Prefix \"%s\" associated to two different namespaces:%n%s%n%s", pr, cr, ns));
-                    }
-                    if (namespaceIsUsed.put(ns, Boolean.FALSE) != null) {
-                        throw new SchemaException(String.format("Duplicated namespace in package %s:%n%s", name, ns));
-                    }
-                }
-            }
-            /*
-             * Lists the type of all values for which an adapter is declared in package-info.
-             * If the type is not explicitly declared, then it is inferred from class signature.
-             */
-            final XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class);
-            if (adapters != null) {
-                for (final XmlJavaTypeAdapter adapter : adapters.value()) {
-                    Class<?> propertyType = adapter.type();
-                    if (propertyType == XmlJavaTypeAdapter.DEFAULT.class) {
-                        for (Class<?> c = adapter.value(); ; c = c.getSuperclass()) {
-                            final Type type = c.getGenericSuperclass();
-                            if (type == null) {
-                                throw new SchemaException(String.format(
-                                        "Can not infer type for %s adapter.", adapter.value().getName()));
-                            }
-                            if (type instanceof ParameterizedType) {
-                                final Type[] p = ((ParameterizedType) type).getActualTypeArguments();
-                                if (p.length == 2) {
-                                    Type pt = p[1];
-                                    if (pt instanceof ParameterizedType) {
-                                        pt = ((ParameterizedType) pt).getRawType();
-                                    }
-                                    if (pt instanceof Class<?>) {
-                                        propertyType = (Class<?>) pt;
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                    if (adapterIsUsed.put((Class<?>) propertyType, Boolean.FALSE) != null) {
-                        throw new SchemaException(String.format(
-                                "More than one adapter for %s in package %s", propertyType, name));
-                    }
-                }
-            }
-        }
-        packageName = name;
-        packageNS = namespace;
-    }
-
-    /**
-     * Verifies {@code @XmlType} and {@code @XmlRootElement} on the class. This method verifies naming convention
-     * (type name should be same as root element name with {@value SchemaCompliance#TYPE_SUFFIX} suffix appended),
-     * ensures that the name exists in the schema, and checks the namespace.
-     *
-     * @param  type  the class on which to verify annotations.
-     */
-    final void verify(final Class<?> type)
-            throws IOException, ParserConfigurationException, SAXException, SchemaException
-    {
-        /*
-         * Reinitialize fields to be updated for each class.
-         */
-        classNS           = null;
-        currentClass      = type;
-        isDeprecatedClass = false;
-        properties        = Collections.emptyMap();
-
-        final XmlType        xmlType = type.getDeclaredAnnotation(XmlType.class);
-        final XmlRootElement xmlRoot = type.getDeclaredAnnotation(XmlRootElement.class);
-        XmlElement codeList = null;
-        /*
-         * Get the type name and namespace from the @XmlType or @XmlRootElement annotations.
-         * If both of them are present, verify that they are consistent (same namespace and
-         * same name with "_Type" suffix in @XmlType). If the type name is not declared, we
-         * assume that it is the same than the class name (this is what Apache SIS 1.0 does
-         * in its org.apache.sis.internal.jaxb.code package for CodeList adapters).
-         */
-        final String isoName;       // ISO class name (not the same than Java class name).
-        if (xmlRoot != null) {
-            classNS = xmlRoot.namespace();
-            isoName = xmlRoot.name();
-            if (xmlType != null) {
-                if (!classNS.equals(xmlType.namespace())) {
-                    throw new SchemaException(errorInClassMember(null)
-                            .append("Mismatched namespace in @XmlType and @XmlRootElement.").toString());
-                }
-                SchemaCompliance.verifyNamingConvention(type.getName(), isoName, xmlType.name(), SchemaCompliance.TYPE_SUFFIX);
-            }
-        } else if (xmlType != null) {
-            classNS = xmlType.namespace();
-            final String name = xmlType.name();
-            isoName = SchemaCompliance.trim(name, SchemaCompliance.TYPE_SUFFIX);
-        } else {
-            /*
-             * If there is neither @XmlRootElement or @XmlType annotation, it may be a code list as implemented
-             * in the org.apache.sis.internal.jaxb.code package. Those adapters have a single @XmlElement which
-             * is to be interpreted as if it was the actual type.
-             */
-            for (final Method method : type.getDeclaredMethods()) {
-                final XmlElement e = method.getDeclaredAnnotation(XmlElement.class);
-                if (e != null) {
-                    if (codeList != null) return;
-                    codeList = e;
-                }
-            }
-            if (codeList == null) return;
-            classNS = codeList.namespace();
-            isoName = codeList.name();
-        }
-        /*
-         * Verify that the namespace declared on the class is not redundant with the namespace
-         * declared in the package. Actually redundant namespaces are not wrong, but we try to
-         * reduce code size.
-         */
-        if (classNS.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            classNS = packageNS;
-        } else if (classNS.equals(packageNS)) {
-            throw new SchemaException(errorInClassMember(null)
-                    .append("Redundant namespace declaration: ").append(classNS).toString());
-        }
-        /*
-         * Verify that the namespace has a prefix associated to it in the package-info file.
-         */
-        if (namespaceIsUsed.put(classNS, Boolean.TRUE) == null) {
-            throw new SchemaException(errorInClassMember(null)
-                    .append("No prefix in package-info for ").append(classNS).toString());
-        }
-        /*
-         * Properties in the legacy GMD or GMI namespaces may be deprecated, depending if a replacement
-         * is already available or not. However properties in other namespaces should not be deprecated.
-         * Some validations of deprecated properties are skipped because we didn't loaded their schema.
-         */
-        isDeprecatedClass = (LEGACY_NAMESPACES.get(classNS) == ALL);
-        if (!isDeprecatedClass) {
-            if (type.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append("Unexpected @Deprecated annotation.").toString());
-            }
-            /*
-             * Verify that class name exists, then verify its namespace (associated to the null key by convention).
-             */
-            properties = schemas.getTypeDefinition(isoName);
-            if (properties == null) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append("Unknown name declared in @XmlRootElement: ").append(isoName).toString());
-            }
-            final String expectedNS = properties.get(null).namespace;
-            if (!classNS.equals(expectedNS)) {
-                throw new SchemaException(errorInClassMember(null)
-                        .append(isoName).append(" shall be associated to namespace ").append(expectedNS).toString());
-            }
-            if (codeList != null) return;                   // If the class was a code list, we are done.
-        }
-        /*
-         * At this point the classNS, className, isDeprecatedClass and properties field have been set.
-         * We can now loop over the XML elements, which may be on fields or on methods (public or private).
-         */
-        for (final Field field : type.getDeclaredFields()) {
-            Class<?> valueType = field.getType();
-            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection) {
-                valueType = Classes.boundOfParameterizedProperty(field);
-            }
-            verify(field, field.getName(), valueType, isCollection);
-        }
-        for (final Method method : type.getDeclaredMethods()) {
-            Class<?> valueType = method.getReturnType();
-            final boolean isCollection = Collection.class.isAssignableFrom(valueType);
-            if (isCollection) {
-                valueType = Classes.boundOfParameterizedProperty(method);
-            }
-            verify(method, method.getName(), valueType, isCollection);
-        }
-    }
-
-    /**
-     * Validate a field or a method against the expected schema.
-     *
-     * @param  property      the field or method to validate.
-     * @param  javaName      the field name or method name in Java code.
-     * @param  valueType     the field type or the method return type, or element type in case of collection.
-     * @param  isCollection  whether the given value type is the element type of a collection.
-     */
-    private void verify(final AnnotatedElement property, final String javaName,
-            final Class<?> valueType, final boolean isCollection) throws SchemaException
-    {
-        final XmlElement element = property.getDeclaredAnnotation(XmlElement.class);
-        if (element == null) {
-            return;                               // No @XmlElement annotation - skip this property.
-        }
-        String name = element.name();
-        if (name.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            name = javaName;
-        }
-        String ns = element.namespace();
-        if (ns.equals(AnnotationConsistencyCheck.DEFAULT)) {
-            ns = classNS;
-        }
-        if (namespaceIsUsed.put(ns, Boolean.TRUE) == null) {
-            throw new SchemaException(errorInClassMember(javaName)
-                    .append("Missing @XmlNs for namespace ").append(ns).toString());
-        }
-        /*
-         * Remember that we need an adapter for this property, unless the method or field defines its own adapter.
-         * In theory we do not need to report missing adapter since JAXB performs its own check, but we do anyway
-         * because JAXB has default adapters for String, Double, Boolean, Date, etc. which do not match the way
-         * OGC/ISO marshal those elements.
-         */
-        if (!property.isAnnotationPresent(XmlJavaTypeAdapter.class) && valueType != null) {
-            /*
-             * Internal classes in Apache SIS "jaxb" subpackages can be marshalled directly.
-             * Apache SIS classes defined in other packages may be code lists, which still need adapters.
-             */
-            if (!valueType.getName().startsWith(Modules.CLASSNAME_PREFIX) || CodeList.class.isAssignableFrom(valueType)) {
-                Class<?> c = valueType;
-                while (adapterIsUsed.replace(c, Boolean.TRUE) == null) {
-                    final Class<?> parent = c.getSuperclass();
-                    if (parent != null) {
-                        c = parent;
-                    } else {
-                        final Class<?>[] p = c.getInterfaces();
-                        if (p.length == 0) {
-                            if (valueType == org.opengis.metadata.Obligation.class)  {
-                                break;
-                            }
-                            throw new SchemaException(errorInClassMember(javaName)
-                                    .append("Missing @XmlJavaTypeAdapter for ").append(valueType).toString());
-                        }
-                        c = p[0];       // Take only the first interface, which should be the "main" parent.
-                    }
-                }
-            }
-        }
-        /*
-         * We do not verify fully the properties in legacy namespaces because we didn't loaded their schemas.
-         * However we verify at least that those properties are not declared as required.
-         */
-        if (LEGACY_NAMESPACES.getOrDefault(ns, Collections.emptySet()).contains(name)) {
-            if (!isDeprecatedClass && element.required()) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Legacy property should not be required.").toString());
-            }
-        } else {
-            /*
-             * Property in non-legacy namespaces should not be deprecated. Verify also their namespace
-             * and whether the property is required or optional, and whether it should be a collection.
-             */
-            if (property.isAnnotationPresent(Deprecated.class)) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Unexpected deprecation status.").toString());
-            }
-            final SchemaCompliance.Element info = properties.get(name);
-            if (info == null) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Unexpected XML element: ").append(name).toString());
-            }
-            if (info.namespace != null && !ns.equals(info.namespace)) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Declared namespace: ").append(ns).append(System.lineSeparator())
-                        .append("Expected namespace: ").append(info.namespace).toString());
-            }
-            if (element.required() != info.isRequired) {
-                throw new SchemaException(errorInClassMember(javaName)
-                        .append("Expected @XmlElement(required = ").append(info.isRequired).append(')').toString());
-            }
-            /*
-             * Following is a continuation of our check for multiplicity, but also the beginning of the check
-             * for return value type. The return type should be an interface with a UML annotation; we check
-             * that this annotation contains the name of the expected type.
-             */
-            if (isCollection) {
-                if (!info.isCollection) {
-                    if (false)  // Temporarily disabled because require GeoAPI modifications.
-                    throw new SchemaException(errorInClassMember(javaName).append("Value should be a singleton.").toString());
-                }
-            } else if (info.isCollection) {
-                if (false)  // Temporarily disabled because require GeoAPI modifications.
-                throw new SchemaException(errorInClassMember(javaName).append("Value should be a collection.").toString());
-            }
-            if (valueType != null) {
-                final UML valueUML = valueType.getAnnotation(UML.class);
-                if (valueUML != null) {
-                    String expected = info.typeName;
-                    String actual   = valueUML.identifier();
-                    expected = TYPE_EQUIVALENCES.getOrDefault(expected, expected);
-                    actual   = TYPE_EQUIVALENCES.getOrDefault(actual,   actual);
-                    if (!expected.equals(actual)) {
-                        if (false)  // Temporarily disabled because require GeoAPI modifications.
-                        throw new SchemaException(errorInClassMember(javaName)
-                                .append("Declared value type: ").append(actual).append(System.lineSeparator())
-                                .append("Expected value type: ").append(expected).toString());
-                    }
-                }
-            }
-            /*
-             * Verify if we have a @XmlNs for the type of the value. This is probably not required, but we
-             * do that as a safety. A common namespace added by this check is Metadata Common Classes (MCC).
-             */
-            final Map<String, SchemaCompliance.Element> valueInfo = schemas.getTypeDefinition(info.typeName);
-            if (valueInfo != null) {
-                final SchemaInformation.Element typeAndNS = valueInfo.get(null);
-                if (typeAndNS != null) {
-                    final String valueNS = typeAndNS.namespace;
-                    if (namespaceIsUsed.put(valueNS, Boolean.TRUE) == null) {
-                        throw new SchemaException(errorInClassMember(javaName)
-                                .append("Missing @XmlNs for property value namespace: ").append(valueNS).toString());
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns a message beginning with "Error in …", to be completed by the caller.
-     * This is an helper method for exception messages.
-     *
-     * @param  name  the property name, or {@code null} if none.
-     */
-    private StringBuilder errorInClassMember(final String name) {
-        final StringBuilder builder = new StringBuilder(80).append("Error in ");
-        if (isDeprecatedClass) {
-            builder.append("legacy ");
-        }
-        builder.append(currentClass.getCanonicalName());
-        if (name != null) {
-            builder.append('.').append(name);
-        }
-        return builder.append(':').append(System.lineSeparator());
-    }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
deleted file mode 100644
index dcca695..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/SchemaCompliance.java
+++ /dev/null
@@ -1,197 +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.test.xml;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
-import java.util.Map;
-import java.util.HashMap;
-import javax.xml.bind.annotation.XmlNs;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.parsers.ParserConfigurationException;
-import org.xml.sax.SAXException;
-import org.opengis.geoapi.Departures;
-import org.opengis.geoapi.DocumentationStyle;
-import org.opengis.geoapi.SchemaInformation;
-import org.opengis.geoapi.SchemaException;
-import org.apache.sis.util.StringBuilders;
-
-
-/**
- * Compares JAXB annotations against the ISO 19115 schemas. This test requires a connection to
- * <a href="https://standards.iso.org/iso/19115/-3/">https://standards.iso.org/iso/19115/-3/</a>.
- * All classes in a given directory are scanned.
- *
- * <h2>Limitations</h2>
- * Current implementation ignores the XML prefix (e.g. {@code "cit:"} in {@code "cit:CI_Citation"}).
- * We assume that there is no name collision, especially given that {@code "CI_"} prefix in front of
- * most OGC/ISO class names have the effect of a namespace. If a collision nevertheless happen, then
- * an exception will be thrown.
- *
- * <p>Current implementation assumes that XML element name, type name, property name and property type
- * name follow some naming convention. For example type names are suffixed with {@code "_Type"} in OGC
- * schemas, while property type names are suffixed with {@code "_PropertyType"}.  This class throws an
- * exception if a type does not follow the expected naming convention. This requirement makes
- * implementation easier, by reducing the amount of {@link Map}s that we need to manage.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class SchemaCompliance extends SchemaInformation {
-    /**
-     * The prefix of XML type names for properties. In ISO/OGC schemas, this prefix does not appear
-     * in the definition of class types but may appear in the definition of property types.
-     */
-    private static final String ABSTRACT_PREFIX = "Abstract_";
-
-    /**
-     * The suffix of XML type names for classes.
-     * This is used by convention in OGC/ISO standards (but not necessarily in other XSD).
-     */
-    static final String TYPE_SUFFIX = "_Type";
-
-    /**
-     * Separator between XML prefix and the actual name.
-     */
-    private static final char PREFIX_SEPARATOR = ':';
-
-    /**
-     * Root directory from which to search for classes.
-     */
-    private final Path classRootDirectory;
-
-    /**
-     * The namespaces associated to prefixes, as declared by JAXB {@link XmlNs} annotations.
-     * Used for verifying that no prefix is defined twice for different namespaces.
-     *
-     * <p>This field is not really related to schema loading process. But we keep it in this class for
-     * {@link PackageVerifier} convenience, as a way to share a single map for all verifier instances.</p>
-     */
-    final Map<String,String> allXmlNS;
-
-    /**
-     * Creates a new verifier for classes under the given directory. The given directory shall be the
-     * root of {@code "*.class"} files. For example if the {@code mypackage.MyClass} class is compiled
-     * in the {@code "MyProject/target/classes/mypackage/MyClass.class"} file, then the root directory
-     * shall be {@code "MyProject/target/classes/"}.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     * @param  schemaRootDirectory  if the computer contains a local copy of ISO schemas, path to that directory.
-     *                              Otherwise {@code null}. This is only for making tests faster.
-     */
-    public SchemaCompliance(final Path classRootDirectory, final Path schemaRootDirectory) {
-        super(schemaRootDirectory, new Departures(), DocumentationStyle.NONE);
-        this.classRootDirectory = classRootDirectory;
-        allXmlNS = new HashMap<>();
-    }
-
-    /**
-     * Verifies {@link XmlElement} annotations on all {@code *.class} files in the given directory and sub-directories.
-     * The given directory must be a sub-directory of the root directory given at construction time.
-     * This method will invoke itself for scanning sub-directories.
-     *
-     * @param  directory  the directory to scan for classes, relative to class root directory.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws ParserConfigurationException if {@link javax.xml.parsers.DocumentBuilder} can not be created.
-     * @throws SAXException if an error occurred while parsing the XSD file.
-     * @throws SchemaException if a XSD file does not comply with our assumptions,
-     *         or a JAXB annotation failed a compliance check.
-     */
-    public void verify(final Path directory)
-            throws IOException, ClassNotFoundException, ParserConfigurationException, SAXException, SchemaException
-    {
-        PackageVerifier verifier = null;
-        final StringBuilder buffer = new StringBuilder();
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        verify(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        buffer.setLength(0);
-                        buffer.append(path.toString()).setLength(buffer.length() - 6);      // Remove ".class" suffix.
-                        StringBuilders.replace(buffer, '/', '.');
-                        final Class<?> c = Class.forName(buffer.toString());
-                        if (verifier == null) {
-                            verifier = new PackageVerifier(this, c.getPackage());
-                        }
-                        verifier.verify(c);
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-    }
-
-    /**
-     * Verifies that the relationship between the name of the given entity and its type are consistent with
-     * OGC/ISO conventions. This method ignores the prefix (e.g. {@code "mdb:"} in {@code "mdb:MD_Metadata"}).
-     *
-     * @param  enclosing  schema or other container where the error happened.
-     * @param  name       the class or property name. Example: {@code "MD_Metadata"}, {@code "citation"}.
-     * @param  type       the type of the above named object. Example: {@code "MD_Metadata_Type"}, {@code "CI_Citation_PropertyType"}.
-     * @param  suffix     the expected suffix at the end of {@code type}.
-     * @throws SchemaException if the given {@code name} and {@code type} are not compliant with expected convention.
-     */
-    static void verifyNamingConvention(final String enclosing,
-            final String name, final String type, final String suffix) throws SchemaException
-    {
-        if (type.endsWith(suffix)) {
-            int nameStart = name.indexOf(PREFIX_SEPARATOR) + 1;        // Skip "mdb:" or similar prefix.
-            int typeStart = type.indexOf(PREFIX_SEPARATOR) + 1;
-            final int plg = ABSTRACT_PREFIX.length();
-            if (name.regionMatches(nameStart, ABSTRACT_PREFIX, 0, plg)) nameStart += plg;
-            if (type.regionMatches(typeStart, ABSTRACT_PREFIX, 0, plg)) typeStart += plg;
-            final int length = name.length() - nameStart;
-            if (type.length() - typeStart - suffix.length() == length &&
-                    type.regionMatches(typeStart, name, nameStart, length))
-            {
-                return;
-            }
-        }
-        throw new SchemaException(String.format("Error in %s:%n" +
-                "The type name should be the name with \"%s\" suffix, but found name=\"%s\" and type=\"%s\">.",
-                enclosing, suffix, name, type));
-    }
-
-    /**
-     * Removes leading and trailing spaces if any, then the prefix and the suffix in the given name.
-     * The prefix is anything before the first {@value #PREFIX_SEPARATOR} character.
-     * The suffix must be the given string, otherwise an exception is thrown.
-     *
-     * @param  name     the name from which to remove prefix and suffix.
-     * @param  suffix   the suffix to remove.
-     * @return the given name without prefix and suffix.
-     * @throws SchemaException if the given name does not end with the given suffix.
-     */
-    static String trim(String name, final String suffix) throws SchemaException {
-        name = name.trim();
-        if (name.endsWith(suffix)) {
-            return name.substring(name.indexOf(PREFIX_SEPARATOR) + 1, name.length() - suffix.length());
-        }
-        throw new SchemaException(String.format("Expected a name ending with \"%s\" but got \"%s\".", suffix, name));
-    }
-}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
index 7f58f3e..9f58900 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/test/xml/package-info.java
@@ -18,7 +18,7 @@
 /**
  * Utility methods for testing XML files or JAXB annotations.
  * {@link org.apache.sis.test.xml.AnnotationConsistencyCheck} and
- * {@link org.apache.sis.test.xml.SchemaCompliance} verifies JAXB annotations.
+ * {@code SchemaCompliance} verifies JAXB annotations.
  * {@link org.apache.sis.test.xml.DocumentComparator} compares an actual XML document with the expected one.
  *
  * <p>Objects defined in this package are only for SIS testing purpose any many change
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java
index 0f92e7a..dbbc34f 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypesTest.java
@@ -30,11 +30,11 @@
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.cs.AxisDirection;
-import org.opengis.parameter.ParameterDirection;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
 
 
 /**
@@ -109,7 +109,7 @@
 
     /**
      * Tests the {@link Types#forEnumName(Class, String)} method with an enumeration from the JDK.
-     * Such enumerations do not implement the {@link org.opengis.util.ControlledVocabulary} interface.
+     * Such enumerations do not implement the {@code org.opengis.util.ControlledVocabulary} interface.
      *
      * @since 0.5
      */
@@ -123,21 +123,6 @@
     }
 
     /**
-     * Tests the {@link Types#forEnumName(Class, String)} method with an enumeration from GeoAPI.
-     * Such enumerations implement the {@link org.opengis.util.ControlledVocabulary} interface.
-     *
-     * @since 0.5
-     */
-    @Test
-    public void testForGeoapiEnumName() {
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "IN_OUT"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "INOUT"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "in out"));
-        assertSame(ParameterDirection.IN_OUT, Types.forEnumName(ParameterDirection.class, "in/out"));
-        assertNull(Types.forEnumName(ParameterDirection.class, "out/in"));
-    }
-
-    /**
      * Tests the {@link Types#forCodeName(Class, String, boolean)} method.
      */
     @Test
@@ -152,8 +137,11 @@
         assertSame(PixelInCell.CELL_CORNER, Types.forCodeName(PixelInCell.class, "cellCorner",  false));
         assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell center", false));
         assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCenter",  false));
-        assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell centre", false));
-        assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCentre",  false));
+
+        if (PENDING_NEXT_GEOAPI_RELEASE) {
+            assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell centre", false));
+            assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCentre",  false));
+        }
     }
 
     /**
@@ -191,7 +179,7 @@
     }
 
     /**
-     * Tests the {@link Types#getDescription(ControlledVocabulary)} method.
+     * Tests the {@code Types.getDescription(ControlledVocabulary)} method.
      */
     @Test
     public void testGetCodeDescription() {
@@ -205,29 +193,27 @@
     }
 
     /**
-     * Tests the examples given in {@link Types#getListName(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getListName(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetListName() {
-        assertEquals("SV_ParameterDirection",   Types.getListName(ParameterDirection.IN_OUT));
         assertEquals("CS_AxisDirection",        Types.getListName(AxisDirection     .NORTH));
         assertEquals("CI_OnLineFunctionCode",   Types.getListName(OnLineFunction    .DOWNLOAD));
         assertEquals("MD_ImagingConditionCode", Types.getListName(ImagingCondition  .BLURRED_IMAGE));
     }
 
     /**
-     * Tests the examples given in {@link Types#getCodeName(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getCodeName(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetCodeName() {
-        assertEquals("in/out",       Types.getCodeName(ParameterDirection.IN_OUT));
         assertEquals("north",        Types.getCodeName(AxisDirection     .NORTH));
         assertEquals("download",     Types.getCodeName(OnLineFunction    .DOWNLOAD));
         assertEquals("blurredImage", Types.getCodeName(ImagingCondition  .BLURRED_IMAGE));
     }
 
     /**
-     * Tests the examples given in {@link Types#getCodeLabel(ControlledVocabulary)} javadoc.
+     * Tests the examples given in {@code Types.getCodeLabel(ControlledVocabulary)} javadoc.
      */
     @Test
     public void testGetCodeLabel() {
@@ -237,7 +223,7 @@
     }
 
     /**
-     * Tests {@link Types#getCodeTitle(ControlledVocabulary)}.
+     * Tests {@code Types.getCodeTitle(ControlledVocabulary)}.
      * Also opportunistically tests {@link Types#forCodeTitle(CharSequence)}.
      */
     @Test
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
index 3d02984..bf4d43a 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/CharSequenceSubstitutionTest.java
@@ -18,10 +18,10 @@
 
 import javax.xml.bind.JAXBException;
 import org.opengis.metadata.citation.Address;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.acquisition.Instrument;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.metadata.identification.InitiativeType;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.internal.jaxb.metadata.replace.ReferenceSystemMetadata;
 import org.apache.sis.internal.xml.LegacyNamespaces;
 import org.apache.sis.internal.metadata.SensorType;
@@ -32,6 +32,9 @@
 
 import static org.apache.sis.test.MetadataAssert.*;
 
+// Branch-dependent imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Tests the XML marshalling of {@code Anchor} and {@code CodeList} as substitution of {@code <gco:CharacterSequence>}.
@@ -71,7 +74,7 @@
                 "</gmd:MD_ReferenceSystem>";
 
         final ReferenceSystemMetadata md = unmarshal(ReferenceSystemMetadata.class, expected);
-        final Identifier id = md.getName();
+        final ReferenceIdentifier id = md.getName();
         assertEquals("codespace", "L101", id.getCodeSpace());
         assertEquals("code", "EPSG:4326", id.getCode());
     }
@@ -98,7 +101,7 @@
                 "  </mcc:codeSpace>\n" +
                 "</mcc:MD_Identifier>";
 
-        final Identifier id = unmarshal(Identifier.class, expected);
+        final DefaultIdentifier id = unmarshal(DefaultIdentifier.class, expected);
         assertEquals("codespace", "L101", id.getCodeSpace());
         assertEquals("code", "EPSG:4326", id.getCode());
     }
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
index ce3e27f..e2c09a1 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/xml/NilReasonTest.java
@@ -19,7 +19,7 @@
 import java.net.URISyntaxException;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArraysExt;
@@ -288,7 +288,7 @@
         assertTrue (c.equals(e2, ComparisonMode.DEBUG));
 
         // Following object should alway be different because it does not implement the same interface.
-        final Responsibility r1 = NilReason.TEMPLATE.createNilObject(Responsibility.class);
+        final ResponsibleParty r1 = NilReason.TEMPLATE.createNilObject(ResponsibleParty.class);
         assertFalse(c.equals(r1, ComparisonMode.STRICT));
         assertFalse(c.equals(r1, ComparisonMode.BY_CONTRACT));
         assertFalse(c.equals(r1, ComparisonMode.IGNORE_METADATA));
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java b/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java
deleted file mode 100644
index 3381457..0000000
--- a/core/sis-metadata/src/test/java/org/apache/sis/xml/RenameListGenerator.java
+++ /dev/null
@@ -1,228 +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.xml;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.nio.file.Path;
-import java.nio.file.Files;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryIteratorException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Collections;
-import java.lang.reflect.Method;
-import javax.xml.bind.annotation.XmlSchema;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.geoapi.SchemaException;
-import org.apache.sis.internal.xml.LegacyNamespaces;
-
-
-/**
- * Creates a file in the {@value TransformingReader#FILENAME} format. This class can be executed if the content
- * has changed, or for verifying the current file. Output format contains namespaces first, then classes,
- * then properties. Example:
- *
- * {@preformat text
- * http://standards.iso.org/iso/19115/-3/cit/1.0
- *   CI_Address
- *     administrativeArea
- *     city
- *   CI_Citation
- *     citedResponsibleParty
- * }
- *
- * This class can be used as a starting point for generating a new file from scratch.
- * It should not be used for updating the existing file (unless a lot of things have changed)
- * because some of {@value TransformingReader#FILENAME} content have been edited by hand.
- * For generating a new file:
- *
- * {@preformat java
- *     public static void main(String[] args) throws Exception {
- *         RenameListGenerator gen = new RenameListGenerator(Paths.get("/path/to/your/classes"));
- *         gen.add(Paths.get("root/package/of/classes/to/add"));
- *         try (final BufferedWriter out = Files.newBufferedWriter(Paths.get("MyOutputFile.lst"))) {
- *             gen.print(out);
- *         }
- *     }
- * }
- */
-public final class RenameListGenerator {
-    /**
-     * Properties in those namespaces do not have older namespaces to map from.
-     */
-    private static final Set<String> LEGACY_NAMESPACES = Collections.unmodifiableSet(new HashSet<>(
-            Arrays.asList(LegacyNamespaces.GMD, LegacyNamespaces.GMI, LegacyNamespaces.SRV)));
-
-    /**
-     * The {@value} string used in JAXB annotations for default names or namespaces.
-     */
-    private static final String DEFAULT = "##default";
-
-    /**
-     * Root directory from which to search for classes.
-     */
-    private final Path classRootDirectory;
-
-    /**
-     * The content to write. Keys in the first (outer) map are namespaces. Keys in the enclosed maps
-     * are class names. Keys in the enclosed set are property names.
-     */
-    private final Map<String, Map<String, Set<String>>> content;
-
-    /**
-     * Creates a new {@value TransformingReader#FILENAME} generator for classes under the given directory.
-     * The given directory shall be the root of {@code "*.class"} files.
-     *
-     * @param  classRootDirectory   the root of compiled class files.
-     */
-    public RenameListGenerator(final Path classRootDirectory) {
-        this.classRootDirectory = classRootDirectory;
-        content = new TreeMap<>();
-    }
-
-    /**
-     * Gets the namespaces, types and properties for all class files in the given directory and sub-directories.
-     * Those information are memorized for future listing with {@link #print(Appendable)}.
-     *
-     * @param  directory  the directory to scan for classes, relative to class root directory.
-     * @throws IOException if an error occurred while reading files or schemas.
-     * @throws ClassNotFoundException if an error occurred while loading a {@code "*.class"} file.
-     * @throws SchemaException if two properties have the same name in the same class and namespace.
-     */
-    public void add(final Path directory) throws IOException, ClassNotFoundException, SchemaException {
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(classRootDirectory.resolve(directory))) {
-            for (Path path : stream) {
-                final String filename = path.getFileName().toString();
-                if (!filename.startsWith(".")) {
-                    if (Files.isDirectory(path)) {
-                        add(path);
-                    } else if (filename.endsWith(".class")) {
-                        path = classRootDirectory.relativize(path);
-                        String classname = path.toString();
-                        classname = classname.substring(0, classname.length() - 6).replace('/', '.');
-                        add(Class.forName(classname));
-                    }
-                }
-            }
-        } catch (DirectoryIteratorException e) {
-            throw e.getCause();
-        }
-    }
-
-    /**
-     * Gets the namespaces, types and properties for the given class.
-     * Properties defined in super-classes will be copied as if they were declared in-line.
-     * Those information are memorized for future listing with {@link #print(Appendable)}.
-     *
-     * @throws SchemaException if two properties have the same name in the same class and namespace.
-     */
-    private void add(Class<?> classe) throws SchemaException {
-        XmlRootElement root = classe.getDeclaredAnnotation(XmlRootElement.class);
-        if (root != null) {
-            /*
-             * Add the following entry:
-             *
-             *     http://a.namespace
-             *      PX_AClass
-             *       …
-             *
-             * Then list all properties below "PX_AClass". Note that the namespace may change because properties
-             * may be declared in different namespaces, but the class name stay the same. If the same properties
-             * are inherited by many classes, they will be repeated in each subclass.
-             */
-            final String topLevelTypeName = root.name();
-            String classNS = namespace(classe, root.namespace());
-            for (;; classNS = namespace(classe, root.namespace())) {
-                for (final Method method : classe.getDeclaredMethods()) {
-                    if (!method.isBridge()) {
-                        final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
-                        if (xe != null) {
-                            String namespace = xe.namespace();
-                            if (namespace.equals(DEFAULT)) {
-                                namespace = classNS;
-                            }
-                            add(namespace, topLevelTypeName, xe.name());
-                        }
-                    }
-                }
-                classe = classe.getSuperclass();
-                root = classe.getDeclaredAnnotation(XmlRootElement.class);
-                if (root == null) break;
-            }
-        } else {
-            /*
-             * In Apache SIS implementation, classes without JAXB annotation except on a single method are
-             * code lists or enumerations. Those classes have exactly one method annotated with @XmlElement,
-             * and that method actually gives a type, not a property (because of the way OGC/ISO wrap every
-             * properties in a type).
-             */
-            XmlElement singleton = null;
-            for (final Method method : classe.getDeclaredMethods()) {
-                final XmlElement xe = method.getDeclaredAnnotation(XmlElement.class);
-                if (xe != null) {
-                    if (singleton != null) return;
-                    singleton = xe;
-                }
-            }
-        }
-    }
-
-    private static String namespace(final Class<?> classe, String classNS) {
-        if (classNS.equals(DEFAULT)) {
-            classNS = classe.getPackage().getDeclaredAnnotation(XmlSchema.class).namespace();
-        }
-        return classNS;
-    }
-
-    /**
-     * Adds a property in the given class in the given namespace.
-     */
-    private void add(final String namespace, final String typeName, final String property) throws SchemaException {
-        if (!LEGACY_NAMESPACES.contains(namespace)) {
-            if (!content.computeIfAbsent(namespace, (k) -> new TreeMap<>())
-                        .computeIfAbsent(typeName,  (k) -> new TreeSet<>())
-                        .add(property))
-            {
-                if (typeName.equals("Integer")) return;     // Exception because of GO_Integer and GO_Integer64.
-                throw new SchemaException(String.format("Duplicated property %s.%s in:%n%s", typeName, property, namespace));
-            }
-        }
-    }
-
-    /**
-     * Prints the {@value TransformingReader#FILENAME} file.
-     *
-     * @param  out  where to print the content.
-     * @throws IOException if an error occurred while printing the content.
-     */
-    public void print(final Appendable out) throws IOException {
-        for (final Map.Entry<String, Map<String, Set<String>>> e : content.entrySet()) {
-            out.append(e.getKey()).append('\n');                                            // Namespace
-            for (final Map.Entry<String, Set<String>> c : e.getValue().entrySet()) {
-                out.append(' ').append(c.getKey()).append('\n');                            // Class
-                for (final String p : c.getValue()) {
-                    out.append("  ").append(p).append('\n');                                // Property
-                }
-            }
-        }
-    }
-}
diff --git a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties
index 8476d40..916a754 100644
--- a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties
+++ b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/iso/api-changes.properties
@@ -22,3 +22,36 @@
 # with the changes in the international standard. The UML identifiers of added methods are "number"
 # and "numberType" respectively.
 #
+org.opengis.metadata.citation.Citation=-getCollectiveTitle +getGraphics:graphic +getOnlineResources:onlineResource
+org.opengis.metadata.citation.Contact=-getAddress +getAddresses:address +getContactType:contactType -getOnlineResource +getOnlineResources:onlineResource -getPhone +getPhones:phone
+org.opengis.metadata.citation.OnlineResource=+getProtocolRequest:protocolRequest
+org.opengis.metadata.citation.ResponsibleParty=-getContactInfo -getIndividualName -getOrganisationName -getPositionName
+org.opengis.metadata.citation.Telephone=-getFacsimiles +getNumber:number +getNumberType:numberType -getVoices
+org.opengis.metadata.constraint.Constraints=+getConstraintApplicationScope:constraintApplicationScope +getGraphics:graphic +getReferences:reference +getReleasability:releasability +getResponsibleParties:responsibleParty
+org.opengis.metadata.content.Band=+getBoundMax:boundMax +getBoundMin:boundMin +getBoundUnits:boundUnits
+org.opengis.metadata.content.CoverageDescription=+getAttributeGroups:attributeGroup -getContentType -getDimensions +getProcessingLevelCode:processingLevelCode
+org.opengis.metadata.content.FeatureCatalogueDescription=+getFeatureTypeInfo:featureTypes -getFeatureTypes -getLanguages +getLocalesAndCharsets:locale
+org.opengis.metadata.content.RangeDimension=+getDescription:description -getDescriptor +getNames:name
+org.opengis.metadata.distribution.DigitalTransferOptions=+getDistributionFormats:distributionFormat -getOffLine +getOffLines:offLine +getTransferFrequency:transferFrequency
+org.opengis.metadata.distribution.Distribution=+getDescription:description
+org.opengis.metadata.distribution.Format=+getFormatSpecificationCitation:formatSpecificationCitation +getMedia:medium -getName -getSpecification -getVersion
+org.opengis.metadata.distribution.Medium=-getDensities +getDensity:density +getIdentifier:identifier
+org.opengis.metadata.distribution.StandardOrderProcess=+getOrderOptionsType:orderOptionsType +getOrderOptions:orderOptions
+org.opengis.metadata.ExtendedElementInformation=-getDomainCode +getRationale:rationale -getRationales -getShortName
+org.opengis.metadata.extent.SpatialTemporalExtent=+getVerticalExtent:verticalExtent
+org.opengis.metadata.identification.AggregateInformation=-getAggregateDataSetIdentifier -getAggregateDataSetName +getMetadataReference:metadataReference +getName:name
+org.opengis.metadata.identification.BrowseGraphic=+getImageConstraints:imageContraints +getLinkages:linkage
+org.opengis.metadata.identification.DataIdentification=-getCharacterSets -getLanguages +getLocalesAndCharsets:defaultLocale+otherLocale
+org.opengis.metadata.identification.Identification=+getAdditionalDocumentations:additionalDocumentation -getAggregationInfo +getAssociatedResources:associatedResource +getExtents:extent +getProcessingLevel:processingLevel +getSpatialRepresentationTypes:spatialRepresentationType +getSpatialResolutions:spatialResolution ~+getTemporalResolutions:temporalResolution +getTopicCategories:topicCategory
+org.opengis.metadata.identification.Keywords=+getKeywordClass:keywordClass
+org.opengis.metadata.identification.Resolution=+getAngularDistance:angularDistance +getLevelOfDetail:levelOfDetail +getVertical:vertical
+org.opengis.metadata.identification.ServiceIdentification=+getAccessProperties:accessProperties +getContainsChain:containsChain +getContainsOperations:containsOperations +getCoupledResources:coupledResource +getCouplingType:couplingType +getOperatedDatasets:operatedDataset +getOperatesOn:operatesOn +getProfiles:profile +getServiceStandards:serviceStandard +getServiceType:serviceType +getServiceTypeVersions:serviceTypeVersion
+org.opengis.metadata.identification.Usage=+getAdditionalDocumentation:additionalDocumentation +getIdentifiedIssues:identifiedIssues +getResponses:response
+org.opengis.metadata.Identifier=+getCodeSpace:codeSpace +getDescription:description +getVersion:version
+org.opengis.metadata.lineage.Lineage=+getAdditionalDocumentation:additionalDocumentation +getScope:scope
+org.opengis.metadata.lineage.ProcessStep=-getDate +getReferences:reference +getScope:scope
+org.opengis.metadata.lineage.Source=-getScaleDenominator +getScope:scope -getSourceExtents +getSourceMetadata:sourceMetadata +getSourceSpatialResolution:sourceSpatialResolution
+org.opengis.metadata.maintenance.MaintenanceInformation=-getDateOfNextUpdate +getMaintenanceDates:maintenanceDate +getMaintenanceScopes:maintenanceScope -getUpdateScopeDescriptions -getUpdateScopes
+org.opengis.metadata.Metadata=+getAlternativeMetadataReferences:alternativeMetadataReference -getCharacterSet -getCharacterSets -getDataSetUri -getDateStamp +getDateInfo:dateInfo -getFileIdentifier -getHierarchyLevelNames -getHierarchyLevels -getLanguage -getLanguages -getLocales +getLocalesAndCharsets:defaultLocale+otherLocale +getMetadataIdentifier:metadataIdentifier +getMetadataLinkages:metadataLinkage +getMetadataProfiles:metadataProfile +getMetadataScopes:metadataScope -getMetadataStandardName -getMetadataStandardVersion +getMetadataStandards:metadataStandard -getParentIdentifier +getParentMetadata:parentMetadata +getResourceLineages:resourceLineage
+org.opengis.metadata.quality.Scope=-getExtent +getExtents:extent
+org.opengis.metadata.spatial.Dimension=+getDimensionDescription:dimensionDescription +getDimensionTitle:dimensionTitle
diff --git a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
index 8b3557d..3dd8f31 100644
--- a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
+++ b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/ServiceIdentification.xml
@@ -76,9 +76,6 @@
               </gco:TypeName>
             </gco:attributeType>
           </srv:name>
-          <srv:direction>
-            <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-          </srv:direction>
           <srv:optionality>
             <gco:CharacterString>Optional</gco:CharacterString>
           </srv:optionality>
diff --git a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
index 8b8954e..adf6f7d 100644
--- a/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
+++ b/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2016/ServiceIdentification.xml
@@ -41,7 +41,7 @@
   </srv:serviceTypeVersion>
 
   <srv:couplingType>
-    <srv:SV_CouplingType codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#SV_CouplingType" codeListValue="loose" codeSpace="eng">Loose</srv:SV_CouplingType>
+    <srv:SV_CouplingType codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#SV_CouplingType" codeListValue="loose">Loose</srv:SV_CouplingType>
   </srv:couplingType>
 
   <srv:coupledResource>
@@ -62,7 +62,7 @@
             <gco:CharacterString>Get Map</gco:CharacterString>
           </srv:operationName>
           <srv:distributedComputingPlatform>
-            <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices" codeSpace="eng">Web services</srv:DCPList>
+            <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices">Web services</srv:DCPList>
           </srv:distributedComputingPlatform>
           <srv:connectPoint gco:nilReason="missing"/>
           <srv:parameter>
@@ -81,9 +81,6 @@
                   </gco:attributeType>
                 </gco:MemberName>
               </srv:name>
-              <srv:direction>
-                <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-              </srv:direction>
               <srv:optionality>
                 <gco:Boolean>true</gco:Boolean>
               </srv:optionality>
@@ -103,7 +100,7 @@
         <gco:CharacterString>Get Map</gco:CharacterString>
       </srv:operationName>
       <srv:distributedComputingPlatform>
-        <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices" codeSpace="eng">Web services</srv:DCPList>
+        <srv:DCPList codeList="http://standards.iso.org/iso/19115/resources/Codelist/cat/codelists.xml#DCPList" codeListValue="WebServices">Web services</srv:DCPList>
       </srv:distributedComputingPlatform>
       <srv:connectPoint gco:nilReason="missing"/>
       <srv:parameter>
@@ -122,9 +119,6 @@
               </gco:attributeType>
             </gco:MemberName>
           </srv:name>
-          <srv:direction>
-            <srv:SV_ParameterDirection>in</srv:SV_ParameterDirection>
-          </srv:direction>
           <srv:optionality>
             <gco:Boolean>true</gco:Boolean>
           </srv:optionality>
diff --git a/core/sis-portrayal/pom.xml b/core/sis-portrayal/pom.xml
index e3f469a..63af3ff 100644
--- a/core/sis-portrayal/pom.xml
+++ b/core/sis-portrayal/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapLayer.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapLayer.java
index a2a1fbe..692db42 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapLayer.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapLayer.java
@@ -17,17 +17,17 @@
 package org.apache.sis.internal.map;
 
 import org.apache.sis.storage.Resource;
-import org.opengis.style.Style;
+//import org.opengis.style.Style;
 
 
 /**
  * Data (resource) associated to visual representation (symbology).
  * Layers are the key elements of a map: they link datas (given by a {@link Resource})
- * to their visual representation (defined by a {@link Style}).
+ * to their visual representation (defined by a {@code Style}).
  * The visual appearance of a layer should be similar with any rendering engine.
  * Some details may very because of different rendering strategies for label placements, 2D or 3D,
- * but the fundamentals aspect of each {@link org.opengis.feature.Feature} or
- * {@link org.opengis.coverage.Coverage} should be unchanged.
+ * but the fundamentals aspect of each {@code org.opengis.feature.Feature} or
+ * {@code org.opengis.coverage.Coverage} should be unchanged.
  *
  * <p>
  * NOTE: this class is a first draft subject to modifications.
@@ -47,7 +47,7 @@
     /**
      * Visual representation of data.
      */
-    private Style style;
+//  private Style style;
 
     /**
      * Constructs an initially empty map layer.
@@ -89,16 +89,16 @@
      *
      * @return description of data visual appearance, or {@code null} if unspecified.
      */
-    public Style getStyle() {
-        return style;
-    }
+//  public Style getStyle() {
+//      return style;
+//  }
 
     /**
      * Sets the visual appearance of the data.
      *
      * @param  style  description of data visual appearance, or {@code null} if unspecified.
      */
-    public void setStyle(Style style) {
-        this.style = style;
-    }
+//  public void setStyle(Style style) {
+//      this.style = style;
+//  }
 }
diff --git a/core/sis-referencing-by-identifiers/pom.xml b/core/sis-referencing-by-identifiers/pom.xml
index 2c6e941..efe20e3 100644
--- a/core/sis-referencing-by-identifiers/pom.xml
+++ b/core/sis-referencing-by-identifiers/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.core</groupId>
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
index e8d02c7..cedbe55 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
@@ -29,9 +29,7 @@
 import org.apache.sis.geometry.GeneralDirectPosition;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -68,7 +66,7 @@
  * @since 0.8
  * @module
  */
-public abstract class AbstractLocation implements Location {
+public abstract class AbstractLocation {
     /**
      * The description of the nature of this geographic identifier, or {@code null} if unspecified.
      *
@@ -77,7 +75,7 @@
      *
      * @see #getLocationType()
      */
-    private LocationType type;
+    private AbstractLocationType type;
 
     /**
      * The geographic identifier, or {@code null} if unspecified.
@@ -90,10 +88,24 @@
      * Creates a new location for the given geographic identifier.
      * This constructor accepts {@code null} arguments, but this is not recommended.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of {@code type} argument may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param type        the description of the nature of this geographic identifier.
      * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
      */
-    protected AbstractLocation(final LocationType type, final CharSequence identifier) {
+    protected AbstractLocation(final ModifiableLocationType type, final CharSequence identifier) {
+        this.type       = type;
+        this.identifier = identifier;
+    }
+
+    /**
+     * Temporary workaround for the lack of {@code LocationType} interface and {@code AbstractLocationType}
+     * being package-private. We do not want to expose {@code AbstractLocationType} in public API for now.
+     */
+    AbstractLocation(final AbstractLocationType type, final CharSequence identifier) {
         this.type       = type;
         this.identifier = identifier;
     }
@@ -112,7 +124,7 @@
      * by the {@linkplain ModifiableLocationType#getIdentifications() location type identifications}.
      *
      * <div class="note"><b>Examples:</b>
-     * if {@link LocationType#getIdentifications()} contain “name”, then geographic identifiers may be country
+     * if {@code LocationType.getIdentifications()} contain “name”, then geographic identifiers may be country
      * names like “Japan” or “France”, or places like “Eiffel Tower”. If location type identifications contain
      * “code”, then geographic identifiers may be “SW1P 3AD” postcode.
      * </div>
@@ -125,7 +137,6 @@
      *
      * @see ModifiableLocationType#getIdentifications()
      */
-    @Override
     public InternationalString getGeographicIdentifier() {
         return Types.toInternationalString(identifier);
     }
@@ -136,7 +147,6 @@
      *
      * @return other identifier(s) for the location instance, or an empty collection if none.
      */
-    @Override
     public Collection<? extends InternationalString> getAlternativeGeographicIdentifiers() {
         return Collections.emptySet();
     }
@@ -147,7 +157,6 @@
      *
      * @return date of creation of this version of the location instance, or {@code null} if none.
      */
-    @Override
     public TemporalExtent getTemporalExtent() {
         return null;
     }
@@ -163,7 +172,6 @@
      * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
      * @see org.apache.sis.metadata.iso.extent.DefaultBoundingPolygon
      */
-    @Override
     public GeographicExtent getGeographicExtent() {
         return null;
     }
@@ -177,7 +185,6 @@
      *
      * @return envelope that encompass the location, or {@code null} if none.
      */
-    @Override
     public Envelope getEnvelope() {
         final GeographicExtent extent = getGeographicExtent();
         return (extent instanceof GeographicBoundingBox) ? new Envelope2D((GeographicBoundingBox) extent) : null;
@@ -191,7 +198,6 @@
      *
      * @return coordinates of a representative point for the location instance, or {@code null} if none.
      */
-    @Override
     public Position getPosition() {
         final Envelope envelope = getEnvelope();
         if (envelope == null) {
@@ -209,10 +215,24 @@
     /**
      * Returns a description of the nature of this geographic identifier.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.
+     * If applied, this method will be made non-final.</div>
+     *
      * @return the nature of the identifier and its associated geographic location.
      */
-    @Override
-    public LocationType getLocationType() {
+    public final ModifiableLocationType getLocationType() {
+        return ModifiableLocationTypeAdapter.copy(type);
+    }
+
+    /**
+     * Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
+     * This workaround will be removed in a future SIS version if the location
+     * type interface is introduced in a future GeoAPI version.
+     */
+    final AbstractLocationType type() {
         return type;
     }
 
@@ -220,14 +240,17 @@
      * Returns the organization responsible for defining the characteristics of the location instance.
      * The default implementation returns the {@linkplain ModifiableLocationType#getOwner() owner}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return organization responsible for defining the characteristics of the location instance, or {@code null}.
      *
      * @see ModifiableLocationType#getOwner()
      * @see ReferencingByIdentifiers#getOverallOwner()
      */
-    @Override
-    public Party getAdministrator() {
-        final LocationType type = getLocationType();
+    public AbstractParty getAdministrator() {
         return (type != null) ? type.getOwner() : null;
     }
 
@@ -235,12 +258,16 @@
      * Returns location instances of a different location type, for which this location instance is a sub-division.
      * The default implementation returns an empty list.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of collection elements may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return parent locations, or an empty collection if none.
      *
      * @see ModifiableLocationType#getParents()
      */
-    @Override
-    public Collection<? extends Location> getParents() {
+    public Collection<? extends AbstractLocation> getParents() {
         return Collections.emptyList();
     }
 
@@ -248,12 +275,16 @@
      * Returns location instances of a different location type which subdivides this location instance.
      * The default implementation returns an empty list.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of collection elements may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return child locations, or an empty collection if none.
      *
      * @see ModifiableLocationType#getChildren()
      */
-    @Override
-    public Collection<? extends Location> getChildren() {
+    public Collection<? extends AbstractLocation> getChildren() {
         return Collections.emptyList();
     }
 
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
index d7752b9..b8bdea3 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocationType.java
@@ -31,8 +31,10 @@
 import org.apache.sis.util.Utilities;
 
 // Branch-dependent imports
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import java.util.Collection;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.GeographicExtent;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -44,7 +46,7 @@
  * @since   0.8
  * @module
  */
-abstract class AbstractLocationType implements LocationType, LenientComparable {
+abstract class AbstractLocationType implements LenientComparable {
     /**
      * For sub-class constructors.
      */
@@ -65,11 +67,11 @@
      * @param  types  the location types for which to take a snapshot.
      * @return unmodifiable copies of the given location types.
      */
-    public static List<LocationType> snapshot(final ReferenceSystemUsingIdentifiers rs, final LocationType... types) {
+    public static List<AbstractLocationType> snapshot(final ReferencingByIdentifiers rs, final AbstractLocationType... types) {
         ArgumentChecks.ensureNonNull("types", types);
-        final List<LocationType> snapshot = FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
-        final Map<LocationType,Boolean> parents = new IdentityHashMap<>();
-        for (final LocationType type : snapshot) {
+        final List<AbstractLocationType> snapshot = FinalLocationType.snapshot(Arrays.asList(types), rs, new IdentityHashMap<>());
+        final Map<AbstractLocationType,Boolean> parents = new IdentityHashMap<>();
+        for (final AbstractLocationType type : snapshot) {
             checkForCycles(type, parents);
         }
         return snapshot;
@@ -80,11 +82,11 @@
      *
      * @throws IllegalArgumentException if an infinite recursivity is detected.
      */
-    private static void checkForCycles(final LocationType type, final Map<LocationType,Boolean> parents) {
+    private static void checkForCycles(final AbstractLocationType type, final Map<AbstractLocationType,Boolean> parents) {
         if (parents.put(type, Boolean.TRUE) != null) {
             throw new IllegalArgumentException(Resources.format(Resources.Keys.LocationTypeCycle_1, type.getName()));
         }
-        for (final LocationType child : type.getChildren()) {
+        for (final AbstractLocationType child : type.getChildren()) {
             checkForCycles(child, parents);
         }
         parents.remove(type);
@@ -101,6 +103,54 @@
     }
 
     /**
+     * Name of the location type.
+     */
+    public abstract InternationalString getName();
+
+    /**
+     * Property used as the defining characteristic of the location type.
+     */
+    public abstract InternationalString getTheme();
+
+    /**
+     * Method(s) of uniquely identifying location instances.
+     */
+    public abstract Collection<? extends InternationalString> getIdentifications();
+
+    /**
+     * The way in which location instances are defined.
+     */
+    public abstract InternationalString getDefinition();
+
+    /**
+     * Geographic area within which the location type occurs.
+     */
+    public abstract GeographicExtent getTerritoryOfUse();
+
+    /**
+     * The reference system that comprises this location type.
+     */
+    public abstract ReferencingByIdentifiers getReferenceSystem();
+
+    /**
+     * Name of organization or class of organization able to create and destroy location instances.
+     */
+    public abstract AbstractParty getOwner();
+
+    /**
+     * Parent location types (location types of which this location type is a sub-division).
+     * A location type can have more than one possible parent. For example the parent of a
+     * location type named <cite>“street”</cite> could be <cite>“locality”</cite>, <cite>“town”</cite>
+     * or <cite>“administrative area”</cite>.
+     */
+    public abstract Collection<? extends AbstractLocationType> getParents();
+
+    /**
+     * Child location types (location types which sub-divides this location type).
+     */
+    public abstract Collection<? extends AbstractLocationType> getChildren();
+
+    /**
      * Compares this location type with the specified object for equality.
      * This method compares the value of {@link #getName()} and {@link #getChildren()} in all modes.
      * At the opposite, values of {@link #getParents()} and {@link #getReferenceSystem()} are never
@@ -129,8 +179,8 @@
                 // Fall through
             }
             case BY_CONTRACT: {
-                if (!(object instanceof LocationType)) break;
-                final LocationType that = (LocationType) object;
+                if (!(object instanceof AbstractLocationType)) break;
+                final AbstractLocationType that = (AbstractLocationType) object;
                 // Do not compare the ReferenceSystem as it may cause an infinite recursion.
                 if (!Utilities.deepEquals(getTheme(),           that.getTheme(),           mode) ||
                     !Utilities.deepEquals(getIdentifications(), that.getIdentifications(), mode) ||
@@ -143,8 +193,8 @@
                 // Fall through
             }
             default: {
-                if (!(object instanceof LocationType)) break;
-                final LocationType that = (LocationType) object;
+                if (!(object instanceof AbstractLocationType)) break;
+                final AbstractLocationType that = (AbstractLocationType) object;
                 if (Objects.equals(getName(), that.getName())) {
                     /*
                      * To be safe, we should apply some check against infinite recursivity here.
@@ -186,7 +236,7 @@
     @Override
     public int hashCode() {
         int code = Objects.hashCode(getName());
-        for (final LocationType child : getChildren()) {
+        for (final AbstractLocationType child : getChildren()) {
             // Take only children name without recursivity over their own children.
             code = code*31 + Objects.hashCode(child.getName());
         }
@@ -222,10 +272,10 @@
      * on the assumption that subclasses verified this constraint by calls
      * to {@link #checkForCycles()}.
      */
-    private static void format(final LocationType type, final TreeTable.Node node) {
+    private static void format(final AbstractLocationType type, final TreeTable.Node node) {
         node.setValue(TableColumn.NAME, type.getName());
         node.setValue(TableColumn.VALUE_AS_TEXT, type.getDefinition());
-        for (final LocationType child : type.getChildren()) {
+        for (final AbstractLocationType child : type.getChildren()) {
             format(child, node.newChild());
         }
     }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
index 47be601..2f9508a 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/FinalLocationType.java
@@ -30,13 +30,11 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
- * Unmodifiable description of a location created as a snapshot of another {@link LocationType} instance
+ * Unmodifiable description of a location created as a snapshot of another {@code LocationType} instance
  * at {@link ReferencingByIdentifiers} construction time. This instance will be set a different reference
  * system than the original location type.
  *
@@ -75,7 +73,7 @@
     /**
      * The reference system that comprises this location type.
      */
-    private final ReferenceSystemUsingIdentifiers referenceSystem;
+    private final ReferencingByIdentifiers referenceSystem;
 
     /**
      * Geographic area within which the location type occurs.
@@ -85,19 +83,19 @@
     /**
      * Name of organization or class of organization able to create and destroy location instances.
      */
-    private final Party owner;
+    private final AbstractParty owner;
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
      * This list is unmodifiable.
      */
-    private final List<LocationType> parents;
+    private final List<AbstractLocationType> parents;
 
     /**
      * Child location types (location types which sub-divides this location type).
      * This list is unmodifiable.
      */
-    final List<LocationType> children;
+    final List<AbstractLocationType> children;
 
     /**
      * Creates a copy of the given location type with the reference system set to the given value.
@@ -107,8 +105,8 @@
      * @param existing  other {@code FinalLocationType} instances created before this one.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    private FinalLocationType(final LocationType source, final ReferenceSystemUsingIdentifiers rs,
-            final Map<LocationType, FinalLocationType> existing)
+    private FinalLocationType(final AbstractLocationType source, final ReferencingByIdentifiers rs,
+            final Map<AbstractLocationType, FinalLocationType> existing)
     {
         /*
          * Put 'this' in the map at the beginning in case the parents and children contain cyclic references.
@@ -126,7 +124,7 @@
          */
         InternationalString theme;
         GeographicExtent    territoryOfUse;
-        Party               owner;
+        AbstractParty       owner;
         /*
          * Copy the value from the source location type, make them unmodifiable,
          * fallback on the ReferenceSystemUsingIdentifiers if necessary.
@@ -135,8 +133,8 @@
         theme           = source.getTheme();
         identifications = snapshot(source.getIdentifications());
         definition      = source.getDefinition();
-        territoryOfUse  = unmodifiable(GeographicExtent.class, source.getTerritoryOfUse());
-        owner           = unmodifiable(Party.class, source.getOwner());
+        territoryOfUse  = (GeographicExtent) unmodifiable(source.getTerritoryOfUse());
+        owner           = (AbstractParty) unmodifiable(source.getOwner());
         parents         = snapshot(source.getParents(),  rs, existing);
         children        = snapshot(source.getChildren(), rs, existing);
         referenceSystem = rs;
@@ -163,12 +161,12 @@
      * @param rs        the reference system to assign to the new location types.
      * @param existing  an initially empty identity hash map for internal usage by this method.
      */
-    static List<LocationType> snapshot(final Collection<? extends LocationType> types,
-            final ReferenceSystemUsingIdentifiers rs, final Map<LocationType, FinalLocationType> existing)
+    static List<AbstractLocationType> snapshot(final Collection<? extends AbstractLocationType> types,
+            final ReferencingByIdentifiers rs, final Map<AbstractLocationType, FinalLocationType> existing)
     {
-        final LocationType[] array = types.toArray(new LocationType[types.size()]);
+        final AbstractLocationType[] array = types.toArray(new AbstractLocationType[types.size()]);
         for (int i=0; i < array.length; i++) {
-            final LocationType source = array[i];
+            final AbstractLocationType source = array[i];
             ArgumentChecks.ensureNonNullElement("types", i, source);
             FinalLocationType copy = existing.get(source);
             if (copy == null) {
@@ -198,14 +196,12 @@
     /**
      * Returns an unmodifiable copy of the given metadata, if necessary and possible.
      *
-     * @param  <T>       compile-time value of the {@code type} argument.
-     * @param  type      the interface of the metadata object to eventually copy.
      * @param  metadata  the metadata object to eventually copy, or {@code null}.
      * @return an unmodifiable copy of the given metadata object, or {@code null} if the given argument is {@code null}.
      */
-    private static <T> T unmodifiable(final Class<T> type, T metadata) {
+    private static Object unmodifiable(Object metadata) {
         if (metadata instanceof ModifiableMetadata) {
-            metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(type, metadata);
+            metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(metadata);
             ((ModifiableMetadata) metadata).transitionTo(ModifiableMetadata.State.FINAL);
         }
         return metadata;
@@ -286,7 +282,7 @@
      * @return the reference system that comprises this location type.
      */
     @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+    public ReferencingByIdentifiers getReferenceSystem() {
         return referenceSystem;
     }
 
@@ -296,7 +292,7 @@
      * @return organization or class of organization able to create and destroy location instances.
      */
     @Override
-    public Party getOwner() {
+    public AbstractParty getOwner() {
         return owner;
     }
 
@@ -309,7 +305,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because unmodifiable
-    public Collection<LocationType> getParents() {
+    public Collection<AbstractLocationType> getParents() {
         return parents;
     }
 
@@ -320,7 +316,7 @@
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because unmodifiable
-    public Collection<LocationType> getChildren() {
+    public Collection<AbstractLocationType> getChildren() {
         return children;
     }
 }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
index 5d3d781..fac78c5 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystem.java
@@ -36,10 +36,6 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Geographic coordinates represented as <cite>geohashes</cite> strings.
@@ -170,10 +166,10 @@
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.8")
-    private static LocationType[] types() {
+    private static ModifiableLocationType[] types() {
         final ModifiableLocationType gzd = new ModifiableLocationType("Geohash");
         gzd.addIdentification(Vocabulary.formatInternational(Vocabulary.Keys.Code));
-        return new LocationType[] {gzd};
+        return new ModifiableLocationType[] {gzd};
     }
 
     /**
@@ -354,11 +350,16 @@
          * Decodes the given geohash into a latitude and a longitude.
          * The axis order depends on the coordinate reference system of the enclosing {@link GeohashReferenceSystem}.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  geohash  geohash string to decode.
          * @return a new geographic coordinate for the given geohash.
          * @throws TransformException if an error occurred while parsing the given string.
          */
-        public Location decode(final CharSequence geohash) throws TransformException {
+        public AbstractLocation decode(final CharSequence geohash) throws TransformException {
             ArgumentChecks.ensureNonEmpty("geohash", geohash);
             return new Decoder(geohash, coordinates);
         }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
index 6febadd..2955bf7 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
@@ -61,13 +61,11 @@
 import org.apache.sis.util.resources.Vocabulary;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
- * Formats {@link Location} instances in a tabular format.
+ * Formats {@code Location} instances in a tabular format.
  * This format assumes a monospaced font and an encoding supporting drawing box characters (e.g. UTF-8).
  *
  * <div class="note"><b>Example:</b>
@@ -100,7 +98,7 @@
  * @since   0.8
  * @module
  */
-public class LocationFormat extends TabularFormat<Location> {
+public class LocationFormat extends TabularFormat<AbstractLocation> {
     /**
      * For cross-version compatibility.
      */
@@ -141,8 +139,8 @@
      * @return the type of values formatted by this {@code Format} instance.
      */
     @Override
-    public Class<Location> getValueType() {
-        return Location.class;
+    public Class<AbstractLocation> getValueType() {
+        return AbstractLocation.class;
     }
 
     /**
@@ -199,13 +197,18 @@
     /**
      * Writes a textual representation of the given location in the given stream or buffer.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of {@code location} parameter may be generalized
+     * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param  location    the location to format.
      * @param  toAppendTo  where to format the location.
      * @throws IOException if an error occurred while writing to the given appendable.
      */
     @Override
     @SuppressWarnings({"fallthrough", "null"})
-    public void format(final Location location, final Appendable toAppendTo) throws IOException {
+    public void format(final AbstractLocation location, final Appendable toAppendTo) throws IOException {
         ArgumentChecks.ensureNonNull("location", location);
         final Locale locale = getLocale(Locale.Category.DISPLAY);
         final Vocabulary vocabulary = Vocabulary.getResources(locale);
@@ -215,7 +218,7 @@
          * Location type.
          */
         table.appendHorizontalSeparator();
-        final LocationType type = location.getLocationType();
+        final AbstractLocationType type = location.type();
         if (type != null) {
             append(table, vocabulary, Vocabulary.Keys.LocationType, toString(type.getName(), locale));
         }
@@ -416,7 +419,7 @@
         /*
          * Organization responsible for defining the characteristics of the location instance.
          */
-        final Party administrator = location.getAdministrator();
+        final AbstractParty administrator = location.getAdministrator();
         if (administrator != null) {
             append(table, vocabulary, Vocabulary.Keys.Administrator, toString(administrator.getName(), locale));
         }
@@ -430,7 +433,7 @@
 
     /**
      * Creates the format to use for formatting a latitude, longitude or projected coordinate.
-     * This method is invoked by {@link #format(Location, Appendable)} when first needed.
+     * This method is invoked by {@code format(Location, Appendable)} when first needed.
      *
      * @param  valueType  {@code Angle.class}. {@code Number.class} or {@code Unit.class}.
      * @return a new {@link AngleFormat}, {@link NumberFormat} or {@link UnitFormat} instance
@@ -483,7 +486,7 @@
      * @throws ParseException if an error occurred while parsing the location.
      */
     @Override
-    public Location parse(CharSequence text, ParsePosition pos) throws ParseException {
+    public AbstractLocation parse(CharSequence text, ParsePosition pos) throws ParseException {
         throw new ParseException(Errors.format(Errors.Keys.UnsupportedOperation_1, "parse"), pos.getIndex());
     }
 
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
index eae965e..cda1c14 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
@@ -50,31 +50,25 @@
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.crs.DefaultProjectedCRS;
 import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
-import org.apache.sis.metadata.sql.MetadataSource;
-import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.StringBuilders;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Workaround;
-import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.geometry.Shapes2D;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.DirectPosition2D;
-import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.Latitude;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -270,14 +264,7 @@
      */
     @Workaround(library="JDK", version="1.8")
     private static Map<String,?> properties() {
-        Party party;
-        try {
-            party = MetadataSource.getProvided().lookup(Party.class, "NATO");
-        } catch (MetadataStoreException e) {
-            party = null;
-            Logging.unexpectedException(Logging.getLogger(Modules.REFERENCING_BY_IDENTIFIERS),
-                    MilitaryGridReferenceSystem.class, "<init>", e);
-        }
+        AbstractParty party = new AbstractParty("North Atlantic Treaty Organization", null);
         return properties(new NamedIdentifier(null, "NATO", Resources.formatInternational(Resources.Keys.MGRS), null, null), party);
     }
 
@@ -286,7 +273,7 @@
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.8")
-    private static LocationType[] types() {
+    private static ModifiableLocationType[] types() {
         final ModifiableLocationType gzd    = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.GridZoneDesignator));
         final ModifiableLocationType square = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.SquareIdentifier100));
         final ModifiableLocationType coord  = new ModifiableLocationType(Resources.formatInternational(Resources.Keys.GridCoordinates));
@@ -294,7 +281,7 @@
         coord .addIdentification(Vocabulary.formatInternational(Vocabulary.Keys.Coordinate));
         square.addParent(gzd);
         coord .addParent(square);
-        return new LocationType[] {gzd};
+        return new ModifiableLocationType[] {gzd};
     }
 
     /**
@@ -641,11 +628,16 @@
          * Decodes the given MGRS reference into a position and an envelope.
          * The Coordinate Reference System (CRS) associated to the returned position depends on the given reference.
          *
+         * <div class="warning"><b>Upcoming API change — generalization</b><br>
+         * in a future SIS version, the type of returned element may be generalized
+         * to the {@code org.opengis.referencing.gazetteer.Location} interface.
+         * This change is pending GeoAPI revision.</div>
+         *
          * @param  reference  MGRS string to decode.
          * @return a new position with the longitude at coordinate 0 and latitude at coordinate 1.
          * @throws TransformException if an error occurred while parsing the given string.
          */
-        public Location decode(final CharSequence reference) throws TransformException {
+        public AbstractLocation decode(final CharSequence reference) throws TransformException {
             ArgumentChecks.ensureNonEmpty("reference", reference);
             return new Decoder(this, reference);
         }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
index e0ea4a6..013f54d 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
@@ -33,8 +33,7 @@
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -144,7 +143,7 @@
     /**
      * Name of organization or class of organization able to create and destroy location instances.
      */
-    private Party owner;
+    private AbstractParty owner;
 
     /**
      * Parent location types (location types of which this location type is a sub-division).
@@ -354,6 +353,11 @@
      * If no organization has been explicitly set, then this method inherits the value from
      * the parents providing that all parents specify the same organization.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return organization or class of organization able to create and destroy location instances,
      *         or {@code null} if no value has been defined or can be inherited.
      *
@@ -361,7 +365,7 @@
      * @see ReferencingByIdentifiers#getOverallOwner()
      */
     @Override
-    public Party getOwner() {
+    public AbstractParty getOwner() {
         return (owner != null) ? owner : inherit(ModifiableLocationType::getOwner);
     }
 
@@ -370,9 +374,14 @@
      * The given value is typically an instance of {@link DefaultOrganisation}.
      * For an alternative where only the organization name is specified, see {@link #setOwner(CharSequence)}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the argument type may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @param  value  the new owner.
      */
-    public void setOwner(final Party value) {
+    public void setOwner(final AbstractParty value) {
         owner = value;
     }
 
@@ -477,12 +486,17 @@
      * the reference system is always null. The reference system is defined when the location types are
      * given to the {@link ReferencingByIdentifiers} constructor for example.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return {@code null}.
      *
      * @see ReferencingByIdentifiers#getLocationTypes()
      */
     @Override
-    public ReferenceSystemUsingIdentifiers getReferenceSystem() {
+    public ReferencingByIdentifiers getReferenceSystem() {
         return null;
     }
 }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java
new file mode 100644
index 0000000..2950e77
--- /dev/null
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationTypeAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.referencing.gazetteer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.IdentityHashMap;
+import org.opengis.util.InternationalString;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+
+
+/**
+ * Workaround for the lack of {@code LocationType} interface in GeoAPI 3.0.
+ * This workaround will be removed if a future GeoAPI version publish that interface,
+ * or if {@link AbstractLocationType} is made public.
+ */
+final class ModifiableLocationTypeAdapter extends ModifiableLocationType {
+    /**
+     * The reference system of the original type.
+     * This is the only information not stored in {@link ModifiableLocationType}.
+     */
+    private final ReferencingByIdentifiers referenceSystem;
+
+    /**
+     * Copies all information from the given type.
+     */
+    private ModifiableLocationTypeAdapter(final AbstractLocationType type,
+            final Map<AbstractLocationType,ModifiableLocationTypeAdapter> previous)
+    {
+        super(type.getName());
+        setTheme(type.getTheme());
+        setDefinition(type.getDefinition());
+        setTerritoryOfUse(type.getTerritoryOfUse());
+        setOwner(type.getOwner());
+        for (final InternationalString s : type.getIdentifications()) {
+            addIdentification(s);
+        }
+        referenceSystem = type.getReferenceSystem();
+        for (final AbstractLocationType c : type.getChildren()) {
+            ModifiableLocationTypeAdapter p = previous.get(c);
+            if (p == null) {
+                p = new ModifiableLocationTypeAdapter(c, previous);
+                previous.put(c, p);
+            }
+            p.addParent(this);
+        }
+    }
+
+    /**
+     * Returns type type as-is if it is already an instance of {@code ModifiableLocationType},
+     * or returns a copy otherwise.
+     */
+    static ModifiableLocationType copy(final AbstractLocationType type) {
+        if (type instanceof ModifiableLocationType) {
+            return (ModifiableLocationType) type;
+        } else {
+            return new ModifiableLocationTypeAdapter(type,
+                    new IdentityHashMap<AbstractLocationType,ModifiableLocationTypeAdapter>());
+        }
+    }
+
+    /**
+     * Copies a list of location types.
+     */
+    static List<ModifiableLocationType> copy(final List<? extends AbstractLocationType> types) {
+        final Map<AbstractLocationType,ModifiableLocationTypeAdapter> previous = new IdentityHashMap<>();
+        final ModifiableLocationType[] nt = new ModifiableLocationType[types.size()];
+        for (int i=0; i<nt.length; i++) {
+            final AbstractLocationType c = types.get(i);
+            ModifiableLocationTypeAdapter p = previous.get(c);
+            if (p == null) {
+                p = new ModifiableLocationTypeAdapter(c, previous);
+                previous.put(c, p);
+            }
+            nt[i] = p;
+        }
+        return UnmodifiableArrayList.wrap(nt);
+    }
+
+    /**
+     * Returns the reference system of the type given to the constructor.
+     */
+    @Override
+    public ReferencingByIdentifiers getReferenceSystem() {
+        return referenceSystem;
+    }
+}
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
index fc92ae1..bd1e46a 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ReferencingByIdentifiers.java
@@ -36,9 +36,7 @@
 import org.apache.sis.util.resources.Vocabulary;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.referencing.gazetteer.LocationType;
-import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
 /**
@@ -61,7 +59,25 @@
  * @module
  */
 @XmlTransient
-public class ReferencingByIdentifiers extends AbstractReferenceSystem implements ReferenceSystemUsingIdentifiers {
+public class ReferencingByIdentifiers extends AbstractReferenceSystem {
+    /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * object factory {@code createFoo(…)} methods.
+     * This is used for setting the value to be returned by {@link #getTheme()}.
+     *
+     * @see #getTheme()
+     */
+    public static final String THEME_KEY = "theme";
+
+    /**
+     * Key for the <code>{@value}</code> property to be given to the
+     * object factory {@code createFoo(…)} methods.
+     * This is used for setting the value to be returned by {@link #getOverallOwner()}.
+     *
+     * @see #getOverallOwner()
+     */
+    public static final String OVERALL_OWNER_KEY = "overallOwner";
+
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -79,7 +95,7 @@
      *
      * @see #getOverallOwner()
      */
-    private final Party overallOwner;
+    private final AbstractParty overallOwner;
 
     /**
      * Description of location type(s) in the spatial reference system.
@@ -87,7 +103,7 @@
      *
      * @see #getLocationTypes()
      */
-    private final List<LocationType> locationTypes;
+    final List<AbstractLocationType> locationTypes;
 
     /**
      * Creates a reference system from the given properties.
@@ -103,13 +119,13 @@
      *     <th>Returned by</th>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#THEME_KEY}</td>
+     *     <td>"theme"</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getTheme()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#OVERALL_OWNER_KEY}</td>
-     *     <td>{@link Party}</td>
+     *     <td>"overallOwner"</td>
+     *     <td>{@code Party}</td>
      *     <td>{@link #getOverallOwner()}</td>
      *   </tr>
      *   <tr>
@@ -147,18 +163,23 @@
      *   </tr>
      * </table>
      *
-     * This constructor copies the given {@link LocationType} instances as per
-     * {@link ModifiableLocationType#snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
+     * This constructor copies the given {@code LocationType} instances as per
+     * {@code ModifiableLocationType.snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
      * Changes in the given location types after construction will not affect this {@code ReferencingByIdentifiers}.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * In a future SIS version, the type of array elements may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.LocationType} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @param properties  the properties to be given to the reference system.
      * @param types       description of location type(s) in the spatial reference system.
      */
     @SuppressWarnings("ThisEscapedInObjectConstruction")
-    public ReferencingByIdentifiers(final Map<String,?> properties, final LocationType... types) {
+    public ReferencingByIdentifiers(final Map<String,?> properties, final ModifiableLocationType... types) {
         super(properties);
         theme = Types.toInternationalString(properties, THEME_KEY);
-        overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, Party.class);
+        overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, AbstractParty.class);
         /*
          * Having the 'this' reference escaped in object construction should not be an issue here because
          * we invoke package-private method in such a way that if an exception is thrown, the whole tree
@@ -174,7 +195,7 @@
      * @param name   the reference system name as an {@link org.opengis.metadata.Identifier} or a {@link String}.
      * @param party  the overall owner, or {@code null} if none.
      */
-    static Map<String,Object> properties(final Object name, final Party party) {
+    static Map<String,Object> properties(final Object name, final AbstractParty party) {
         final Map<String,Object> properties = new HashMap<>(6);
         properties.put(NAME_KEY, name);
         properties.put(DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
@@ -182,18 +203,6 @@
         properties.put(OVERALL_OWNER_KEY, party);
         return properties;
     }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The default implementation returns {@code ReferenceSystemUsingIdentifiers.class}.
-     *
-     * @return the GeoAPI interface implemented by this class.
-     */
-    @Override
-    public Class<? extends ReferenceSystemUsingIdentifiers> getInterface() {
-        return ReferenceSystemUsingIdentifiers.class;
-    }
-
     /**
      * Property used to characterize the spatial reference system.
      *
@@ -201,7 +210,6 @@
      *
      * @see ModifiableLocationType#getTheme()
      */
-    @Override
     public InternationalString getTheme() {
         return theme;
     }
@@ -209,13 +217,17 @@
     /**
      * Authority with overall responsibility for the spatial reference system.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of returned element may be generalized to the
+     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
+     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
+     *
      * @return authority with overall responsibility for the spatial reference system.
      *
      * @see ModifiableLocationType#getOwner()
      * @see AbstractLocation#getAdministrator()
      */
-    @Override
-    public Party getOverallOwner() {
+    public AbstractParty getOverallOwner() {
         return overallOwner;
     }
 
@@ -223,20 +235,24 @@
      * Description of location type(s) in the spatial reference system.
      * The collection returned by this method is unmodifiable.
      *
+     * <div class="warning"><b>Upcoming API change — generalization</b><br>
+     * in a future SIS version, the type of elements type may be generalized to the
+     * {@code org.opengis.referencing.gazetteer.Location} interface.
+     * This change is pending GeoAPI revision.</div>
+     *
      * @return description of location type(s) in the spatial reference system.
      *
      * @see ModifiableLocationType#getReferenceSystem()
      */
-    @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Because the collection is unmodifiable.
-    public List<? extends LocationType> getLocationTypes() {
-        return locationTypes;
+    public List<? extends ModifiableLocationType> getLocationTypes() {
+        return ModifiableLocationTypeAdapter.copy(locationTypes);
     }
 
     /**
      * Returns the first location type.
      */
-    final LocationType rootType() {
+    final AbstractLocationType rootType() {
         return locationTypes.get(0);
     }
 
@@ -267,7 +283,7 @@
                        locationTypes.equals(that.locationTypes);
             }
             case BY_CONTRACT: {
-                final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
+                final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
                 if (!Utilities.deepEquals(getTheme(),        that.getTheme(),        mode) ||
                     !Utilities.deepEquals(getOverallOwner(), that.getOverallOwner(), mode))
                 {
@@ -277,8 +293,8 @@
             }
             default: {
                 // Theme and owner are metadata, so they can be ignored.
-                final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
-                return Utilities.deepEquals(getLocationTypes(), that.getLocationTypes(), mode);
+                final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
+                return Utilities.deepEquals(locationTypes, that.locationTypes, mode);
             }
         }
     }
@@ -314,7 +330,7 @@
             formatter.newLine();
             formatter.append(new SubElement("Owner", overallOwner.getName()));
         }
-        for (final LocationType type : locationTypes) {
+        for (final AbstractLocationType type : locationTypes) {
             formatter.newLine();
             formatter.append(new SubElement("LocationType", type.getName()));
         }
diff --git a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java
index 7a98cbd..a5d6270 100644
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/SimpleLocation.java
@@ -21,7 +21,6 @@
 import org.opengis.geometry.coordinate.Position;
 import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.referencing.gazetteer.LocationType;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -91,7 +90,7 @@
      * @param type        the description of the nature of this geographic identifier.
      * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
      */
-    SimpleLocation(final LocationType type, final CharSequence identifier) {
+    SimpleLocation(final AbstractLocationType type, final CharSequence identifier) {
         super(type, identifier);
     }
 
@@ -339,7 +338,7 @@
          * @param type        the description of the nature of this geographic identifier.
          * @param identifier  the geographic identifier to be returned by {@link #getGeographicIdentifier()}.
          */
-        Projected(final LocationType type, final CharSequence identifier) {
+        Projected(final AbstractLocationType type, final CharSequence identifier) {
             super(type, identifier);
         }
 
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
index 61be1e3..a39ebdf 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/GeohashReferenceSystemTest.java
@@ -29,10 +29,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests methods from the {@link GeohashReferenceSystem} class.
@@ -145,7 +141,7 @@
      */
     private void testDecode(final GeohashReferenceSystem.Coder coder, final int λi, final int φi) throws TransformException {
         for (final Place place : PLACES) {
-            final Location location = coder.decode(place.geohash);
+            final AbstractLocation location = coder.decode(place.geohash);
             final DirectPosition result = location.getPosition().getDirectPosition();
             assertEquals(place.name, place.longitude, result.getOrdinate(λi), TOLERANCE);
             assertEquals(place.name, place.latitude,  result.getOrdinate(φi), TOLERANCE);
@@ -163,7 +159,7 @@
         assertEquals("theme", "Mapping",      rs.getTheme().toString(Locale.ENGLISH));
         assertEquals("theme", "Cartographie", rs.getTheme().toString(Locale.FRENCH));
 
-        final LocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
+        final AbstractLocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
         assertEquals("type", "Geohash", type.getName().toString(Locale.ENGLISH));
         assertEquals("parent",   0, type.getParents().size());
         assertEquals("children", 0, type.getChildren().size());
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
index 34a3971..e69e23a 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationTypeTest.java
@@ -25,9 +25,6 @@
 
 import static org.apache.sis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests {@link AbstractLocationType}, {@link FinalLocationType} and {@link ModifiableLocationType}.
@@ -115,7 +112,7 @@
     /**
      * Verifies the value of a "administrative area" location type.
      */
-    private static void verify(final LocationType[] type) {
+    private static void verify(final AbstractLocationType[] type) {
         assertEquals(5, type.length);
         verify(type[0], "administrative area",
                         "local administration",
@@ -147,8 +144,8 @@
     /**
      * Verifies the value of a location type created by or copied from {@link #create(boolean)}.
      */
-    private static void verify(final LocationType type, final String name, final String theme, final String definition,
-            final String identification, final String owner)
+    private static void verify(final AbstractLocationType type, final String name, final String theme,
+            final String definition, final String identification, final String owner)
     {
         assertEquals("name",           name,           String.valueOf(type.getName()));
         assertEquals("theme",          theme,          String.valueOf(type.getTheme()));
@@ -183,8 +180,8 @@
     @Test
     @DependsOnMethod("testInheritance")
     public void testSnapshot() {
-        final List<LocationType> snapshot = ModifiableLocationType.snapshot(null, create(true));
-        verify(snapshot.toArray(new LocationType[snapshot.size()]));
+        final List<AbstractLocationType> snapshot = ModifiableLocationType.snapshot(null, create(true));
+        verify(snapshot.toArray(new AbstractLocationType[snapshot.size()]));
     }
 
     /**
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
index f7ad5a0..7e07a2b 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
@@ -41,12 +41,9 @@
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.util.Debug;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-
 
 /**
- * A Swing panel drawing {@link Location} instances.
+ * A Swing panel drawing {@code Location} instances.
  * This is used for debugging purpose only.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -215,7 +212,7 @@
      * @throws FactoryException if a transformation to the display CRS can not be obtained.
      * @throws TransformException if an error occurred while transforming an envelope.
      */
-    public void addLocation(final String label, final Location location) throws FactoryException, TransformException {
+    public void addLocation(final String label, final AbstractLocation location) throws FactoryException, TransformException {
         final Envelope envelope = location.getEnvelope();
         final MathTransform2D tr = (MathTransform2D) CRS.findOperation(
                 envelope.getCoordinateReferenceSystem(), displayCRS, null).getMathTransform();
diff --git a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
index 71bf9b2..7429fed 100644
--- a/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
+++ b/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
@@ -43,10 +43,6 @@
 
 import static org.junit.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
-
 
 /**
  * Tests {@link MilitaryGridReferenceSystem}.
@@ -67,15 +63,15 @@
         assertEquals("theme", "Mapping",      rs.getTheme().toString(Locale.ENGLISH));
         assertEquals("theme", "Cartographie", rs.getTheme().toString(Locale.FRENCH));
 
-        final LocationType gzd = TestUtilities.getSingleton(rs.getLocationTypes());
+        final AbstractLocationType gzd = TestUtilities.getSingleton(rs.getLocationTypes());
         assertEquals("type", "Grid zone designator", gzd.getName().toString(Locale.ENGLISH));
         assertEquals("parent", 0, gzd.getParents().size());
 
-        final LocationType sid = TestUtilities.getSingleton(gzd.getChildren());
+        final AbstractLocationType sid = TestUtilities.getSingleton(gzd.getChildren());
         assertEquals("type", "100 km square identifier", sid.getName().toString(Locale.ENGLISH));
         assertSame("parent", gzd, TestUtilities.getSingleton(sid.getParents()));
 
-        final LocationType gc = TestUtilities.getSingleton(sid.getChildren());
+        final AbstractLocationType gc = TestUtilities.getSingleton(sid.getChildren());
         assertEquals("type", "Grid coordinate", gc.getName().toString(Locale.ENGLISH));
         assertSame("parent", sid, TestUtilities.getSingleton(gc.getParents()));
     }
@@ -213,7 +209,7 @@
     private static DirectPosition decode(final MilitaryGridReferenceSystem.Coder coder, final String reference)
             throws TransformException
     {
-        final Location loc = coder.decode(reference);
+        final AbstractLocation loc = coder.decode(reference);
         final Envelope2D envelope = new Envelope2D(loc.getEnvelope());
         final DirectPosition2D pos = new DirectPosition2D(loc.getPosition().getDirectPosition());
         assertTrue(reference, envelope.contains(pos));
diff --git a/core/sis-referencing/pom.xml b/core/sis-referencing/pom.xml
index 5c3bcfd..d421aa0 100644
--- a/core/sis-referencing/pom.xml
+++ b/core/sis-referencing/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
   <groupId>org.apache.sis.core</groupId>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
index da831a5..1028380 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
@@ -25,7 +25,6 @@
 import java.util.Objects;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index ffbbdb8..a8d6b73 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -29,8 +29,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
-import org.opengis.geometry.UnmodifiableGeometryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.CoordinateSystem;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java
index 5dd86cd..6d6a0bb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ArrayEnvelope.java
@@ -27,7 +27,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.cs.RangeMeaning;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
index 6d9cd3c..08c9b4e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
@@ -21,7 +21,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
index 6951a6b..f4b2733 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
@@ -33,7 +33,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.apache.sis.internal.referencing.TemporalAccessor;
 import org.apache.sis.util.resources.Errors;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java
index 0124952..e674408 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/ImmutableEnvelope.java
@@ -25,7 +25,6 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/MismatchedReferenceSystemException.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/MismatchedReferenceSystemException.java
new file mode 100644
index 0000000..ce27ce0
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/MismatchedReferenceSystemException.java
@@ -0,0 +1,63 @@
+/*
+ * 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.geometry;
+
+
+/**
+ * Indicates that an object cannot be constructed because of a mismatch in the
+ * {@linkplain org.opengis.referencing.ReferenceSystem reference systems} of
+ * geometric components.
+ *
+ * @author  Martin Desruisseaux (IRD)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public class MismatchedReferenceSystemException extends IllegalArgumentException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 6222334569692693273L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public MismatchedReferenceSystemException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public MismatchedReferenceSystemException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message and cause.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     * @param  cause The cause.
+     */
+    public MismatchedReferenceSystemException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/UnmodifiableGeometryException.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/UnmodifiableGeometryException.java
new file mode 100644
index 0000000..3285613
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/UnmodifiableGeometryException.java
@@ -0,0 +1,54 @@
+/*
+ * 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.geometry;
+
+
+/**
+ * Indicates that an operation is not allowed on a geometry object
+ * because it is unmodifiable. Note that unmodifiable geometries are not necessarily immutable;
+ * they are just not allowed to be modified through the {@code setFoo(...)} method that
+ * raised this exception. Whatever an unmodifiable geometry is immutable or not is
+ * implementation dependent.
+ *
+ * @author  Martin Desruisseaux (IRD)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public class UnmodifiableGeometryException extends UnsupportedOperationException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 8679047625299612669L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public UnmodifiableGeometryException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param  message The detail message. The detail message is saved for
+     *         later retrieval by the {@link #getMessage()} method.
+     */
+    public UnmodifiableGeometryException(final String message) {
+        super(message);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
index 4012cb7..7c28f3e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CC_GeneralOperationParameter.java
@@ -261,7 +261,7 @@
          */
         final Map<String,Object> merged = new HashMap<>(expected);
         merged.putAll(actual);  // May overwrite pre-defined properties.
-        mergeArrays(GeneralParameterDescriptor.ALIAS_KEY,       GenericName.class, provided.getAlias(),       merged, complete.getName());
+        mergeArrays(GeneralParameterDescriptor.ALIAS_KEY,       GenericName.class, provided.getAlias(), merged, complete.getName());
         mergeArrays(GeneralParameterDescriptor.IDENTIFIERS_KEY, ReferenceIdentifier.class, provided.getIdentifiers(), merged, null);
         if (isGroup) {
             final List<GeneralParameterDescriptor> descriptors = ((ParameterDescriptorGroup) provided).descriptors();
@@ -455,8 +455,8 @@
     private static NamedIdentifier toNamedIdentifier(final Object name) {
         if (name == null || name.getClass() == NamedIdentifier.class) {
             return (NamedIdentifier) name;
-        } else if (name instanceof Identifier) {
-            return new NamedIdentifier((Identifier) name);
+        } else if (name instanceof ReferenceIdentifier) {
+            return new NamedIdentifier((ReferenceIdentifier) name);
         } else {
             return new NamedIdentifier((GenericName) name);
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java
index 408b95f..ba34080 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CD_ParametricDatum.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.referencing;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.referencing.datum.ParametricDatum;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
@@ -32,7 +31,7 @@
  * @since   0.7
  * @module
  */
-public final class CD_ParametricDatum extends PropertyType<CD_ParametricDatum, ParametricDatum> {
+public final class CD_ParametricDatum extends PropertyType<CD_ParametricDatum, DefaultParametricDatum> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -47,14 +46,14 @@
      * @return {@code ParametricDatum.class}
      */
     @Override
-    protected Class<ParametricDatum> getBoundType() {
-        return ParametricDatum.class;
+    protected Class<DefaultParametricDatum> getBoundType() {
+        return DefaultParametricDatum.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CD_ParametricDatum(final ParametricDatum datum) {
+    private CD_ParametricDatum(final DefaultParametricDatum datum) {
         super(datum);
     }
 
@@ -66,7 +65,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CD_ParametricDatum wrap(final ParametricDatum datum) {
+    protected CD_ParametricDatum wrap(final DefaultParametricDatum datum) {
         return new CD_ParametricDatum(datum);
     }
 
@@ -79,7 +78,7 @@
      */
     @XmlElement(name = "ParametricDatum")
     public DefaultParametricDatum getElement() {
-        return DefaultParametricDatum.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java
index 7cb4899..d361200 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/CS_ParametricCS.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.jaxb.referencing;
 
 import javax.xml.bind.annotation.XmlElement;
-import org.opengis.referencing.cs.ParametricCS;
 import org.apache.sis.referencing.cs.DefaultParametricCS;
 import org.apache.sis.internal.jaxb.gco.PropertyType;
 
@@ -31,7 +30,7 @@
  * @since   0.7
  * @module
  */
-public final class CS_ParametricCS extends PropertyType<CS_ParametricCS, ParametricCS> {
+public final class CS_ParametricCS extends PropertyType<CS_ParametricCS, DefaultParametricCS> {
     /**
      * Empty constructor for JAXB only.
      */
@@ -46,14 +45,14 @@
      * @return {@code ParametricCS.class}
      */
     @Override
-    protected Class<ParametricCS> getBoundType() {
-        return ParametricCS.class;
+    protected Class<DefaultParametricCS> getBoundType() {
+        return DefaultParametricCS.class;
     }
 
     /**
      * Constructor for the {@link #wrap} method only.
      */
-    private CS_ParametricCS(final ParametricCS cs) {
+    private CS_ParametricCS(final DefaultParametricCS cs) {
         super(cs);
     }
 
@@ -65,7 +64,7 @@
      * @return a {@code PropertyType} wrapping the given the element.
      */
     @Override
-    protected CS_ParametricCS wrap(final ParametricCS cs) {
+    protected CS_ParametricCS wrap(final DefaultParametricCS cs) {
         return new CS_ParametricCS(cs);
     }
 
@@ -78,7 +77,7 @@
      */
     @XmlElement(name = "ParametricCS")
     public DefaultParametricCS getElement() {
-        return DefaultParametricCS.castOrCopy(metadata);
+        return metadata;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
index 8a90963..f34ac29 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/Code.java
@@ -76,7 +76,7 @@
      *
      * @param identifier  the identifier from which to get the values.
      */
-    Code(final Identifier identifier) {
+    Code(final ReferenceIdentifier identifier) {
         code      = identifier.getCode();
         codeSpace = identifier.getCodeSpace();
         String version = identifier.getVersion();
@@ -165,12 +165,12 @@
      * @param  identifiers  the object identifiers, or {@code null} if none.
      * @return the {@code <gml:identifier>} as a {@code Code} instance, or {@code null} if none.
      */
-    public static Code forIdentifiedObject(final Class<?> type, final Iterable<? extends Identifier> identifiers) {
+    public static Code forIdentifiedObject(final Class<?> type, final Iterable<? extends ReferenceIdentifier> identifiers) {
         if (identifiers != null) {
             boolean isHTTP = false;
             boolean isEPSG = false;
-            Identifier fallback = null;
-            for (final Identifier identifier : identifiers) {
+            ReferenceIdentifier fallback = null;
+            for (final ReferenceIdentifier identifier : identifiers) {
                 final String code = identifier.getCode();
                 if (code == null) continue;                                                 // Paranoiac check.
                 if (code.regionMatches(true, 0, "urn:", 0, 4)) {
@@ -232,10 +232,12 @@
                             if (authority != null) {
                                 for (final Identifier id : authority.getIdentifiers()) {
                                     if (Constants.EPSG.equalsIgnoreCase(id.getCode())) {
-                                        final String cs = id.getCodeSpace();
-                                        if (cs != null) {
-                                            code.codeSpace = cs;
-                                            break;
+                                        if (id instanceof ReferenceIdentifier) {
+                                            final String cs = ((ReferenceIdentifier) id).getCodeSpace();
+                                            if (cs != null) {
+                                                code.codeSpace = cs;
+                                                break;
+                                            }
                                         }
                                     }
                                 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java
index 031c299..027e186 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/jaxb/referencing/RS_Identifier.java
@@ -17,6 +17,7 @@
 package org.apache.sis.internal.jaxb.referencing;
 
 import javax.xml.bind.annotation.adapters.XmlAdapter;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.Identifier;
 
 
@@ -36,7 +37,7 @@
  *   <gml:identifier codeSpace="EPSG">4326</gml:identifier>
  * }
  *
- * If the {@code Identifier} to marshal contains a {@linkplain Identifier#getVersion() version},
+ * If the {@code Identifier} to marshal contains a {@linkplain ReferenceIdentifier#getVersion() version},
  * then this adapter concatenates the version to the codespace in a "URI-like" way like below:
  *
  * {@preformat xml
@@ -61,7 +62,7 @@
  * @since   0.4
  * @module
  */
-public final class RS_Identifier extends XmlAdapter<Code, Identifier> {
+public final class RS_Identifier extends XmlAdapter<Code, ReferenceIdentifier> {
     /**
      * Substitutes the wrapper value read from an XML stream by the object which will
      * represents the identifier. JAXB calls automatically this method at unmarshalling time.
@@ -70,7 +71,7 @@
      * @return an identifier which represents the value.
      */
     @Override
-    public Identifier unmarshal(final Code value) {
+    public ReferenceIdentifier unmarshal(final Code value) {
         return (value != null) ? value.getIdentifier() : null;
     }
 
@@ -82,7 +83,7 @@
      * @return the adapter for the given metadata.
      */
     @Override
-    public Code marshal(final Identifier value) {
+    public Code marshal(final ReferenceIdentifier value) {
         return (value != null) ? new Code(value) : null;
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
index 545760c..5d816d8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
@@ -35,7 +35,7 @@
 
 import static org.opengis.referencing.cs.AxisDirection.*;
 import static org.opengis.annotation.Obligation.CONDITIONAL;
-import static org.opengis.annotation.Specification.ISO_19162;
+import static org.opengis.annotation.Specification.UNSPECIFIED;
 import static org.apache.sis.util.CharSequences.*;
 
 
@@ -78,7 +78,7 @@
      *
      * @since 0.7
      */
-    @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=ISO_19162)
+    @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM");
 
     /**
@@ -87,7 +87,7 @@
      *
      * @since 0.7
      */
-    @UML(identifier="clockwise", obligation=CONDITIONAL, specification=ISO_19162)
+    @UML(identifier="clockwise", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public static final AxisDirection CLOCKWISE = AxisDirection.valueOf("CLOCKWISE");
 
     /**
@@ -96,7 +96,7 @@
      *
      * @since 0.7
      */
-    @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=ISO_19162)
+    @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=UNSPECIFIED)
     public static final AxisDirection COUNTER_CLOCKWISE = AxisDirection.valueOf("COUNTER_CLOCKWISE");
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
index a9f9ec0..ce81440 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
@@ -21,7 +21,7 @@
 import org.apache.sis.geometry.AbstractDirectPosition;
 
 // Branch-dependent imports
-import org.opengis.geometry.UnmodifiableGeometryException;
+import org.apache.sis.geometry.UnmodifiableGeometryException;
 
 
 /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxy.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxy.java
index b1ea2ef..26359da 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxy.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/EPSGFactoryProxy.java
@@ -27,7 +27,6 @@
 import org.opengis.referencing.crs.GeocentricCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.ImageCRS;
-import org.opengis.referencing.crs.ParametricCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
@@ -135,11 +134,6 @@
     }
 
     @Override
-    public ParametricCRS createParametricCRS(String code) throws FactoryException {
-        return factory().createParametricCRS(code);
-    }
-
-    @Override
     public Set<String> getAuthorityCodes(Class<? extends IdentifiedObject> type) throws FactoryException {
         return factory().getAuthorityCodes(type);
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java
index cac8734..46b6c40 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/NilReferencingObject.java
@@ -16,11 +16,16 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Set;
+import java.util.Collection;
+import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.metadata.extent.Extent;
 import org.apache.sis.xml.NilReason;
 import org.apache.sis.xml.NilObject;
+import org.apache.sis.io.wkt.UnformattableObjectException;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.util.resources.Vocabulary;
 
@@ -75,6 +80,21 @@
      * Returning null for collection are okay in the particular case of SIS implementation,
      * because the constructor will replace empty collections by null references anyway.
      */
-    @Override public ReferenceIdentifier getName()  {return UNNAMED;}
-    @Override public InternationalString getScope() {return null;}
+    @Override public ReferenceIdentifier      getName()        {return UNNAMED;}
+    @Override public Collection<GenericName>  getAlias()       {return null;}
+    @Override public Set<ReferenceIdentifier> getIdentifiers() {return null;}
+    @Override public InternationalString      getRemarks()     {return null;}
+    @Override public InternationalString      getScope()       {return null;}
+    @Override public Extent getDomainOfValidity()              {return null;}
+
+    /**
+     * Throws the exception in all cases.
+     *
+     * @return never return.
+     * @throws UnformattableObjectException always thrown.
+     */
+    @Override
+    public String toWKT() throws UnformattableObjectException {
+        throw new UnformattableObjectException();
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
index 78560bc..c27fd14 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
@@ -35,6 +35,9 @@
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.util.resources.Errors;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+
 
 /**
  * A container of factories frequently used together.
@@ -104,7 +107,7 @@
      * Factory for fetching operation methods and creating defining conversions.
      * This is needed only for user-defined projected coordinate reference system.
      */
-    private CoordinateOperationFactory operationFactory;
+    private DefaultCoordinateOperationFactory operationFactory;
 
     /**
      * The {@linkplain org.opengis.referencing.operation.MathTransform math transform} factory.
@@ -146,7 +149,7 @@
         this.crsFactory        = crsFactory;
         this.csFactory         = csFactory;
         this.datumFactory      = datumFactory;
-        this.operationFactory  = operationFactory;
+        this.operationFactory  = (DefaultCoordinateOperationFactory) operationFactory;      // Because of GeoAPI 3.0 limitation.
         this.mtFactory         = mtFactory;
     }
 
@@ -175,7 +178,7 @@
         if (type == CRSFactory.class)                 return crsFactory       != (crsFactory       = (CRSFactory)                 factory);
         if (type == CSFactory.class)                  return csFactory        != (csFactory        = (CSFactory)                  factory);
         if (type == DatumFactory.class)               return datumFactory     != (datumFactory     = (DatumFactory)               factory);
-        if (type == CoordinateOperationFactory.class) return operationFactory != (operationFactory = (CoordinateOperationFactory) factory);
+        if (type == CoordinateOperationFactory.class) return operationFactory != (operationFactory = (DefaultCoordinateOperationFactory) factory);
         if (type == MathTransformFactory.class)       return mtFactory        != (mtFactory        = (MathTransformFactory)       factory);
         throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "type", type));
     }
@@ -315,10 +318,15 @@
      *
      * @return the Coordinate Operation factory (never {@code null}).
      */
-    public final CoordinateOperationFactory getCoordinateOperationFactory() {
+    public final DefaultCoordinateOperationFactory getCoordinateOperationFactory() {
         if (operationFactory == null) {
-            operationFactory = CoordinateOperations.getCoordinateOperationFactory(defaultProperties, mtFactory, crsFactory, csFactory);
+            CoordinateOperationFactory op = CoordinateOperations.getCoordinateOperationFactory(defaultProperties, mtFactory, crsFactory, csFactory);
             defaultProperties = null;       // Not needed anymore.
+            if (op instanceof DefaultCoordinateOperationFactory) {
+                operationFactory = (DefaultCoordinateOperationFactory) op;
+            } else {
+                operationFactory = CoordinateOperations.factory();
+            }
         }
         return operationFactory;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
index 7e6489a..feb0967 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java
@@ -422,7 +422,7 @@
         final UML uml = type.getAnnotation(UML.class);
         if (uml != null) {
             final Specification spec = uml.specification();
-            if (spec == Specification.ISO_19111 || spec == Specification.ISO_19111_2) {
+            if (spec == Specification.ISO_19111) {
                 final String name = uml.identifier();
                 final int length = name.length();
                 final StringBuilder buffer = new StringBuilder(length).append(name, name.indexOf('_') + 1, length);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
index 289ff48..6f4beff 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ServicesForMetadata.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Map;
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Locale;
@@ -24,18 +25,27 @@
 
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
+import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.datum.DatumFactory;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.CRSFactory;
+import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.SingleOperation;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.OnLineFunction;
 import org.opengis.metadata.citation.OnlineResource;
@@ -49,25 +59,34 @@
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.CoordinateFormat;
+import org.apache.sis.internal.metadata.NameToIdentifier;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
 import org.apache.sis.referencing.crs.DefaultTemporalCRS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
+import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
 import org.apache.sis.parameter.DefaultParameterDescriptor;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.TemporalUtilities;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.util.Deprecable;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Utilities;
 
@@ -462,7 +481,7 @@
          * If above code did not found an EPSG code, discard EPSG codes that
          * we may find in the loop below because they are probably invalid.
          */
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             if (!Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 return IdentifiedObjects.toString(id);
             }
@@ -491,6 +510,86 @@
     }
 
     /**
+     * Creates a parametric CS. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the coordinate system name, and optionally other properties.
+     * @param  axis        the axis of the parametric coordinate system.
+     * @param  factory     the factory to use for creating the coordinate system.
+     * @return a parametric coordinate system using the given axes.
+     * @throws FactoryException if the parametric object creation failed.
+     *
+     * @since 0.7
+     */
+    public static CoordinateSystem createParametricCS(final Map<String,?> properties, final CoordinateSystemAxis axis,
+            CSFactory factory) throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = DefaultFactories.forBuildin(CSFactory.class, GeodeticObjectFactory.class);
+        }
+        return ((GeodeticObjectFactory) factory).createParametricCS(properties, axis);
+    }
+
+    /**
+     * Creates a parametric datum. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the datum name, and optionally other properties.
+     * @param  factory     the factory to use for creating the datum.
+     * @return a parametric datum using the given name.
+     * @throws FactoryException if the parametric object creation failed.
+     *
+     * @since 0.7
+     */
+    public static Datum createParametricDatum(final Map<String,?> properties, DatumFactory factory)
+            throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = DefaultFactories.forBuildin(DatumFactory.class, GeodeticObjectFactory.class);
+        }
+        return ((GeodeticObjectFactory) factory).createParametricDatum(properties);
+    }
+
+    /**
+     * Creates a parametric CRS. This method requires the SIS factory
+     * since parametric CRS were not available in GeoAPI 3.0.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  properties  the coordinate reference system name, and optionally other properties.
+     * @param  datum       the parametric datum.
+     * @param  cs          the parametric coordinate system.
+     * @param  factory     the factory to use for creating the coordinate reference system.
+     * @return a parametric coordinate system using the given axes.
+     * @throws FactoryException if the parametric object creation failed.
+     *
+     * @since 0.7
+     */
+    public static SingleCRS createParametricCRS(final Map<String,?> properties, final Datum datum,
+            final CoordinateSystem cs, CRSFactory factory) throws FactoryException
+    {
+        if (!(factory instanceof GeodeticObjectFactory)) {
+            factory = DefaultFactories.forBuildin(CRSFactory.class, GeodeticObjectFactory.class);
+        }
+        try {
+            return ((GeodeticObjectFactory) factory).createParametricCRS(properties,
+                    (DefaultParametricDatum) datum, (DefaultParametricCS) cs);
+        } catch (ClassCastException e) {
+            throw new InvalidGeodeticParameterException(e.toString(), e);
+        }
+    }
+
+    /**
      * Creates a format for {@link DirectPosition} instances.
      *
      * @param  locale    the locale for the new {@code Format}, or {@code null} for {@code Locale.ROOT}.
@@ -515,6 +614,70 @@
     }
 
     /**
+     * Returns the coordinate operation method for the given classification.
+     * This method checks if the given {@code opFactory} is a SIS implementation
+     * before to fallback on a slower fallback.
+     *
+     * <p>This method is actually not needed anymore for {@code sis-metadata} module,
+     * but is still defined here for historical reason. This method is removed on SIS
+     * branches using a GeoAPI versions more recent than 3.0.</p>
+     *
+     * @param  opFactory  The coordinate operation factory to use if it is a SIS implementation.
+     * @param  mtFactory  The math transform factory to use as a fallback.
+     * @param  identifier The name or identifier of the operation method to search.
+     * @return The coordinate operation method for the given name or identifier.
+     * @throws FactoryException if an error occurred which searching for the given method.
+     *
+     * @since 0.6
+     */
+    public static OperationMethod getOperationMethod(final CoordinateOperationFactory opFactory,
+            final MathTransformFactory mtFactory, final String identifier) throws FactoryException
+    {
+        if (opFactory instanceof DefaultCoordinateOperationFactory) {
+            ((DefaultCoordinateOperationFactory) opFactory).getOperationMethod(identifier);
+        }
+        final OperationMethod method = getOperationMethod(mtFactory.getAvailableMethods(SingleOperation.class), identifier);
+        if (method != null) {
+            return method;
+        }
+        throw new NoSuchIdentifierException("No such operation method: " + identifier, identifier);
+    }
+
+    /**
+     * Returns the operation method for the specified name or identifier. The given argument shall be either a
+     * method name (e.g. <cite>"Transverse Mercator"</cite>) or one of its identifiers (e.g. {@code "EPSG:9807"}).
+     *
+     * @param  methods     the method candidates.
+     * @param  identifier  the name or identifier of the operation method to search.
+     * @return the coordinate operation method for the given name or identifier, or {@code null} if none.
+     *
+     * @see org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory#getOperationMethod(String)
+     * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getOperationMethod(String)
+     *
+     * @since 0.6
+     */
+    private static OperationMethod getOperationMethod(final Iterable<? extends OperationMethod> methods, final String identifier) {
+        OperationMethod fallback = null;
+        for (final OperationMethod method : methods) {
+            if (IdentifiedObjects.isHeuristicMatchForName(method, identifier) ||
+                    NameToIdentifier.isHeuristicMatchForIdentifier(method.getIdentifiers(), identifier))
+            {
+                /*
+                 * Stop the iteration at the first non-deprecated method.
+                 * If we find only deprecated methods, take the first one.
+                 */
+                if (!(method instanceof Deprecable) || !((Deprecable) method).isDeprecated()) {
+                    return method;
+                }
+                if (fallback == null) {
+                    fallback = method;
+                }
+            }
+        }
+        return fallback;
+    }
+
+    /**
      * Returns information about the Apache SIS configuration.
      * See super-class for a list of keys.
      *
@@ -537,9 +700,9 @@
                     final String msg = Exceptions.getLocalizedMessage(e, locale);
                     return (msg != null) ? msg : e.toString();
                 }
-                if (authority != null) {
+                if (authority instanceof DefaultCitation) {
                     final OnLineFunction f = OnLineFunction.valueOf(CONNECTION);
-                    for (final OnlineResource res : authority.getOnlineResources()) {
+                    for (final OnlineResource res : ((DefaultCitation) authority).getOnlineResources()) {
                         if (f.equals(res.getFunction())) {
                             final InternationalString i18n = res.getDescription();
                             if (i18n != null) return i18n.toString(locale);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java
index c409d60..7fa5b60 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/VerticalDatumTypes.java
@@ -17,7 +17,6 @@
 package org.apache.sis.internal.referencing;
 
 import java.util.Collection;
-import java.util.function.Predicate;
 import javax.measure.Unit;
 import org.opengis.util.CodeList;
 import org.opengis.util.GenericName;
@@ -35,15 +34,15 @@
  * Those constants are not in public API because they were intentionally omitted from ISO 19111,
  * and the ISO experts said that they should really not be public.
  *
- * <p>This class implements {@link Predicate} for opportunist reasons.
+ * <p>This class implements {@code CodeList.Filter} for opportunist reasons.
  * This implementation convenience may change in any future SIS version.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 0.7
  * @since   0.4
  * @module
  */
-public final class VerticalDatumTypes implements Predicate<CodeList<?>> {
+public final class VerticalDatumTypes implements CodeList.Filter {
     /**
      * A vertical datum for ellipsoidal heights that are measured along the
      * normal to the ellipsoid used in the definition of horizontal datum.
@@ -188,7 +187,7 @@
             for (int i=0; i<name.length();) {
                 final int c = name.codePointAt(i);
                 if (Character.isLetter(c)) {
-                    return CodeList.valueOf(VerticalDatumType.class, new VerticalDatumTypes(name), null);
+                    return CodeList.valueOf(VerticalDatumType.class, new VerticalDatumTypes(name));
                 }
                 i += Character.charCount(c);
             }
@@ -231,8 +230,19 @@
      * @return {@code true} if the code matches the criterion.
       */
     @Override
-    public boolean test(final CodeList<?> code) {
+    public boolean accept(final CodeList<?> code) {
         final int i = datum.indexOf(code.name());
         return (i == 0) || (i >= 0 && Character.isWhitespace(datum.codePointBefore(i)));
     }
+
+    /**
+     * Returns {@code null} for disabling the creation of new code list elements.
+     * This method is public as an implementation side-effect and should be ignored.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public String codename() {
+        return null;
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
index 4b149b0..d5e3cad 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WKTUtilities.java
@@ -21,12 +21,12 @@
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import javax.measure.quantity.Angle;
-import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
@@ -337,7 +337,7 @@
      */
     public static boolean isEPSG(final GeneralParameterDescriptor descriptor, final boolean ifUndefined) {
         if (descriptor != null) {
-            final Identifier id = descriptor.getName();
+            final ReferenceIdentifier id = descriptor.getName();
             if (id != null) {
                 final String cs = id.getCodeSpace();
                 if (cs != null) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
index 92780cd..4ba2b70 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/Formatter.java
@@ -35,8 +35,8 @@
 import javax.measure.Unit;
 import javax.measure.Quantity;
 
+import org.opengis.util.CodeList;
 import org.opengis.util.InternationalString;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
@@ -46,6 +46,7 @@
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.GeneralParameterValue;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.crs.CompoundCRS;
@@ -831,17 +832,17 @@
             appendForSubtypes(object);
         }
         if (showIDs) {
-            Collection<? extends Identifier> identifiers = object.getIdentifiers();
+            Collection<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                                  // Paranoiac check
                 if (filterID) {
-                    for (final Identifier id : identifiers) {
+                    for (final ReferenceIdentifier id : identifiers) {
                         if (Citations.identifierMatches(authority, id.getAuthority())) {
                             identifiers = Collections.singleton(id);
                             break;
                         }
                     }
                 }
-                for (Identifier id : identifiers) {
+                for (ReferenceIdentifier id : identifiers) {
                     if (!(id instanceof FormattableObject)) {
                         id = ImmutableIdentifier.castOrCopy(id);
                     }
@@ -1126,7 +1127,7 @@
      *
      * @param  code  the code list to append to the WKT, or {@code null} if none.
      */
-    public void append(final ControlledVocabulary code) {
+    public void append(final CodeList<?> code) {
         if (code != null) {
             appendSeparator();
             final String name = convention.majorVersion() == 1 ? code.name() : Types.getCodeName(code);
@@ -1498,13 +1499,11 @@
             } else {
                 append(number.doubleValue());
             }
-        } else if (value instanceof ControlledVocabulary) {
-            append((ControlledVocabulary) value);
-        } else if (value instanceof Date) {
-            append((Date) value);
-        } else if (value instanceof Boolean) {
-            append((Boolean) value);
-        } else if (value instanceof CharSequence) {
+        }
+        else if (value instanceof CodeList<?>) append((CodeList<?>) value);
+        else if (value instanceof Date)        append((Date)        value);
+        else if (value instanceof Boolean)     append((Boolean)     value);
+        else if (value instanceof CharSequence) {
             append((value instanceof InternationalString) ?
                     ((InternationalString) value).toString(locale) : value.toString(), null);
         } else if (value.getClass().isArray()) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
index f5aea8e..ccc011c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -43,6 +43,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.ObjectFactory;
 import org.opengis.util.FactoryException;
@@ -74,6 +75,7 @@
 import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.metadata.TransformationAccuracy;
+import org.apache.sis.internal.referencing.ServicesForMetadata;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 import org.apache.sis.internal.referencing.EllipsoidalHeightCombiner;
 import org.apache.sis.internal.referencing.VerticalDatumTypes;
@@ -952,7 +954,7 @@
             }
             case WKTKeywords.parametric: {
                 if (axes.length != (dimension = 1)) break;
-                return csFactory.createParametricCS(csProperties, axes[0]);
+                return ServicesForMetadata.createParametricCS(csProperties, axes[0], csFactory);
             }
             default: {
                 warning(parent, WKTKeywords.CS, Errors.formatInternational(Errors.Keys.UnknownType_1, type), null);
@@ -1260,14 +1262,16 @@
          */
         FactoryException suppressed = null;
         final CoordinateOperationFactory opFactory = factories.getCoordinateOperationFactory();
-        if (id != null) try {
+        final MathTransformFactory mtFactory = factories.getMathTransformFactory();
+        if (id instanceof ReferenceIdentifier) try {
             // CodeSpace is a mandatory attribute in ID[…] elements, so we do not test for null values.
-            return opFactory.getOperationMethod(id.getCodeSpace() + Constants.DEFAULT_SEPARATOR + id.getCode());
+            return ServicesForMetadata.getOperationMethod(opFactory, mtFactory,
+                    ((ReferenceIdentifier) id).getCodeSpace() + Constants.DEFAULT_SEPARATOR + id.getCode());
         } catch (FactoryException e) {
             suppressed = e;
         }
         try {
-            return opFactory.getOperationMethod(name);
+            return ServicesForMetadata.getOperationMethod(opFactory, mtFactory, name);
         } catch (FactoryException e) {
             if (suppressed != null) {
                 e.addSuppressed(suppressed);
@@ -1472,10 +1476,10 @@
      *
      * @param  mode    {@link #FIRST}, {@link #OPTIONAL} or {@link #MANDATORY}.
      * @param  parent  the parent element.
-     * @return the {@code "ParametricDatum"} element as a {@link ParametricDatum} object.
+     * @return the {@code "ParametricDatum"} element as a {@code ParametricDatum} object.
      * @throws ParseException if the {@code "ParametricDatum"} element can not be parsed.
      */
-    private ParametricDatum parseParametricDatum(final int mode, final Element parent) throws ParseException {
+    private Datum parseParametricDatum(final int mode, final Element parent) throws ParseException {
         final Element element = parent.pullElement(mode, WKTKeywords.ParametricDatum, WKTKeywords.PDatum);
         if (element == null) {
             return null;
@@ -1483,7 +1487,7 @@
         final String name = element.pullString("name");
         final DatumFactory datumFactory = factories.getDatumFactory();
         try {
-            return datumFactory.createParametricDatum(parseAnchorAndClose(element, name));
+            return ServicesForMetadata.createParametricDatum(parseAnchorAndClose(element, name), datumFactory);
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
         }
@@ -2013,7 +2017,7 @@
          * A ParametricCRS can be either a "normal" one (with a non-null datum), or a DerivedCRS of kind ParametricCRS.
          * In the later case, the datum is null and we have instead DerivingConversion element from a BaseParametricCRS.
          */
-        ParametricDatum datum    = null;
+        Datum           datum    = null;
         SingleCRS       baseCRS  = null;
         Conversion      fromBase = null;
         if (!isBaseCRS) {
@@ -2039,12 +2043,12 @@
         try {
             cs = parseCoordinateSystem(element, WKTKeywords.parametric, 1, false, unit, datum);
             final Map<String,?> properties = parseMetadataAndClose(element, name, datum);
-            if (cs instanceof ParametricCS) {
+            if (cs != null) {
                 final CRSFactory crsFactory = factories.getCRSFactory();
                 if (baseCRS != null) {
                     return crsFactory.createDerivedCRS(properties, baseCRS, fromBase, cs);
                 }
-                return crsFactory.createParametricCRS(properties, datum, (ParametricCS) cs);
+                return ServicesForMetadata.createParametricCRS(properties, datum, cs, crsFactory);
             }
         } catch (FactoryException exception) {
             throw element.parseFailed(exception);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
index fd0711f..8a7c141 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/io/wkt/MathTransformParser.java
@@ -60,7 +60,7 @@
  * @author  Rueben Schulz (UBC)
  * @version 1.0
  *
- * @see <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
+ * @see <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</a>
  *
  * @since 0.6
  * @module
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
index 5801217..f8b88db 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/AbstractParameterDescriptor.java
@@ -17,12 +17,10 @@
 package org.apache.sis.parameter;
 
 import java.util.Map;
-import java.util.Objects;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlSeeAlso;
 import javax.xml.bind.annotation.XmlSchemaType;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
@@ -34,7 +32,6 @@
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Debug;
 
-import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.internal.jaxb.referencing.CC_GeneralOperationParameter.DEFAULT_OCCURRENCE;
 
 
@@ -69,7 +66,7 @@
  *     <td class="sep">Also known as “definition”.</td>
  *   </tr>
  *   <tr>
- *     <td>{@link #getDirection()}</td>
+ *     <td>{@code getDirection()}</td>
  *     <td class="sep"></td>
  *     <td class="sep"></td>
  *     <td class="sep">{@code direction}</td>
@@ -146,7 +143,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -156,7 +153,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -227,17 +224,6 @@
     }
 
     /**
-     * Returns an indication if the parameter is an input to the service, an output or both.
-     * The default implementation returns {@link ParameterDirection#IN}.
-     *
-     * @return indication if the parameter is an input or output to the service, or {@code null} if unspecified.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        return ParameterDirection.IN;
-    }
-
-    /**
      * The minimum number of times that values for this parameter group or parameter are required.
      * A value of 0 means an optional parameter.
      *
@@ -276,9 +262,7 @@
                 default: {
                     final GeneralParameterDescriptor that = (GeneralParameterDescriptor) object;
                     return getMinimumOccurs() == that.getMinimumOccurs() &&
-                           getMaximumOccurs() == that.getMaximumOccurs() &&
-                           Objects.equals(getDirection(), that.getDirection()) &&
-                           deepEquals(getDescription(), that.getDescription(), mode);
+                           getMaximumOccurs() == that.getMaximumOccurs();
                 }
             }
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
index 6c14677..783b8eb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
@@ -134,7 +134,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -144,11 +144,11 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
index ebfc70a..2341753 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptorGroup.java
@@ -24,7 +24,6 @@
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
@@ -125,7 +124,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -135,11 +134,11 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -320,31 +319,6 @@
     }
 
     /**
-     * Returns an indication if all parameters in this group are inputs to the service, outputs or both.
-     * If this group contains parameters with different direction, then this method returns {@code null}.
-     *
-     * @return indication if all parameters are inputs or outputs to the service, or {@code null} if undetermined.
-     */
-    @Override
-    public ParameterDirection getDirection() {
-        ParameterDirection dir = null;
-        for (final GeneralParameterDescriptor param : descriptors) {
-            final ParameterDirection c = param.getDirection();
-            if (c == null) {
-                return null;
-            }
-            if (c != dir) {
-                if (dir == null) {
-                    dir = c;
-                } else {
-                    return null;
-                }
-            }
-        }
-        return dir;
-    }
-
-    /**
      * Returns all parameters in this group.
      *
      * @return the parameter descriptors in this group.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
index 863eef0..4ec5e96 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
@@ -343,7 +343,7 @@
 
     /**
      * Filters names, aliases and identifiers by their code spaces. If the given array is non-null, then the only names,
-     * aliases and identifiers to be formatted are those having a {@link Identifier#getCodeSpace()},
+     * aliases and identifiers to be formatted are those having a {@link ReferenceIdentifier#getCodeSpace()},
      * {@link ScopedName#head()} or {@link GenericName#scope()} value in the given list, unless no name or alias
      * matches this criterion.
      *
@@ -841,7 +841,7 @@
             final Set<ReferenceIdentifier> identifiers = object.getIdentifiers();
             if (identifiers != null) {                                              // Paranoiac check.
                 Identifier identifier = null;
-                for (final Identifier candidate : identifiers) {
+                for (final ReferenceIdentifier candidate : identifiers) {
                     if (candidate != null) {                                        // Paranoiac check.
                         if (isPreferredCodespace(candidate.getCodeSpace())) {
                             identifier = candidate;
@@ -862,7 +862,7 @@
              * in the current row and clear the 'name' locale variable. Otherwise, keep the 'name'
              * locale variable in case we found no alias to format.
              */
-            Identifier name = object.getName();
+            ReferenceIdentifier name = object.getName();
             if (name != null) { // Paranoiac check.
                 final String codespace = name.getCodeSpace();
                 if (isPreferredCodespace(codespace)) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java
index 3b0e965..e755226 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterTableRow.java
@@ -34,6 +34,7 @@
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.io.wkt.Colors;
 import org.apache.sis.io.wkt.ElementKind;
 import org.apache.sis.util.Characters;
@@ -129,7 +130,7 @@
         values = new ArrayList<>(2);            // In the vast majority of cases, we will have only one value.
         units  = new ArrayList<>(2);
         identifiers = new LinkedHashMap<>();
-        Identifier name = object.getName();
+        ReferenceIdentifier name = object.getName();
         if (name != null) {                                             // Paranoiac check.
             final String codespace = name.getCodeSpace();
             if (preferredCodespaces == null || preferredCodespaces.contains(codespace)) {
@@ -172,9 +173,9 @@
          * Add identifiers (detailed mode only).
          */
         if (!isBrief) {
-            final Collection<? extends Identifier> ids = object.getIdentifiers();
+            final Collection<? extends ReferenceIdentifier> ids = object.getIdentifiers();
             if (ids != null) {                                          // Paranoiac check.
-                for (final Identifier id : ids) {
+                for (final ReferenceIdentifier id : ids) {
                     if (!isDeprecated(id)) {
                         final String codespace = id.getCodeSpace();
                         if (preferredCodespaces == null || preferredCodespaces.contains(codespace)) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
index 3496127..caff949 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
@@ -806,8 +806,8 @@
                     try {
                         target = destination.parameter(name);
                     } catch (ParameterNotFoundException cause) {
-                        throw new InvalidParameterNameException(Errors.format(
-                                    Errors.Keys.UnexpectedParameter_1, name), cause, name);
+                        throw (InvalidParameterNameException) new InvalidParameterNameException(Errors.format(
+                                    Errors.Keys.UnexpectedParameter_1, name), name).initCause(cause);
                     }
                 } else {
                     target = (ParameterValue<?>) getOrCreate(destination, name, occurrence);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
index fc4fbfc..fce229f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
@@ -701,7 +701,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link DefaultParameterDescriptorGroup#getName()}</td>
      *   </tr>
      *   <tr>
@@ -711,7 +711,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link DefaultParameterDescriptorGroup#getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -785,8 +785,8 @@
                     cause = e;
                 }
                 if (indices == null) {
-                    throw new InvalidParameterNameException(Errors.format(
-                                Errors.Keys.UnexpectedParameter_1, name), cause, name);
+                    throw (InvalidParameterNameException) new InvalidParameterNameException(Errors.format(
+                                Errors.Keys.UnexpectedParameter_1, name), name).initCause(cause);
                 }
                 matrix.setElement(indices[0], indices[1], ((ParameterValue<?>) param).doubleValue());
             }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
index 53c1524..c20bab5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
@@ -72,6 +72,7 @@
 
 // Branch-dependent imports
 import org.opengis.referencing.ReferenceIdentifier;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 
 
 /**
@@ -273,7 +274,7 @@
      *     <td>{@link NamedIdentifier#getVersion()} on the {@linkplain #getName() name}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link String}</td>
      *     <td>{@link NamedIdentifier#getDescription()} on the {@linkplain #getName() name}</td>
      *   </tr>
@@ -530,7 +531,14 @@
      */
     @XmlElement(name = "description")
     public InternationalString getDescription() {
-        return (name != null) ? name.getDescription() : null;
+        final ReferenceIdentifier name = getName();
+        if (name instanceof ImmutableIdentifier) {
+            return ((ImmutableIdentifier) name).getDescription();
+        }
+        if (name instanceof DefaultIdentifier) {
+            return ((DefaultIdentifier) name).getDescription();
+        }
+        return null;
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
index 1f3457a..f342934 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
@@ -24,7 +24,7 @@
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceSystem;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.extent.Extent;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ComparisonMode;
@@ -123,7 +123,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -133,7 +133,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
index a8617a1..bd1522e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
@@ -208,7 +208,7 @@
 
     /**
      * The codespace as a {@code NameSpace} object, or {@code null} if not yet created.
-     * This object is built from the {@value org.opengis.metadata.Identifier#CODESPACE_KEY} value when first needed.
+     * This object is built from the "codespace" value when first needed.
      */
     private transient NameSpace namespace;
 
@@ -404,7 +404,7 @@
      * @since 0.6
      */
     private String getCodeSpace() {
-        return (String) properties.get(Identifier.CODESPACE_KEY);
+        return (String) properties.get(ReferenceIdentifier.CODESPACE_KEY);
     }
 
     /**
@@ -436,7 +436,7 @@
      * @see ImmutableIdentifier#getCodeSpace()
      */
     public B setCodeSpace(final Citation authority, final String codespace) {
-        if (!setProperty(Identifier.CODESPACE_KEY, codespace)) {
+        if (!setProperty(ReferenceIdentifier.CODESPACE_KEY, codespace)) {
             namespace = null;
         }
         setProperty(Identifier.AUTHORITY_KEY, authority);
@@ -452,7 +452,7 @@
      * @since 0.6
      */
     private String getVersion() {
-        return (String) properties.get(Identifier.VERSION_KEY);
+        return (String) properties.get(ReferenceIdentifier.VERSION_KEY);
     }
 
     /**
@@ -473,7 +473,7 @@
      *         once since builder construction or since the last call to a {@code createXXX(…)} method.
      */
     public B setVersion(final String version) {
-        setProperty(Identifier.VERSION_KEY, version);
+        setProperty(ReferenceIdentifier.VERSION_KEY, version);
         return self();
     }
 
@@ -886,7 +886,7 @@
      * or {@code null} if none.
      */
     private InternationalString getDescription() {
-        return (InternationalString) properties.get(Identifier.DESCRIPTION_KEY);
+        return (InternationalString) properties.get("description");
     }
 
     /**
@@ -920,7 +920,7 @@
          * Convert to InternationalString now in order to share the same instance if
          * the same description is used both for an Identifier and an IdentifiedObject.
          */
-        properties.put(Identifier.DESCRIPTION_KEY, Types.toInternationalString(description));
+        properties.put("description", Types.toInternationalString(description));
         return self();
     }
 
@@ -1016,7 +1016,7 @@
         if (cleanup) {
             properties .put(IdentifiedObject.NAME_KEY, null);
             properties .remove(IdentifiedObject.REMARKS_KEY);
-            properties .remove(Identifier.DESCRIPTION_KEY);
+            properties .remove("description");
             properties .remove(AbstractIdentifiedObject.DEPRECATED_KEY);
             aliases    .clear();
             identifiers.clear();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
index 3b599c4..781e344 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
@@ -49,9 +49,7 @@
 import org.opengis.referencing.operation.OperationNotFoundException;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.extent.BoundingPolygon;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.metadata.extent.GeographicExtent;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.measure.Units;
@@ -90,9 +88,6 @@
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Static;
 
-// Branch-dependent imports
-import org.opengis.geometry.Geometry;
-
 
 /**
  * Static methods working on {@linkplain CoordinateReferenceSystem Coordinate Reference Systems}.
@@ -785,16 +780,6 @@
      * If non-null, then the returned envelope will use the same coordinate reference system them the given CRS
      * argument.
      *
-     * <p>This method looks in two places:</p>
-     * <ol>
-     *   <li>First, it checks the {@linkplain org.apache.sis.referencing.crs.AbstractCRS#getDomainOfValidity()
-     *       domain of validity} associated with the given CRS. Only geographic extents that are instances of
-     *       {@link BoundingPolygon} associated to the given CRS are taken in account for this first step.</li>
-     *   <li>If the above step did not found found any bounding polygon, then the
-     *       {@linkplain #getGeographicBoundingBox(CoordinateReferenceSystem) geographic bounding boxes}
-     *       are used as a fallback and tranformed to the given CRS.</li>
-     * </ol>
-     *
      * @param  crs  the coordinate reference system, or {@code null}.
      * @return the envelope with coordinates in the given CRS, or {@code null} if none.
      *
@@ -806,37 +791,7 @@
     public static Envelope getDomainOfValidity(final CoordinateReferenceSystem crs) {
         Envelope envelope = null;
         GeneralEnvelope merged = null;
-        if (crs != null) {
-            final Extent domainOfValidity = crs.getDomainOfValidity();
-            if (domainOfValidity != null) {
-                for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) {
-                    if (extent instanceof BoundingPolygon && !Boolean.FALSE.equals(extent.getInclusion())) {
-                        for (final Geometry geometry : ((BoundingPolygon) extent).getPolygons()) {
-                            final Envelope candidate = geometry.getEnvelope();
-                            if (candidate != null) {
-                                final CoordinateReferenceSystem sourceCRS = candidate.getCoordinateReferenceSystem();
-                                if (sourceCRS == null || Utilities.equalsIgnoreMetadata(sourceCRS, crs)) {
-                                    if (envelope == null) {
-                                        envelope = candidate;
-                                    } else {
-                                        if (merged == null) {
-                                            envelope = merged = new GeneralEnvelope(envelope);
-                                        }
-                                        merged.add(envelope);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        /*
-         * If no envelope was found, uses the geographic bounding box as a fallback. We will
-         * need to transform it from WGS84 to the supplied CRS. This step was not required in
-         * the previous block because the later selected only envelopes in the right CRS.
-         */
-        if (envelope == null) {
+        /* if (envelope == null) */ {   // Condition needed on other branches but not on trunk.
             final GeographicBoundingBox bounds = getGeographicBoundingBox(crs);
             if (bounds != null && !Boolean.FALSE.equals(bounds.getInclusion())) {
                 /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
index 17b943c..2f84a1a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
@@ -29,6 +29,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.ConcatenatedOperation;
@@ -722,11 +723,16 @@
         if (identifier == null) {
             return null;
         }
-        String cs = identifier.getCodeSpace();
+        String cs = null;
+        if (identifier instanceof ReferenceIdentifier) {
+            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
+        }
         if (cs == null || cs.isEmpty()) {
             cs = Identifiers.getIdentifier(identifier.getAuthority(), true);
         }
-        return NameMeaning.toURN(type, cs, identifier.getVersion(), identifier.getCode());
+        return NameMeaning.toURN(type, cs,
+                (identifier instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) identifier).getVersion() : null,
+                identifier.getCode());
     }
 
     /**
@@ -736,7 +742,7 @@
      * <ul>
      *   <li>If the given identifier implements the {@link GenericName} interface,
      *       then this method delegates to the {@link GenericName#toString()} method.</li>
-     *   <li>Otherwise if the given identifier has a {@linkplain Identifier#getCodeSpace() code space},
+     *   <li>Otherwise if the given identifier has a {@linkplain ReferenceIdentifier#getCodeSpace() code space},
      *       then formats the identifier as "{@code codespace:code}".</li>
      *   <li>Otherwise if the given identifier has an {@linkplain Identifier#getAuthority() authority},
      *       then formats the identifier as "{@code authority:code}".</li>
@@ -763,7 +769,10 @@
             return identifier.toString();
         }
         final String code = identifier.getCode();
-        String cs = identifier.getCodeSpace();
+        String cs = null;
+        if (identifier instanceof ReferenceIdentifier) {
+            cs = ((ReferenceIdentifier) identifier).getCodeSpace();
+        }
         if (cs == null || cs.isEmpty()) {
             cs = Citations.toCodeSpace(identifier.getAuthority());
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java
index 401ca16..f603083 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/ImmutableIdentifier.java
@@ -28,6 +28,7 @@
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.metadata.TitleProperty;
+import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.metadata.Identifiers;
 import org.apache.sis.internal.metadata.NameMeaning;
@@ -119,6 +120,12 @@
     private static final long serialVersionUID = 1804606250548055829L;
 
     /**
+     * Key for the {@value} property in the map to be given to the constructor.
+     * This can be used for setting the value to be returned by {@link #getDescription()}.
+     */
+    public static final String DESCRIPTION_KEY = "description";
+
+    /**
      * The person or party responsible for maintenance of the namespace, or {@code null} if not available.
      *
      * @see #getAuthority()
@@ -160,15 +167,19 @@
      *
      * @param identifier  the identifier to copy.
      *
-     * @see #castOrCopy(Identifier)
+     * @see #castOrCopy(ReferenceIdentifier)
      */
-    public ImmutableIdentifier(final Identifier identifier) {
+    public ImmutableIdentifier(final ReferenceIdentifier identifier) {
         ensureNonNull("identifier", identifier);
         code        = identifier.getCode();
         codeSpace   = identifier.getCodeSpace();
         authority   = identifier.getAuthority();
         version     = identifier.getVersion();
-        description = identifier.getDescription();
+        if (identifier instanceof DefaultIdentifier) {
+            description = ((DefaultIdentifier) identifier).getDescription();
+        } else {
+            description = null;
+        }
         validate(null);
     }
 
@@ -225,22 +236,22 @@
      *     <td>{@link #getCode()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#CODESPACE_KEY}</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getCodeSpace()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#AUTHORITY_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#AUTHORITY_KEY}</td>
      *     <td>{@link String} or {@link Citation}</td>
      *     <td>{@link #getAuthority()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+     *     <td>{@value org.opengis.referencing.ReferenceIdentifier#VERSION_KEY}</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getVersion()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>{@value #DESCRIPTION_KEY}</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -327,7 +338,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code ImmutableIdentifier}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code ImmutableIdentifier} instance is created using the
-     *       {@linkplain #ImmutableIdentifier(Identifier) copy constructor} and returned.
+     *       {@linkplain #ImmutableIdentifier(ReferenceIdentifier) copy constructor} and returned.
      *       Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -336,7 +347,7 @@
      * @return a SIS implementation containing the values of the given object (may be the
      *         given object itself), or {@code null} if the argument was null.
      */
-    public static ImmutableIdentifier castOrCopy(final Identifier object) {
+    public static ImmutableIdentifier castOrCopy(final ReferenceIdentifier object) {
         if (object == null || object instanceof ImmutableIdentifier) {
             return (ImmutableIdentifier) object;
         }
@@ -408,7 +419,6 @@
      *
      * @since 0.5
      */
-    @Override
     public InternationalString getDescription() {
         return description;
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
index 5a82be5..d54d386 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
@@ -94,7 +94,7 @@
  * @since 0.4
  * @module
  */
-public class NamedIdentifier extends ImmutableIdentifier implements GenericName, ReferenceIdentifier {
+public class NamedIdentifier extends ImmutableIdentifier implements GenericName {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -122,9 +122,9 @@
      *
      * @param  identifier  the identifier to copy.
      *
-     * @see #castOrCopy(Identifier)
+     * @see #castOrCopy(ReferenceIdentifier)
      */
-    public NamedIdentifier(final Identifier identifier) {
+    public NamedIdentifier(final ReferenceIdentifier identifier) {
         super(identifier);
         if (identifier instanceof GenericName) {
             name = (GenericName) identifier;
@@ -142,7 +142,7 @@
      * @see #castOrCopy(GenericName)
      */
     public NamedIdentifier(final GenericName name) {
-        super(name instanceof Identifier ? (Identifier) name : new NameToIdentifier(name));
+        super(name instanceof ReferenceIdentifier ? (ReferenceIdentifier) name : new NameToIdentifier(name));
         this.name = name;
         isNameSupplied = true;
     }
@@ -173,7 +173,7 @@
      *     <td>{@link #getCode()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+     *     <td>"codespace"</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getCodeSpace()}</td>
      *   </tr>
@@ -183,12 +183,12 @@
      *     <td>{@link #getAuthority()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+     *     <td>"version"</td>
      *     <td>{@link String}</td>
      *     <td>{@link #getVersion()}</td>
      *   </tr>
      *   <tr>
-     *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+     *     <td>"description"</td>
      *     <td>{@link String} or {@link InternationalString}</td>
      *     <td>{@link #getDescription()}</td>
      *   </tr>
@@ -340,7 +340,7 @@
      *   <li>Otherwise if the given object is already an instance of
      *       {@code NamedIdentifier}, then it is returned unchanged.</li>
      *   <li>Otherwise a new {@code NamedIdentifier} instance is created using the
-     *       {@linkplain #NamedIdentifier(Identifier) copy constructor} and returned.
+     *       {@linkplain #NamedIdentifier(ReferenceIdentifier) copy constructor} and returned.
      *       Note that this is a <cite>shallow</cite> copy operation, since the other
      *       metadata contained in the given object are not recursively copied.</li>
      * </ul>
@@ -351,7 +351,7 @@
      *
      * @since 1.0
      */
-    public static NamedIdentifier castOrCopy(final Identifier object) {
+    public static NamedIdentifier castOrCopy(final ReferenceIdentifier object) {
         if (object == null || object instanceof NamedIdentifier) {
             return (NamedIdentifier) object;
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
index 30c7b97..8d23613 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
@@ -140,7 +140,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -150,7 +150,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
index 2b57f12..2d3b33d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
@@ -35,7 +35,6 @@
 import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.TemporalCRS;
-import org.opengis.referencing.crs.ParametricCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
@@ -156,7 +155,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -166,7 +165,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
index 8c6a4e4..1ad71fb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultDerivedCRS.java
@@ -61,9 +61,8 @@
 import org.apache.sis.util.Classes;
 
 // Branch-dependent imports
-import org.opengis.referencing.cs.ParametricCS;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.opengis.referencing.datum.ParametricDatum;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
 
 /**
@@ -299,7 +298,7 @@
                 case WKTKeywords.GeodeticCRS:   return new Geodetic  (properties, (GeodeticCRS)   baseCRS, conversion,                derivedCS);
                 case WKTKeywords.VerticalCRS:   return new Vertical  (properties, (VerticalCRS)   baseCRS, conversion,   (VerticalCS) derivedCS);
                 case WKTKeywords.TimeCRS:       return new Temporal  (properties, (TemporalCRS)   baseCRS, conversion,       (TimeCS) derivedCS);
-                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, conversion, (ParametricCS) derivedCS);
+                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, conversion, (DefaultParametricCS) derivedCS);
                 case WKTKeywords.EngineeringCRS: {
                     /*
                      * This case may happen for baseCRS of kind GeodeticCRS, ProjectedCRS or EngineeringCRS.
@@ -354,7 +353,7 @@
                 case WKTKeywords.GeodeticCRS:   return new Geodetic  (properties, (GeodeticCRS)   baseCRS, interpolationCRS, method, baseToDerived,                derivedCS);
                 case WKTKeywords.VerticalCRS:   return new Vertical  (properties, (VerticalCRS)   baseCRS, interpolationCRS, method, baseToDerived,  (VerticalCS)  derivedCS);
                 case WKTKeywords.TimeCRS:       return new Temporal  (properties, (TemporalCRS)   baseCRS, interpolationCRS, method, baseToDerived,      (TimeCS)  derivedCS);
-                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, interpolationCRS, method, baseToDerived, (ParametricCS) derivedCS);
+                case WKTKeywords.ParametricCRS: return new Parametric(properties, (ParametricCRS) baseCRS, interpolationCRS, method, baseToDerived, (DefaultParametricCS) derivedCS);
                 case WKTKeywords.EngineeringCRS: {
                     if (baseCRS instanceof EngineeringCRS) {
                         // See the comment in create(Map, SingleCRS, Conversion, CoordinateSystem)
@@ -638,7 +637,7 @@
             return WKTKeywords.VerticalCRS;
         } else if (TemporalCRS.class.isAssignableFrom(type) && derivedCS instanceof TimeCS) {
             return WKTKeywords.TimeCRS;
-        } else if (ParametricCRS.class.isAssignableFrom(type) && derivedCS instanceof ParametricCS) {
+        } else if (ParametricCRS.class.isAssignableFrom(type) && derivedCS instanceof DefaultParametricCS) {
             return WKTKeywords.ParametricCRS;
         } else if (ProjectedCRS.class.isAssignableFrom(type) || EngineeringCRS.class.isAssignableFrom(type)) {
             return WKTKeywords.EngineeringCRS;
@@ -820,32 +819,32 @@
         }
 
         /** Creates a new parametric CRS from the given properties. */
-        Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, ParametricCS derivedCS) {
+        Parametric(Map<String,?> properties, ParametricCRS baseCRS, Conversion conversion, DefaultParametricCS derivedCS) {
             super(properties, baseCRS, conversion, derivedCS);
         }
 
         /** Creates a new parametric CRS from the given properties. */
         Parametric(Map<String,?> properties, ParametricCRS baseCRS, CoordinateReferenceSystem interpolationCRS,
-                OperationMethod method, MathTransform baseToDerived, ParametricCS derivedCS)
+                OperationMethod method, MathTransform baseToDerived, DefaultParametricCS derivedCS)
         {
             super(properties, baseCRS, interpolationCRS, method, baseToDerived, derivedCS);
         }
 
         /** Returns the datum of the base parametric CRS. */
-        @Override public ParametricDatum getDatum() {
-            return (ParametricDatum) super.getDatum();
+        @Override public DefaultParametricDatum getDatum() {
+            return (DefaultParametricDatum) super.getDatum();
         }
 
         /** Returns the coordinate system given at construction time. */
-        @Override public ParametricCS getCoordinateSystem() {
-            return (ParametricCS) super.getCoordinateSystem();
+        @Override public DefaultParametricCS getCoordinateSystem() {
+            return (DefaultParametricCS) super.getCoordinateSystem();
         }
 
         /** Returns a coordinate reference system of the same type than this CRS but with different axes. */
         @Override AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem derivedCS) {
             final Conversion conversionFromBase = getConversionFromBase();
             return new Parametric(properties, (ParametricCRS) conversionFromBase.getSourceCRS(),
-                    conversionFromBase, (ParametricCS) derivedCS);
+                    conversionFromBase, (DefaultParametricCS) derivedCS);
         }
 
         /** Returns the WKT keyword for this derived CRS type. */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
index 8d8965e..586b204 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultEngineeringCRS.java
@@ -104,7 +104,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -114,7 +114,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
index e9256b9..19e152a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeocentricCRS.java
@@ -109,7 +109,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -119,7 +119,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
index 44ce788..d3a1208 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultGeographicCRS.java
@@ -20,7 +20,7 @@
 import java.util.HashMap;
 import java.util.Arrays;
 import javax.xml.bind.annotation.XmlTransient;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
@@ -123,7 +123,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -133,7 +133,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -258,7 +258,7 @@
         if (axis.getMinimumValue() == Longitude.MIN_VALUE &&
             axis.getMaximumValue() == Longitude.MAX_VALUE) // For excluding the AxesConvention.POSITIVE_RANGE case.
         {
-            for (final Identifier identifier : super.getIdentifiers()) {
+            for (final ReferenceIdentifier identifier : super.getIdentifiers()) {
                 if (EPSG.equals(identifier.getCodeSpace())) try {
                     final int i = Arrays.binarySearch(EPSG_CODES, Short.parseShort(identifier.getCode()));
                     if (i >= 0) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java
index 5931780..d699626 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultImageCRS.java
@@ -97,7 +97,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -107,7 +107,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java
index 8e83767..70e3e9a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultParametricCRS.java
@@ -30,9 +30,8 @@
 import static org.apache.sis.referencing.crs.AbstractCRS.isBaseCRS;
 
 // Branch-dependent imports
-import org.opengis.referencing.cs.ParametricCS;
-import org.opengis.referencing.crs.ParametricCRS;
-import org.opengis.referencing.datum.ParametricDatum;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
 
 
 /**
@@ -76,11 +75,11 @@
      * The datum.
      *
      * <p><b>Consider this field as final!</b>
-     * This field is modified only at unmarshalling time by {@link #setDatum(ParametricDatum)}</p>
+     * This field is modified only at unmarshalling time by {@code setDatum(ParametricDatum)}</p>
      *
      * @see #getDatum()
      */
-    private ParametricDatum datum;
+    private DefaultParametricDatum datum;
 
     /**
      * Creates a coordinate reference system from the given properties, datum and coordinate system.
@@ -127,15 +126,17 @@
      *   </tr>
      * </table>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter types may be changed to
+     * {@code org.opengis.referencing.datum.ParametricDatum} and {@code org.opengis.referencing.cs.ParametricCS}
+     * Those change are pending GeoAPI revision.</div>
+     *
      * @param  properties  the properties to be given to the coordinate reference system.
      * @param  datum       the datum.
      * @param  cs          the coordinate system.
-     *
-     * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createParametricCRS(Map, ParametricDatum, ParametricCS)
      */
     public DefaultParametricCRS(final Map<String,?> properties,
-                                final ParametricDatum datum,
-                                final ParametricCS    cs)
+                                final DefaultParametricDatum datum,
+                                final DefaultParametricCS cs)
     {
         super(properties, cs);
         ensureNonNull("datum", datum);
@@ -149,54 +150,24 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
-     * @param  crs  the coordinate reference system to copy.
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div>
      *
-     * @see #castOrCopy(ParametricCRS)
+     * @param  crs  the coordinate reference system to copy.
      */
-    protected DefaultParametricCRS(final ParametricCRS crs) {
+    protected DefaultParametricCRS(final DefaultParametricCRS crs) {
         super(crs);
         datum = crs.getDatum();
     }
 
     /**
-     * Returns a SIS coordinate reference system implementation with the same values than the given
-     * arbitrary implementation. If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricCRS castOrCopy(final ParametricCRS object) {
-        return (object == null || object instanceof DefaultParametricCRS)
-                ? (DefaultParametricCRS) object : new DefaultParametricCRS(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricCRS.class}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ParametricCRS}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
-     * own set of interfaces.</div>
-     *
-     * @return {@code ParametricCRS.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricCRS> getInterface() {
-        return ParametricCRS.class;
-    }
-
-    /**
      * Returns the datum.
      *
      * @return the datum.
      */
     @Override
     @XmlElement(name = "parametricDatum", required = true)
-    public ParametricDatum getDatum() {
+    public DefaultParametricDatum getDatum() {
         return datum;
     }
 
@@ -207,8 +178,8 @@
      */
     @Override
     @XmlElement(name = "parametricCS", required = true)
-    public ParametricCS getCoordinateSystem() {
-        return (ParametricCS) super.getCoordinateSystem();
+    public DefaultParametricCS getCoordinateSystem() {
+        return (DefaultParametricCS) super.getCoordinateSystem();
     }
 
     /**
@@ -226,7 +197,7 @@
      */
     @Override
     final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
-        return new DefaultParametricCRS(properties, datum, (ParametricCS) cs);
+        return new DefaultParametricCRS(properties, datum, (DefaultParametricCS) cs);
     }
 
     /**
@@ -281,7 +252,7 @@
      *
      * @see #getDatum()
      */
-    private void setDatum(final ParametricDatum value) {
+    private void setDatum(final DefaultParametricDatum value) {
         if (datum == null) {
             datum = value;
         } else {
@@ -294,7 +265,7 @@
      *
      * @see #getCoordinateSystem()
      */
-    private void setCoordinateSystem(final ParametricCS cs) {
+    private void setCoordinateSystem(final DefaultParametricCS cs) {
         setCoordinateSystem("parametricCS", cs);
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index fc4b07a..c15a6f9 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -126,7 +126,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -136,7 +136,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
index 159aa74..8d6a65b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultVerticalCRS.java
@@ -95,7 +95,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -105,7 +105,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/ParametricCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/ParametricCRS.java
new file mode 100644
index 0000000..b1adba1
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/ParametricCRS.java
@@ -0,0 +1,31 @@
+/*
+ * 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.referencing.crs;
+
+import org.opengis.referencing.crs.SingleCRS;
+
+
+/**
+ * Place-holder for an interface not yet present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+interface ParametricCRS extends SingleCRS {
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
index 15f95b4..ee58ac3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
@@ -141,7 +141,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -151,7 +151,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java
index 1d632bc..b163e6a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultAffineCS.java
@@ -86,7 +86,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -96,7 +96,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
index 08259fe..4b347f2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
@@ -95,7 +95,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -105,7 +105,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java
index f5e023e..67b3aad 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCompoundCS.java
@@ -82,7 +82,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -92,7 +92,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
index 10a2664..3ed7b7e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
@@ -259,7 +259,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -269,7 +269,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
index f753a20..f0d4009 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCylindricalCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
index 5982a82..1f615a3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
index 9833869..e3115dc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultLinearCS.java
@@ -85,7 +85,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -95,7 +95,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java
index 6b24b18..dca9747 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultParametricCS.java
@@ -21,9 +21,6 @@
 import javax.xml.bind.annotation.XmlType;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 
-// Branch-dependent imports
-import org.opengis.referencing.cs.ParametricCS;
-
 
 /**
  * A 1-dimensional coordinate system for parametric values or functions.
@@ -56,7 +53,7 @@
  */
 @XmlType(name = "ParametricCSType")
 @XmlRootElement(name = "ParametricCS")
-public class DefaultParametricCS extends AbstractCS implements ParametricCS {
+public class DefaultParametricCS extends AbstractCS {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -122,46 +119,16 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  cs  the coordinate system to copy.
-     *
-     * @see #castOrCopy(ParametricCS)
      */
-    protected DefaultParametricCS(final ParametricCS cs) {
+    protected DefaultParametricCS(final DefaultParametricCS cs) {
         super(cs);
     }
 
     /**
-     * Returns a SIS coordinate system implementation with the same values than the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricCS castOrCopy(final ParametricCS object) {
-        return (object == null) || (object instanceof DefaultParametricCS)
-                ? (DefaultParametricCS) object : new DefaultParametricCS(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricCS.class}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code ParametricCS}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with
-     * their own set of interfaces.</div>
-     *
-     * @return {@code ParametricCS.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricCS> getInterface() {
-        return ParametricCS.class;
-    }
-
-    /**
      * {@inheritDoc}
      *
      * @return {@inheritDoc}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java
index f4557d4..5012d50 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultPolarCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java
index 59b3997..0a11fd6 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultSphericalCS.java
@@ -91,7 +91,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -101,7 +101,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java
index e0baaee..0963d7a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultTimeCS.java
@@ -88,7 +88,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -98,7 +98,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
index ae8b520..f6fc9a1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultUserDefinedCS.java
@@ -79,7 +79,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -89,7 +89,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java
index 6366d1e..f80ec96 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultVerticalCS.java
@@ -99,7 +99,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -109,7 +109,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
+     *     <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
index 9536826..3629fc4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
@@ -25,10 +25,10 @@
 import javax.xml.bind.annotation.XmlSeeAlso;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.datum.Datum;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.iso.Types;
@@ -174,7 +174,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -184,7 +184,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
index e272c20..4e6031c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
@@ -27,8 +27,8 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.referencing.datum.Ellipsoid;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.internal.jaxb.gml.Measure;
@@ -182,7 +182,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -192,7 +192,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
index bddab2c..464b0e7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEngineeringDatum.java
@@ -21,7 +21,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.EngineeringDatum;
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
@@ -69,7 +69,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -79,7 +79,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
index 47cf530..7c6f601 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultGeodeticDatum.java
@@ -25,8 +25,8 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.PrimeMeridian;
@@ -202,7 +202,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -212,7 +212,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java
index ddae660..e46bb6e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultImageDatum.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.ImageDatum;
 import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.internal.referencing.WKTKeywords;
@@ -86,7 +86,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -96,7 +96,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java
index c9c9200..7bddff4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultParametricDatum.java
@@ -23,9 +23,6 @@
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.io.wkt.Formatter;
 
-// Branch-dependent imports
-import org.opengis.referencing.datum.ParametricDatum;
-
 
 /**
  * Defines the origin of a parametric coordinate reference system.
@@ -36,7 +33,7 @@
  *
  * <ol>
  *   <li>Create a {@code ParametricDatum} from an identifier in a database by invoking
- *       {@link org.opengis.referencing.datum.DatumAuthorityFactory#createParametricDatum(String)}.</li>
+ *       {@code DatumAuthorityFactory.createParametricDatum(String)}.</li>
  *   <li>Create a {@code ParametricDatum} by invoking the {@code DatumFactory.createParametricDatum(…)} method,
  *       (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li>
  *   <li>Create a {@code DefaultParametricDatum} by invoking the
@@ -60,7 +57,7 @@
  */
 @XmlType(name = "ParametricDatumType")
 @XmlRootElement(name = "ParametricDatum")
-public class DefaultParametricDatum extends AbstractDatum implements ParametricDatum {
+public class DefaultParametricDatum extends AbstractDatum {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -135,46 +132,16 @@
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  datum  the datum to copy.
-     *
-     * @see #castOrCopy(ParametricDatum)
      */
-    protected DefaultParametricDatum(final ParametricDatum datum) {
+    protected DefaultParametricDatum(final DefaultParametricDatum datum) {
         super(datum);
     }
 
     /**
-     * Returns a SIS datum implementation with the same values than the given arbitrary implementation.
-     * If the given object is {@code null}, then this method returns {@code null}.
-     * Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
-     * Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
-     *
-     * @param  object  the object to get as a SIS implementation, or {@code null} if none.
-     * @return a SIS implementation containing the values of the given object (may be the
-     *         given object itself), or {@code null} if the argument was null.
-     */
-    public static DefaultParametricDatum castOrCopy(final ParametricDatum object) {
-        return (object == null) || (object instanceof DefaultParametricDatum) ?
-                (DefaultParametricDatum) object : new DefaultParametricDatum(object);
-    }
-
-    /**
-     * Returns the GeoAPI interface implemented by this class.
-     * The SIS implementation returns {@code ParametricDatum.class}.
-     *
-     * <div class="note"><b>Note for implementers:</b>
-     * Subclasses usually do not need to override this method since GeoAPI does not define {@code TemporalDatum}
-     * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
-     * own set of interfaces.</div>
-     *
-     * @return {@code ParametricDatum.class} or a user-defined sub-interface.
-     */
-    @Override
-    public Class<? extends ParametricDatum> getInterface() {
-        return ParametricDatum.class;
-    }
-
-    /**
      * Formats this datum as a <cite>Well Known Text</cite> {@code ParametricDatum[…]} element.
      *
      * <div class="note"><b>Compatibility note:</b>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
index ef2decc..873f540 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
@@ -25,7 +25,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.PrimeMeridian;
 import org.opengis.referencing.crs.GeneralDerivedCRS;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
@@ -128,7 +128,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -138,7 +138,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
index 9e7f2ce..ac8a5e8 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
@@ -26,7 +26,7 @@
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.TemporalDatum;
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.internal.jaxb.gml.UniversalTimeAdapter;
@@ -112,7 +112,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -122,7 +122,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
index 355b4ff..237c68d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultVerticalDatum.java
@@ -23,7 +23,7 @@
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.VerticalDatumType;
 import org.apache.sis.io.wkt.Formatter;
@@ -113,7 +113,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
-     *     <td>{@link Identifier} or {@link String}</td>
+     *     <td>{@link ReferenceIdentifier} or {@link String}</td>
      *     <td>{@link #getName()}</td>
      *   </tr>
      *   <tr>
@@ -123,7 +123,7 @@
      *   </tr>
      *   <tr>
      *     <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
-     *     <td>{@link Identifier} (optionally as array)</td>
+     *     <td>{@link ReferenceIdentifier} (optionally as array)</td>
      *     <td>{@link #getIdentifiers()}</td>
      *   </tr>
      *   <tr>
@@ -228,7 +228,7 @@
     private VerticalDatumType type() {
         VerticalDatumType t = type;
         if (t == null) {
-            final Identifier name = super.getName();
+            final ReferenceIdentifier name = super.getName();
             type = t = VerticalDatumTypes.guess(name != null ? name.getCode() : null, super.getAlias(), null);
         }
         return t;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index 4f272cc..ce78d94 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -40,6 +40,11 @@
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Classes;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * Creates geodetic objects from codes defined by an authority.
@@ -405,6 +410,9 @@
      * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)} and casts the result.
      * If the result can not be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.crs.ParametricCRS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the coordinate reference system for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -412,8 +420,8 @@
      *
      * @see org.apache.sis.referencing.crs.DefaultParametricCRS
      */
-    public ParametricCRS createParametricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricCRS.class, createCoordinateReferenceSystem(code), code);
+    public DefaultParametricCRS createParametricCRS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricCRS.class, createCoordinateReferenceSystem(code), code);
     }
 
     /**
@@ -645,6 +653,9 @@
      * The default implementation delegates to {@link #createDatum(String)} and casts the result.
      * If the result can not be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the datum for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -652,8 +663,8 @@
      *
      * @see org.apache.sis.referencing.datum.DefaultParametricDatum
      */
-    public ParametricDatum createParametricDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricDatum.class, createDatum(code), code);
+    public DefaultParametricDatum createParametricDatum(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricDatum.class, createDatum(code), code);
     }
 
     /**
@@ -931,6 +942,9 @@
      * The default implementation delegates to {@link #createCoordinateSystem(String)} and casts the result.
      * If the result can not be casted, then a {@link NoSuchAuthorityCodeException} is thrown.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  code  value allocated by authority.
      * @return the coordinate system for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
@@ -938,8 +952,8 @@
      *
      * @see org.apache.sis.referencing.cs.DefaultParametricCS
      */
-    public ParametricCS createParametricCS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
-        return cast(ParametricCS.class, createCoordinateSystem(code), code);
+    public DefaultParametricCS createParametricCS(final String code) throws NoSuchAuthorityCodeException, FactoryException {
+        return cast(DefaultParametricCS.class, createCoordinateSystem(code), code);
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
index 33250aa..163a2e9 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
@@ -118,17 +118,17 @@
  *     <td>{@link NamedIdentifier#getCode()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
  *   <tr>
- *     <td>{@value org.opengis.metadata.Identifier#CODESPACE_KEY}</td>
+ *     <td>"codespace"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getCodeSpace()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
  *   <tr>
- *     <td>{@value org.opengis.metadata.Identifier#VERSION_KEY}</td>
+ *     <td>"version"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getVersion()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
  *   <tr>
- *     <td>{@value org.opengis.metadata.Identifier#DESCRIPTION_KEY}</td>
+ *     <td>"description"</td>
  *     <td>{@link String}</td>
  *     <td>{@link NamedIdentifier#getDescription()} on the {@linkplain AbstractIdentifiedObject#getName() name}</td>
  *   </tr>
@@ -1053,17 +1053,21 @@
      *
      * The default implementation creates a {@link DefaultParametricCRS} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the parameter types may be changed to
+     * {@code org.opengis.referencing.datum.ParametricDatum} and {@code org.opengis.referencing.cs.ParametricCS},
+     * and the return type may be changed to {@code org.opengis.referencing.crs.ParametricCRS}.
+     * Those change are pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @param  datum       the parametric datum to use in created CRS.
      * @param  cs          the parametric coordinate system for the created CRS.
      * @throws FactoryException if the object creation failed.
      *
-     * @see DefaultParametricCRS#DefaultParametricCRS(Map, ParametricDatum, ParametricCS)
+     * @see DefaultParametricCRS#DefaultParametricCRS(Map, DefaultParametricDatum, DefaultParametricCS)
      * @see GeodeticAuthorityFactory#createParametricCRS(String)
      */
-    @Override
-    public ParametricCRS createParametricCRS(final Map<String,?> properties,
-            final ParametricDatum datum, final ParametricCS cs) throws FactoryException
+    public DefaultParametricCRS createParametricCRS(final Map<String,?> properties,
+            final DefaultParametricDatum datum, final DefaultParametricCS cs) throws FactoryException
     {
         final DefaultParametricCRS crs;
         try {
@@ -1078,14 +1082,16 @@
      * Creates a parametric datum.
      * The default implementation creates a {@link DefaultParametricDatum} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.datum.ParametricDatum}. This change is pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultParametricDatum#DefaultParametricDatum(Map)
      * @see GeodeticAuthorityFactory#createParametricDatum(String)
      */
-    @Override
-    public ParametricDatum createParametricDatum(final Map<String,?> properties)
+    public DefaultParametricDatum createParametricDatum(final Map<String,?> properties)
             throws FactoryException
     {
         final DefaultParametricDatum datum;
@@ -1109,6 +1115,9 @@
      *
      * The default implementation creates a {@link DefaultParametricCS} instance.
      *
+     * <div class="warning"><b>Warning:</b> in a future SIS version, the return type may be changed
+     * to {@code org.opengis.referencing.cs.ParametricCS}. This change is pending GeoAPI revision.</div>
+     *
      * @param  properties  name and other properties to give to the new object.
      * @param  axis        the single axis.
      * @throws FactoryException if the object creation failed.
@@ -1116,8 +1125,7 @@
      * @see DefaultParametricCS#DefaultParametricCS(Map, CoordinateSystemAxis)
      * @see GeodeticAuthorityFactory#createParametricCS(String)
      */
-    @Override
-    public ParametricCS createParametricCS(Map<String, ?> properties, CoordinateSystemAxis axis) throws FactoryException {
+    public DefaultParametricCS createParametricCS(Map<String, ?> properties, CoordinateSystemAxis axis) throws FactoryException {
         final DefaultParametricCS cs;
         try {
             cs = new DefaultParametricCS(complete(properties), axis);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 7d6d56e..c7cdea5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -52,7 +52,6 @@
 import org.opengis.util.InternationalString;
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
-import org.opengis.metadata.Identifier;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.OnLineFunction;
@@ -79,11 +78,11 @@
 import org.apache.sis.internal.referencing.SignReversalComment;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.referencing.ServicesForMetadata;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.system.Semaphores;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
@@ -120,6 +119,11 @@
 import static org.apache.sis.internal.util.StandardDateFormat.UTC;
 import static org.apache.sis.internal.referencing.ServicesForMetadata.CONNECTION;
 
+// Branch-dependent imports
+import org.apache.sis.internal.util.StandardDateFormat;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * <cite>Data Access Object</cite> (DAO) creating geodetic objects from a JDBC connection to an EPSG database.
@@ -1558,10 +1562,10 @@
                      *   PARAMETRIC CRS
                      * ---------------------------------------------------------------------- */
                     case "parametric": {
-                        final ParametricCS    cs    = owner.createParametricCS   (getString(code, result, 8));
-                        final ParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
-                        crs = crsFactory.createParametricCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), datum, cs);
+                        final DefaultParametricCS    cs    = owner.createParametricCS   (getString(code, result, 8));
+                        final DefaultParametricDatum datum = owner.createParametricDatum(getString(code, result, 9));
+                        crs = ServicesForMetadata.createParametricCRS(createProperties("Coordinate Reference System",
+                                name, epsg, area, scope, remarks, deprecated), datum, cs, crsFactory);
                         break;
                     }
                     /* ----------------------------------------------------------------------
@@ -1729,7 +1733,7 @@
                         break;
                     }
                     case "parametric": {
-                        datum = datumFactory.createParametricDatum(properties);
+                        datum = ServicesForMetadata.createParametricDatum(properties, datumFactory);
                         break;
                     }
                     default: {
@@ -2187,7 +2191,7 @@
                     }
                     case WKTKeywords.parametric: {
                         switch (dimension) {
-                            case 1: cs = csFactory.createParametricCS(properties, axes[0]); break;
+                            case 1: cs = ServicesForMetadata.createParametricCS(properties, axes[0], csFactory); break;
                         }
                         break;
                     }
@@ -2600,7 +2604,7 @@
                 }
                 final Map<String, Object> properties =
                         createProperties("Coordinate_Operation Parameter", name, epsg, isReversible, deprecated);
-                properties.put(Identifier.DESCRIPTION_KEY, description);
+                properties.put(ImmutableIdentifier.DESCRIPTION_KEY, description);
                 final ParameterDescriptor<?> descriptor = new DefaultParameterDescriptor<>(properties,
                         1, 1, type, valueDomain, null, null);
                 returnValue = ensureSingleton(descriptor, returnValue, code);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
index daa232b..585755f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -25,6 +25,11 @@
 import org.apache.sis.internal.referencing.WKTKeywords;
 import org.apache.sis.util.CharSequences;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+import org.apache.sis.referencing.cs.DefaultParametricCS;
+import org.apache.sis.referencing.datum.DefaultParametricDatum;
+
 
 /**
  * Information about a specific table. The MS-Access dialect of SQL is assumed;
@@ -72,7 +77,7 @@
                 "COORD_REF_SYS_KIND",
                 new Class<?>[] { ProjectedCRS.class,   GeographicCRS.class,   GeocentricCRS.class,
                                  VerticalCRS.class,    CompoundCRS.class,     EngineeringCRS.class,
-                                 DerivedCRS.class,     TemporalCRS.class,     ParametricCRS.class},     // See comment below
+                                 DerivedCRS.class,     TemporalCRS.class,     DefaultParametricCRS.class},     // See comment below
                 new String[]   {"projected",          "geographic",          "geocentric",
                                 "vertical",           "compound",            "engineering",
                                 "derived",            "temporal",            "parametric"},             // See comment below
@@ -91,7 +96,7 @@
                 "COORD_SYS_TYPE",
                 new Class<?>[] {CartesianCS.class,      EllipsoidalCS.class,      VerticalCS.class,      LinearCS.class,
                                 SphericalCS.class,      PolarCS.class,            CylindricalCS.class,
-                                TimeCS.class,           ParametricCS.class,       AffineCS.class},
+                                TimeCS.class,           DefaultParametricCS.class, AffineCS.class},
                 new String[]   {WKTKeywords.Cartesian,  WKTKeywords.ellipsoidal,  WKTKeywords.vertical,  WKTKeywords.linear,
                                 WKTKeywords.spherical,  WKTKeywords.polar,        WKTKeywords.cylindrical,
                                 WKTKeywords.temporal,   WKTKeywords.parametric,   WKTKeywords.affine},      // Same comment than in the CRS case above.
@@ -110,7 +115,7 @@
                 "DATUM_NAME",
                 "DATUM_TYPE",
                 new Class<?>[] { GeodeticDatum.class,  VerticalDatum.class,   EngineeringDatum.class,
-                                 TemporalDatum.class,  ParametricDatum.class},
+                                 TemporalDatum.class,  DefaultParametricDatum.class},
                 new String[]   {"geodetic",           "vertical",            "engineering",
                                 "temporal",           "parametric"},         // Same comment than in the CRS case above.
                 null),
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 43f8db0..afb6d9c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -799,7 +799,7 @@
                     method = DefaultOperationMethod.redimension(method, sourceDimensions, targetDimensions);
                 } catch (IllegalArgumentException ex) {
                     try {
-                        method = factory.getOperationMethod(method.getName().getCode());
+                        method = factorySIS.getOperationMethod(method.getName().getCode());
                         method = DefaultOperationMethod.redimension(method, sourceDimensions, targetDimensions);
                     } catch (NoSuchIdentifierException | IllegalArgumentException se) {
                         ex.addSuppressed(se);
@@ -1145,10 +1145,10 @@
                 if (descriptor != null) {
                     final Identifier name = descriptor.getName();
                     if (name != null) {
-                        method = factory.getOperationMethod(name.getCode());
+                        method = factorySIS.getOperationMethod(name.getCode());
                     }
                     if (method == null) {
-                        method = factory.createOperationMethod(properties,
+                        method = factorySIS.createOperationMethod(properties,
                                 sourceCRS.getCoordinateSystem().getDimension(),
                                 targetCRS.getCoordinateSystem().getDimension(),
                                 descriptor);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index 128a808..45af728 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -303,7 +303,6 @@
      *
      * @see DefaultMathTransformFactory#getOperationMethod(String)
      */
-    @Override
     public OperationMethod getOperationMethod(String name) throws FactoryException {
         name = CharSequences.trimWhitespaces(name);
         ArgumentChecks.ensureNonEmpty("name", name);
@@ -367,7 +366,6 @@
      *
      * @see DefaultOperationMethod#DefaultOperationMethod(Map, Integer, Integer, ParameterDescriptorGroup)
      */
-    @Override
     public OperationMethod createOperationMethod(final Map<String,?> properties,
             final Integer sourceDimensions, final Integer targetDimensions,
             ParameterDescriptorGroup parameters) throws FactoryException
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java
index bb46bf9..23c2441 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MismatchedDatumException.java
@@ -36,7 +36,6 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.6
  *
- * @see org.opengis.geometry.MismatchedReferenceSystemException
  * @see org.opengis.geometry.MismatchedDimensionException
  * @see org.apache.sis.referencing.operation.matrix.MismatchedMatrixSizeException
  *
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java
index 2576836..4aecf45 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/SubOperationInfo.java
@@ -26,6 +26,9 @@
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.crs.DefaultParametricCRS;
+
 
 /**
  * Information about the relationship between a source component and a target component
@@ -54,7 +57,7 @@
         {GeodeticCRS.class},
         {VerticalCRS.class, GeodeticCRS.class},
         {TemporalCRS.class},
-        {ParametricCRS.class},
+        {DefaultParametricCRS.class},
         {EngineeringCRS.class},
         {ImageCRS.class}
     };
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
index b0bf6e2..756d8d5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -320,7 +320,7 @@
      * </ul>
      *
      * This method ignores the {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS}, which may be null.
-     * Actually this method is used more often for {@linkplain org.opengis.coverage.grid.GridEnvelope grid envelopes}
+     * Actually this method is used more often for grid envelopes
      * (which have no CRS) than geodetic envelopes.
      *
      * <h4>Spanning the anti-meridian of a Geographic CRS</h4>
@@ -461,7 +461,7 @@
      * </ul>
      *
      * This method ignores the {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS}, which may be null.
-     * Actually this method is used more often for {@linkplain org.opengis.coverage.grid.GridEnvelope grid envelopes}
+     * Actually this method is used more often for grid envelopes
      * (which have no CRS) than geodetic envelopes.
      *
      * <h4>Spanning the anti-meridian of a Geographic CRS</h4>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
index f7cdf26..610e3f4 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MismatchedMatrixSizeException.java
@@ -30,7 +30,6 @@
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.4
  *
- * @see org.opengis.geometry.MismatchedReferenceSystemException
  * @see org.apache.sis.referencing.operation.MismatchedDatumException
  *
  * @since 0.4
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index b6e2a0d..5d127cc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -22,7 +22,6 @@
 import java.util.Objects;
 import java.io.Serializable;
 import java.lang.reflect.Modifier;
-import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
@@ -61,6 +60,7 @@
 
 import static java.lang.Math.*;
 
+import org.opengis.referencing.ReferenceIdentifier;
 
 /**
  * Base class for conversion services between ellipsoidal and cartographic projections.
@@ -474,7 +474,7 @@
      */
     static boolean identMatch(final OperationMethod method, final String regex, final String identifier) {
         if (identifier != null) {
-            for (final Identifier id : method.getIdentifiers()) {
+            for (final ReferenceIdentifier id : method.getIdentifiers()) {
                 if (Constants.EPSG.equals(id.getCodeSpace())) {
                     return identifier.equals(id.getCode());
                 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
index b7e86e3..cfc7ecd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
@@ -1368,7 +1368,7 @@
 
     /**
      * Creates a math transform object from a
-     * <a href="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
+     * <a href="http://www.geoapi.org/3.0/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
      * Known Text</cite> (WKT)</a>.
      * If the given text contains non-fatal anomalies (unknown or unsupported WKT elements,
      * inconsistent unit definitions, <i>etc.</i>), warnings may be reported in a
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
index 7379d6c..1a23ff2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransforms.java
@@ -275,8 +275,6 @@
      * @param  values    the output values (<var>y</var>) in the function range, or {@code null}.
      * @return the <i>y=f(x)</i> function.
      *
-     * @see org.opengis.coverage.InterpolationMethod
-     *
      * @since 0.7
      */
     public static MathTransform1D interpolate(final double[] preimage, final double[] values) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
index 2d7522b..99110de 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/SpecializableTransform.java
@@ -25,7 +25,7 @@
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.geometry.MismatchedReferenceSystemException;
+import org.apache.sis.geometry.MismatchedReferenceSystemException;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
index b6a7700..8c79280 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
@@ -93,7 +93,7 @@
             }
             default: throw new IllegalArgumentException(String.valueOf(type));
         }
-        if (type != RECTANGLE) {
+        if (PENDING_NEXT_GEOAPI_RELEASE) {
             validate(envelope);
         }
         return envelope;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
index 3892e1b..0ecb54e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
@@ -55,7 +55,7 @@
      * This is set to {@code true} only when we intentionally want to create an invalid envelope,
      * for example in order to test normalization.
      */
-    boolean skipValidation;
+    boolean skipValidation = !PENDING_NEXT_GEOAPI_RELEASE;
 
     /**
      * Creates a new geographic envelope for the given coordinate values.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java
index cec3e88..bbec651 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CC_OperationParameterGroupTest.java
@@ -198,7 +198,6 @@
                                              final ParameterDescriptor<?> actual)
     {
         assertEquals("name",          expected.getName(),         actual.getName());
-        assertEquals("description",   expected.getDescription(),  actual.getDescription());
         assertEquals("valueClass",    expected.getValueClass(),   actual.getValueClass());
         assertEquals("validValues",   expected.getValidValues(),  actual.getValidValues());
         assertEquals("unit",          expected.getUnit(),         actual.getUnit());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java
index 79b2b2a..3386220 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/jaxb/referencing/CodeTest.java
@@ -18,7 +18,7 @@
 
 import java.util.Collections;
 import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.simple.SimpleCitation;
 import org.apache.sis.referencing.ImmutableIdentifier;
@@ -41,15 +41,15 @@
  */
 public final strictfp class CodeTest extends TestCase {
     /**
-     * Tests the {@link Code#Code(Identifier)} constructor with {@code "EPSG:4326"} identifier.
+     * Tests the {@link Code#Code(ReferenceIdentifier)} constructor with {@code "EPSG:4326"} identifier.
      * This test intentionally uses an identifier with the {@code IOGP} authority instead than
      * EPSG in order to make sure that the {@code codeSpace} attribute is set from
-     * {@link Identifier#getCodeSpace()}, not from {@link Identifier#getAuthority()}.
+     * {@code Identifier.getCodeSpace()}, not from {@code Identifier.getAuthority()}.
      */
     @Test
     public void testSimple() {
         final SimpleCitation IOGP = new SimpleCitation("IOGP");
-        final Identifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326");  // See above javadoc.
+        final ReferenceIdentifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326");  // See above javadoc.
         final Code value = new Code(id);
         assertEquals("codeSpace", "EPSG", value.codeSpace);
         assertEquals("code",      "4326", value.code);
@@ -57,7 +57,7 @@
          * Reverse operation. Note that the authority is lost since there is no room for that in a
          * <gml:identifier> element. Current implementation sets the authority to the code space.
          */
-        final Identifier actual = value.getIdentifier();
+        final ReferenceIdentifier actual = value.getIdentifier();
         assertSame  ("authority",  Citations.EPSG, actual.getAuthority());
         assertEquals("codeSpace", "EPSG", actual.getCodeSpace());
         assertNull  ("version",           actual.getVersion());
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Tests the {@link Code#Code(Identifier)} constructor with {@code "EPSG:8.3:4326"} identifier.
+     * Tests the {@link Code#Code(ReferenceIdentifier)} constructor with {@code "EPSG:8.3:4326"} identifier.
      * This test intentionally uses an identifier with the {@code IOGP} authority instead than EPSG
      * for the same reason than {@link #testSimple()}.
      */
@@ -73,7 +73,7 @@
     @DependsOnMethod("testSimple")
     public void testWithVersion() {
         final SimpleCitation IOGP = new SimpleCitation("IOGP");
-        final Identifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326", "8.2", null);  // See above javadoc.
+        final ReferenceIdentifier id = new ImmutableIdentifier(IOGP, "EPSG", "4326", "8.2", null);  // See above javadoc.
         final Code value = new Code(id);
         assertEquals("codeSpace", "EPSG:8.2", value.codeSpace);
         assertEquals("code",      "4326",     value.code);
@@ -81,7 +81,7 @@
          * Reverse operation. Note that the authority is lost since there is no room for that in a
          * <gml:identifier> element. Current implementation sets the authority to the code space.
          */
-        final Identifier actual = value.getIdentifier();
+        final ReferenceIdentifier actual = value.getIdentifier();
         assertSame  ("authority",  Citations.EPSG, actual.getAuthority());
         assertEquals("codeSpace", "EPSG", actual.getCodeSpace());
         assertEquals("version",   "8.2",  actual.getVersion());
@@ -94,7 +94,7 @@
     @Test
     @DependsOnMethod("testWithVersion")
     public void testForIdentifiedObject() {
-        final Identifier id = new ImmutableIdentifier(Citations.EPSG, "EPSG", "4326", "8.2", null);
+        final ReferenceIdentifier id = new ImmutableIdentifier(Citations.EPSG, "EPSG", "4326", "8.2", null);
         final Code value = Code.forIdentifiedObject(GeographicCRS.class, Collections.singleton(id));
         assertNotNull(value);
         assertEquals("codeSpace", Constants.IOGP, value.codeSpace);
@@ -111,7 +111,7 @@
         final DefaultCitation authority = new DefaultCitation("EPSG");
         authority.getIdentifiers().add(new ImmutableIdentifier(null, "OGP", "EPSG"));
 
-        final Identifier id = new ImmutableIdentifier(authority, "EPSG", "4326", "8.2", null);
+        final ReferenceIdentifier id = new ImmutableIdentifier(authority, "EPSG", "4326", "8.2", null);
         final Code value = Code.forIdentifiedObject(GeographicCRS.class, Collections.singleton(id));
         assertNotNull(value);
         assertEquals("codeSpace", "OGP", value.codeSpace);
@@ -129,7 +129,7 @@
         final Code value = new Code();
         value.codeSpace = "OGP";
         value.code = "urn:ogc:def:crs:EPSG:8.2:4326";
-        final Identifier actual = value.getIdentifier();
+        final ReferenceIdentifier actual = value.getIdentifier();
         assertSame  ("authority",  Citations.EPSG, actual.getAuthority());
         assertEquals("codeSpace", "EPSG", actual.getCodeSpace());
         assertEquals("version",   "8.2",  actual.getVersion());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
index 7e01c6c..e0cc2a9 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
@@ -119,7 +119,7 @@
         assertEquals("cylindricalCS",    toPropertyName(CoordinateSystem.class, CylindricalCS   .class).toString());
         assertEquals("ellipsoidalCS",    toPropertyName(CoordinateSystem.class, EllipsoidalCS   .class).toString());
         assertEquals("linearCS",         toPropertyName(CoordinateSystem.class, LinearCS        .class).toString());
-        assertEquals("parametricCS",     toPropertyName(CoordinateSystem.class, ParametricCS    .class).toString());
+//      assertEquals("parametricCS",     toPropertyName(CoordinateSystem.class, ParametricCS    .class).toString());
         assertEquals("polarCS",          toPropertyName(CoordinateSystem.class, PolarCS         .class).toString());
         assertEquals("sphericalCS",      toPropertyName(CoordinateSystem.class, SphericalCS     .class).toString());
         assertEquals("timeCS",           toPropertyName(CoordinateSystem.class, TimeCS          .class).toString());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
index 7108002..dcee0d1 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WKTUtilitiesTest.java
@@ -51,7 +51,7 @@
         assertEquals(WKTKeywords.cylindrical, toType(CoordinateSystem.class, CylindricalCS   .class));
         assertEquals(WKTKeywords.ellipsoidal, toType(CoordinateSystem.class, EllipsoidalCS   .class));
         assertEquals(WKTKeywords.linear,      toType(CoordinateSystem.class, LinearCS        .class));
-        assertEquals(WKTKeywords.parametric,  toType(CoordinateSystem.class, ParametricCS    .class));
+//      assertEquals(WKTKeywords.parametric,  toType(CoordinateSystem.class, ParametricCS    .class));
         assertEquals(WKTKeywords.polar,       toType(CoordinateSystem.class, PolarCS         .class));
         assertEquals(WKTKeywords.spherical,   toType(CoordinateSystem.class, SphericalCS     .class));
         assertEquals(WKTKeywords.temporal,    toType(CoordinateSystem.class, TimeCS          .class));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java
index d24fd55..7e7d548 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/CoordinateFrameRotationTest.java
@@ -82,6 +82,7 @@
         tolerance  = Formulas.ANGULAR_TOLERANCE;
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         createTransform(new CoordinateFrameRotation3D());
         assertFalse(transform instanceof LinearTransform);
         verifyTransform(PositionVector7ParamTest.samplePoint(1),
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java
index 8a13416..145c6c5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/FranceGeocentricInterpolationTest.java
@@ -39,10 +39,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.7
- *
- * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
- * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
- *
  * @since 0.7
  * @module
  */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
index a9e0c49..840f677 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/GeocentricTranslationTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.internal.referencing.provider;
 
-import java.util.Arrays;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -24,12 +23,10 @@
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.apache.sis.referencing.operation.matrix.Matrix4;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.referencing.operation.transform.EllipsoidToCentricTransform;
@@ -230,6 +227,7 @@
      */
     private void datumShift(final int sourceStep, final int targetStep) throws TransformException {
         tolerance = precision(targetStep);
+        tolerance = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         verifyTransform(samplePoint(sourceStep), samplePoint(targetStep));
         validate();
     }
@@ -263,43 +261,11 @@
         derivativeDeltas = new double[] {delta, delta, 100};    // (Δλ, Δφ, Δh)
         zTolerance = Formulas.LINEAR_TOLERANCE / 2;             // Half the precision of h value given by EPSG
         zDimension = new int[] {2};                             // Dimension of h where to apply zTolerance
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         datumShift(1, 4);
     }
 
     /**
-     * Tests the point used in {@link FranceGeocentricInterpolationTest}. We use this test for making sure
-     * that we get the expected value when using the real geocentric translation method. This test will be
-     * completed by an equivalent test in {@code MolodenskyTransformTest} for verifying the quality of our
-     * approximation.
-     *
-     * @throws FactoryException if an error occurred while creating the transform.
-     * @throws TransformException if transformation of a point failed.
-     *
-     * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
-     */
-    @Test
-    public void testFranceGeocentricInterpolationPoint() throws FactoryException, TransformException {
-        transform = createDatumShiftForGeographic3D(DefaultFactories.forBuildin(MathTransformFactory.class),
-                 HardCodedDatum.NTF.getEllipsoid(),
-                 CommonCRS.ETRS89.ellipsoid(),
-                -FranceGeocentricInterpolation.TX,
-                -FranceGeocentricInterpolation.TY,
-                -FranceGeocentricInterpolation.TZ);
-
-        final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres
-        derivativeDeltas = new double[] {delta, delta, 100};                // (Δλ, Δφ, Δh)
-        tolerance  = FranceGeocentricInterpolationTest.ANGULAR_TOLERANCE;
-        zTolerance = 0.005;                       // Half the precision of the target[2] value set below.
-        zDimension = new int[] {2};
-
-        final double[] source   = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
-        final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
-        expected[2] = 43.15;  // Anti-regression (this value is not provided in NTG_88 guidance note).
-        verifyTransform(source, expected);
-        validate();
-    }
-
-    /**
      * Tests conversion of random points.
      *
      * @throws FactoryException if an error occurred while creating the transform.
@@ -310,7 +276,7 @@
     public void testRandomPoints() throws FactoryException, TransformException {
         testGeographicDomain();                     // For creating the transform.
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.GEOGRAPHIC;
+//      toleranceModifier = ToleranceModifier.GEOGRAPHIC;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 831342815);
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
index 76ed840..60c52b6 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/Geographic3Dto2DTest.java
@@ -27,7 +27,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
index 2d3ad76..24df893 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/MapProjectionTest.java
@@ -18,7 +18,7 @@
 
 import java.util.Iterator;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.apache.sis.metadata.iso.citation.Citations;
@@ -127,8 +127,8 @@
         assertEquals("minimumOccurs", isMandatory ? 1 : 0, actual.getMinimumOccurs());
         if (epsgName != null) {
             for (final GenericName alias : actual.getAlias()) {
-                if (alias instanceof Identifier && ((Identifier) alias).getAuthority() != Citations.EPSG) {
-                    assertOgcIdentifierEquals(ogcName, (Identifier) alias);
+                if (alias instanceof ReferenceIdentifier && ((ReferenceIdentifier) alias).getAuthority() != Citations.EPSG) {
+                    assertOgcIdentifierEquals(ogcName, (ReferenceIdentifier) alias);
                     return;
                 }
             }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java
index 523752f..0bec535 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NADCONTest.java
@@ -32,7 +32,7 @@
 import org.apache.sis.measure.Units;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java
index 5d29b20..c87ef93 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/NTv2Test.java
@@ -35,7 +35,7 @@
 import org.apache.sis.measure.Units;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -43,10 +43,6 @@
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
- *
- * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
- * @see org.apache.sis.referencing.operation.transform.MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()
- *
  * @since 0.7
  * @module
  */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PoleRotationMock.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PoleRotationMock.java
index bdc9c6e..a25fe35 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PoleRotationMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PoleRotationMock.java
@@ -26,7 +26,7 @@
  * The provider for <cite>"Pole rotation"</cite> conversion.
  *
  * This conversion is not yet implemented in Apache SIS, but we need to at least accept the parameters
- * for a Well Known Text (WKT) parsing test in the {@link org.apache.sis.io.wkt.WKTParserTest} class.
+ * for a Well Known Text (WKT) parsing test in the {@code org.apache.sis.io.wkt.WKTParserTest} class.
  *
  * <p>This class may be promoted to a real operation if we implement the formulas in a future Apache SIS version.</p>
  *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java
index 7a1ee4b..43a818c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/PositionVector7ParamTest.java
@@ -138,6 +138,7 @@
         tolerance  = Formulas.ANGULAR_TOLERANCE;
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
+        tolerance  = Formulas.LINEAR_TOLERANCE;                 // Other SIS branches use a stricter threshold.
         createTransform(new PositionVector7Param3D());
         assertFalse(transform instanceof LinearTransform);
         verifyTransform(samplePoint(1), samplePoint(4));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java
index 1ab8e13..b6253e0 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProviderMock.java
@@ -27,7 +27,7 @@
 /**
  * Base class of mock provider for coordinate operations not yet implemented in Apache SIS.
  * This is used for operations needed for executing some Well Known Text (WKT) parsing tests
- * in the {@link org.apache.sis.io.wkt.WKTParserTest} class, without doing any real coordinate
+ * in the {@code org.apache.sis.io.wkt.WKTParserTest} class, without doing any real coordinate
  * operations with the parsed objects.
  *
  * <p>Subclasses may be promoted to a real operation if we implement their formulas in a future Apache SIS version.</p>
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
index a78648b..019020c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
@@ -25,6 +25,7 @@
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
+import org.apache.sis.parameter.DefaultParameterDescriptor;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -255,8 +256,8 @@
      */
     @Test
     public void testDescription() {
-        assertFalse(SatelliteTracking.SATELLITE_ORBIT_INCLINATION.getDescription().length() == 0);
-        assertFalse(SatelliteTracking.SATELLITE_ORBITAL_PERIOD   .getDescription().length() == 0);
-        assertFalse(SatelliteTracking.ASCENDING_NODE_PERIOD      .getDescription().length() == 0);
+        assertFalse(((DefaultParameterDescriptor<Double>) SatelliteTracking.SATELLITE_ORBIT_INCLINATION).getDescription().length() == 0);
+        assertFalse(((DefaultParameterDescriptor<Double>) SatelliteTracking.SATELLITE_ORBITAL_PERIOD   ).getDescription().length() == 0);
+        assertFalse(((DefaultParameterDescriptor<Double>) SatelliteTracking.ASCENDING_NODE_PERIOD      ).getDescription().length() == 0);
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java
index 03349dc..3e5ba40 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/SeismicBinGridMock.java
@@ -26,7 +26,7 @@
  * The provider for <cite>"P6 (I = J-90°) seismic bin grid transformation"</cite> transformation (EPSG:1049).
  *
  * This conversion is not yet implemented in Apache SIS, but we need to at least accept the parameters
- * for a Well Known Text (WKT) parsing test in the {@link org.apache.sis.io.wkt.WKTParserTest} class.
+ * for a Well Known Text (WKT) parsing test in the {@code org.apache.sis.io.wkt.WKTParserTest} class.
  *
  * <p>This class may be promoted to a real operation if we implement the formulas in a future Apache SIS version.</p>
  *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TopocentricConversionMock.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TopocentricConversionMock.java
index 14ed531..7ba8eab 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TopocentricConversionMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TopocentricConversionMock.java
@@ -26,7 +26,7 @@
  * The provider for <cite>"Geographic/topocentric conversions"</cite> conversion (EPSG:9837).
  *
  * This conversion is not yet implemented in Apache SIS, but we need to at least accept the parameters
- * for a Well Known Text (WKT) parsing test in the {@link org.apache.sis.io.wkt.WKTParserTest} class.
+ * for a Well Known Text (WKT) parsing test in the {@code org.apache.sis.io.wkt.WKTParserTest} class.
  *
  * <p>This class may be promoted to a real operation if we implement the formulas in a future Apache SIS version.</p>
  *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java
index a53adc7..b68d37e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/ComparisonWithEPSG.java
@@ -21,7 +21,6 @@
 import org.apache.sis.referencing.factory.TestFactorySource;
 import org.apache.sis.referencing.factory.sql.EPSGFactory;
 import org.apache.sis.referencing.CRS;
-import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.BeforeClass;
 import org.junit.AfterClass;
@@ -39,7 +38,6 @@
  * @since   1.0
  * @module
  */
-@DependsOn(WKTParserTest.class)
 public final strictfp class ComparisonWithEPSG extends TestCase {
     /**
      * Creates the factory to use for all tests in this class.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
index f70e0e8..fef0a4e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/FormatterTest.java
@@ -110,7 +110,7 @@
     }
 
     /**
-     * Tests (indirectly) {@link Formatter#append(ControlledVocabulary)}.
+     * Tests (indirectly) {@code Formatter.append(ControlledVocabulary)}.
      */
     @Test
     public void testAppendCodeList() {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
index c5e3b04..8fa86a1 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/GeodeticObjectParserTest.java
@@ -70,8 +70,7 @@
     org.apache.sis.referencing.crs.DefaultCompoundCRSTest.class,
     org.apache.sis.referencing.crs.DefaultEngineeringCRSTest.class,
     org.apache.sis.referencing.crs.DefaultImageCRSTest.class,
-    org.apache.sis.referencing.cs.DirectionAlongMeridianTest.class,
-    org.apache.sis.referencing.factory.GeodeticObjectFactoryTest.class
+    org.apache.sis.referencing.cs.DirectionAlongMeridianTest.class
 })
 public final strictfp class GeodeticObjectParserTest extends TestCase {
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
index 7728b78..47aee8f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/MathTransformParserTest.java
@@ -31,7 +31,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java b/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
deleted file mode 100644
index cb937db..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
+++ /dev/null
@@ -1,700 +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.io.wkt;
-
-import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.crs.CRSFactory;
-import org.opengis.referencing.crs.VerticalCRS;
-import org.opengis.referencing.datum.VerticalDatumType;
-import org.opengis.util.FactoryException;
-import org.opengis.test.wkt.CRSParserTest;
-import org.apache.sis.internal.metadata.AxisNames;
-import org.apache.sis.test.TestRunner;
-import org.apache.sis.test.DependsOn;
-import org.junit.Test;
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Tests Well-Known Text parser using the tests defined in GeoAPI. Those tests use the
- * {@link org.apache.sis.referencing.factory.GeodeticObjectFactory#createFromWKT(String)} method.
- *
- * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
- * @since   0.6
- * @module
- */
-@RunWith(TestRunner.class)
-@DependsOn(GeodeticObjectParserTest.class)
-public final strictfp class WKTParserTest extends CRSParserTest {
-    /**
-     * Whether the test should replace the curly quotation marks “ and ” by the straight quotation mark ".
-     * The ISO 19162 specification uses only straight quotation marks, but SIS supports both.
-     * Curly quotation marks are convenient for identifying bugs, so we test them first.
-     */
-    private boolean useStraightQuotes;
-
-    /**
-     * Creates a new test case using the default {@code CRSFactory} implementation.
-     */
-    public WKTParserTest() {
-        super(org.apache.sis.internal.system.DefaultFactories.forClass(CRSFactory.class));
-    }
-
-    /**
-     * Pre-process the WKT string before parsing. This method may replace curly quotation marks
-     * ({@code “} and {@code ”}) by straight quotation marks ({@code "}).
-     * The Apache SIS parser should understand both forms transparently.
-     *
-     * @param  wkt  the Well-Known Text to pre-process.
-     * @return the Well-Known Text to parse.
-     */
-    @Override
-    protected String preprocessWKT(String wkt) {
-        if (useStraightQuotes) {
-            wkt = super.preprocessWKT(wkt);
-        }
-        return wkt;
-    }
-
-    /**
-     * Verifies the axis names of a geographic CRS. This method is invoked when the parsed object is
-     * expected to have <cite>"Geodetic latitude"</cite> and <cite>"Geodetic longitude"</cite> names.
-     */
-    @SuppressWarnings("fallthrough")
-    private void verifyEllipsoidalCS() {
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        switch (cs.getDimension()) {
-            default: assertEquals("name", AxisNames.ELLIPSOIDAL_HEIGHT, cs.getAxis(2).getName().getCode());
-            case 2:  assertEquals("name", AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode());
-            case 1:  assertEquals("name", AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode());
-            case 0:  break;
-        }
-        switch (cs.getDimension()) {
-            default: assertEquals("abbreviation", "h", cs.getAxis(2).getAbbreviation());
-            case 2:  assertEquals("abbreviation", "λ", cs.getAxis(1).getAbbreviation());
-            case 1:  assertEquals("abbreviation", "φ", cs.getAxis(0).getAbbreviation());
-            case 0:  break;
-        }
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“WGS 84”,
-     *    DATUM[“World Geodetic System 1984”,
-     *      ELLIPSOID[“WGS 84”, 6378137, 298.257223563,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,3],
-     *      AXIS[“(lat)”,north,ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      AXIS[“(lon)”,east,ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      AXIS[“ellipsoidal height (h)”,up,LENGTHUNIT[“metre”,1.0]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographic3D() throws FactoryException {
-        super.testGeographic3D();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographic3D();                           // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“S-95”,
-     *    DATUM[“Pulkovo 1995”,
-     *      ELLIPSOID[“Krassowsky 1940”, 6378245, 298.3,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“degree”,0.0174532925199433],
-     *    REMARK[“Система Геодеэических Координвт года 1995(СК-95)”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithUnicode() throws FactoryException {
-        super.testGeographicWithUnicode();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithUnicode();                  // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“NAD83”,
-     *    DATUM[“North American Datum 1983”,
-     *      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101, LENGTHUNIT[“metre”,1.0]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north],
-     *      AXIS[“longitude”,east],
-     *      ANGLEUNIT[“degree”,0.017453292519943],
-     *    ID[“EPSG”,4269],
-     *    REMARK[“1986 realisation”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithIdentifier() throws FactoryException {
-        super.testGeographicWithIdentifier();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithIdentifier();               // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODCRS[“NTF (Paris)”,
-     *    DATUM[“Nouvelle Triangulation Francaise”,
-     *      ELLIPSOID[“Clarke 1880 (IGN)”, 6378249.2, 293.4660213]],
-     *    PRIMEM[“Paris”,2.5969213],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“grad”,0.015707963267949],
-     *    REMARK[“Nouvelle Triangulation Française”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeographicWithGradUnits() throws FactoryException {
-        super.testGeographicWithGradUnits();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testGeographicWithGradUnits();                // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   GEODETICCRS[“JGD2000”,
-     *    DATUM[“Japanese Geodetic Datum 2000”,
-     *      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]],
-     *    CS[Cartesian,3],
-     *      AXIS[“(X)”,geocentricX],
-     *      AXIS[“(Y)”,geocentricY],
-     *      AXIS[“(Z)”,geocentricZ],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    SCOPE[“Geodesy, topographic mapping and cadastre”],
-     *    AREA[“Japan”],
-     *    BBOX[17.09,122.38,46.05,157.64],
-     *    TIMEEXTENT[2002-04-01,2011-10-21],
-     *    ID[“EPSG”,4946,URI[“urn:ogc:def:crs:EPSG::4946”]],
-     *    REMARK[“注:JGD2000ジオセントリックは現在JGD2011に代わりました。”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testGeocentric() throws FactoryException {
-        super.testGeocentric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEOCENTRIC_X, cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEOCENTRIC_Y, cs.getAxis(1).getName().getCode());
-        assertEquals("name", AxisNames.GEOCENTRIC_Z, cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testGeocentric();                             // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Ignored for now, because the Lambert Azimuthal Equal Area projection method is not yet implemented.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    @Ignore("Lambert Azimuthal Equal Area projection method not yet implemented.")
-    public void testProjectedYX() throws FactoryException {
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   PROJCRS[“NAD27 / Texas South Central”,
-     *    BASEGEODCRS[“NAD27”,
-     *      DATUM[“North American Datum 1927”,
-     *        ELLIPSOID[“Clarke 1866”, 20925832.164, 294.97869821,
-     *          LENGTHUNIT[“US survey foot”,0.304800609601219]]]],
-     *    CONVERSION[“Texas South Central SPCS27”,
-     *      METHOD[“Lambert Conic Conformal (2SP)”,ID[“EPSG”,9802]],
-     *      PARAMETER[“Latitude of false origin”,27.83333333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8821]],
-     *      PARAMETER[“Longitude of false origin”,-99.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8822]],
-     *      PARAMETER[“Latitude of 1st standard parallel”,28.383333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8823]],
-     *      PARAMETER[“Latitude of 2nd standard parallel”,30.283333333333,
-     *        ANGLEUNIT[“degree”,0.0174532925199433],ID[“EPSG”,8824]],
-     *      PARAMETER[“Easting at false origin”,2000000.0,
-     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8826]],
-     *      PARAMETER[“Northing at false origin”,0.0,
-     *        LENGTHUNIT[“US survey foot”,0.304800609601219],ID[“EPSG”,8827]]],
-     *    CS[Cartesian,2],
-     *      AXIS[“(x)”,east],
-     *      AXIS[“(y)”,north],
-     *      LENGTHUNIT[“US survey foot”,0.304800609601219],
-     *    REMARK[“Fundamental point: Meade’s Ranch KS, latitude 39°13'26.686"N, longitude 98°32'30.506"W.”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testProjectedWithFootUnits() throws FactoryException {
-        super.testProjectedWithFootUnits();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.EASTING,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.NORTHING, cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testProjectedWithFootUnits();                  // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters and the line feed in {@code REMARK}):
-     *
-     * <blockquote><pre>PROJCRS[“NAD83 UTM 10”,
-     *  BASEGEODCRS[“NAD83(86)”,
-     *    DATUM[“North American Datum 1983”,
-     *      ELLIPSOID[“GRS 1980”,6378137,298.257222101]],
-     *    ANGLEUNIT[“degree”,0.0174532925199433],
-     *    PRIMEM[“Greenwich”,0]],
-     *  CONVERSION[“UTM zone 10N”,ID[“EPSG”,16010],
-     *    METHOD[“Transverse Mercator”],
-     *    PARAMETER[“Latitude of natural origin”,0.0],
-     *    PARAMETER[“Longitude of natural origin”,-123.0],
-     *    PARAMETER[“Scale factor”,0.9996],
-     *    PARAMETER[“False easting”,500000.0],
-     *    PARAMETER[“False northing”,0.0]],
-     *  CS[Cartesian,2],
-     *    AXIS[“(E)”,east,ORDER[1]],
-     *    AXIS[“(N)”,north,ORDER[2]],
-     *    LENGTHUNIT[“metre”,1.0],
-     *  REMARK[“In this example units are implied. This is allowed for backward compatibility.
-     *          It is recommended that units are explicitly given in the string,
-     *          as in the previous two examples.”]]</pre></blockquote>
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testProjectedWithImplicitParameterUnits() throws FactoryException {
-        super.testProjectedWithImplicitParameterUnits();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.EASTING,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.NORTHING, cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testProjectedWithImplicitParameterUnits();    // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name and vertical datum type.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   VERTCRS[“NAVD88”,
-     *    VDATUM[“North American Vertical Datum 1988”],
-     *    CS[vertical,1],
-     *      AXIS[“gravity-related height (H)”,up],LENGTHUNIT[“metre”,1.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testVertical() throws FactoryException {
-        super.testVertical();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GRAVITY_RELATED_HEIGHT, cs.getAxis(0).getName().getCode());
-        assertEquals("datumType", VerticalDatumType.GEOIDAL, ((VerticalCRS) object).getDatum().getVerticalDatumType());
-
-        useStraightQuotes = true;
-        super.testVertical();                               // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   TIMECRS[“GPS Time”,
-     *     TDATUM[“Time origin”,TIMEORIGIN[1980-01-01T00:00:00.0Z]],
-     *     CS[temporal,1],AXIS[“time”,future],TIMEUNIT[“day”,86400.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testTemporal() throws FactoryException {
-        super.testTemporal();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.TIME, cs.getAxis(0).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testTemporal();                               // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis name.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   PARAMETRICCRS[“WMO standard atmosphere layer 0”,
-     *     PDATUM[“Mean Sea Level”,ANCHOR[“1013.25 hPa at 15°C”]],
-     *     CS[parametric,1],
-     *     AXIS[“pressure (hPa)”,up],
-     *     PARAMETRICUNIT[“hPa”,100.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testParametric() throws FactoryException {
-        super.testParametric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", "pressure", cs.getAxis(0).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testParametric();                             // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   ENGINEERINGCRS[“Astra Minas Grid”,
-     *    ENGINEERINGDATUM[“Astra Minas”],
-     *    CS[Cartesian,2],
-     *      AXIS[“northing (X)”,north,ORDER[1]],
-     *      AXIS[“westing (Y)”,west,ORDER[2]],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    ID[“EPSG”,5800]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineering() throws FactoryException {
-        super.testEngineering();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.NORTHING, cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.WESTING,  cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testEngineering();                            // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   ENGCRS[“A construction site CRS”,
-     *    EDATUM[“P1”,ANCHOR[“Peg in south corner”]],
-     *    CS[Cartesian,2],
-     *      AXIS[“site east”,southWest,ORDER[1]],
-     *      AXIS[“site north”,southEast,ORDER[2]],
-     *      LENGTHUNIT[“metre”,1.0],
-     *    TIMEEXTENT[“date/time t1”,“date/time t2”]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineeringRotated() throws FactoryException {
-        super.testEngineeringRotated();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", "site east",  cs.getAxis(0).getName().getCode());
-        assertEquals("name", "site north", cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testEngineeringRotated();                     // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *   ENGCRS[“A ship-centred CRS”,
-     *    EDATUM[“Ship reference point”,ANCHOR[“Centre of buoyancy”]],
-     *    CS[Cartesian,3],
-     *      AXIS[“(x)”,forward],
-     *      AXIS[“(y)”,starboard],
-     *      AXIS[“(z)”,down],
-     *      LENGTHUNIT[“metre”,1.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testEngineeringForShip() throws FactoryException {
-        super.testEngineeringForShip();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        /*
-         * In this case we had no axis names, so Apache SIS reused the abbreviations.
-         * This could change in any future SIS version if we update Transliterator.
-         */
-        assertEquals("name", "x", cs.getAxis(0).getName().getCode());
-        assertEquals("name", "y", cs.getAxis(1).getName().getCode());
-        assertEquals("name", "z", cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testEngineeringForShip();                     // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  GEODCRS[“ETRS89 Lambert Azimuthal Equal Area CRS”,
-     *    BASEGEODCRS[“WGS 84”,
-     *      DATUM[“WGS 84”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.2572236,LENGTHUNIT[“metre”,1.0]]]],
-     *    DERIVINGCONVERSION[“Atlantic pole”,
-     *      METHOD[“Pole rotation”,ID[“Authority”,1234]],
-     *      PARAMETER[“Latitude of rotated pole”,52.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Longitude of rotated pole”,-30.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Axis rotation”,-25.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]]],
-     *    CS[ellipsoidal,2],
-     *      AXIS[“latitude”,north,ORDER[1]],
-     *      AXIS[“longitude”,east,ORDER[2]],
-     *      ANGLEUNIT[“degree”,0.0174532925199433]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedGeodetic() throws FactoryException {
-        super.testDerivedGeodetic();
-        verifyEllipsoidalCS();
-        useStraightQuotes = true;
-        super.testDerivedGeodetic();                        // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  ENGCRS[“Topocentric example A”,
-     *    BASEGEODCRS[“WGS 84”,
-     *      DATUM[“WGS 84”,
-     *        ELLIPSOID[“WGS 84”, 6378137, 298.2572236, LENGTHUNIT[“metre”,1.0]]]],
-     *    DERIVINGCONVERSION[“Topocentric example A”,
-     *      METHOD[“Geographic/topocentric conversions”,ID[“EPSG”,9837]],
-     *      PARAMETER[“Latitude of topocentric origin”,55.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Longitude of topocentric origin”,5.0,
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      PARAMETER[“Ellipsoidal height of topocentric origin”,0.0,
-     *        LENGTHUNIT[“metre”,1.0]]],
-     *    CS[Cartesian,3],
-     *      AXIS[“Topocentric East (U)”,east,ORDER[1]],
-     *      AXIS[“Topocentric North (V)”,north,ORDER[2]],
-     *      AXIS[“Topocentric height (W)”,up,ORDER[3]],
-     *      LENGTHUNIT[“metre”,1.0]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedEngineeringFromGeodetic() throws FactoryException {
-        super.testDerivedEngineeringFromGeodetic();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", "Topocentric East",   cs.getAxis(0).getName().getCode());
-        assertEquals("name", "Topocentric North",  cs.getAxis(1).getName().getCode());
-        assertEquals("name", "Topocentric height", cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testDerivedEngineeringFromGeodetic();         // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testDerivedEngineeringFromProjected() throws FactoryException {
-        super.testDerivedEngineeringFromProjected();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        /*
-         * In this case we had no axis names, so Apache SIS reused the abbreviations.
-         * This could change in any future SIS version if we update Transliterator.
-         */
-        assertEquals("name", "I", cs.getAxis(0).getName().getCode());
-        assertEquals("name", "J", cs.getAxis(1).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testDerivedEngineeringFromProjected();        // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  COMPOUNDCRS[“NAD83 + NAVD88”,
-     *    GEODCRS[“NAD83”,
-     *      DATUM[“North American Datum 1983”,
-     *        ELLIPSOID[“GRS 1980”,6378137,298.257222101,
-     *          LENGTHUNIT[“metre”,1.0]]],
-     *        PRIMEMERIDIAN[“Greenwich”,0],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“latitude”,north,ORDER[1]],
-     *        AXIS[“longitude”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *      VERTCRS[“NAVD88”,
-     *        VDATUM[“North American Vertical Datum 1988”],
-     *        CS[vertical,1],
-     *          AXIS[“gravity-related height (H)”,up],
-     *          LENGTHUNIT[“metre”,1]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithVertical() throws FactoryException {
-        super.testCompoundWithVertical();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEODETIC_LATITUDE,      cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEODETIC_LONGITUDE,     cs.getAxis(1).getName().getCode());
-        assertEquals("name", AxisNames.GRAVITY_RELATED_HEIGHT, cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testCompoundWithVertical();                   // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  COMPOUNDCRS[“GPS position and time”,
-     *    GEODCRS[“WGS 84”,
-     *      DATUM[“World Geodetic System 1984”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.257223563]],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“(lat)”,north,ORDER[1]],
-     *        AXIS[“(lon)”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *    TIMECRS[“GPS Time”,
-     *      TIMEDATUM[“Time origin”,TIMEORIGIN[1980-01-01]],
-     *      CS[temporal,1],
-     *        AXIS[“time (T)”,future],
-     *        TIMEUNIT[“day”,86400]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithTime() throws FactoryException {
-        super.testCompoundWithTime();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode());
-        assertEquals("name", AxisNames.TIME,               cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testCompoundWithTime();                       // Test again with “ and ” replaced by ".
-    }
-
-    /**
-     * Completes the GeoAPI tests with a check of axis names.
-     * The WKT parsed by this test is (except for quote characters):
-     *
-     * {@preformat wkt
-     *  COMPOUNDCRS[“ICAO layer 0”,
-     *    GEODETICCRS[“WGS 84”,
-     *      DATUM[“World Geodetic System 1984”,
-     *        ELLIPSOID[“WGS 84”,6378137,298.257223563,
-     *          LENGTHUNIT[“metre”,1.0]]],
-     *      CS[ellipsoidal,2],
-     *        AXIS[“latitude”,north,ORDER[1]],
-     *        AXIS[“longitude”,east,ORDER[2]],
-     *        ANGLEUNIT[“degree”,0.0174532925199433]],
-     *    PARAMETRICCRS[“WMO standard atmosphere”,
-     *      PARAMETRICDATUM[“Mean Sea Level”,
-     *        ANCHOR[“Mean Sea Level = 1013.25 hPa”]],
-     *          CS[parametric,1],
-     *            AXIS[“pressure (P)”,unspecified],
-     *            PARAMETRICUNIT[“hPa”,100]]]
-     * }
-     *
-     * @throws FactoryException if an error occurred during the WKT parsing.
-     */
-    @Test
-    @Override
-    public void testCompoundWithParametric() throws FactoryException {
-        super.testCompoundWithParametric();
-        final CoordinateSystem cs = object.getCoordinateSystem();
-        assertEquals("name", AxisNames.GEODETIC_LATITUDE,  cs.getAxis(0).getName().getCode());
-        assertEquals("name", AxisNames.GEODETIC_LONGITUDE, cs.getAxis(1).getName().getCode());
-        assertEquals("name", "pressure",                   cs.getAxis(2).getName().getCode());
-
-        useStraightQuotes = true;
-        super.testCompoundWithParametric();                 // Test again with “ and ” replaced by ".
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
index 629f3d3..d9c709e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorGroupTest.java
@@ -20,7 +20,6 @@
 import java.util.List;
 import java.util.HashMap;
 import java.util.Collections;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.ParameterNotFoundException;
 import org.apache.sis.internal.util.Constants;
@@ -105,7 +104,6 @@
      */
     @Test
     public void validateTestObjects() {
-        assertEquals(ParameterDirection.IN, M1_M1_O1_O2.getDirection());
         for (final GeneralParameterDescriptor descriptor : M1_M1_O1_O2.descriptors()) {
             AssertionError error = null;
             try {
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java
index 31aacc3..e39d6a4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/ParametersTest.java
@@ -21,7 +21,6 @@
 import java.util.Collections;
 import javax.measure.Unit;
 import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.parameter.ParameterDirection;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.parameter.ParameterValueGroup;
@@ -119,8 +118,6 @@
             @Override public Collection<GenericName>  getAlias()         {return descriptor.getAlias();}
             @Override public Set<ReferenceIdentifier> getIdentifiers()   {return descriptor.getIdentifiers();}
             @Override public InternationalString      getRemarks()       {return descriptor.getRemarks();}
-            @Override public InternationalString      getDescription()   {return descriptor.getDescription();}
-            @Override public ParameterDirection       getDirection()     {return descriptor.getDirection();}
             @Override public int                      getMinimumOccurs() {return descriptor.getMinimumOccurs();}
             @Override public int                      getMaximumOccurs() {return descriptor.getMaximumOccurs();}
             @Override public Class<T>                 getValueClass()    {return descriptor.getValueClass();}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java
index dd4f198..a89901e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/parameter/TensorParametersTest.java
@@ -30,7 +30,6 @@
 import org.junit.Test;
 
 import static java.util.Collections.singletonMap;
-import static org.opengis.test.Validators.validate;
 import static org.apache.sis.test.ReferencingAssert.*;
 import static org.apache.sis.internal.util.Constants.NUM_ROW;
 import static org.apache.sis.internal.util.Constants.NUM_COL;
@@ -321,7 +320,6 @@
                 }
                 final ParameterValueGroup group = param.createValueGroup(
                         singletonMap(ParameterDescriptor.NAME_KEY, "Test"), matrix);
-                validate(group);
                 assertEquals(NUM_ROW,    numRow, group.parameter(NUM_ROW).intValue());
                 assertEquals(NUM_COL,    numCol, group.parameter(NUM_COL).intValue());
                 assertEquals("elements", matrix, param.toMatrix(group));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
index f00a858..a88283e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
@@ -24,6 +24,7 @@
 import org.opengis.util.NameFactory;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.internal.simple.SimpleCitation;
 import org.apache.sis.internal.simple.SimpleIdentifier;
 import org.apache.sis.internal.system.DefaultFactories;
@@ -87,17 +88,17 @@
         /*
          * The failed attempt to set a new codespace shall not have modified builder state.
          */
-        assertEquals("EPSG",         builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG",         builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  (Citations.EPSG, builder.properties.get(Identifier.AUTHORITY_KEY));
         /*
          * After a cleanup (normally after a createXXX(…) method call), user shall be allowed to
          * set a new codespace again. Note that the cleanup operation shall not clear the codespace.
          */
         builder.onCreate(true);
-        assertEquals("EPSG",         builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG",         builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  (Citations.EPSG, builder.properties.get(Identifier.AUTHORITY_KEY));
         builder.setCodeSpace(IOGP, "EPSG");
-        assertEquals("EPSG", builder.properties.get(Identifier.CODESPACE_KEY));
+        assertEquals("EPSG", builder.properties.get(ReferenceIdentifier.CODESPACE_KEY));
         assertSame  ( IOGP,  builder.properties.get(Identifier.AUTHORITY_KEY));
     }
 
@@ -224,7 +225,7 @@
                     assertSame("Authority and codespace shall be unchanged.", Citations.EPSG, value);
                     break;
                 }
-                case Identifier.CODESPACE_KEY: {
+                case ReferenceIdentifier.CODESPACE_KEY: {
                     assertEquals("Authority and codespace shall be unchanged.", "EPSG", value);
                     break;
                 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
index 61e7029..33a95e1 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
@@ -232,7 +232,7 @@
             final String        name  = e.name();
             final VerticalDatum datum = e.datum();
             final VerticalCRS   crs   = e.crs();
-            if (e.isEPSG) {
+            if (e.isEPSG && !name.startsWith("NAV")) {
                 /*
                  * BAROMETRIC, ELLIPSOIDAL and OTHER_SURFACE uses an axis named "Height", which is not
                  * a valid axis name according ISO 19111. We skip the validation test for those enums.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java
index 2d9ae74..2223ac7 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/GeodeticCalculatorTest.java
@@ -26,7 +26,6 @@
 import java.io.IOException;
 import java.io.LineNumberReader;
 import org.opengis.geometry.DirectPosition;
-import org.opengis.referencing.cs.AxisDirection;
 import org.apache.sis.internal.referencing.j2d.ShapeUtilitiesExt;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.referencing.crs.HardCodedCRS;
@@ -222,8 +221,6 @@
     @DependsOnMethod("testGeodesicDistanceAndAzimuths")
     public void testUsingTransform() {
         final GeodeticCalculator c = create(true);
-        assertAxisDirectionsEqual("GeographicCRS", c.getGeographicCRS().getCoordinateSystem(), AxisDirection.NORTH, AxisDirection.EAST);
-        assertAxisDirectionsEqual("PositionCRS",     c.getPositionCRS().getCoordinateSystem(), AxisDirection.EAST, AxisDirection.NORTH);
         final double φ = -33.0;
         final double λ = -71.6;
         c.setStartPoint(new DirectPosition2D(λ, φ));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java
index 297e5b6..7955d4d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/ImmutableIdentifierTest.java
@@ -33,7 +33,7 @@
 import org.junit.Test;
 
 import static org.apache.sis.test.ReferencingAssert.*;
-import static org.opengis.metadata.Identifier.*;
+import static org.opengis.referencing.ReferenceIdentifier.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
index b43ac5b..bd34cc2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/NamedIdentifierTest.java
@@ -17,11 +17,11 @@
 package org.apache.sis.referencing;
 
 import java.util.Locale;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.util.InternationalString;
 import org.opengis.util.NameSpace;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
-import org.opengis.metadata.Identifier;
 import org.opengis.test.Validators;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.internal.system.DefaultFactories;
@@ -49,7 +49,7 @@
     @Test
     public void testCreateFromCode() {
         final NamedIdentifier identifier = new NamedIdentifier(Citations.EPSG, "EPSG", "4326", "8.3", null);
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
@@ -75,7 +75,7 @@
         final NameFactory factory = DefaultFactories.forBuildin(NameFactory.class);
         final NameSpace scope = factory.createNameSpace(factory.createLocalName(null, "IOGP"), null);
         final NamedIdentifier identifier = new NamedIdentifier(factory.createGenericName(scope, "EPSG", "4326"));
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
@@ -113,7 +113,7 @@
     @DependsOnMethod("testCreateFromCode")
     public void testCreateFromInternationalString() {
         final NamedIdentifier identifier = createI18N();
-        Validators.validate((Identifier)  identifier);
+        Validators.validate((ReferenceIdentifier) identifier);
         Validators.validate((GenericName) identifier);
 
         // ImmutableIdentifier properties
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
index 08209b5..f7d0a9d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultDerivedCRSTest.java
@@ -115,7 +115,7 @@
     @Test
     public void testConstruction() {
         final DefaultDerivedCRS crs = createLongitudeRotation();
-        Validators.validate(crs);
+//      Validators.validate(crs);
 
         assertEquals("name",    "Back to Greenwich",                crs.getName().getCode());
         assertEquals("baseCRS", "NTF (Paris)",                      crs.getBaseCRS().getName().getCode());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
index 36dec06..d0bbfee 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/DefaultGeographicCRSTest.java
@@ -17,10 +17,10 @@
 package org.apache.sis.referencing.crs;
 
 import org.opengis.test.Validators;
-import org.opengis.metadata.Identifier;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.io.wkt.Convention;
@@ -92,7 +92,7 @@
     @Test
     public void testIdentifiers() {
         GeographicCRS crs = CommonCRS.WGS72.geographic();
-        Identifier identifier = getSingleton(crs.getIdentifiers());
+        ReferenceIdentifier identifier = getSingleton(crs.getIdentifiers());
         assertEquals("codespace", "EPSG", identifier.getCodeSpace());
         assertEquals("code",      "4322", identifier.getCode());
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
index 609ac38..566067a 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/crs/HardCodedCRSTest.java
@@ -48,8 +48,8 @@
     public void validate() {
         final ValidatorContainer validators = new ValidatorContainer();
         validators.validate(WGS84);
-        validators.validate(WGS84_3D);           validators.crs.enforceStandardNames = false;
-        validators.validate(ELLIPSOIDAL_HEIGHT); validators.crs.enforceStandardNames = true;
+        validators.validate(WGS84_3D);
+        validators.validate(ELLIPSOIDAL_HEIGHT);
         validators.validate(GRAVITY_RELATED_HEIGHT);
         validators.validate(TIME);
         validators.validate(SPHERICAL);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
index 59763de..3cb79e1 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultCylindricalCSTest.java
@@ -25,7 +25,7 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
index 5f5052d..555c6ef 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultPolarCSTest.java
@@ -25,7 +25,7 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
index 1b994f6..7a8ed21 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/DefaultSphericalCSTest.java
@@ -23,7 +23,7 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java
index bc3b985..1ac9316 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/datum/GeodeticDatumMock.java
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.referencing.datum;
 
+import java.util.Date;
 import javax.measure.Unit;
 import javax.measure.quantity.Length;
+import org.opengis.util.InternationalString;
+import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
@@ -117,12 +120,16 @@
         return new Object[] {getCode(), alias, semiMajorAxis, semiMinorAxis, inverseFlattening, isIvfDefinitive};
     }
 
-    @Override public PrimeMeridian getPrimeMeridian()     {return PrimeMeridianMock.GREENWICH;}
-    @Override public Ellipsoid     getEllipsoid()         {return this;}
-    @Override public Unit<Length>  getAxisUnit()          {return Units.METRE;}
-    @Override public double        getSemiMajorAxis()     {return semiMajorAxis;}
-    @Override public double        getSemiMinorAxis()     {return semiMinorAxis;}
-    @Override public double        getInverseFlattening() {return inverseFlattening;}
-    @Override public boolean       isSphere()             {return semiMajorAxis == semiMinorAxis;}
-    @Override public boolean       isIvfDefinitive()      {return isIvfDefinitive;}
+    @Override public PrimeMeridian        getPrimeMeridian()      {return PrimeMeridianMock.GREENWICH;}
+    @Override public Ellipsoid            getEllipsoid()          {return this;}
+    @Override public Unit<Length>         getAxisUnit()           {return Units.METRE;}
+    @Override public double               getSemiMajorAxis()      {return semiMajorAxis;}
+    @Override public double               getSemiMinorAxis()      {return semiMinorAxis;}
+    @Override public double               getInverseFlattening()  {return inverseFlattening;}
+    @Override public boolean              isSphere()              {return semiMajorAxis == semiMinorAxis;}
+    @Override public boolean              isIvfDefinitive()       {return isIvfDefinitive;}
+    @Override public InternationalString  getAnchorPoint()        {return null;}
+    @Override public Date                 getRealizationEpoch()   {return null;}
+    @Override public Extent               getDomainOfValidity()   {return null;}
+    @Override public InternationalString  getScope()              {return null;}
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
index 204e621..def1fdf 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
@@ -364,7 +364,7 @@
                 "  SCOPE[“Horizontal component of 3D system.\\E.*\\Q”],\n" +
                 "  AREA[“World\\E.*\\Q”],\n" +
                 "  BBOX[-90.00, -180.00, 90.00, 180.00],\n" +
-                "  ID[“CRS”, 84, CITATION[“OGC:WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]]\\E", crs);
+                "  ID[“CRS”, 84, CITATION[“WMS”], URI[“urn:ogc:def:crs:OGC:1.3:CRS84”]]]\\E", crs);
         /*
          * Note: the WKT specification defines the ID element as:
          *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2001.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2001.java
deleted file mode 100644
index 3b36b5f..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2001.java
+++ /dev/null
@@ -1,73 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createUnit(String)}.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
- * @since   0.7
- * @module
- */
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2001 extends org.opengis.test.referencing.gigs.GIGS2001 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2001() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2002.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2002.java
deleted file mode 100644
index ffcf2ba..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2002.java
+++ /dev/null
@@ -1,121 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.system.Loggers;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createEllipsoid(String)}.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class,     // Units created from EPSG codes
-    GIGS3002.class      // Ellipsoids created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2002 extends org.opengis.test.referencing.gigs.GIGS2002 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2002() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testClarkeMichigan() throws FactoryException {
-        super.testClarkeMichigan();
-        loggings.assertNextLogContains("EPSG:7009");
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testPopularVisualisationSphere() throws FactoryException {
-        super.testPopularVisualisationSphere();
-        loggings.assertNextLogContains("EPSG:7059");
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2003.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2003.java
deleted file mode 100644
index 3a4abde..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2003.java
+++ /dev/null
@@ -1,77 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createPrimeMeridian(String)}.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class,     // Units created from EPSG codes
-    GIGS3003.class      // Prime meridians created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2003 extends org.opengis.test.referencing.gigs.GIGS2003 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2003() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2004.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2004.java
deleted file mode 100644
index a712512..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2004.java
+++ /dev/null
@@ -1,140 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.internal.system.Loggers;
-import org.apache.sis.util.CharSequences;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * for geodetic (geographic or geocentric) CRS.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2002.class,     // Ellipsoids created from EPSG codes
-    GIGS2003.class,     // Prime meridians created from EPSG codes
-    GIGS3004.class      // Geodetic datums created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2004 extends org.opengis.test.referencing.gigs.GIGS2004 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2004() {
-        super(TestFactorySource.factory, TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Removes the accented characters from the object name, so it can be compared against the expected name.
-     *
-     * @param  object  the object from which to get a name than can be verified against the expected name.
-     * @return the name of the given object, eventually modified in order to match the expected name.
-     */
-    @Override
-    protected String getVerifiableName(final IdentifiedObject object) {
-        return CharSequences.toASCII(super.getVerifiableName(object)).toString();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testNAD27_Michigan() throws FactoryException {
-        super.testNAD27_Michigan();
-        loggings.assertNextLogContains("EPSG:6268");    // Datum replaced by 6267
-        loggings.skipNextLogIfContains("EPSG:7009");    // Ellipsoid replaced by 7008 (may have been created by GIGS2002)
-        loggings.assertNextLogContains("EPSG:4268");    // CRS replaced by 4267
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testPopularVisualisation() throws FactoryException {
-        super.testPopularVisualisation();
-        loggings.assertNextLogContains("EPSG:6055");
-        loggings.skipNextLogIfContains("EPSG:7059");    // Ellipsoid may have been created by GIGS2002.
-        loggings.assertNextLogContains("EPSG:4055");
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2005.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2005.java
deleted file mode 100644
index 62c30ad..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2005.java
+++ /dev/null
@@ -1,123 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.system.Loggers;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateOperation(String)}
- * for map projections.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class,     // Units created from EPSG codes
-    GIGS3005.class      // Conversions created from properties
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2005 extends org.opengis.test.referencing.gigs.GIGS2005 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2005() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testAustralianMapGridZones() throws FactoryException {
-        super.testAustralianMapGridZones();
-        loggings.assertNextLogContains("EPSG:17448");    // Falls outside EEZ area.
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testUSStatePlaneZones_LCC() throws FactoryException {
-        super.testUSStatePlaneZones_LCC();
-        loggings.assertNextLogContains("EPSG:12112");    // Method changed to accord with NGS practice.
-        loggings.assertNextLogContains("EPSG:12113");
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2006.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2006.java
deleted file mode 100644
index b35a847..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2006.java
+++ /dev/null
@@ -1,118 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-import org.apache.sis.internal.system.Loggers;
-
-// Test imports
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-import org.apache.sis.test.LoggingWatcher;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * for projected CRS.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2004.class,     // Geodetic CRSs created from EPSG codes
-    GIGS2005.class      // Conversions created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2006 extends org.opengis.test.referencing.gigs.GIGS2006 {
-    /**
-     * A JUnit {@link Rule} for listening to log events. This field is public because JUnit requires us to
-     * do so, but should be considered as an implementation details (it should have been a private field).
-     */
-    @Rule
-    public final LoggingWatcher loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
-
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2006() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-
-    /**
-     * Overrides the GeoAPI test for verifying the log messages emitted during the construction of deprecated objects.
-     *
-     * @throws FactoryException if an error occurred while creating the object.
-     */
-    @Test
-    @Override
-    public void testNAD27_Michigan() throws FactoryException {
-        super.testNAD27_Michigan();
-        loggings.skipNextLogIfContains("EPSG:6268");     // Datum replaced by 6267
-        loggings.skipNextLogIfContains("EPSG:7009");     // Ellipsoid replaced by 7008
-        loggings.skipNextLogIfContains("EPSG:4268");     // CRS replaced by 4267
-        loggings.assertNextLogContains("EPSG:12111");
-        loggings.assertNextLogContains("EPSG:26811");    // ProjectedCRS (no replacement).
-        loggings.skipNextLogIfContains("EPSG:12112");    // Conversion replaced by 6198
-        loggings.assertNextLogContains("EPSG:26812");    // ProjectedCRS replaced by 6201
-        loggings.skipNextLogIfContains("EPSG:12113");    // Conversion replaced by 6199
-        loggings.assertNextLogContains("EPSG:26813");    // ProjectedCRS replaced by 6202
-    }
-
-    /**
-     * Verifies that no unexpected warning has been emitted in this test.
-     */
-    @After
-    public void assertNoUnexpectedLog() {
-        loggings.assertNoUnexpectedLog();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2007.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2007.java
deleted file mode 100644
index 727fe65..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2007.java
+++ /dev/null
@@ -1,77 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateOperation(String)}
- * for horizontal coordinate transformations.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2006.class      // Projected CRSs created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2007 extends org.opengis.test.referencing.gigs.GIGS2007 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2007() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2008.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2008.java
deleted file mode 100644
index eb34bf2..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2008.java
+++ /dev/null
@@ -1,77 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateReferenceSystem(String)}
- * for vertical CRS.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2001.class      // Units created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2008 extends org.opengis.test.referencing.gigs.GIGS2008 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2008() {
-        super(TestFactorySource.factory, TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2009.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2009.java
deleted file mode 100644
index 9350671..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS2009.java
+++ /dev/null
@@ -1,77 +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.referencing.factory;
-
-import org.opengis.util.FactoryException;
-
-// Test imports
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-import org.apache.sis.test.DependsOn;
-
-
-/**
- * Tests {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createCoordinateOperation(String)}
- * for vertical coordinate transformations.
- * This is part of <cite>Geospatial Integrity of Geoscience Software</cite> (GIGS) tests implemented in GeoAPI.
- *
- * <div class="note"><b>Note:</b>
- * this test is defined in this package instead than in the {@code sql} sub-package because of the need to access
- * package-private methods in {@link ConcurrentAuthorityFactory}, and for keeping all GIGS tests together.</div>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
- * @since   0.7
- * @module
- */
-@DependsOn({
-    GIGS2008.class      // Vertical CRSs created from EPSG codes
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS2009 extends org.opengis.test.referencing.gigs.GIGS2009 {
-    /**
-     * Creates a new test using the default authority factory.
-     */
-    public GIGS2009() {
-        super(TestFactorySource.factory);
-    }
-
-    /**
-     * Creates the factory to use for all tests in this class.
-     *
-     * @throws FactoryException if an error occurred while creating the factory.
-     */
-    @BeforeClass
-    public static void createFactory() throws FactoryException {
-        TestFactorySource.createFactory();
-    }
-
-    /**
-     * Forces release of JDBC connections after the tests in this class.
-     *
-     * @throws FactoryException if an error occurred while closing the connections.
-     */
-    @AfterClass
-    public static void close() throws FactoryException {
-        TestFactorySource.close();
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3002.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3002.java
deleted file mode 100644
index f5dcb9f..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3002.java
+++ /dev/null
@@ -1,49 +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.referencing.factory;
-
-import org.opengis.referencing.datum.DatumFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.datum.DefaultEllipsoid} objects creation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    org.apache.sis.referencing.datum.DefaultEllipsoidTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3002 extends org.opengis.test.referencing.gigs.GIGS3002 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3002() {
-        super(DefaultFactories.forBuildin(DatumFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3003.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3003.java
deleted file mode 100644
index 7e84715..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3003.java
+++ /dev/null
@@ -1,49 +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.referencing.factory;
-
-import org.opengis.referencing.datum.DatumFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.datum.DefaultPrimeMeridian} objects creation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    org.apache.sis.referencing.datum.DefaultPrimeMeridianTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3003 extends org.opengis.test.referencing.gigs.GIGS3003 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3003() {
-        super(DefaultFactories.forBuildin(DatumFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3004.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3004.java
deleted file mode 100644
index 4e80d01..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3004.java
+++ /dev/null
@@ -1,56 +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.referencing.factory;
-
-import org.opengis.referencing.cs.CSFactory;
-import org.opengis.referencing.crs.CRSFactory;
-import org.opengis.referencing.datum.DatumFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.datum.DefaultGeodeticDatum} objects creation.
- * {@code GIGS3004} tests also geographic and geocentric CRS creations with the tested geodetic datum.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    GIGS3002.class,     // Ellipsoids created from properties
-    GIGS3003.class,     // Prime meridians created from properties
-    org.apache.sis.referencing.datum.DefaultGeodeticDatumTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3004 extends org.opengis.test.referencing.gigs.GIGS3004 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3004() {
-        super(DefaultFactories.forBuildin(DatumFactory.class),
-              DefaultFactories.forBuildin(CSFactory.class),
-              DefaultFactories.forBuildin(CRSFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3005.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3005.java
deleted file mode 100644
index 9cb3f93..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GIGS3005.java
+++ /dev/null
@@ -1,49 +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.referencing.factory;
-
-import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.test.DependsOn;
-import org.junit.FixMethodOrder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.runners.MethodSorters;
-
-
-/**
- * Runs the <cite>Geospatial Integrity of Geoscience Software</cite> tests on
- * {@link org.apache.sis.referencing.operation.DefaultConversion} objects creation.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
- * @since   0.6
- * @module
- */
-@DependsOn({
-    org.apache.sis.referencing.operation.DefaultConversionTest.class
-})
-@RunWith(JUnit4.class)
-@FixMethodOrder(MethodSorters.JVM)      // Intentionally want some randomness
-public final strictfp class GIGS3005 extends org.opengis.test.referencing.gigs.GIGS3005 {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GIGS3005() {
-        super(DefaultFactories.forBuildin(CoordinateOperationFactory.class));
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
deleted file mode 100644
index 4fc04a0..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/GeodeticObjectFactoryTest.java
+++ /dev/null
@@ -1,252 +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.referencing.factory;
-
-import java.util.Map;
-import java.util.Collections;
-import javax.measure.Unit;
-import javax.measure.quantity.Angle;
-import javax.measure.quantity.Length;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CSFactory;
-import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.EllipsoidalCS;
-import org.opengis.referencing.cs.AxisDirection;
-import org.opengis.referencing.cs.CoordinateSystemAxis;
-import org.opengis.referencing.crs.CRSFactory;
-import org.opengis.referencing.crs.GeodeticCRS;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.crs.ProjectedCRS;
-import org.opengis.referencing.datum.DatumFactory;
-import org.opengis.referencing.datum.Ellipsoid;
-import org.opengis.referencing.datum.PrimeMeridian;
-import org.opengis.referencing.datum.GeodeticDatum;
-import org.opengis.referencing.operation.CoordinateOperationFactory;
-import org.opengis.referencing.operation.OperationMethod;
-import org.opengis.referencing.operation.Conversion;
-import org.opengis.parameter.ParameterValueGroup;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.referencing.operation.DefaultConversion;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.io.wkt.Convention;
-import org.apache.sis.measure.Units;
-
-// Test dependencies
-import org.opengis.test.referencing.ObjectFactoryTest;
-import org.apache.sis.test.TestRunner;
-import org.apache.sis.test.DependsOn;
-import org.junit.runner.RunWith;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.apache.sis.test.ReferencingAssert.*;
-
-
-/**
- * Tests {@link GeodeticObjectFactory} using the suite of tests provided in the GeoAPI project.
- * Note that this does not include authority factories tests or GIGS tests.
- *
- * @author  Cédric Briançon (Geomatys)
- * @version 0.7
- * @since   0.6
- * @module
- */
-@RunWith(TestRunner.class)
-@DependsOn({
-    org.apache.sis.referencing.crs.DefaultGeocentricCRSTest.class,
-    org.apache.sis.referencing.crs.DefaultGeographicCRSTest.class,
-    org.apache.sis.referencing.crs.DefaultProjectedCRSTest.class
-})
-public final strictfp class GeodeticObjectFactoryTest extends ObjectFactoryTest {
-    /**
-     * Creates a new test suite using the singleton factory instance.
-     */
-    public GeodeticObjectFactoryTest() {
-        super(DefaultFactories.forBuildin(DatumFactory.class),
-              DefaultFactories.forBuildin(CSFactory   .class),
-              DefaultFactories.forBuildin(CRSFactory  .class),
-              DefaultFactories.forBuildin(CoordinateOperationFactory.class));
-    }
-
-    @Override
-    @Deprecated
-    @Ignore("Replaced by testProjectedWithGeoidalHeight()")
-    public void testProjected3D() throws FactoryException {
-    }
-
-    /**
-     * Tests {@link GeodeticObjectFactory#createFromWKT(String)}. We test only a very small WKT here because
-     * it is not the purpose of this class to test the parser. The main purpose of this test is to verify
-     * that {@link GeodeticObjectFactory} has been able to instantiate the parser.
-     *
-     * @throws FactoryException if the parsing failed.
-     */
-    @Test
-    public void testCreateFromWKT() throws FactoryException {
-        final GeodeticCRS crs = (GeodeticCRS) crsFactory.createFromWKT(
-                "GEOGCS[“WGS 84”,\n" +
-                "  DATUM[“World Geodetic System 1984”,\n" +
-                "    SPHEROID[“WGS84”, 6378137.0, 298.257223563]],\n" +
-                "  PRIMEM[“Greenwich”, 0.0],\n" +
-                "  UNIT[“degree”, 0.017453292519943295]]");
-
-        assertEquals("name",  "WGS 84", crs.getName().getCode());
-        assertEquals("datum", "World Geodetic System 1984", crs.getDatum().getName().getCode());
-    }
-
-    /**
-     * Tests {@link GeodeticObjectFactory#createFromWKT(String)} with an erroneous projection parameter name.
-     * The intent is to verify that the expected exception is thrown.
-     *
-     * @throws FactoryException if the parsing failed for another reason than the expected one.
-     */
-    @Test
-    public void testInvalidParameterInWKT() throws FactoryException {
-        try {
-            crsFactory.createFromWKT(
-                "PROJCRS[“Custom”,\n" +
-                "  BASEGEODCRS[“North American 1983”,\n" +
-                "    DATUM[“North American 1983”,\n" +
-                "      ELLIPSOID[“GRS 1980”, 6378137, 298.257222101]]],\n" +
-                "  CONVERSION[“Custom”,\n" +
-                "    METHOD[“Lambert Conformal Conic”],\n" +
-                "    PARAMETER[“Standard parallel 1”, 43.0],\n" +
-                "    PARAMETER[“Standard parallel 2”, 45.5],\n" +
-                "    PARAMETER[“Central parallel”, 41.75]],\n" +       // Wrong parameter.
-                "  CS[Cartesian, 2],\n" +
-                "    AXIS[“(Y)”, north],\n" +
-                "    AXIS[“(X)”, east]]");
-            fail("Should not have parsed a WKT with wrong projection parameter.");
-        } catch (InvalidGeodeticParameterException e) {
-            final String message = e.getMessage();
-            assertTrue(message, message.contains("Central parallel"));
-        }
-    }
-
-    /**
-     * Convenience method creating a map with only the "{@code name"} property.
-     * This is the only mandatory property for object creation.
-     */
-    private static Map<String,?> name(final String name) {
-        return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
-    }
-
-    /**
-     * Tests step-by-step the creation of a new projected coordinate reference systems.
-     * This test creates every objects itself and compares with expected WKT 1 after each step.
-     *
-     * <p>Note that practical applications may use existing constants declared in the
-     * {@link CommonCRS} class instead than creating everything like this test does.</p>
-     *
-     * @throws FactoryException if the creation of a geodetic component failed.
-     *
-     * @since 0.7
-     */
-    @Test
-    public void testStepByStepCreation() throws FactoryException {
-        /*
-         * List of all objects to be created in this test.
-         */
-        final Unit<Length>         linearUnit;
-        final Unit<Angle>          angularUnit;
-        final Ellipsoid            ellipsoid;
-        final PrimeMeridian        meridian;
-        final GeodeticDatum        datum;
-        final CoordinateSystemAxis longitude, latitude, easting, northing;
-        final EllipsoidalCS        geographicCS;
-        final GeographicCRS        geographicCRS;
-        final OperationMethod      method;
-        final ParameterValueGroup  parameters;
-        final Conversion           projection;
-        final CartesianCS          projectedCS;
-        final ProjectedCRS         projectedCRS;
-        /*
-         * Prime meridian
-         */
-        angularUnit = Units.DEGREE;
-        meridian = datumFactory.createPrimeMeridian(name("Greenwich"), 0, angularUnit);
-        assertWktEquals(Convention.WKT1,
-                "PRIMEM[“Greenwich”, 0.0]", meridian);
-        /*
-         * Ellipsoid
-         */
-        linearUnit = Units.METRE;
-        ellipsoid = datumFactory.createEllipsoid(name("Airy1830"), 6377563.396, 6356256.910, linearUnit);
-        assertWktEquals(Convention.WKT1,
-                "SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]", ellipsoid);
-        /*
-         * Geodetic datum
-         */
-        datum = datumFactory.createGeodeticDatum(name("Airy1830"), ellipsoid, meridian);
-        assertWktEquals(Convention.WKT1,
-                "DATUM[“Airy1830”,\n" +
-                "  SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]]", datum);
-        /*
-         * Base coordinate reference system
-         */
-        longitude     =  csFactory.createCoordinateSystemAxis(name("Longitude"), "long", AxisDirection.EAST,  angularUnit);
-        latitude      =  csFactory.createCoordinateSystemAxis(name("Latitude"),  "lat",  AxisDirection.NORTH, angularUnit);
-        geographicCS  =  csFactory.createEllipsoidalCS(name("Ellipsoidal"), longitude, latitude);
-        geographicCRS = crsFactory.createGeographicCRS(name("Airy1830"), datum, geographicCS);
-        assertWktEquals(Convention.WKT1,
-                "GEOGCS[“Airy1830”,\n" +
-                "  DATUM[“Airy1830”,\n" +
-                "    SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]],\n" +
-                "    PRIMEM[“Greenwich”, 0.0],\n" +
-                "  UNIT[“degree”, 0.017453292519943295],\n" +
-                "  AXIS[“Longitude”, EAST],\n" +
-                "  AXIS[“Latitude”, NORTH]]", geographicCRS);
-        /*
-         * Defining conversion
-         */
-        method = copFactory.getOperationMethod("Transverse_Mercator");
-        parameters = method.getParameters().createValue();
-        parameters.parameter("semi_major")        .setValue(ellipsoid.getSemiMajorAxis());
-        parameters.parameter("semi_minor")        .setValue(ellipsoid.getSemiMinorAxis());
-        parameters.parameter("central_meridian")  .setValue(     49);
-        parameters.parameter("latitude_of_origin").setValue(     -2);
-        parameters.parameter("false_easting")     .setValue( 400000);
-        parameters.parameter("false_northing")    .setValue(-100000);
-        projection = new DefaultConversion(name("GBN grid"), method, null, parameters);
-        /*
-         * Projected coordinate reference system
-         */
-        easting      =  csFactory.createCoordinateSystemAxis(name("Easting"),  "x", AxisDirection.EAST,  linearUnit);
-        northing     =  csFactory.createCoordinateSystemAxis(name("Northing"), "y", AxisDirection.NORTH, linearUnit);
-        projectedCS  =  csFactory.createCartesianCS(name("Cartesian"), easting, northing);
-        projectedCRS = crsFactory.createProjectedCRS(name("Great_Britian_National_Grid"), geographicCRS, projection, projectedCS);
-        assertWktEquals(Convention.WKT1,
-                "PROJCS[“Great_Britian_National_Grid”,\n" +
-                "  GEOGCS[“Airy1830”,\n" +
-                "    DATUM[“Airy1830”,\n" +
-                "      SPHEROID[“Airy1830”, 6377563.396, 299.3249753150345]],\n" +
-                "      PRIMEM[“Greenwich”, 0.0],\n" +
-                "    UNIT[“degree”, 0.017453292519943295],\n" +
-                "    AXIS[“Longitude”, EAST],\n" +
-                "    AXIS[“Latitude”, NORTH]],\n" +
-                "  PROJECTION[“Transverse_Mercator”, AUTHORITY[“EPSG”, “9807”]],\n" +
-                "  PARAMETER[“latitude_of_origin”, -2.0],\n" +
-                "  PARAMETER[“central_meridian”, 49.0],\n" +
-                "  PARAMETER[“scale_factor”, 1.0],\n" +
-                "  PARAMETER[“false_easting”, 400000.0],\n" +
-                "  PARAMETER[“false_northing”, -100000.0],\n" +
-                "  UNIT[“metre”, 1],\n" +
-                "  AXIS[“Easting”, EAST],\n" +
-                "  AXIS[“Northing”, NORTH]]", projectedCRS);
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index 87465ab..cf92857 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -84,7 +84,6 @@
  * @module
  */
 @DependsOn({
-    org.apache.sis.referencing.factory.GeodeticObjectFactoryTest.class,
     org.apache.sis.referencing.factory.AuthorityFactoryProxyTest.class,
     org.apache.sis.referencing.factory.IdentifiedObjectFinderTest.class
 })
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
index 8ffed08..643aff8 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
@@ -121,8 +121,7 @@
      */
     private static InstallationScriptProvider getScripts() throws IOException {
         final InstallationScriptProvider scripts = new InstallationScriptProvider.Default(null);
-        assumeTrue("EPSG scripts not found in Databases/ExternalSources directory.",
-                scripts.getAuthorities().contains(Constants.EPSG));
+        assumeTrue(scripts.getAuthorities().contains(Constants.EPSG));
         return scripts;
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html
index cf5a089..1aa0ff7 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/epsg/package.html
@@ -131,7 +131,7 @@
 mvn clean install
 export CLASSPATH=~/.m2/repository/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar
 export CLASSPATH=$PWD/core/sis-metadata/target/test-classes:$CLASSPATH
-export CLASSPATH=$PWD/target/binaries/sis-referencing-2.0-SNAPSHOT.jar:$CLASSPATH
+export CLASSPATH=$PWD/target/binaries/sis-referencing-1.1-SNAPSHOT.jar:$CLASSPATH
 export CLASSPATH=$PWD/core/sis-metadata/target/test-classes:$CLASSPATH
 export CLASSPATH=$PWD/core/sis-referencing/target/test-classes:$CLASSPATH
 cd <i>&lt;path to local copy of <a href="http://svn.apache.org/repos/asf/sis/data/non-free/">http://svn.apache.org/repos/asf/sis/data/non-free/</a>&gt;</i>
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
index b381f62..c3fb178 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
@@ -57,7 +57,7 @@
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.Assert;
+import org.apache.sis.test.Assert;
 import org.junit.BeforeClass;
 import org.junit.AfterClass;
 import org.junit.Test;
@@ -300,6 +300,7 @@
         if (targetCRS.getCoordinateSystem().getDimension() == 2) {
             target = TestUtilities.dropLastDimensions(target, 3, 2);
         }
+        tolerance = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(source, target);
         validate();
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
index 3bf1def..c8954d4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationRegistryTest.java
@@ -18,10 +18,10 @@
 
 import java.util.List;
 import java.text.ParseException;
-import org.opengis.metadata.Identifier;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.SingleOperation;
@@ -114,7 +114,7 @@
      */
     public CoordinateOperationRegistryTest() throws FactoryException {
         crsFactory = CRS.getAuthorityFactory("EPSG");
-        assumeTrue("EPSG factory required.", crsFactory instanceof CoordinateOperationAuthorityFactory);
+        assumeTrue(crsFactory instanceof CoordinateOperationAuthorityFactory);
         registry = new CoordinateOperationRegistry((CoordinateOperationAuthorityFactory) crsFactory, factory, null);
     }
 
@@ -290,6 +290,7 @@
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
         λDimension = new int[] {1};
+        tolerance  = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(new double[] {54.271680278,  0.098269657, 20.00},      // in grads east of Paris
                         new double[] {48.844443528,  2.424952028, 63.15});     // in degrees east of Greenwich
         validate();
@@ -325,6 +326,7 @@
         zTolerance = Formulas.LINEAR_TOLERANCE;
         zDimension = new int[] {2};
         λDimension = new int[] {1};
+        tolerance  = zTolerance; // Because GeoAPI 3.0 does not distinguish z axis from other axes (fixed in GeoAPI 3.1).
         verifyTransform(new double[] {0.088442691, 48.844512250, 20.00},      // in degrees east of Paris
                         new double[] {2.424952028, 48.844443528, 63.15});     // in degrees east of Greenwich
         validate();
@@ -380,7 +382,7 @@
     private static void assertEpsgNameWithoutIdentifierEqual(final String name, final IdentifiedObject object) {
         assertNotNull(name, object);
         assertEquals("name", name, object.getName().getCode());
-        for (final Identifier id : object.getIdentifiers()) {
+        for (final ReferenceIdentifier id : object.getIdentifiers()) {
             assertFalse("EPSG identifier not allowed for modified objects.", "EPSG".equalsIgnoreCase(id.getCodeSpace()));
         }
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
index ef0aa2f..cdb1ec0 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultOperationMethodTest.java
@@ -21,6 +21,7 @@
 import org.opengis.metadata.Identifier;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.util.ComparisonMode;
@@ -65,7 +66,7 @@
     {
         final Map<String,Object> properties = new HashMap<>(8);
         assertNull(properties.put(OperationMethod.NAME_KEY, method));
-        assertNull(properties.put(Identifier.CODESPACE_KEY, "EPSG"));
+        assertNull(properties.put(ReferenceIdentifier.CODESPACE_KEY, "EPSG"));
         assertNull(properties.put(Identifier.AUTHORITY_KEY, Citations.EPSG));
         /*
          * The parameter group for a Mercator projection is actually not empty, but it is not the purpose of
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
index aca3566..84c5141 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/DefaultPassThroughOperationTest.java
@@ -28,7 +28,7 @@
 import org.junit.Test;
 
 import static org.apache.sis.test.TestUtilities.getSingleton;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
index 189348d..800e646 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/builder/ResidualGridTest.java
@@ -24,7 +24,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
index 3632436..a8c6208 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
@@ -20,7 +20,7 @@
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.referencing.operation.matrix.Matrix4.SIZE;
 
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
index 48c3172..09710a6 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
@@ -23,7 +23,7 @@
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
index f29d1fa..a30c1cc 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
@@ -24,7 +24,6 @@
 import static java.lang.Double.NaN;
 
 // Test dependencies
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestUtilities;
@@ -74,7 +73,6 @@
 
         final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
         final AlbersEqualArea kernel = (AlbersEqualArea) getKernel();
         assertTrue("isSpherical", isSpherical(kernel));
@@ -123,7 +121,6 @@
 
         final double delta = toRadians(100.0 / 60) / 1852;                  // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
         final AlbersEqualArea kernel = (AlbersEqualArea) getKernel();
         assertFalse("isSpherical", isSpherical(kernel));
@@ -163,7 +160,6 @@
     @Test
     @DependsOnMethod("testEllipse")
     public void compareWithProj4() throws FactoryException, TransformException {
-        toleranceModifier = ToleranceModifier.PROJECTION;
         tolerance = Formulas.LINEAR_TOLERANCE;
 
         // Spherical case
@@ -215,7 +211,6 @@
                 0);         // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         verifyTransform(new double[] {0,        0,
                                       0,      +90,
                                       0,      -90},
@@ -246,7 +241,6 @@
                 200);       // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
         verifyInDomain(new double[] {-40, 10},                  // Minimal input coordinate values
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
index 96a770d..adf1795 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
@@ -194,13 +194,6 @@
                 return projection.dy_dφ(sinφ, cos(φ)) * transform(φ);
             }
         };
-        isInverseTransformSupported = false;
-        derivativeDeltas = new double[] {2E-8};
-        tolerance = 1E-7;
-        verifyInDomain(new double[] {-89 * (PI/180)},                  // Minimal value to test.
-                       new double[] {+89 * (PI/180)},                  // Maximal value to test.
-                       new int[]    {100},                             // Number of points to test.
-                       TestUtilities.createRandomNumberGenerator());
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
index 4998d6e..5000887 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/CylindricalEqualAreaTest.java
@@ -18,7 +18,6 @@
 
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualArea;
 import org.apache.sis.internal.referencing.provider.LambertCylindricalEqualAreaSpherical;
@@ -72,7 +71,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222638.98;             // Test point from Proj.4.
@@ -102,7 +100,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222390.10;             // Anti-regression values (not from an external source).
@@ -135,7 +132,6 @@
                 0,          // False easting
                 0);         // False northing
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double λ = 2;
         final double φ = 1;
         final double x = 222390.10;             // Anti-regression values (not from an external source).
@@ -166,7 +162,6 @@
                 200);       // False northing
 
         tolerance = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
         final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
         derivativeDeltas = new double[] {delta, delta};
         verifyInDomain(CoordinateDomain.GEOGRAPHIC_SAFE, 0);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
index 7ccb2c3..31155dd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
@@ -26,7 +26,6 @@
 import org.apache.sis.internal.referencing.provider.LambertConformal2SP;
 import org.apache.sis.internal.referencing.provider.LambertConformalWest;
 import org.apache.sis.internal.referencing.provider.LambertConformalBelgium;
-import org.apache.sis.internal.referencing.provider.LambertConformalMichigan;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.internal.util.DoubleDouble;
@@ -39,6 +38,10 @@
 import static java.lang.Double.*;
 import static org.apache.sis.test.Assert.*;
 
+// Branch-specific imports
+import static org.junit.Assume.assumeTrue;
+import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
+
 
 /**
  * Tests the {@link LambertConicConformal} class. We test using various values of the latitude of origin.
@@ -183,8 +186,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     @DependsOnMethod({"testSpecialLatitudes", "testDerivative"})
@@ -198,8 +199,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     @DependsOnMethod("testLambertConicConformal1SP")
@@ -213,8 +212,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformal1SP()
      */
     @Test
     @DependsOnMethod({"testLambertConicConformal2SP", "verifyBelgeConstant"})
@@ -228,13 +225,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testLambertConicConformalMichigan()
      */
     @Test
     @DependsOnMethod("testLambertConicConformal2SP")
     public void testLambertConicConformalMichigan() throws FactoryException, TransformException {
-        createGeoApiTest(new LambertConformalMichigan()).testLambertConicConformalMichigan();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
index 0385c7b..8c0b62b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.referencing.ParameterizedTransformTest;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.referencing.provider.MapProjection;
@@ -79,8 +78,8 @@
      * @param  provider  the provider of the projection to test.
      * @return the GeoAPI test class using the given provider.
      */
-    static ParameterizedTransformTest createGeoApiTest(final MapProjection provider) {
-        return new ParameterizedTransformTest(new MathTransformFactoryMock(provider));
+    static ParameterizedTransformTestMock createGeoApiTest(final MapProjection provider) {
+        return new ParameterizedTransformTestMock(new MathTransformFactoryMock(provider));
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
index 90c9422..d6e95ff 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorTest.java
@@ -22,10 +22,8 @@
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.referencing.provider.Mercator1SP;
 import org.apache.sis.internal.referencing.provider.Mercator2SP;
-import org.apache.sis.internal.referencing.provider.MercatorSpherical;
 import org.apache.sis.internal.referencing.provider.PseudoMercator;
 import org.apache.sis.internal.referencing.provider.MillerCylindrical;
-import org.apache.sis.internal.referencing.provider.RegionalMercator;
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -36,6 +34,10 @@
 import static org.opengis.test.Assert.*;
 import static org.apache.sis.referencing.operation.projection.ConformalProjectionTest.LN_INFINITY;
 
+// Branch-specific imports
+import static org.junit.Assume.assumeTrue;
+import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
+
 
 /**
  * Tests the {@link Mercator} projection.
@@ -179,8 +181,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercator1SP()
      */
     @Test
     @DependsOnMethod({"testSpecialLatitudes", "testDerivative"})
@@ -194,8 +194,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercator2SP()
      */
     @Test
     @DependsOnMethod("testMercator1SP")
@@ -209,13 +207,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercatorVariantC()
      */
     @Test
     @DependsOnMethod("testMercator2SP")
     public void testRegionalMercator() throws FactoryException, TransformException {
-        createGeoApiTest(new RegionalMercator()).testMercatorVariantC();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
@@ -224,13 +220,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMercatorSpherical()
      */
     @Test
     @DependsOnMethod("testMercator1SP")
     public void testMercatorSpherical() throws FactoryException, TransformException {
-        createGeoApiTest(new MercatorSpherical()).testMercatorSpherical();
+        assumeTrue(PENDING_NEXT_GEOAPI_RELEASE);   // Test not available in GeoAPI 3.0
     }
 
     /**
@@ -239,8 +233,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPseudoMercator()
      */
     @Test
     @DependsOnMethod("testMercatorSpherical")
@@ -254,8 +246,6 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testMiller()
      */
     @Test
     @DependsOnMethod("testMercator1SP")
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
index 5312864..c3b2486 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NormalizedProjectionTest.java
@@ -16,7 +16,7 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import org.opengis.test.referencing.TransformTestCase;
+import org.apache.sis.referencing.operation.transform.TransformTestCase;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
index 67e8d70..0633419 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueMercatorTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.operation.TransformException;
@@ -78,34 +77,4 @@
         verifyDerivative(toRadians(15), toRadians(40));
         verifyDerivative(toRadians(10), toRadians(60));
     }
-
-    /**
-     * Tests with an azimuth of 90°.
-     *
-     * @throws TransformException if an error occurred while converting a point.
-     */
-    @Test
-    public void testAzimuth90() throws TransformException {
-        tolerance = 1E-9;
-        transform = create(10, 20, 90);
-        validate();
-
-        final double delta = toRadians(100.0 / 60) / 1852;      // Approximately 100 metres.
-        derivativeDeltas = new double[] {delta, delta};
-        verifyInverse(toRadians(15), toRadians(25));
-    }
-
-    /**
-     * Tests the <cite>"Hotine Oblique Mercator (variant B)"</cite> (EPSG:9815) projection method.
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testHotineObliqueMercator()
-     */
-    @Test
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new ObliqueMercatorCenter()).testHotineObliqueMercator();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
index a4050df..78c3fcf 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ObliqueStereographicTest.java
@@ -24,7 +24,6 @@
 import org.opengis.util.FactoryException;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.operation.transform.ContextualParameters;
-import org.apache.sis.referencing.operation.matrix.Matrix2;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.measure.Units;
@@ -33,7 +32,7 @@
 import org.junit.Test;
 
 import static java.lang.StrictMath.*;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -211,21 +210,6 @@
     }
 
     /**
-     * Tests the <cite>Oblique Stereographic</cite> case (EPSG:9809).
-     * This test is defined in GeoAPI conformance test suite.
-     *
-     * @throws FactoryException if an error occurred while creating the map projection.
-     * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testObliqueStereographic()
-     */
-    @Test
-    @DependsOnMethod({"testTransform", "testInverseTransform"})
-    public void testObliqueStereographic() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.ObliqueStereographic()).testObliqueStereographic();
-    }
-
-    /**
      * Verifies the consistency of spherical formulas with the elliptical formulas.
      * This test transforms the point given in the EPSG guide and takes the result
      * of the elliptical implementation as a reference.
@@ -337,9 +321,7 @@
         final Matrix derivative = spherical.transform(srcPts, 0, null, 0, true);
 
         tolerance = 1E-12;
-        assertMatrixEquals("Spherical derivative", reference, derivative,
-                new Matrix2(tolerance, tolerance,
-                            tolerance, tolerance));
+        assertMatrixEquals("Spherical derivative", reference, derivative, tolerance);
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java
new file mode 100644
index 0000000..1588401
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ParameterizedTransformTestMock.java
@@ -0,0 +1,80 @@
+/*
+ * 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.referencing.operation.projection;
+
+import org.opengis.referencing.operation.MathTransformFactory;
+
+import static org.junit.Assume.*;
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+ * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.6
+ * @module
+ */
+final class ParameterizedTransformTestMock {
+    ParameterizedTransformTestMock(MathTransformFactory factory) {
+        // See GeoAPI 3.1 for real construction.
+    }
+
+    public void testMercator1SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testMercator2SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testPseudoMercator() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testMiller() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformal1SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformal2SP() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    public void testLambertConicConformalBelgium() {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
index 4d99553..0367236 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java
@@ -120,12 +120,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicA()
      */
     @Test
     public void testPolarStereographicA() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicA()).testPolarStereographicA();
+        new PolarStereographicA();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -134,12 +132,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicB()
      */
     @Test
     public void testPolarStereographicB() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicB()).testPolarStereographicB();
+        new PolarStereographicB();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -148,12 +144,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicC()
      */
     @Test
     public void testPolarStereographicC() throws FactoryException, TransformException {
-        createGeoApiTest(new PolarStereographicC()).testPolarStereographicC();
+        new PolarStereographicC();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
index 9f1cbe9..86ed32b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolyconicTest.java
@@ -140,16 +140,4 @@
         verifyDerivative( -56, 50);
         verifyDerivative( -20, 47);
     }
-
-    /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException   if the transform can not be created.
-     * @throws TransformException if an error occurred while projecting a point.
-     */
-    @Test
-    @DependsOnMethod("testEllipsoidal")
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.Polyconic()).testPolyconic();
-    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
index 75aa143..0969170 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SatelliteTrackingTest.java
@@ -87,16 +87,7 @@
          * coordinate values. The transform results are between 0 and 1, while the inverse transform results
          * are between -90° and 90°, which is an increase in magnitude close to ×100.
          */
-        toleranceModifier = (tolerance, coordinate, mode) -> {
-            switch (mode) {
-                case INVERSE_TRANSFORM: {
-                    for (int i=0; i<tolerance.length; i++) {
-                        tolerance[i] *= 50;
-                    }
-                    break;
-                }
-            }
-        };
+        tolerance *= 50;    // Finer tolerance setting require GeoAPI 3.1.
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
index 164d1ac..cd5c4d6 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
@@ -37,7 +37,6 @@
 import static java.lang.StrictMath.toRadians;
 import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.internal.referencing.provider.TransverseMercator.LATITUDE_OF_ORIGIN;
-import org.opengis.test.CalculationType;
 
 
 /**
@@ -71,12 +70,10 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercator()
      */
     @Test
     public void testTransverseMercator() throws FactoryException, TransformException {
-        createGeoApiTest(new org.apache.sis.internal.referencing.provider.TransverseMercator()).testTransverseMercator();
+        new org.apache.sis.internal.referencing.provider.TransverseMercator();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -85,13 +82,11 @@
      *
      * @throws FactoryException if an error occurred while creating the map projection.
      * @throws TransformException if an error occurred while projecting a coordinate.
-     *
-     * @see org.opengis.test.referencing.ParameterizedTransformTest#testTransverseMercatorSouthOrientated()
      */
     @Test
     @DependsOnMethod("testTransverseMercator")
     public void testTransverseMercatorSouthOrientated() throws FactoryException, TransformException {
-        createGeoApiTest(new TransverseMercatorSouth()).testTransverseMercatorSouthOrientated();
+        new TransverseMercatorSouth();  // Test creation only, as GeoAPI 3.0 did not yet had the test method.
     }
 
     /**
@@ -224,7 +219,7 @@
                     else if (longitude <= 76) tolerance = 30;
                     else                      tolerance = 1000;
                     transform.transform(source, 0, source, 0, 1);
-                    assertCoordinateEquals(line, target, source, reader.getLineNumber(), CalculationType.DIRECT_TRANSFORM);
+                    assertCoordinateEquals(line, target, source, reader.getLineNumber(), false);
                 }
             }
         }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
index eafcca3..f2f35dd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import org.opengis.test.CalculationType;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
@@ -61,8 +60,6 @@
      *
      * @throws FactoryException if an error occurred while creating a transform step.
      * @throws TransformException if a transformation failed.
-     *
-     * @see MolodenskyTransformTest#compareWithGeocentricTranslation()
      */
     @Test
     public void compareWithReferenceImplementation() throws FactoryException, TransformException {
@@ -77,7 +74,7 @@
         for (int i=0; i<sources.length; i+=2) {
             transform.transform(sources, i, targets,  0, 1);        // Use the overridden method.
             transform.transform(sources, i, expected, 0, false);    // Use the MolodenskyTransform method.
-            assertCoordinateEquals("transform", expected, targets, 0, CalculationType.DIRECT_TRANSFORM);
+            assertCoordinateEquals("transform", expected, targets, 0, false);
         }
     }
 
@@ -113,7 +110,6 @@
     public void testConsistency() throws FactoryException, TransformException {
         transform = create();
         validate();
-        isDerivativeSupported = false;
         isInverseTransformSupported = false;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, -1941624852762631518L);
         /*
@@ -122,7 +118,6 @@
          * we are not completely sure that there is no bug in derivative formula).
          */
         tolerance *= 10;
-        isDerivativeSupported = true;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 4350796528249596132L);
     }
 
@@ -138,7 +133,6 @@
     public void testSerialization() throws FactoryException, TransformException {
         transform = assertSerializedEquals(create());
         validate();
-        isDerivativeSupported = false;
         isInverseTransformSupported = false;
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 3534897149662911157L);
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
index fc20bcd..533503d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToPolarTest.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.operation.TransformException;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
index 58ec3fc..21bcaaf 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CartesianToSphericalTest.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.operation.TransformException;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
index eac37f4..a124c04 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ContextualParametersTest.java
@@ -31,7 +31,7 @@
 
 import static java.lang.StrictMath.PI;
 import static java.lang.StrictMath.toRadians;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
index cc4ab05..b159f99 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java
@@ -48,7 +48,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
index f6c2339..164cecd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
@@ -33,7 +33,6 @@
 import static java.lang.StrictMath.toRadians;
 
 // Test dependencies
-import org.opengis.test.ToleranceModifier;
 import org.apache.sis.internal.referencing.provider.GeocentricTranslationTest;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
@@ -116,6 +115,7 @@
         tolerance  = GeocentricTranslationTest.precision(1);        // Required precision for (λ,φ)
         zTolerance = Formulas.LINEAR_TOLERANCE / 2;                 // Required precision for h
         zDimension = new int[] {2};                                 // Dimension of h where to apply zTolerance
+        tolerance  = 1E-4;                                          // Other SIS branches use a stricter threshold.
         verifyTransform(GeocentricTranslationTest.samplePoint(2),   // X = 3771793.968,  Y = 140253.342,  Z = 5124304.349 metres
                         GeocentricTranslationTest.samplePoint(1));  // 53°48'33.820"N, 02°07'46.380"E, 73.00 metres
     }
@@ -135,7 +135,7 @@
         final double delta = toRadians(100.0 / 60) / 1852;          // Approximately 100 metres
         derivativeDeltas  = new double[] {delta, delta, 100};       // (Δλ, Δφ, Δh)
         tolerance         = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
+//      toleranceModifier = ToleranceModifier.PROJECTION;
         createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), true);
         verifyInDomain(CoordinateDomain.GEOGRAPHIC, 306954540);
     }
@@ -157,8 +157,8 @@
         final double delta = toRadians(100.0 / 60) / 1852;
         derivativeDeltas  = new double[] {delta, delta, 100};
         tolerance         = Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifier.PROJECTION;
-        verifyInverse(40, 30, 10000);
+//      toleranceModifier = ToleranceModifier.PROJECTION;
+        verifyInverse(new double[] {40, 30, 10000});
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
index 5a29fc4..2859fe5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ExponentialTransform1DTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.transform;
 
-import java.util.EnumSet;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
 import static java.lang.StrictMath.*;
@@ -27,11 +26,6 @@
 import org.apache.sis.test.DependsOnMethod;
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
-import org.opengis.test.ToleranceModifiers;
-
 
 /**
  * Tests the {@link ExponentialTransform1D} class. Note that this is closely related to
@@ -66,9 +60,8 @@
      * Creates a new test case.
      */
     public ExponentialTransform1DTest() {
-        tolerance         = 1E-14;
-        toleranceModifier = ToleranceModifier.RELATIVE;
-        derivativeDeltas  = new double[] {0.001};
+        tolerance         = 1E-5; // Tolerance is much smaller on other branches.
+//      toleranceModifier = ToleranceModifier.RELATIVE; // Not available on GeoAPI 3.0.
     }
 
     /**
@@ -104,7 +97,6 @@
             expected[i] = value;
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                      // Test at a hard-coded point.
     }
 
     /**
@@ -138,13 +130,6 @@
     private void testAffinePostConcatenation(final double base) throws TransformException {
         transform = MathTransforms.concatenate(ExponentialTransform1D.create(base, SCALE),
                 LinearTransform1D.create(C1, C0));
-        /*
-         * The inverse transforms in this test case have high rounding errors.
-         * Those errors are low for values close to zero, and increase fast for higher values.
-         * We scale the default tolerance (1E-14) by 1E+8, which give us a tolerance of 1E-6.
-         */
-        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
-                ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1E+8));
         run(ConcatenatedTransformDirect1D.class, base, SCALE, false, true);
     }
 
@@ -154,10 +139,6 @@
     private void testAffineConcatenations(final double base) throws TransformException {
         final LinearTransform1D linear = LinearTransform1D.create(C1, C0);
         transform = MathTransforms.concatenate(linear, ExponentialTransform1D.create(base, SCALE), linear);
-
-        // See testAffinePostConcatenation for an explanation about why we relax tolerance.
-        toleranceModifier = ToleranceModifiers.concatenate(toleranceModifier,
-                ToleranceModifiers.scale(EnumSet.of(CalculationType.INVERSE_TRANSFORM), 1E+8));
         run(ConcatenatedTransformDirect1D.class, base, SCALE, true, true);
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
index 08f5c04..2ab9472 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/InterpolatedMolodenskyTransformTest.java
@@ -33,8 +33,8 @@
  * instead than the real geocentric translation is verified by the following tests:
  *
  * <ul>
- *   <li>{@link GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()}</li>
- *   <li>{@link MolodenskyTransformTest#testFranceGeocentricInterpolationPoint()}</li>
+ *   <li>{@code GeocentricTranslationTest.testFranceGeocentricInterpolationPoint()}</li>
+ *   <li>{@code MolodenskyTransformTest.testFranceGeocentricInterpolationPoint()}</li>
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
index 4b3ecfc..a6e509c 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1DTest.java
@@ -20,7 +20,6 @@
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
 import org.junit.Test;
 
@@ -36,7 +35,7 @@
  * @since   0.7
  * @module
  */
-public final strictfp class LinearInterpolator1DTest extends TransformTestCase {
+public final strictfp class LinearInterpolator1DTest extends MathTransformTestCase {
     /**
      * The values of the <i>y=f(x)</i> function to test.
      */
@@ -255,7 +254,6 @@
         verifyTransform(new double[] {0,  1, 0.5, -0.5, -1, -2,   3, 3.5,   4,   5},        // Values to transform.
                         new double[] {5, 10, 7.5,  2.5,  0, -5, 250, 325, 400, 550});       // Expected results.
 
-        verifyConsistency(0f, 1f, 0.5f, -0.5f, -1f, -2f, 3f, 3.5f, 4f, 5f);
         verifyDerivative(0.25);     // Interpolation (verified by safety)
         verifyDerivative(-8);       // Extrapolation
         verifyDerivative( 8);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
index 20d1aa5..0168262 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LinearTransformTest.java
@@ -28,6 +28,9 @@
 import org.apache.sis.test.TestRunner;
 import static org.opengis.test.Assert.*;
 
+// Branch-dependent imports
+import org.junit.Ignore;
+
 
 /**
  * Tests various implementation of the {@link LinearTransform} interface by inheriting the tests defined
@@ -69,6 +72,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testIdentity1D() throws FactoryException, TransformException {
         super.testIdentity1D();
         assertInstanceOf("Unexpected implementation.", IdentityTransform1D.class, transform);
@@ -82,6 +86,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testIdentity2D() throws FactoryException, TransformException {
         super.testIdentity2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -95,6 +100,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testIdentity3D() throws FactoryException, TransformException {
         super.testIdentity3D();
         assertInstanceOf("Unexpected implementation.", IdentityTransform.class, transform);
@@ -108,6 +114,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testAxisSwapping2D() throws FactoryException, TransformException {
         super.testAxisSwapping2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -121,6 +128,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testSouthOrientated2D() throws FactoryException, TransformException {
         super.testSouthOrientated2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -134,6 +142,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testTranslatation2D() throws FactoryException, TransformException {
         super.testTranslatation2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -147,6 +156,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testUniformScale2D() throws FactoryException, TransformException {
         super.testUniformScale2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -160,6 +170,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testGenericScale2D() throws FactoryException, TransformException {
         super.testGenericScale2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -173,6 +184,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testRotation2D() throws FactoryException, TransformException {
         super.testRotation2D();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -186,6 +198,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testGeneral() throws FactoryException, TransformException {
         super.testGeneral();
         assertInstanceOf("Unexpected implementation.", AffineTransform2D.class, transform);
@@ -199,6 +212,7 @@
      */
     @Test
     @Override
+    @Ignore(MESSAGE)
     public void testDimensionReduction() throws FactoryException, TransformException {
         super.testDimensionReduction();
         assertInstanceOf("Unexpected implementation.", ProjectiveTransform.class, transform);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
index bd93fc3..b1fcdd8 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/LogarithmicTransform1DTest.java
@@ -26,9 +26,6 @@
 import org.apache.sis.test.DependsOnMethod;
 import static org.apache.sis.test.ReferencingAssert.*;
 
-// Branch-dependent imports
-import org.opengis.test.ToleranceModifier;
-
 
 /**
  * Tests the {@link LogarithmicTransform1D} class. Note that this is closely related to
@@ -55,9 +52,8 @@
      * Creates a new test case.
      */
     public LogarithmicTransform1DTest() {
-        tolerance         = 2E-14;
-        toleranceModifier = ToleranceModifier.RELATIVE;
-        derivativeDeltas  = new double[] {0.001};
+        tolerance         = 1E-5; // Tolerance is much smaller on other branches.
+//      toleranceModifier = ToleranceModifier.RELATIVE; // Not available on GeoAPI 3.0.
     }
 
     /**
@@ -95,7 +91,6 @@
             expected[i] = value;
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                          // Test at a hard-coded point.
     }
 
     /**
@@ -224,6 +219,5 @@
             expected[i] = ExponentialTransform1DTest.SCALE * pow(10, log(value) / lnBase + C0);
         }
         verifyTransform(values, expected);
-        verifyDerivative(2.5);                                  // Test at a hard-coded point.
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
index 37373fa..9ae7a42 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java
@@ -111,6 +111,13 @@
 
     /** Default implementation throws an exception. */
     @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) throws FactoryException {
+        throw new FactoryException(MESSAGE);
+    }
+
+    /** Default implementation throws an exception. */
+    @Override
     public MathTransform createFromWKT(String wkt) throws FactoryException {
         throw new FactoryException(MESSAGE);
     }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
index 62ee860..0be16de 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java
@@ -159,6 +159,18 @@
     /**
      * Unimplemented method.
      *
+     * @param  xml  ignored.
+     * @return never returned.
+     */
+    @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unimplemented method.
+     *
      * @param  wkt  ignored.
      * @return never returned.
      */
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index 00a7c59..98faed8 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@ -19,17 +19,14 @@
 import java.util.Random;
 import java.io.IOException;
 import java.io.UncheckedIOException;
-import org.opengis.util.Factory;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.metadata.Identifier;
 import org.apache.sis.parameter.Parameterized;
-import org.apache.sis.measure.Longitude;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.ArraysExt;
@@ -40,14 +37,10 @@
 
 // Test imports
 import org.opengis.test.Validators;
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.ReferencingAssert;
 import static org.opengis.test.Assert.*;
 
-// Branch-dependent imports
-import org.opengis.test.CalculationType;
-
 
 /**
  * Base class for tests of {@link AbstractMathTransform} implementations.
@@ -56,7 +49,6 @@
  * <p>Various assertion methods:</p>
  * <ul>
  *   <li>{@link #assertCoordinateEquals assertCoordinateEquals(…)}  — from GeoAPI</li>
- *   <li>{@link #assertMatrixEquals     assertMatrixEquals(…)}      — from GeoAPI</li>
  *   <li>{@link #assertParameterEquals  assertParameterEquals(…)}   — from Apache SIS</li>
  *   <li>{@link #assertWktEquals        assertWktEquals(…)}         — from Apache SIS</li>
  * </ul>
@@ -113,63 +105,6 @@
      * Creates a new test case.
      */
     protected MathTransformTestCase() {
-        this(new Factory[0]);
-    }
-
-    /**
-     * Creates a new test case which will use the given factories. Those factories will be given to
-     * {@link org.opengis.test.ImplementationDetails#configuration(Factory[])} in order to decide
-     * which tests should be enabled.
-     *
-     * @param factories  the factories to be used by the test.
-     */
-    protected MathTransformTestCase(final Factory... factories) {
-        super(factories);
-        /*
-         * Use 'zTolerance' threshold instead of 'tolerance' when comparing vertical coordinate values.
-         */
-        toleranceModifier = (final double[] tolerance, final DirectPosition coordinate, final CalculationType mode) -> {
-            if (mode != CalculationType.IDENTITY) {
-                final int i = forComparison(zDimension, mode);
-                if (i >= 0 && i < tolerance.length) {
-                    tolerance[i] = zTolerance;
-                }
-            }
-        };
-    }
-
-    /**
-     * Returns the value to use from the {@link #λDimension} or {@link zDimension} for the
-     * given comparison mode, or -1 if none.
-     */
-    @SuppressWarnings("fallthrough")
-    private static int forComparison(final int[] config, final CalculationType mode) {
-        if (config != null) {
-            switch (mode) {
-                case INVERSE_TRANSFORM: if (config.length >= 2) return config[1];       // Intentional fallthrough.
-                case DIRECT_TRANSFORM:  if (config.length >= 1) return config[0];
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Invoked by all {@code assertCoordinateEquals(…)} methods before two positions are compared.
-     * The SIS implementation ensures that longitude values are contained in the ±180° range,
-     * applying 360° shifts if needed.
-     *
-     * @param  expected  the expected coordinate values provided by the test case.
-     * @param  actual    the coordinate values computed by the {@linkplain #transform transform} being tested.
-     * @param  mode      indicates if the coordinates being compared are the result of a direct
-     *                   or inverse transform, or if strict equality is requested.
-     */
-    @Override
-    protected final void normalize(final DirectPosition expected, final DirectPosition actual, final CalculationType mode) {
-        final int i = forComparison(λDimension, mode);
-        if (i >= 0) {
-            expected.setOrdinate(i, Longitude.normalize(expected.getOrdinate(i)));
-            actual  .setOrdinate(i, Longitude.normalize(actual  .getOrdinate(i)));
-        }
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
index c8dce85..db41461 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformsTest.java
@@ -32,7 +32,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
index ddfecfe..0de4a1e 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MolodenskyTransformTest.java
@@ -17,24 +17,18 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.Arrays;
-import java.io.IOException;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.datum.Ellipsoid;
-import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.parameter.ParameterValueGroup;
 import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation;
-import org.apache.sis.internal.referencing.provider.AbridgedMolodensky;
 import org.apache.sis.internal.referencing.provider.Molodensky;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.math.StatisticsFormat;
-import org.apache.sis.math.Statistics;
 
 import static java.lang.StrictMath.*;
-import static org.apache.sis.internal.metadata.ReferencingServices.NAUTICAL_MILE;
 
 // Test dependencies
 import org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolationTest;
@@ -43,18 +37,13 @@
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestUtilities;
-import org.apache.sis.test.TestCase;
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
-import org.opengis.test.ToleranceModifiers;
-import org.opengis.test.referencing.ParameterizedTransformTest;
 import org.junit.Test;
 
 import static org.apache.sis.test.Assert.*;
 
 
 /**
- * Tests {@link MolodenskyTransform}. The {@link #compareWithGeocentricTranslation()}
+ * Tests {@link MolodenskyTransform}. The {@code compareWithGeocentricTranslation()}
  * method uses {@link EllipsoidToCentricTransform} as a reference implementation.
  * The errors compared to geocentric translations should not be greater than
  * approximately 1 centimetre.
@@ -85,65 +74,6 @@
     }
 
     /**
-     * Compares the Molodensky (non-abridged) transform with a geocentric translation.
-     * Molodensky is an approximation of geocentric translation, so we test here how good this approximation is.
-     * If {@link TestCase#VERBOSE} is {@code true}, then this method will print error statistics.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     * @throws IOException should never happen.
-     *
-     * @see #compareWithGeocentricTranslation()
-     */
-    @SuppressWarnings("fallthrough")
-    private void compareWithGeocentricTranslation(
-            final Ellipsoid source, final Ellipsoid target,
-            final double tX,   final double tY,   final double tZ,
-            final double xmin, final double ymin, final double zmin,
-            final double xmax, final double ymax, final double zmax)
-            throws FactoryException, TransformException, IOException
-    {
-        final MathTransform reference;
-        final MathTransformFactory factory = DefaultFactories.forBuildin(MathTransformFactory.class);
-        transform = MolodenskyTransform.createGeodeticTransformation(factory, source, true, target, true, tX, tY, tZ, false);
-        reference = GeocentricTranslationTest.createDatumShiftForGeographic3D(factory, source, target, tX, tY, tZ);
-        final float[] srcPts = verifyInDomain(
-                new double[] {xmin, ymin, zmin},
-                new double[] {xmax, ymax, zmax},
-                new int[]    {  10,   10,   10},
-                TestUtilities.createRandomNumberGenerator(103627524044558476L));
-        /*
-         * Transform the same input coordinates using Molodensky transform (actual) and using the reference
-         * implementation (expected). If we were asked to print statistics, compute them before to test the
-         * values since the statistics may be a useful information in case of problem.
-         */
-        final double[] actual   = new double[srcPts.length];
-        final double[] expected = new double[srcPts.length];
-        transform.transform(srcPts, 0, actual,   0, srcPts.length / 3);
-        reference.transform(srcPts, 0, expected, 0, srcPts.length / 3);
-        if (TestCase.VERBOSE) {
-            final Statistics[] stats = {
-                new Statistics("|Δλ| (~cm)"),
-                new Statistics("|Δφ| (~cm)"),
-                new Statistics("|Δh| (cm)")
-            };
-            for (int i=0; i<srcPts.length; i++) {
-                double Δ = actual[i] - expected[i];
-                final int j = i % stats.length;
-                switch (j) {
-                    case 0: Δ *= cos(toRadians(expected[i+1]));     // Fall through
-                    case 1: Δ *= 60 * NAUTICAL_MILE; break;         // Approximate conversion to metres
-                }
-                Δ *= 100;   // Conversion to centimetres.
-                stats[j].accept(abs(Δ));
-            }
-            StatisticsFormat.getInstance().format(stats, TestCase.out);
-        }
-        assertCoordinatesEqual("Comparison of Molodensky and geocentric translation", 3,
-                expected, 0, actual, 0, expected.length / 3, CalculationType.DIRECT_TRANSFORM);
-    }
-
-    /**
      * Creates a Molodensky transform for a datum shift from WGS84 to ED50.
      * Tolerance thresholds are also initialized.
      *
@@ -214,6 +144,7 @@
         final double[] sample   = GeocentricTranslationTest.samplePoint(1);
         final double[] expected = GeocentricTranslationTest.samplePoint(5);
         isInverseTransformSupported = false;
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         verifyTransform(sample, expected);
         /*
          * When testing the inverse transformation, we need to relax slightly
@@ -243,6 +174,7 @@
         final double[] sample   = GeocentricTranslationTest.samplePoint(1);
         final double[] expected = GeocentricTranslationTest.samplePoint(4);
         isInverseTransformSupported = false;
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         verifyTransform(sample, expected);
         /*
          * When testing the inverse transformation, we need to relax slightly
@@ -261,8 +193,6 @@
      *
      * @throws FactoryException if an error occurred while creating the transform.
      * @throws TransformException if transformation of a point failed.
-     *
-     * @see GeocentricTranslationTest#testFranceGeocentricInterpolationPoint()
      */
     @Test
     @DependsOnMethod("testMolodensky")
@@ -279,7 +209,7 @@
          * Code below is a copy-and-paste of GeocentricTranslationTest.testFranceGeocentricInterpolationPoint(),
          * but with the tolerance threshold increased. We do not let the error goes beyond 1 cm however.
          */
-        tolerance = min(Formulas.ANGULAR_TOLERANCE, FranceGeocentricInterpolationTest.ANGULAR_TOLERANCE * 6);
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Other SIS branches use a stricter threshold.
         final double[] source   = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(1), 3);
         final double[] expected = Arrays.copyOf(FranceGeocentricInterpolationTest.samplePoint(2), 3);
         expected[2] = 43.15;  // Anti-regression (this value is not provided in NTG_88 guidance note).
@@ -288,47 +218,6 @@
     }
 
     /**
-     * Compares the Molodensky (non-abridged) transforms with geocentric translations.
-     * Molodensky is an approximation of geocentric translation, so we test here how good this
-     * approximation is. This test performs the comparison for the following transformations:
-     *
-     * <ul>
-     *   <li>Transformation from NTF to RGF93. Those CRS are the source and target of <cite>"France geocentric
-     *       interpolation"</cite> (ESPG:9655). This test allows us to verify the accuracy documented in
-     *       {@link InterpolatedGeocentricTransform}.</li>
-     *   <li>(More areas may be added later).</li>
-     * </ul>
-     *
-     * If {@link TestCase#VERBOSE} is {@code true}, then this method will print error statistics.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     * @throws IOException should never happen.
-     *
-     * @see #testFranceGeocentricInterpolationPoint()
-     */
-    @Test
-    @DependsOnMethod("testFranceGeocentricInterpolationPoint")
-    public void compareWithGeocentricTranslation() throws FactoryException, TransformException, IOException {
-        /*
-         * Disable the test for inverse transformations because they are not the purpose of this test.
-         * Errors of inverse transformations are added to the error of forward transformations, which
-         * would force us to double the tolerance threshold.
-         */
-        isInverseTransformSupported = false;
-        tolerance         = 3*Formulas.LINEAR_TOLERANCE; // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
-        zTolerance        = 4*Formulas.LINEAR_TOLERANCE;
-        toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
-        compareWithGeocentricTranslation(HardCodedDatum.NTF.getEllipsoid(),   // Clarke 1880 (IGN)
-                                         CommonCRS.ETRS89.ellipsoid(),        // GRS 1980 ellipsoid
-                                         FranceGeocentricInterpolation.TX,
-                                         FranceGeocentricInterpolation.TY,
-                                         FranceGeocentricInterpolation.TZ,
-                                         -5.5, 41.0, -200,   // Geographic area of GR2DF97A datum shift grid.
-                                         10.0, 52.0, +200);
-    }
-
-    /**
      * Tests conversion of random points. The test is performed with the Molodensky transform,
      * not the abridged one, because the errors caused by the abridged Molodensky method are
      * too high for this test.
@@ -342,7 +231,7 @@
         create(false);
         tolerance  = Formulas.LINEAR_TOLERANCE * 3;     // To be converted in degrees by ToleranceModifier.GEOGRAPHIC
         zTolerance = Formulas.LINEAR_TOLERANCE * 2;
-        toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
+//      toleranceModifier = ToleranceModifiers.concatenate(ToleranceModifier.GEOGRAPHIC, toleranceModifier);
         verifyInDomain(new double[] {-179, -85, -500},
                        new double[] {+179, +85, +500},
                        new int[]    {   8,   8,    8},
@@ -377,18 +266,6 @@
     }
 
     /**
-     * Runs the test defined in the GeoAPI-conformance module.
-     *
-     * @throws FactoryException if an error occurred while creating a transform step.
-     * @throws TransformException if a transformation failed.
-     */
-    @Test
-    @DependsOnMethod("testProvider")
-    public void runGeoapiTest() throws FactoryException, TransformException {
-        new ParameterizedTransformTest(new MathTransformFactoryMock(new AbridgedMolodensky())).testAbridgedMolodensky();
-    }
-
-    /**
      * Verifies that creating a Molodensky operation with same source and target ellipsoid and zero translation
      * results in an identity affine transform.
      *
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
index 1f34339..053d7fd 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
@@ -32,11 +32,10 @@
 import org.junit.Test;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOn;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.test.CalculationType;
-import org.opengis.test.ToleranceModifier;
+// (all imports removed)
 
 
 /**
@@ -221,21 +220,19 @@
          * Now process to the transform and compares the results with the expected ones.
          */
         tolerance         = 0;          // Results should be strictly identical because we used the same inputs.
-        toleranceModifier = null;
         final double[] transformedData = new double[StrictMath.max(sourceDim, targetDim) * numPts];
         transform.transform(passthroughData, 0, transformedData, 0, numPts);
         assertCoordinatesEqual("PassThroughTransform results do not match the results computed by this test.",
-                targetDim, expectedData, 0, transformedData, 0, numPts, CalculationType.DIRECT_TRANSFORM);
+                targetDim, expectedData, 0, transformedData, 0, numPts, false);
         /*
          * Test inverse transform.
          */
         if (isInverseTransformSupported) {
             tolerance         = 1E-8;
-            toleranceModifier = ToleranceModifier.RELATIVE;
             Arrays.fill(transformedData, Double.NaN);
             transform.inverse().transform(expectedData, 0, transformedData, 0, numPts);
             assertCoordinatesEqual("Inverse of PassThroughTransform do not give back the original data.",
-                    sourceDim, passthroughData, 0, transformedData, 0, numPts, CalculationType.INVERSE_TRANSFORM);
+                    sourceDim, passthroughData, 0, transformedData, 0, numPts, false);
         }
         /*
          * Verify the consistency between different 'transform(…)' methods.
@@ -243,16 +240,6 @@
         final float[] sourceAsFloat = ArraysExt.copyAsFloats(passthroughData);
         final float[] targetAsFloat = verifyConsistency(sourceAsFloat);
         assertEquals("Unexpected length of transformed array.", expectedData.length, targetAsFloat.length);
-        /*
-         * We use a relatively high tolerance threshold because result are
-         * computed using inputs stored as float values.
-         */
-        if (transform instanceof LinearTransform) {
-            tolerance         = 1E-4;
-            toleranceModifier = ToleranceModifier.RELATIVE;
-            assertCoordinatesEqual("PassThroughTransform.transform(…) variants produce inconsistent results.",
-                    sourceDim, expectedData, 0, targetAsFloat, 0, numPts, CalculationType.DIRECT_TRANSFORM);
-        }
     }
 
     /**
@@ -283,7 +270,7 @@
                 0, 0, 1, 0, 0, 0, 0,
                 0, 0, 0, 1, 0, 0, 0,
                 0, 0, 0, 0, 0, 1, 0,
-                0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), null);
+                0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), 0);
         /*
          * The number of pass-through dimensions have decreased from 2 to 1 on both sides of the sub-transform.
          */
@@ -299,6 +286,6 @@
                 1, 0, 0, 0, 0, 0,
                 0, 0, 0, 1, 0, 0,
                 0, 0, 0, 0, 1, 0,
-                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), null);
+                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), 0);
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
index d0ee8b7..223b523 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PolarToCartesianTest.java
@@ -24,7 +24,6 @@
 import static java.lang.StrictMath.*;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
index bd86e1c..52e468d 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ProjectiveTransformTest.java
@@ -21,7 +21,6 @@
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.MathTransformFactory;
-import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.internal.referencing.provider.Affine;
@@ -33,11 +32,14 @@
 import org.apache.sis.test.DependsOn;
 import org.junit.runner.RunWith;
 import org.junit.After;
-import org.junit.Test;
 import static org.opengis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.test.referencing.AffineTransformTest;
+import org.junit.Test;
+import org.junit.Ignore;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.test.referencing.TransformTestCase;
 
 
 /**
@@ -53,19 +55,29 @@
  */
 @RunWith(TestRunner.class)
 @DependsOn({AbstractMathTransformTest.class, ScaleTransformTest.class})
-public strictfp class ProjectiveTransformTest extends AffineTransformTest {
+public strictfp class ProjectiveTransformTest extends TransformTestCase {
+    /**
+     * The factory to use for creating linear transforms.
+     */
+    private final MathTransformFactory mtFactory;
+
+    /**
+     * The matrix for the tested transform.
+     */
+    private Matrix matrix;
+
     /**
      * For {@link LinearTransformTest} constructor only.
      */
     ProjectiveTransformTest(final MathTransformFactory factory) {
-        super(factory);
+        mtFactory = factory;
     }
 
     /**
      * Creates a new test suite.
      */
     public ProjectiveTransformTest() {
-        super(new MathTransformFactoryBase() {
+        this(new MathTransformFactoryBase() {
             @Override
             public MathTransform createAffineTransform(final Matrix matrix) {
                 final ProjectiveTransform pt;
@@ -88,7 +100,10 @@
     }
 
     /*
-     * Inherit all the tests from GeoAPI:
+     * GeoAPI 3.1 defines the following tests. However since those tests are not available
+     * in GeoAPI 3.0, we put empty placeholder. For running the real test, see for example
+     * the JDK6 branch of Apache SIS.
+     *
      *    - testIdentity1D()
      *    - testIdentity2D()
      *    - testIdentity3D()
@@ -102,6 +117,64 @@
      *    - testDimensionReduction()
      */
 
+    static final String MESSAGE = "This test is not available in GeoAPI 3.0. "
+            + "See Apache SIS JDK6, JDK7 or JDK8 branch for the actual tests.";
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testIdentity1D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testIdentity2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testIdentity3D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testAxisSwapping2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testSouthOrientated2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testTranslatation2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testUniformScale2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testGenericScale2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testRotation2D() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testGeneral() throws FactoryException, TransformException {
+    }
+
+    @Test
+    @Ignore(MESSAGE)
+    public void testDimensionReduction() throws FactoryException, TransformException {
+    }
+
     /**
      * Tests {@link ProjectiveTransform#optimize()}. In particular this method verifies that a non-square matrix
      * that looks like diagonal is not confused with a real diagonal matrix.
@@ -121,14 +194,14 @@
         });
         transform = new ProjectiveTransform(matrix).optimize();
         assertInstanceOf("Non-diagonal matrix shall not be handled by ScaleTransform.", ProjectiveTransform.class, transform);
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
+        verifyConsistency(new float[] {1, 2, 3,   -3, -2, -1});
         /*
          * Remove the "problematic" row. The new transform should now be optimizable.
          */
         matrix = ((MatrixSIS) matrix).removeRows(3, 4);
         transform = new ProjectiveTransform(matrix).optimize();
         assertInstanceOf("Diagonal matrix should be handled by a specialized class.", ScaleTransform.class, transform);
-        verifyConsistency(1, 2, 3,   -3, -2, -1);
+        verifyConsistency(new float[] {1, 2, 3,   -3, -2, -1});
     }
 
     /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
index fe9e654..3212123 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/ScaleTransformTest.java
@@ -25,7 +25,7 @@
 
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.Assert;
+import org.apache.sis.test.Assert;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
index c49dd50..f2e1a14 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SpecializableTransformTest.java
@@ -133,7 +133,6 @@
     public void testForwardConsistency() throws TransformException {
         transform = create(false);
         tolerance = 1E-14;
-        isDerivativeSupported = false;          // Actually supported, but our test transform has discontinuities.
         verifyInDomain(CoordinateDomain.RANGE_10, -672445632505596619L);
     }
 
@@ -150,7 +149,6 @@
     public void testInverseConsistency() throws TransformException {
         transform = create(false).inverse();
         tolerance = 1E-12;
-        isDerivativeSupported = false;          // Actually supported, but our test transform has discontinuities.
         verifyInDomain(CoordinateDomain.RANGE_100, 4308397764777385180L);
     }
 
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
index 9428cad..b014e95 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/SphericalToCartesianTest.java
@@ -24,7 +24,6 @@
 import static java.lang.StrictMath.*;
 
 // Test dependencies
-import org.opengis.test.referencing.TransformTestCase;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
index 2c960f4..127ff60 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransferFunctionTest.java
@@ -28,7 +28,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
index 569f16c..bf6ec99 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformResultComparator.java
@@ -24,7 +24,7 @@
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
index 6793ab5..c9768d4 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
@@ -38,7 +38,7 @@
 import org.junit.Test;
 
 import static java.lang.Double.NaN;
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformTestCase.java
new file mode 100644
index 0000000..1a9ba7e
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformTestCase.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.referencing.operation.transform;
+
+import java.util.Random;
+
+import static org.junit.Assume.*;
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+ * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.5
+ * @version 0.6
+ * @module
+ */
+public class TransformTestCase extends org.opengis.test.referencing.TransformTestCase {
+    /**
+     * The deltas to use for approximating math transform derivatives by the finite differences method.
+     */
+    protected double[] derivativeDeltas;
+
+    /**
+     * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+     * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+     *
+     * @param coordinate Ignored.
+     */
+    protected final void verifyDerivative(final double... coordinate) {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+
+    /**
+     * Placeholder for a GeoAPI 3.1 method which was not available in GeoAPI 3.0.
+     * This placeholder does nothing. See Apache SIS JDK6 branch for a real test.
+     *
+     * @param minOrdinates    Ignored.
+     * @param maxOrdinates    Ignored.
+     * @param numOrdinates    Ignored.
+     * @param randomGenerator Ignored.
+     */
+    protected final void verifyInDomain(final double[] minOrdinates, final double[] maxOrdinates,
+            final int[] numOrdinates, final Random randomGenerator)
+    {
+        // See GeoAPI 3.1 for the real test.
+        // The test is run on Apache SIS branches.
+       assumeTrue(PENDING_NEXT_GEOAPI_RELEASE); // For reporting the test as skippped.
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
index ae4c4aa..00381b5 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
@@ -25,10 +25,9 @@
 
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.Assert;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -52,7 +51,7 @@
         final TranslationTransform tr = new TranslationTransform(matrix.getNumRow(), elements);
         assertEquals("sourceDimensions", dimensions, tr.getSourceDimensions());
         assertEquals("targetDimensions", dimensions, tr.getTargetDimensions());
-        Assert.assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
+        assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
         assertArrayEquals("elements", elements, tr.getExtendedElements(), 0.0);
         transform = tr;
         validate();
@@ -106,7 +105,7 @@
         final TranslationTransform tr = new TranslationTransform(4, elements);
         assertEquals("sourceDimensions", 3, tr.getSourceDimensions());
         assertEquals("targetDimensions", 3, tr.getTargetDimensions());
-        Assert.assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
+        assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
         assertArrayEquals("elements", elements, tr.getExtendedElements(), 0.0);
         transform = tr;
         validate();
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
index c14f3a0..d048df7 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateOperationMethods.java
@@ -50,7 +50,7 @@
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.metadata.Identifier;
+import org.opengis.referencing.ReferenceIdentifier;
 
 
 /**
@@ -248,7 +248,7 @@
          * ────────────────    EPSG IDENTIFIERS    ────────────────────────────────────
          */
         final StringBuilder buffer = new StringBuilder();
-        for (final Identifier id : method.getIdentifiers()) {
+        for (final ReferenceIdentifier id : method.getIdentifiers()) {
             if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 if (buffer.length() != 0) {
                     buffer.append(", ");
@@ -390,7 +390,7 @@
     private void writeName(final ParameterDescriptor<?> param) throws IOException {
         final int td = openTag("td class=\"sep\"");
         openTag("details");
-        final Identifier name = param.getName();
+        final ReferenceIdentifier name = param.getName();
         final String codeSpace = name.getCodeSpace();
         if (Constants.EPSG.equalsIgnoreCase(codeSpace)) {
             println("summary", escape(name.getCode()));
@@ -542,8 +542,8 @@
     /**
      * Returns the first EPSG code found in the given collection, or {@code null} if none.
      */
-    private static String getFirstEpsgCode(final Iterable<? extends Identifier> identifiers) {
-        for (final Identifier id : identifiers) {
+    private static String getFirstEpsgCode(final Iterable<? extends ReferenceIdentifier> identifiers) {
+        for (final ReferenceIdentifier id : identifiers) {
             if (Constants.EPSG.equalsIgnoreCase(id.getCodeSpace())) {
                 return id.getCode();
             }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateReferenceSystems.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateReferenceSystems.java
deleted file mode 100644
index 18cce72..0000000
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/report/CoordinateReferenceSystems.java
+++ /dev/null
@@ -1,824 +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.referencing.report;
-
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Map;
-import java.util.HashSet;
-import java.util.TreeMap;
-import java.util.NavigableMap;
-import java.io.File;
-import java.io.IOException;
-
-import org.opengis.metadata.Identifier;
-import org.opengis.util.FactoryException;
-import org.opengis.util.InternationalString;
-import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.cs.CartesianCS;
-import org.opengis.referencing.cs.SphericalCS;
-import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.crs.CompoundCRS;
-import org.opengis.referencing.crs.VerticalCRS;
-import org.opengis.referencing.crs.GeocentricCRS;
-import org.opengis.referencing.crs.GeographicCRS;
-import org.opengis.referencing.crs.EngineeringCRS;
-import org.opengis.referencing.crs.GeneralDerivedCRS;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.datum.Datum;
-import org.opengis.referencing.datum.VerticalDatumType;
-import org.opengis.referencing.operation.OperationMethod;
-import org.opengis.test.report.AuthorityCodesReport;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.internal.referencing.DeprecatedCode;
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.referencing.CommonCRS;
-import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.crs.AbstractCRS;
-import org.apache.sis.referencing.cs.AxesConvention;
-import org.apache.sis.util.logging.Logging;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.Deprecable;
-import org.apache.sis.util.Utilities;
-import org.apache.sis.util.Version;
-
-import static org.junit.Assert.*;
-
-
-/**
- * Generates a list of supported Coordinate Reference Systems in the current directory.
- * This class is for manual execution after the EPSG database has been updated,
- * or the projection implementations changed.
- *
- * <p><b>WARNING:</b>
- * this class implements heuristic rules for nicer sorting (e.g. of CRS having numbers as Roman letters).
- * Those heuristic rules were determined specifically for the EPSG dataset expanded with WMS codes.
- * This class is not likely to produce good results for any other authorities, and many need to be updated
- * after any upgrade of the EPSG dataset.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   0.7
- * @module
- */
-public final strictfp class CoordinateReferenceSystems extends AuthorityCodesReport {
-    /**
-     * The titles of some sections where to group CRS. By default CRS are grouped by datum names.
-     * But if a name is listed in this map, then that alternative name will be used for grouping purpose.
-     * Sometime the change is only cosmetic (e.g. "Reseau Geodesique Francais" → "Réseau Géodésique Français").
-     * But sometime the changes have the effect of merging different datum under the same section.
-     * For example we merge the "Arc 1950" and "Arc 1960" sections into a single "Arc" section,
-     * since those sections were small and we do not want to scatter the HTML page with too many sections.
-     * However we do not merge "NAD83" and "NAD83(HARN)" because those sections are already quite large,
-     * and merging them will result in a too large section.
-     *
-     * <p>The decision to merge or not is arbitrary. Generally, we try to avoid small sections (less that 5 CRS)
-     * but without merging together unrelated datum. Every CRS having a datum whose name <em>starts</em> with a
-     * value in the left column will be reported in the section given in the right column.</p>
-     */
-    private static final NavigableMap<String,String> SECTION_TITLES = new TreeMap<>();
-    static {
-        rd("American Samoa",                                          "American Samoa");
-        rd("Arc",                                                     "Arc");
-        rd("Ancienne Triangulation Francaise",                        "Ancienne Triangulation Française");
-        rd("Australian Geodetic Datum",                               "Australian Geodetic Datum");
-        rd("Australian Height Datum",                                 "Australian Height Datum");
-        rd("Azores Central Islands",                                  "Azores Islands");
-        rd("Azores Occidental Islands",                               "Azores Islands");
-        rd("Azores Oriental Islands",                                 "Azores Islands");
-        rd("Baltic",                                                  "Baltic");
-        rd("Batavia",                                                 "Batavia");
-        rd("Bermuda",                                                 "Bermuda");
-        rd("Bogota 1975",                                             "Bogota 1975");
-        rd("Carthage",                                                "Carthage");
-        rd("Bern 1938",                                               "Bern / CH1903");
-        rd("Cais",                                                    "Cais");
-        rd("Cayman Brac",                                             "Cayman Islands");
-        rd("Cayman Islands",                                          "Cayman Islands");
-        rd("CH1903",                                                  "Bern / CH1903");
-        rd("CH1903+",                                                 "Bern / CH1903");
-        rd("Canadian Geodetic Vertical Datum",                        "Canadian Geodetic Vertical Datum");
-        rd("Chatham Islands Datum",                                   "Chatham Islands Datum");
-        rd("Corrego Alegre",                                          "Corrego Alegre");
-        rd("Croatian Terrestrial Reference System",                   "Croatian Reference System");
-        rd("Croatian Vertical Reference System",                      "Croatian Reference System");
-        rd("Danger 1950",                                             "Saint Pierre et Miquelon 1950");
-        rd("Dansk",                                                   "Dansk");
-        rd("Dealul Piscului",                                         "Dealul Piscului");
-        rd("Deutsches Haupthoehennetz",                               "Deutsches Haupthoehennetz");
-        rd("Douala",                                                  "Douala");
-        rd("Dunedin",                                                 "Dunedin");
-        rd("Dunedin-Bluff",                                           "Dunedin");
-        rd("EGM2008 geoid",                                           "EGM geoid");
-        rd("EGM84 geoid",                                             "EGM geoid");
-        rd("EGM96 geoid",                                             "EGM geoid");
-        rd("Egypt",                                                   "Egypt");
-        rd("EPSG example",                                            "EPSG example");
-        rd("Estonia",                                                 "Estonia");
-        rd("European Datum",                                          "European Datum");
-        rd("European Terrestrial Reference Frame",                    "European Terrestrial Reference Frame");
-        rd("European Vertical Reference Frame",                       "European Vertical Reference Frame");
-        rd("Fahud",                                                   "Fahud");
-        rd("Fao",                                                     "Fao");
-        rd("Fehmarnbelt",                                             "Fehmarnbelt");
-        rd("Faroe Datum",                                             "Faroe Islands");
-        rd("Faroe Islands",                                           "Faroe Islands");
-        rd("fk89",                                                    "Faroe Islands");
-        rd("Fiji",                                                    "Fiji");
-        rd("Gan 1970",                                                "Gandajika");
-        rd("Grand Cayman",                                            "Grand Cayman");
-        rd("Greek",                                                   "Greek");
-        rd("Greenland",                                               "Greenland");
-        rd("Guadeloupe",                                              "Guadeloupe");
-        rd("Guam",                                                    "Guam");
-        rd("Gunung Segara",                                           "Gunung Segara");
-        rd("Helsinki",                                                "Helsinki");
-        rd("High Water",                                              "High Water");
-        rd("Higher High Water",                                       "High Water");
-        rd("Highest Astronomic Tide",                                 "High Water");
-        rd("Hong Kong",                                               "Hong Kong");
-        rd("Hungarian",                                               "Hungarian Datum");
-        rd("IG05",                                                    "Israeli Grid");
-        rd("IGb",                                                     "IGb");
-        rd("IGN",                                                     "IGN");
-        rd("IGS",                                                     "IGS");
-        rd("Indian",                                                  "Indian");
-        rd("International Great Lakes Datum",                         "International Great Lakes Datum");
-        rd("International Terrestrial Reference Frame",               "International Terrestrial Reference Frame");
-        rd("Islands Net",                                             "Islands Net");
-        rd("Israeli Geodetic Datum",                                  "Israeli Geodetic Datum");
-        rd("Jamaica",                                                 "Jamaica");
-        rd("Japanese Geodetic Datum 2000",                            "Japanese Geodetic Datum 2000");
-        rd("Japanese Geodetic Datum 2011",                            "Japanese Geodetic Datum 2011");
-        rd("Japanese Standard Levelling Datum",                       "Japanese Standard Levelling Datum");
-        rd("Kalianpur",                                               "Kalianpur");
-        rd("Kertau",                                                  "Kertau");
-        rd("KOC Construction Datum",                                  "KOC Construction Datum / Well Datum");
-        rd("KOC Well Datum",                                          "KOC Construction Datum / Well Datum");
-        rd("Korean Datum",                                            "Korean Datum");
-        rd("Kuwait Oil Company",                                      "Kuwait Oil Company / Kuwait Utility");
-        rd("Kuwait PWD",                                              "Kuwait Oil Company / Kuwait Utility");
-        rd("Kuwait Utility",                                          "Kuwait Oil Company / Kuwait Utility");
-        rd("Lao",                                                     "Lao");
-        rd("Latvia",                                                  "Latvia");
-        rd("Lisbon",                                                  "Lisbon");
-        rd("Lower Low Water Large Tide",                              "Low Water");
-        rd("Lowest Astronomic Tide",                                  "Low Water");
-        rd("Macao",                                                   "Macao");
-        rd("Makassar",                                                "Makassar");
-        rd("Manoca",                                                  "Manoca");
-        rd("Martinique",                                              "Martinique");
-        rd("Maupiti",                                                 "Maupiti");
-        rd("Mean High Water",                                         "Mean Sea Level");
-        rd("Mean Higher High Water",                                  "Mean Sea Level");
-        rd("Mean Low Water",                                          "Mean Sea Level");
-        rd("Mean Lower Low Water",                                    "Mean Sea Level");
-        rd("Missao Hidrografico Angola y Sao Tome 1951",              "Missao Hidrografico Angola y Sao Tome");
-        rd("Mhast (offshore)",                                        "Missao Hidrografico Angola y Sao Tome");
-        rd("Mhast (onshore)",                                         "Missao Hidrografico Angola y Sao Tome");
-        rd("Militar-Geographische Institut (Ferro)",                  "Militar-Geographische Institut");
-        rd("MOMRA",                                                   "MOMRA");
-        rd("Monte Mario (Rome)",                                      "Monte Mario");
-        rd("Moorea",                                                  "Moorea");
-        rd("Nahrwan",                                                 "Nahrwan");
-        rd("Naparima",                                                "Naparima");
-        rd("Nivellement General de la Corse",                         "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de la France",                        "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de Nouvelle Caledonie",               "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General de Polynesie Francaise",              "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General du Luxembourg",                       "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("Nivellement General Guyanais",                            "Nivellement Général Corse / France / Nouvelle-Calédonie / Polynésie Française / Luxembourd / Guyanais");
-        rd("NGO 1948",                                                "NGO 1948");
-        rd("Nouvelle Triangulation Francaise",                        "Nouvelle Triangulation Française");
-        rd("NAD83 Canadian Spatial Reference System",                 "North American Datum 1983 — Canadian Spatial Reference System");
-        rd("NAD83 (Continuously Operating Reference Station 1996)",   "North American Datum 1983 — Continuously Operating Reference Station 1996");       // For better sort order.
-        rd("NAD83 (Federal Base Network)",                            "North American Datum 1983 — Federal Base Network");
-        rd("NAD83 (High Accuracy Reference Network)",                 "North American Datum 1983 — High Accuracy Reference Network");
-        rd("NAD83 (High Accuracy Reference Network - Corrected)",     "North American Datum 1983 — High Accuracy Reference Network");
-        rd("NAD83 (National Spatial Reference System 2007)",          "North American Datum 1983 — National Spatial Reference System 2007");
-        rd("NAD83 (National Spatial Reference System 2011)",          "North American Datum 1983 — National Spatial Reference System 2011");
-        rd("NAD83 (National Spatial Reference System MA11)",          "North American Datum 1983 — National Spatial Reference System MA11 / PA11");
-        rd("NAD83 (National Spatial Reference System PA11)",          "North American Datum 1983 — National Spatial Reference System MA11 / PA11");
-        rd("North American Datum of 1983 (CSRS)",                     "North American Datum 1983 — CSRS");
-        rd("North American Datum of 1983 (CSRS96)",                   "North American Datum 1983 — CSRS");
-        rd("New Zealand Vertical Datum",                              "New Zealand Vertical Datum");
-        rd("Norway Normal Null",                                      "Norway Normal Null");
-        rd("Ordnance Datum Newlyn",                                   "Ordnance Datum Newlyn");
-        rd("OSGB",                                                    "OSGB");
-        rd("Padang 1884",                                             "Padang 1884");
-        rd("Parametry Zemli 1990",                                    "Parametry Zemli 1990");
-        rd("PDO Height Datum 1993",                                   "PDO Survey / Height Datum 1993");
-        rd("PDO Survey Datum 1993",                                   "PDO Survey / Height Datum 1993");
-        rd("Pitcairn",                                                "Pitcairn");
-        rd("Port Moresby",                                            "Port Moresby");
-        rd("Porto Santo",                                             "Porto Santo");
-        rd("Posiciones Geodésicas Argentinas",                        "Posiciones Geodésicas Argentinas");
-        rd("Puerto Rico",                                             "Puerto Rico");
-        rd("Qatar",                                                   "Qatar");
-        rd("Qornoq",                                                  "Qornoq");
-        rd("Reseau Geodesique de Nouvelle Caledonie",                 "Réseau Géodésique de Nouvelle-Calédonie");
-        rd("Reseau National Belge",                                   "Réseau National Belge");
-        rd("Reunion",                                                 "Réunion");
-        rd("Rikets hojdsystem",                                       "Rikets hojdsystem");
-        rd("Santa Cruz",                                              "Santa Cruz");
-        rd("Serbian",                                                 "Serbian Reference System / Network");
-        rd("Sierra Leone",                                            "Sierra Leone");
-        rd("SIRGAS",                                                  "SIRGAS");
-        rd("Slovenia",                                                "Slovenia");
-        rd("Slovenian",                                               "Slovenia");
-        rd("South American Datum",                                    "South American Datum");
-        rd("Sri Lanka",                                               "Sri Lanka");
-        rd("Stockholm 1938",                                          "Stockholm 1938");
-        rd("St. Helena",                                              "St. Helena");
-        rd("System of the Unified Trigonometrical Cadastral Network", "System of the Unified Trigonometrical Cadastral Network");
-        rd("Tahaa",                                                   "Tahaa");
-        rd("Tahiti",                                                  "Tahiti");
-        rd("Taiwan",                                                  "Taiwan");
-        rd("Tananarive 1925",                                         "Tananarive 1925");
-        rd("Tokyo",                                                   "Tokyo");
-        rd("Viti Levu",                                               "Viti Levu");
-        rd("Voirol",                                                  "Voirol");
-        rd("WGS 72 Transit Broadcast Ephemeris",                      "World Geodetic System 1972 — Transit Broadcast Ephemeris");
-        rd("World Geodetic System 1984",                              "World Geodetic System 1984");
-        rd("Yellow Sea",                                              "Yellow Sea");
-    }
-
-    /**
-     * The datums from the above list which are deprecated, but that we do not want to replace by the non-deprecated
-     * datum. We disable some replacements when they allow better sorting of deprecated CRS.
-     */
-    private static final Set<String> KEEP_DEPRECATED_DATUM = new HashSet<>(Arrays.asList(
-        "Dealul Piscului 1970"));           // Datum does not exist but is an alias for S-42 in Romania.
-
-    /**
-     * Shortcut for {@link #SECTION_TITLES} initialization.
-     * {@code "rd"} stands for "rename datum".
-     */
-    private static void rd(final String datum, final String display) {
-        assertNull(datum, SECTION_TITLES.put(datum, display));
-    }
-
-    /**
-     * Words to ignore in a datum name in order to detect if a CRS name is the acronym of the datum name.
-     */
-    private static final Set<String> DATUM_WORDS_TO_IGNORE = new HashSet<>(Arrays.asList(
-            "of",           // VIVD:   Virgin Islands Vertical Datum of 2009
-            "de",           // RRAF:   Reseau de Reference des Antilles Francaises
-            "des",          // RGAF:   Reseau Geodesique des Antilles Francaises
-            "la",           // RGR:    Reseau Geodesique de la Reunion
-            "et",           // RGSPM:  Reseau Geodesique de Saint Pierre et Miquelon
-            "para",         // SIRGAS: Sistema de Referencia Geocentrico para America del Sur 1995
-            "del",          // SIRGAS: Sistema de Referencia Geocentrico para America del Sur 1995
-            "las",          // SIRGAS: Sistema de Referencia Geocentrico para las AmericaS 2000
-            "Tides"));      // MLWS:   Mean Low Water Spring Tides
-
-    /**
-     * The keywords before which to cut the CRS names when sorting by alphabetical order.
-     * The main intent here is to preserve the "far west", "west", "central west", "central",
-     * "central east", "east", "far east" order.
-     */
-    private static final String[] CUT_BEFORE = {
-        " far west",        // "MAGNA-SIRGAS / Colombia Far West zone"
-        " far east",
-        " west",            // "Bogota 1975 / Colombia West zone"
-        " east",            // "Bogota 1975 / Colombia East Central zone"
-        " central",         // "Korean 1985 / Central Belt" (between "East Belt" and "West Belt")
-        " old central",     // "NAD Michigan / Michigan Old Central"
-        " bogota zone",     // "Bogota 1975 / Colombia Bogota zone"
-        // Do not declare "North" and "South" as it causes confusion with "WGS 84 / North Pole" and other cases.
-    };
-
-    /**
-     * The keywords after which to cut the CRS names when sorting by alphabetical order.
-     *
-     * Note: alphabetical sorting of Roman numbers work for zones from I to VIII inclusive.
-     * If there is more zones (for example with "JGD2000 / Japan Plane Rectangular"), then
-     * we need to cut before those numbers in order to use sorting by EPSG codes instead.
-     *
-     * Note 2: if alphabetical sorting is okay for Roman numbers, it is actually preferable
-     * because it give better position of names with height like "zone II + NGF IGN69 height".
-     */
-    private static final String[] CUT_AFTER = {
-        " cs ",                     // "JGD2000 / Japan Plane Rectangular CS IX"
-        " tm",                      // "ETRS89 / TM35FIN(E,N)" — we want to not interleave them between "TM35" and "TM36".
-        " dktm",                    // "ETRS89 / DKTM1 + DVR90 height"
-        "-gk",                      // "ETRS89 / ETRS-GK19FIN"
-        " philippines zone ",       // "Luzon 1911 / Philippines zone IV"
-        " california zone ",        // "NAD27 / California zone V"
-        " ngo zone ",               // "NGO 1948 (Oslo) / NGO zone I"
-        " lambert zone ",           // "NTF (Paris) / Lambert zone II + NGF IGN69 height"
-        "fiji 1956 / utm zone "     // Two zones: 60S and 1S with 60 before 1.
-    };
-
-    /**
-     * The symbol to write in from of EPSG code of CRS having an axis order different
-     * then the (longitude, latitude) one.
-     */
-    private static final char YX_ORDER = '\u21B7';
-
-    /**
-     * The factory which create CRS instances.
-     */
-    private final CRSAuthorityFactory factory;
-
-    /**
-     * The datum from the {@link #SECTION_TITLES} that we didn't found after we processed all codes.
-     * Used for verification purpose only.
-     */
-    private final Set<String> unusedDatumMapping;
-
-    /**
-     * Creates a new instance.
-     */
-    private CoordinateReferenceSystems() throws FactoryException {
-        super(null);
-        unusedDatumMapping = new HashSet<>(SECTION_TITLES.keySet());
-        properties.setProperty("TITLE",           "Apache SIS™ Coordinate Reference System (CRS) codes");
-        properties.setProperty("PRODUCT.NAME",    "Apache SIS™");
-        properties.setProperty("PRODUCT.VERSION", getVersion());
-        properties.setProperty("PRODUCT.URL",     "http://sis.apache.org");
-        properties.setProperty("JAVADOC.GEOAPI",  "http://www.geoapi.org/snapshot/javadoc");
-        properties.setProperty("FACTORY.NAME",    "EPSG");
-        properties.setProperty("FACTORY.VERSION", "9.7");
-        properties.setProperty("FACTORY.VERSION.SUFFIX", ", together with other sources");
-        properties.setProperty("PRODUCT.VERSION.SUFFIX", " (provided that <a href=\"http://sis.apache.org/epsg.html\">a connection to an EPSG database exists</a>)");
-        properties.setProperty("DESCRIPTION", "<p><b>Notation:</b></p>\n" +
-                "<ul>\n" +
-                "  <li>The " + YX_ORDER + " symbol in front of authority codes (${PERCENT.ANNOTATED} of them) identifies" +
-                " left-handed coordinate systems (for example with <var>latitude</var> axis before <var>longitude</var>).</li>\n" +
-                "  <li>The <del>codes with a strike</del> (${PERCENT.DEPRECATED} of them) identify deprecated CRS." +
-                " In some cases, the remarks column indicates the replacement.</li>\n" +
-                "</ul>");
-        factory = CRS.getAuthorityFactory(null);
-        add(factory);
-    }
-
-    /**
-     * Generates the HTML report.
-     *
-     * @param  args  ignored.
-     * @throws FactoryException if an error occurred while fetching the CRS.
-     * @throws IOException if an error occurred while writing the HTML file.
-     */
-    @SuppressWarnings("UseOfSystemOutOrSystemErr")
-    public static void main(final String[] args) throws FactoryException, IOException {
-        Locale.setDefault(Locale.US);   // We have to use this hack for now because exceptions are formatted in the current locale.
-        final CoordinateReferenceSystems writer = new CoordinateReferenceSystems();
-        final File file = writer.write(new File("CoordinateReferenceSystems.html"));
-        System.out.println("Created " + file.getAbsolutePath());
-        if (!writer.unusedDatumMapping.isEmpty()) {
-            System.out.println();
-            System.out.println("WARNING: the following datums were expected but not found. Maybe their spelling changed?");
-            for (final String name : writer.unusedDatumMapping) {
-                System.out.print("  - ");
-                System.out.println(name);
-            }
-        }
-    }
-
-    /**
-     * Returns the current Apache SIS version, with the {@code -SNAPSHOT} trailing part omitted.
-     *
-     * @return the current Apache SIS version.
-     */
-    private static String getVersion() {
-        String version = Version.SIS.toString();
-        final int snapshot = version.lastIndexOf('-');
-        if (snapshot >= 2) {
-            version = version.substring(0, snapshot);
-        }
-        return version;
-    }
-
-    /**
-     * Creates the text to show in the "Remarks" column for the given CRS.
-     */
-    private String getRemark(final CoordinateReferenceSystem crs) {
-        if (crs instanceof GeographicCRS) {
-            return (crs.getCoordinateSystem().getDimension() == 3) ? "Geographic 3D" : "Geographic";
-        }
-        if (crs instanceof GeneralDerivedCRS) {
-            final OperationMethod method = ((GeneralDerivedCRS) crs).getConversionFromBase().getMethod();
-            final Identifier identifier = IdentifiedObjects.getIdentifier(method, Citations.EPSG);
-            if (identifier != null) {
-                return "<a href=\"CoordinateOperationMethods.html#" + identifier.getCode()
-                       + "\">" + method.getName().getCode().replace('_', ' ') + "</a>";
-            }
-        }
-        if (crs instanceof GeocentricCRS) {
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs instanceof CartesianCS) {
-                return "Geocentric (Cartesian coordinate system)";
-            } else if (cs instanceof SphericalCS) {
-                return "Geocentric (spherical coordinate system)";
-            }
-            return "Geocentric";
-        }
-        if (crs instanceof VerticalCRS) {
-            final VerticalDatumType type = ((VerticalCRS) crs).getDatum().getVerticalDatumType();
-            return CharSequences.camelCaseToSentence(type.name().toLowerCase(getLocale())) + " height";
-        }
-        if (crs instanceof CompoundCRS) {
-            final StringBuilder buffer = new StringBuilder();
-            for (final CoordinateReferenceSystem component : ((CompoundCRS) crs).getComponents()) {
-                if (buffer.length() != 0) {
-                    buffer.append(" + ");
-                }
-                buffer.append(getRemark(component));
-            }
-            return buffer.toString();
-        }
-        if (crs instanceof EngineeringCRS) {
-            return "Engineering (" + crs.getCoordinateSystem().getName().getCode() + ')';
-        }
-        return "";
-    }
-
-    /**
-     * Omits the trailing number, if any.
-     * For example if the given name is "Abidjan 1987", then this method returns "Abidjan".
-     */
-    private static String omitTrailingNumber(String name) {
-        int i = CharSequences.skipTrailingWhitespaces(name, 0, name.length());
-        while (i != 0) {
-            final char c = name.charAt(--i);
-            if (c < '0' || c > '9') {
-                name = name.substring(0, CharSequences.skipTrailingWhitespaces(name, 0, i+1));
-                break;
-            }
-        }
-        return name;
-    }
-
-    /**
-     * If the first word of the CRS name seems to be an acronym of the datum name,
-     * puts that acronym in a {@code <abbr title="datum name">...</abbr>} element.
-     */
-    static String insertAbbreviationTitle(final String crsName, final String datumName) {
-        int s = crsName.indexOf(' ');
-        if (s < 0) s = crsName.length();
-        int p = crsName.indexOf('(');
-        if (p >= 0 && p < s) s = p;
-        p = datumName.indexOf('(');
-        if (p < 0) p = datumName.length();
-        final String acronym = crsName.substring(0, s);
-        final String ar = omitTrailingNumber(acronym);
-        final String dr = omitTrailingNumber(datumName.substring(0, p));
-        if (dr.startsWith(ar)) {
-            return crsName;                                 // Avoid redudancy between CRS name and datum name.
-        }
-        /*
-         * If the first CRS word does not seem to be an acronym of the datum name, verify
-         * if there is some words that we should ignore in the datum name and try again.
-         */
-        if (!CharSequences.isAcronymForWords(ar, dr)) {
-            final String[] words = (String[]) CharSequences.split(dr, ' ');
-            int n = 0;
-            for (final String word : words) {
-                if (!DATUM_WORDS_TO_IGNORE.contains(word)) {
-                    words[n++] = word;
-                }
-            }
-            if (n == words.length || n < 2) {
-                return crsName;
-            }
-            final StringBuilder b = new StringBuilder();
-            for (int i=0; i<n; i++) {
-                if (i != 0) b.append(' ');
-                b.append(words[i]);
-            }
-            if (!CharSequences.isAcronymForWords(ar, b)) {
-                return crsName;
-            }
-        }
-        return "<abbr title=\"" + datumName + "\">" + acronym + "</abbr>" + crsName.substring(s);
-    }
-
-    /**
-     * Invoked when a CRS has been successfully created. This method modifies the default
-     * {@link org.opengis.test.report.AuthorityCodesReport.Row} attribute values created
-     * by GeoAPI.
-     *
-     * @param  code    the authority code of the created object.
-     * @param  object  the object created from the given authority code.
-     * @return the created row, or {@code null} if the row should be ignored.
-     */
-    @Override
-    protected Row createRow(final String code, final IdentifiedObject object) {
-        final Row row = super.createRow(code, object);
-        final CoordinateReferenceSystem crs = (CoordinateReferenceSystem) object;
-        final CoordinateReferenceSystem crsXY = AbstractCRS.castOrCopy(crs).forConvention(AxesConvention.RIGHT_HANDED);
-        if (!Utilities.deepEquals(crs.getCoordinateSystem(), crsXY.getCoordinateSystem(), ComparisonMode.IGNORE_METADATA)) {
-            row.annotation = YX_ORDER;
-        }
-        CoordinateReferenceSystem replacement = crs;
-        row.remark = getRemark(crs);
-        /*
-         * If the object is deprecated, find the replacement.
-         * We do not take the whole comment because it may be pretty long.
-         */
-        if (object instanceof Deprecable) {
-            row.isDeprecated = ((Deprecable) object).isDeprecated();
-            if (row.isDeprecated) {
-                String replacedBy = null;
-                InternationalString i18n = object.getRemarks();
-                for (final Identifier id : object.getIdentifiers()) {
-                    if (id instanceof Deprecable && ((Deprecable) id).isDeprecated()) {
-                        i18n = ((Deprecable) id).getRemarks();
-                        if (id instanceof DeprecatedCode) {
-                            replacedBy = ((DeprecatedCode) id).replacedBy;
-                        }
-                        break;
-                    }
-                }
-                if (i18n != null) {
-                    row.remark = i18n.toString(getLocale());
-                }
-                /*
-                 * If a replacement exists for a deprecated CRS, use the datum of the replacement instead than
-                 * the datum of the deprecated CRS for determining in which section to put the CRS. The reason
-                 * is that some CRS are deprecated because they were associated to the wrong datum, in which
-                 * case the deprecated CRS would appear in the wrong section if we do not apply this correction.
-                 */
-                if (!KEEP_DEPRECATED_DATUM.contains(CRS.getSingleComponents(crs).get(0).getDatum().getName().getCode())) {
-                    if (replacedBy != null) try {
-                        replacement = factory.createCoordinateReferenceSystem("EPSG:" + replacedBy);
-                    } catch (FactoryException e) {
-                        // Ignore - keep the datum of the deprecated object.
-                    }
-                }
-            }
-        }
-        ((ByName) row).setup(CRS.getSingleComponents(replacement).get(0).getDatum(), unusedDatumMapping);
-        return row;
-    }
-
-    /**
-     * Invoked when a CRS creation failed. This method modifies the default
-     * {@link org.opengis.test.report.AuthorityCodesReport.Row} attribute values
-     * created by GeoAPI.
-     *
-     * @param  code       the authority code of the object to create.
-     * @param  exception  the exception that occurred while creating the identified object.
-     * @return the created row, or {@code null} if the row should be ignored.
-     */
-    @Override
-    protected Row createRow(final String code, final FactoryException exception) {
-        if (code.startsWith("Proj4:")) {
-            return null;
-        }
-        final Row row = super.createRow(code, exception);
-        try {
-            row.name = factory.getDescriptionText(code).toString(getLocale());
-        } catch (FactoryException e) {
-            Logging.unexpectedException(null, CoordinateReferenceSystems.class, "createRow", e);
-        }
-        if (code.startsWith("AUTO2:")) {
-            // It is normal to be unable to instantiate an "AUTO" CRS,
-            // because those authority codes need parameters.
-            row.hasError = false;
-            row.remark = "Projected";
-            ((ByName) row).setup(CommonCRS.WGS84.datum(), unusedDatumMapping);
-        } else {
-            row.remark = exception.getLocalizedMessage();
-            ((ByName) row).setup(null, unusedDatumMapping);
-        }
-        return row;
-    }
-
-    /**
-     * Invoked by {@link AuthorityCodesReport} for creating a new row instance.
-     *
-     * @return the new row instance.
-     */
-    @Override
-    protected Row newRow() {
-        return new ByName();
-    }
-
-
-
-
-    /**
-     * A row with an natural ordering that use the first part of the name before to use the authority code.
-     * We use only the part of the name prior some keywords (e.g. {@code "zone"}).
-     * For example if the following codes:
-     *
-     * {@preformat text
-     *    EPSG:32609    WGS 84 / UTM zone 9N
-     *    EPSG:32610    WGS 84 / UTM zone 10N
-     * }
-     *
-     * We compare only the "WGS 84 / UTM" string, then the code. This is a reasonably easy way to keep a more
-     * natural ordering ("9" sorted before "10", "UTM North" projections kept together and same for South).
-     */
-    private static final class ByName extends Row {
-        /**
-         * A string derived from the {@link #name} to use for sorting.
-         */
-        private String reducedName;
-
-        /**
-         * The datum name, or {@code null} if unknown.
-         * If non-null, this is used for grouping CRS names by sections.
-         */
-        String section;
-
-        /**
-         * Creates a new row.
-         */
-        ByName() {
-        }
-
-        /**
-         * Computes the {@link #reducedName} field value.
-         */
-        final void setup(final Datum datum, final Set<String> unusedDatumMapping) {
-            final String datumName;
-            if (datum != null) {
-                datumName = datum.getName().getCode();
-            } else {
-                // Temporary patch (TODO: remove after we implemented the missing methods in SIS)
-                if (name.startsWith("NSIDC EASE-Grid")) {
-                    datumName = "Unspecified datum";
-                } else if (code.equals("EPSG:2163")) {
-                    datumName = "Unspecified datum";
-                } else if (code.equals("EPSG:5818")) {
-                    datumName = "Seismic bin grid datum";
-                } else {
-                    datumName = null;       // Keep ordering based on the name.
-                }
-            }
-            if (datumName != null) {
-                final String prefix;
-                final Map.Entry<String,String> group = SECTION_TITLES.floorEntry(datumName);
-                if (group != null && datumName.startsWith(prefix = group.getKey())) {
-                    unusedDatumMapping.remove(prefix);
-                    section = group.getValue();
-                } else {
-                    section = datumName;
-                }
-            }
-            /*
-             * Get a copy of the name in all lower case.
-             */
-            final StringBuilder b = new StringBuilder(name);
-            for (int i=0; i<b.length(); i++) {
-                b.setCharAt(i, Character.toLowerCase(b.charAt(i)));
-            }
-            /*
-             * Cut the string to a shorter length if we find a keyword.
-             * This will result in many string equals, which will then be sorted by EPSG codes.
-             * This is useful when the EPSG codes give a better ordering than the alphabetic one
-             * (for example with Roman numbers).
-             */
-            int s = 0;
-            for (final String keyword : CUT_BEFORE) {
-                int i = b.lastIndexOf(keyword);
-                if (i > 0 && (s == 0 || i < s)) s = i;
-            }
-            for (final String keyword : CUT_AFTER) {
-                int i = b.lastIndexOf(keyword);
-                if (i >= 0) {
-                    i += keyword.length();
-                    if (i > s) s = i;
-                }
-            }
-            if (s != 0) b.setLength(s);
-            uniformizeZoneNumber(b);
-            reducedName = b.toString();
-            if (datumName != null) {
-                name = insertAbbreviationTitle(name, datumName);
-            }
-        }
-
-        /**
-         * If the string ends with a number optionally followed by "N" or "S", replaces the hemisphere
-         * symbol by a sign and makes sure that the number uses at least 3 digits (e.g. "2N" → "+002").
-         * This string will be used for better sorting order.
-         */
-        private static void uniformizeZoneNumber(final StringBuilder b) {
-            if (b.indexOf("/") < 0) {
-                /*
-                 * Do not process names like "WGS 84". We want to process only names like "WGS 84 / UTM zone 2N",
-                 * otherwise the replacement of "WGS 84" by "WGS 084" causes unexpected sorting.
-                 */
-                return;
-            }
-            int  i = b.length();
-            char c = b.charAt(i - 1);
-            if (c == ')') {
-                // Ignore suffix like " (ftUS)".
-                i = b.lastIndexOf(" (");
-                if (i < 0) return;
-                c = b.charAt(i - 1);
-            }
-            char sign;
-            switch (c) {
-                default:            sign =  0;       break;
-                case 'e': case 'n': sign = '+'; i--; break;
-                case 'w': case 's': sign = '-'; i--; break;
-            }
-            int upper = i;
-            do {
-                if (i == 0) return;
-                c = b.charAt(--i);
-            } while (c >= '0' && c <= '9');
-            switch (upper - ++i) {
-                case 2: b.insert(i,  '0'); upper++;  break;     // Found 2 digits.
-                case 1: b.insert(i, "00"); upper+=2; break;     // Only one digit found.
-                case 0: return;                                 // No digit.
-            }
-            if (sign != 0) {
-                b.insert(i, sign);
-                upper++;
-            }
-            b.setLength(upper);
-        }
-
-        /**
-         * Compares this row with the given row for ordering by name.
-         */
-        @Override
-        public int compareTo(final Row o) {
-            int n = reducedName.compareTo(((ByName) o).reducedName);
-            if (n == 0) {
-                n = super.compareTo(o);
-            }
-            return n;
-        }
-    }
-
-    /**
-     * Sorts the rows, then inserts sections between CRS instances that use different datums.
-     */
-    @Override
-    protected void sortRows() {
-        super.sortRows();
-        @SuppressWarnings("SuspiciousToArrayCall")
-        final ByName[] data = rows.toArray(new ByName[rows.size()]);
-        final Map<String,String> sections = new TreeMap<>();
-        for (final ByName row : data) {
-            final String section = row.section;
-            if (section != null) {
-                sections.put(CharSequences.toASCII(section).toString().toLowerCase(), section);
-            }
-        }
-        rows.clear();
-        /*
-         * Recopy the rows, but section-by-section. We do this sorting here instead than in the Row.compareTo(Row)
-         * method in order to preserve the alphabetical order of rows with unknown datum.
-         * Algorithm below is inefficient, but this class should be rarely used anyway and only by site maintainer.
-         */
-        for (final String section : sections.values()) {
-            final Row separator = new Row();
-            separator.isSectionHeader = true;
-            separator.name = section;
-            rows.add(separator);
-            boolean found = false;
-            for (int i=0; i<data.length; i++) {
-                final ByName row = data[i];
-                if (row != null) {
-                    if (row.section != null) {
-                        found = section.equals(row.section);
-                    }
-                    if (found) {
-                        rows.add(row);
-                        data[i] = null;
-                        found = true;
-                    }
-                }
-            }
-        }
-        boolean found = false;
-        for (final ByName row : data) {
-            if (row != null) {
-                if (!found) {
-                    final Row separator = new Row();
-                    separator.isSectionHeader = true;
-                    separator.name = "Unknown";
-                    rows.add(separator);
-                }
-                rows.add(row);
-                found = true;
-            }
-        }
-    }
-}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java b/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
index b4f61f0..30d6d62 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
@@ -29,6 +29,7 @@
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.cs.AxisDirection;
@@ -87,7 +88,7 @@
      *
      * @since 0.6
      */
-    public static void assertOgcIdentifierEquals(final String expected, final Identifier actual) {
+    public static void assertOgcIdentifierEquals(final String expected, final ReferenceIdentifier actual) {
         assertNotNull(actual);
         assertEquals("code",       expected,      actual.getCode());
         assertEquals("codeSpace",  Constants.OGC, actual.getCodeSpace());
@@ -108,7 +109,7 @@
     public static void assertEpsgIdentifierEquals(final String expected, final Identifier actual) {
         assertNotNull(actual);
         assertEquals("code",       expected,        actual.getCode());
-        assertEquals("codeSpace",  Constants.EPSG,  actual.getCodeSpace());
+        assertEquals("codeSpace",  Constants.EPSG,  (actual instanceof ReferenceIdentifier) ? ((ReferenceIdentifier) actual).getCodeSpace() : null);
         assertEquals("authority",  Constants.EPSG,  Citations.toCodeSpace(actual.getAuthority()));
         assertEquals("identifier", Constants.EPSG + Constants.DEFAULT_SEPARATOR + expected,
                 IdentifiedObjects.toString(actual));
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java
index 614da83..fd09e75 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/ConsistencyTest.java
@@ -111,7 +111,7 @@
      */
     @Test
     public void testCoordinateReferenceSystems() throws FactoryException {
-        assumeTrue("Extensive tests not enabled.", RUN_EXTENSIVE_TESTS);
+        assumeTrue(RUN_EXTENSIVE_TESTS);
         final WKTFormat v1  = new WKTFormat(null, null);
         final WKTFormat v1c = new WKTFormat(null, null);
         final WKTFormat v2  = new WKTFormat(null, null);
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
index bd82939..1099417 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataTest.java
@@ -79,6 +79,8 @@
 
 import static org.apache.sis.test.Assert.*;
 
+import org.apache.sis.internal.geoapi.evolution.UnsupportedCodeList;
+
 
 /**
  * Tests XML (un)marshalling of a metadata object containing various elements
@@ -134,6 +136,18 @@
     }
 
     /**
+     * Creates a telephone number of the given type.
+     *
+     * @param type Either {@code "VOICE"}, {@code "FACSIMILE"} or {@code "SMS"}.
+     */
+    private static DefaultTelephone telephone(final String number, final String type) {
+        final DefaultTelephone tel = new DefaultTelephone();
+        tel.setNumber(number);
+        tel.setNumberType(UnsupportedCodeList.valueOf(type));
+        return tel;
+    }
+
+    /**
      * Programmatically creates the metadata to marshal, or to compare against the unmarshalled metadata.
      *
      * @return the hard-coded representation of {@value #XML_FILE} content.
@@ -158,8 +172,8 @@
             final DefaultContact contact = new DefaultContact(online);
             contact.getIdentifierMap().putSpecialized(IdentifierSpace.ID, "IFREMER");
             contact.setPhones(Arrays.asList(
-                    new DefaultTelephone("+33 (0)2 xx.xx.xx.x6", TelephoneType.VOICE),
-                    new DefaultTelephone("+33 (0)2 xx.xx.xx.x4", TelephoneType.FACSIMILE)
+                    telephone("+33 (0)2 xx.xx.xx.x6", "VOICE"),
+                    telephone("+33 (0)2 xx.xx.xx.x4", "FACSIMILE")
             ));
             final DefaultAddress address = new DefaultAddress();
             address.setDeliveryPoints(singleton("Brest institute"));
@@ -187,8 +201,8 @@
                 online.setProtocol("http");
                 final DefaultContact contact = new DefaultContact(online);
                 contact.setPhones(Arrays.asList(
-                        new DefaultTelephone("+33 (0)4 xx.xx.xx.x5", TelephoneType.VOICE),
-                        new DefaultTelephone("+33 (0)4 xx.xx.xx.x8", TelephoneType.FACSIMILE)
+                        telephone("+33 (0)4 xx.xx.xx.x5", "VOICE"),
+                        telephone("+33 (0)4 xx.xx.xx.x8", "FACSIMILE")
                 ));
                 final DefaultAddress address = new DefaultAddress();
                 address.setDeliveryPoints(singleton("Oceanology institute"));
@@ -238,7 +252,7 @@
              */
             {
                 final DefaultLegalConstraints constraint = new DefaultLegalConstraints();
-                constraint.setAccessConstraints(singleton(Restriction.LICENCE));
+                constraint.setAccessConstraints(singleton(Restriction.LICENSE));
                 identification.setResourceConstraints(singleton(constraint));
             }
             /*
@@ -246,14 +260,14 @@
              */
             {
                 @SuppressWarnings("deprecation")
-                final DefaultAssociatedResource aggregateInfo = new DefaultAggregateInformation();
+                final DefaultAggregateInformation aggregateInfo = new DefaultAggregateInformation();
                 final DefaultCitation name = new DefaultCitation("Some oceanographic campaign");
                 name.setAlternateTitles(singleton(new SimpleInternationalString("Pseudo group of data")));
                 name.setDates(singleton(new DefaultCitationDate(TestUtilities.date("1990-06-04 22:00:00"), DateType.REVISION)));
                 aggregateInfo.setName(name);
                 aggregateInfo.setInitiativeType(InitiativeType.CAMPAIGN);
-                aggregateInfo.setAssociationType(AssociationType.LARGER_WORK_CITATION);
-                identification.setAssociatedResources(singleton(aggregateInfo));
+                aggregateInfo.setAssociationType(AssociationType.LARGER_WORD_CITATION); // There is a typo ("WORD" → "WORK"), but we have to use the wrong spelling for this branch.
+                identification.setAggregationInfo(singleton(aggregateInfo));
             }
             /*
              * Data indentification / Extent.
@@ -407,6 +421,7 @@
                      "<gmx:Anchor xlink:href=\"SDN:L231:3:CDI\">Pseudo Common Data Index record</gmx:Anchor>");
         replace(xml, "<gcol:CharacterString>4326</gcol:CharacterString>",
                      "<gmx:Anchor xlink:href=\"SDN:L101:2:4326\">4326</gmx:Anchor>");
+        replace(xml, "License", "Licence");
         /*
          * The <gmd:EX_TemporalExtent> block can not be marshalled yet, since it requires the sis-temporal module.
          * We need to instruct the XML comparator to ignore this block during the comparison. We also ignore for
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java
index 7178e51..38c4e99 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/integration/MetadataVerticalTest.java
@@ -18,7 +18,6 @@
 
 import java.net.URI;
 import java.util.Locale;
-import java.nio.charset.StandardCharsets;
 import javax.xml.bind.JAXBException;
 
 import org.opengis.metadata.*;
@@ -29,6 +28,7 @@
 import org.opengis.metadata.spatial.GeometricObjectType;
 import org.opengis.metadata.spatial.SpatialRepresentation;
 import org.opengis.metadata.spatial.VectorSpatialRepresentation;
+import org.opengis.metadata.identification.CharacterSet;
 import org.opengis.metadata.identification.DataIdentification;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.cs.AxisDirection;
@@ -101,10 +101,10 @@
     @Test
     public void testMetadataWithVerticalCRS() throws JAXBException {
         final Metadata metadata = unmarshalFile(Metadata.class, XML_FILE);
-        assertEquals("fileIdentifier", "20090901",                     metadata.getMetadataIdentifier().getCode());
-        assertEquals("language",       Locale.ENGLISH,                 getSingleton(metadata.getLocalesAndCharsets().keySet()));
-        assertEquals("characterSet",   StandardCharsets.UTF_8,         getSingleton(metadata.getLocalesAndCharsets().values()));
-        assertEquals("dateStamp",      xmlDate("2014-01-04 00:00:00"), getSingleton(metadata.getDateInfo()).getDate());
+        assertEquals("fileIdentifier", "20090901",                     metadata.getFileIdentifier());
+        assertEquals("language",       Locale.ENGLISH,                 metadata.getLanguage());
+        assertEquals("characterSet",   CharacterSet.UTF_8,             metadata.getCharacterSet());
+        assertEquals("dateStamp",      xmlDate("2014-01-04 00:00:00"), metadata.getDateStamp());
         /*
          * <gmd:contact>
          *   <gmd:CI_ResponsibleParty>
@@ -112,13 +112,10 @@
          *   </gmd:CI_ResponsibleParty>
          * </gmd:contact>
          */
-        final Responsibility contact        = getSingleton(metadata   .getContacts());
-        final Party          party          = getSingleton(contact    .getParties());
-        final Contact        contactInfo    = getSingleton(party      .getContactInfo());
-        final OnlineResource onlineResource = getSingleton(contactInfo.getOnlineResources());
-        assertInstanceOf("party", Organisation.class, party);
+        final ResponsibleParty contact = getSingleton(metadata.getContacts());
+        final OnlineResource onlineResource = contact.getContactInfo().getOnlineResource();
         assertNotNull("onlineResource", onlineResource);
-        assertEquals("organisationName", "Apache SIS", party.getName().toString());
+        assertEquals("organisationName", "Apache SIS", contact.getOrganisationName().toString());
         assertEquals("linkage", URI.create("http://sis.apache.org"), onlineResource.getLinkage());
         assertEquals("function", OnLineFunction.INFORMATION, onlineResource.getFunction());
         assertEquals("role", Role.PRINCIPAL_INVESTIGATOR, contact.getRole());
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index 8020457..d0a3236 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -207,30 +207,13 @@
 
     // Direct (not from authority codes) geodetic object creations.
     org.apache.sis.referencing.StandardDefinitionsTest.class,
-    org.apache.sis.referencing.factory.GeodeticObjectFactoryTest.class,
-    org.apache.sis.referencing.factory.GIGS3002.class,
-    org.apache.sis.referencing.factory.GIGS3003.class,
-    org.apache.sis.referencing.factory.GIGS3004.class,
-    org.apache.sis.referencing.factory.GIGS3005.class,
 
     // Well Known Text parsing require above factory.
     org.apache.sis.io.wkt.MathTransformParserTest.class,
     org.apache.sis.io.wkt.GeodeticObjectParserTest.class,
     org.apache.sis.io.wkt.WKTFormatTest.class,
-    org.apache.sis.io.wkt.WKTParserTest.class,
     org.apache.sis.io.wkt.ComparisonWithEPSG.class,
 
-    // Geodetic object creations from authority codes.
-    org.apache.sis.referencing.factory.GIGS2001.class,
-    org.apache.sis.referencing.factory.GIGS2002.class,
-    org.apache.sis.referencing.factory.GIGS2003.class,
-    org.apache.sis.referencing.factory.GIGS2004.class,
-    org.apache.sis.referencing.factory.GIGS2005.class,
-    org.apache.sis.referencing.factory.GIGS2006.class,
-    org.apache.sis.referencing.factory.GIGS2007.class,
-    org.apache.sis.referencing.factory.GIGS2008.class,
-    org.apache.sis.referencing.factory.GIGS2009.class,
-
     // Following tests may use indirectly EPSG factory.
     org.apache.sis.referencing.CommonCRSTest.class,
     org.apache.sis.referencing.factory.CommonAuthorityFactoryTest.class,
diff --git a/core/sis-utility/pom.xml b/core/sis-utility/pom.xml
index 0364ed0..6021fd0 100644
--- a/core/sis-utility/pom.xml
+++ b/core/sis-utility/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>core</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Duration.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Duration.java
new file mode 100644
index 0000000..0da87d2
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Duration.java
@@ -0,0 +1,29 @@
+/*
+ * 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.geoapi.temporal;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   1.0
+ * @version 1.0
+ * @module
+ */
+public interface Duration {
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Instant.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Instant.java
new file mode 100644
index 0000000..307dada
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Instant.java
@@ -0,0 +1,38 @@
+/*
+ * 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.geoapi.temporal;
+
+import java.util.Date;
+import org.opengis.temporal.TemporalPrimitive;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.5
+ * @module
+ */
+public interface Instant extends TemporalPrimitive {
+    /**
+     * Gets the date of this instant.
+     *
+     * @return The date.
+     */
+    Date getDate();
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Period.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Period.java
new file mode 100644
index 0000000..5212fe4
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Period.java
@@ -0,0 +1,44 @@
+/*
+ * 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.geoapi.temporal;
+
+import org.opengis.temporal.TemporalPrimitive;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface Period extends TemporalPrimitive {
+    /**
+     * Links this period to the instant at which it ends.
+     *
+     * @return The beginning instant.
+     */
+    Instant getBeginning();
+
+    /**
+     * Links this period to the instant at which it ends.
+     *
+     * @return The end instant.
+     */
+    Instant getEnding();
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/PeriodDuration.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/PeriodDuration.java
new file mode 100644
index 0000000..b417b85
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/PeriodDuration.java
@@ -0,0 +1,69 @@
+/*
+ * 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.geoapi.temporal;
+
+import org.opengis.util.InternationalString;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces which is still incomplete in GeoAPI 3.0.
+ * We reproduce here the GeoAPI 3.1-pending API. Note that at the time of writing,
+ * this is a bad API (values shall not be instances of {@link InternationalString}).
+ * This will be fixed in a future GeoAPI version.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface PeriodDuration extends org.opengis.temporal.PeriodDuration {
+    /**
+     * A positive integer, followed by the character "Y",
+     * which indicated the number of years in the period.
+     */
+    InternationalString getYears();
+
+    /**
+     * A positive integer, followed by the character "M",
+     * which indicated the number of months in the period.
+     */
+    InternationalString getMonths();
+
+    /**
+     * A positive integer, followed by the character "D",
+     * which indicated the number of days in the period.
+     */
+    InternationalString getDays();
+
+    /**
+     * A positive integer, followed by the character "H",
+     * which indicated the number of hours in the period.
+     */
+    InternationalString getHours();
+
+    /**
+     * A positive integer, followed by the character "M",
+     * which indicated the number of minutes in the period.
+     */
+    InternationalString getMinutes();
+
+    /**
+     * A positive integer, followed by the character "S",
+     * which indicated the number of seconds in the period.
+     */
+    InternationalString getSeconds();
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
new file mode 100644
index 0000000..a6bb000
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/Position.java
@@ -0,0 +1,37 @@
+/*
+ * 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.geoapi.temporal;
+
+import java.util.Date;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface Position {
+    /**
+     * Returns the time value as a {@code Date} object.
+     *
+     * @return The temporal value.
+     */
+    Date getDate();
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/TemporalFactory.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/TemporalFactory.java
new file mode 100644
index 0000000..e062bd0
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/TemporalFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.geoapi.temporal;
+
+import java.util.Date;
+import org.opengis.util.InternationalString;
+
+
+/**
+ * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public interface TemporalFactory {
+    Instant createInstant(Date date);
+
+    Period createPeriod(Instant begin, Instant end);
+
+    PeriodDuration createPeriodDuration(InternationalString years, InternationalString months,
+            InternationalString week, InternationalString days, InternationalString hours,
+            InternationalString minutes, InternationalString seconds);
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
new file mode 100644
index 0000000..61f8d32
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/geoapi/temporal/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/**
+ * Placeholder for GeoAPI interfaces not present in GeoAPI 3.0.
+ *
+ * <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
+ * @module
+ */
+package org.apache.sis.internal.geoapi.temporal;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
index b254b21..6e5ec6a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
@@ -109,7 +109,7 @@
      *
      * @see org.apache.sis.util.Version
      */
-    public static final int MINOR_VERSION = 9;
+    public static final int MINOR_VERSION = 1;
 
     /**
      * The prefix of all classnames in Apache SIS, including a trailing dot.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
index 8d87e58..21d9819 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
@@ -60,7 +60,7 @@
      * Whatever JMX agent is enabled. Setting this variable to {@code false} allows the
      * Java compiler to omit any dependency to this {@code Supervisor} class.
      */
-    static final boolean ENABLED = true;
+    static final boolean ENABLED = false;
 
     /**
      * The JMX object name for the {@code Supervisor} service.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java
index ee9d73d..02d119a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CodeLists.java
@@ -19,25 +19,22 @@
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.UndeclaredThrowableException;
-import java.util.function.Predicate;
 import org.opengis.util.CodeList;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters.Filter;
 
-// Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
-
 
 /**
  * Implementation of some {@link org.apache.sis.util.iso.Types} methods needed by {@code sis-utility} module.
- * This class opportunistically implements {@link Predicate} interface, but this is an implementation details.
+ * This class opportunistically implements {@link CodeList.Filter} interface, but this should be considered
+ * an implementation details.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-public final class CodeLists implements Predicate<CodeList<?>> {
+public final class CodeLists implements CodeList.Filter {
     /**
      * The name of bundle resources for code list titles. The resources should be loaded with
      * the same class loader than {@code org.opengis.annotation.UML.class.getClassLoader()}.
@@ -51,10 +48,26 @@
     private final String codename;
 
     /**
+     * {@code true} if {@link CodeList#valueOf} is allowed to create new code lists.
+     */
+    private final boolean canCreate;
+
+    /**
      * Creates a new filter for the specified code name.
      */
-    private CodeLists(final String codename) {
+    private CodeLists(final String codename, final boolean canCreate) {
         this.codename  = codename;
+        this.canCreate = canCreate;
+    }
+
+    /**
+     * Returns the name of the code to create, or {@code null} if no new code list shall be created.
+     *
+     * @return the name specified at construction time.
+     */
+    @Override
+    public String codename() {
+        return canCreate ? codename : null;
     }
 
     /**
@@ -63,7 +76,7 @@
      * @param  code  the code list candidate.
      */
     @Override
-    public boolean test(final CodeList<?> code) {
+    public boolean accept(final CodeList<?> code) {
         for (final String candidate : code.names()) {
             if (accept(candidate, codename)) {
                 return true;
@@ -98,7 +111,7 @@
         if (name == null || name.isEmpty()) {
             return null;
         }
-        return CodeList.valueOf(codeType, new CodeLists(name), canCreate ? name : null);
+        return CodeList.valueOf(codeType, new CodeLists(name, canCreate));
     }
 
     /**
@@ -120,19 +133,9 @@
             if (values == null) {
                 throw e;
             }
-            if (values instanceof ControlledVocabulary[]) {
-                for (final ControlledVocabulary code : (ControlledVocabulary[]) values) {
-                    for (final String candidate : code.names()) {
-                        if (accept(candidate, name)) {
-                            return enumType.cast(code);
-                        }
-                    }
-                }
-            } else {
-                for (final Enum<?> code : values) {
-                    if (accept(code.name(), name)) {
-                        return enumType.cast(code);
-                    }
+            for (final Enum<?> code : values) {
+                if (accept(code.name(), name)) {
+                    return enumType.cast(code);
                 }
             }
         }
@@ -149,7 +152,7 @@
      * @see org.apache.sis.util.iso.Types#getCodeValues(Class)
      */
     @SuppressWarnings("unchecked")
-    public static <T extends ControlledVocabulary> T[] values(final Class<T> codeType) {
+    public static <T extends CodeList<?>> T[] values(final Class<T> codeType) {
         Object values;
         try {
             values = codeType.getMethod("values", (Class<?>[]) null).invoke(null, (Object[]) null);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java
index e5101ec..9e16304 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/MetadataServices.java
@@ -31,7 +31,7 @@
 import org.apache.sis.util.CharSequences;
 
 // Branch-dependent imports
-import org.opengis.util.ControlledVocabulary;
+import org.opengis.util.CodeList;
 
 
 /**
@@ -116,9 +116,9 @@
      * @param  locale  desired locale for the title.
      * @return the title.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeTitle(ControlledVocabulary)
+     * @see org.apache.sis.util.iso.Types#getCodeTitle(CodeList)
      */
-    public String getCodeTitle(final ControlledVocabulary code, final Locale locale) {
+    public String getCodeTitle(final CodeList<?> code, final Locale locale) {
         /*
          * Following code reproduces the work done by org.apache.sis.util.iso.Types.getCodeList(…)
          * with less handling of special cases. It is executed only if the sis-metadata module is
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
index 46b554d..6228eb0 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/TemporalUtilities.java
@@ -17,9 +17,9 @@
 package org.apache.sis.internal.util;
 
 import java.util.Date;
-import org.opengis.temporal.Instant;
-import org.opengis.temporal.Period;
-import org.opengis.temporal.TemporalFactory;
+import org.apache.sis.internal.geoapi.temporal.Instant;
+import org.apache.sis.internal.geoapi.temporal.Period;
+import org.apache.sis.internal.geoapi.temporal.TemporalFactory;
 import org.opengis.temporal.TemporalPrimitive;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.resources.Errors;
@@ -44,7 +44,7 @@
      *
      * This constant will be removed after SIS release a sis-temporal module.
      */
-    public static final boolean REPORT_MISSING_MODULE = true;
+    public static final boolean REPORT_MISSING_MODULE = false;
 
     /**
      * Do not allow instantiation of this class.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java b/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java
index cc139f7..16bb33e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/IdentifiedObjectFormat.java
@@ -21,8 +21,8 @@
 import java.text.FieldPosition;
 import java.text.ParsePosition;
 import org.opengis.util.GenericName;
-import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.MetadataServices;
@@ -60,7 +60,7 @@
      */
     @Override
     public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
-        final Identifier identifier = ((IdentifiedObject) obj).getName();
+        final ReferenceIdentifier identifier = ((IdentifiedObject) obj).getName();
         if (identifier == null) {
             return toAppendTo.append(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Unnamed));
         }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java b/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java
index 1df8894..e70feff 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/LineAppender.java
@@ -279,7 +279,6 @@
      *
      * @param  lineSeparator  the new line separator, or {@code null} for forwarding EOL <i>as-is</i>.
      *
-     * @see System#lineSeparator()
      * @see Characters#isLineOrParagraphSeparator(int)
      */
     public void setLineSeparator(final String lineSeparator) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java b/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
index 726f748..bbf262f 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/io/TabularFormat.java
@@ -214,7 +214,7 @@
      *   <li>If present, {@code '?'} shall be the first character in the pattern.</li>
      *   <li>The repeated character (specified inside the pair of brackets) is mandatory.</li>
      *   <li>In the current implementation, the repeated character must be in the
-     *       {@linkplain Character#isBmpCodePoint(int) Basic Multilanguage Plane}.</li>
+     *       Basic Multilanguage Plane.</li>
      *   <li>If {@code '/'} is present, anything on its right side shall be compliant
      *       with the {@link Pattern} syntax.</li>
      * </ul>
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
index 51a7c96..fe6e540 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
@@ -208,7 +208,7 @@
      * returns directly the {@linkplain Math#abs(double) absolute value} of that element
      * without computing {@code sqrt(v²)}, in order to avoid rounding error. This special case
      * has been implemented because this method is often invoked for computing the length of
-     * {@linkplain org.opengis.coverage.grid.RectifiedGrid#getOffsetVectors() offset vectors},
+     * offset vectors,
      * typically aligned with the axes of a {@linkplain org.opengis.referencing.cs.CartesianCS
      * Cartesian coordinate system}.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
index d1691dd..722c69f 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
@@ -258,13 +258,6 @@
      * instead than {@code MeasurementRange}. Nevertheless this method may return {@code null} if a unit
      * <em>should</em> exist but for some reason is unavailable.
      *
-     * <div class="note"><b>Example:</b>
-     * ISO 19115-1 {@code SampleDimension} specifies that its
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getUnits() unit} property is mandatory if the
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getMinValue() minimum value} or
-     * {@linkplain org.opengis.metadata.content.SampleDimension#getMaxValue() maximum value} are provided.
-     * Nevertheless it happens sometime that this information is missing in metadata.</div>
-     *
      * @return the unit of measurement, or {@code null}.
      */
     @Override
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
index c5d4989..733968d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java
@@ -60,8 +60,8 @@
  * <a href="http://en.wikipedia.org/wiki/Interval_%28mathematics%29">mathematical definition of interval</a>.
  * It is closely related, while not identical, to the ISO 19123 (<cite>Coverage geometry and functions</cite>)
  * definition of "ranges". At the difference of the parent {@link Range} class, which can be used only with
- * {@linkplain org.opengis.coverage.DiscreteCoverage discrete coverages}, the {@code NumberRange} class can
- * also be used with {@linkplain org.opengis.coverage.ContinuousCoverage continuous coverages}.
+ * discrete coverages, the {@code NumberRange} class can
+ * also be used with continuous coverages.
  *
  * <h2>Immutability and thread safety</h2>
  * This class and the {@link MeasurementRange} subclasses are immutable, and thus inherently thread-safe.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
index e192dcb..579517d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
@@ -56,8 +56,8 @@
  *
  * <h2>Relationship with ISO 19123 definition of range</h2>
  * The ISO 19123 standard (<cite>Coverage geometry and functions</cite>) defines the range as the set
- * (either finite or {@linkplain org.opengis.geometry.TransfiniteSet transfinite}) of feature attribute
- * values associated by a function (the {@linkplain org.opengis.coverage.Coverage coverage}) with the
+ * (either finite or transfinite) of feature attribute
+ * values associated by a function (the coverage) with the
  * elements of the coverage domain. In other words, if we see a coverage as a function, then a range
  * is the set of possible return values.
  *
@@ -68,9 +68,8 @@
  * is closely related, but not identical, to the ISO 19123 definition or range.</p>
  *
  * <p>Ranges are not necessarily numeric. Numeric and non-numeric ranges can be associated to
- * {@linkplain org.opengis.coverage.DiscreteCoverage discrete coverages}, while typically only
- * numeric ranges can be associated to {@linkplain org.opengis.coverage.ContinuousCoverage
- * continuous coverages}.</p>
+ * discrete coverages, while typically only
+ * numeric ranges can be associated to continuous coverages.</p>
  *
  * <h2>Immutability and thread safety</h2>
  * This class and the {@link NumberRange} / {@link MeasurementRange} subclasses are immutable,
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
index 1b4188d..b45177a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
@@ -33,11 +33,11 @@
 import java.text.ParsePosition;
 import java.text.ParseException;
 import java.util.regex.Matcher;
+import org.opengis.util.CodeList;
 import java.nio.charset.Charset;
 import org.opengis.util.Type;
 import org.opengis.util.Record;
 import org.opengis.util.GenericName;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.io.LineAppender;
 import org.apache.sis.io.TableAppender;
@@ -754,8 +754,8 @@
                 text = ((InternationalString) value).toString(getDisplayLocale());
             } else if (value instanceof CharSequence) {
                 text = value.toString();
-            } else if (value instanceof ControlledVocabulary) {
-                text = MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) value, getDisplayLocale());
+            } else if (value instanceof CodeList<?>) {
+                text = MetadataServices.getInstance().getCodeTitle((CodeList<?>) value, getDisplayLocale());
             } else if (value instanceof Enum<?>) {
                 text = CharSequences.upperCaseToSentence(((Enum<?>) value).name());
             } else if (value instanceof Type) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java b/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
index 827fa94..1600164 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
@@ -20,7 +20,6 @@
 import java.util.Formatter;
 import java.util.Formattable;
 import java.util.FormattableFlags;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.util.CharSequences;
@@ -40,7 +39,7 @@
  * by a {@link org.opengis.util.CodeList} value. This can be done with:
  *
  * <ul>
- *   <li>{@link Types#getCodeTitle(ControlledVocabulary)} for getting the {@link InternationalString}
+ *   <li>{@link Types#getCodeTitle(CodeList)} for getting the {@link InternationalString}
  *       instance to store in a metadata property.</li>
  *   <li>{@link Types#forCodeTitle(CharSequence)} for retrieving the {@link org.opengis.util.CodeList}
  *       previously stored as an {@code InternationalString}.</li>
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
index 1427a08..4492e67 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -32,7 +32,6 @@
 import java.lang.reflect.Modifier;
 import javax.measure.Unit;
 import org.opengis.util.CodeList;
-import org.opengis.util.ControlledVocabulary;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
@@ -424,8 +423,8 @@
                 replacement = message;
             } else if (element instanceof Class<?>) {
                 replacement = Classes.getShortName(getPublicType((Class<?>) element));
-            } else if (element instanceof ControlledVocabulary) {
-                replacement = MetadataServices.getInstance().getCodeTitle((ControlledVocabulary) element, getLocale());
+            } else if (element instanceof CodeList<?>) {
+                replacement = MetadataServices.getInstance().getCodeTitle((CodeList<?>) element, getLocale());
             } else if (element instanceof Range<?>) {
                 final Range<?> range = (Range<?>) element;
                 replacement = new RangeFormat(getLocale(), range.getElementType()).format(range);
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java b/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
index 36d0edc..1717565 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
@@ -48,7 +48,7 @@
  * @since   0.3
  * @module
  */
-public strictfp class Assert extends org.opengis.test.Assert {
+public strictfp class Assert extends GeoapiAssert {
     /**
      * For subclass constructor only.
      */
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/ContentVerifier.java b/core/sis-utility/src/test/java/org/apache/sis/test/ContentVerifier.java
new file mode 100644
index 0000000..51c5362
--- /dev/null
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/ContentVerifier.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.test;
+
+import static org.junit.Assume.assumeTrue;
+
+import org.opengis.metadata.Metadata;
+
+
+/**
+ * Place-holder for a GeoAPI 3.1 class. Used only for allowing the code to compile.
+ * For real test execution, see the development branches on GeoAPI 4.0-SNAPSHOT.
+ */
+public class ContentVerifier {
+    public void addPropertyToIgnore(Class<?> type, String property) {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+    }
+
+    public void addMetadataToVerify(Metadata actual) {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+    }
+
+    public void assertMetadataEquals(final String path, final Object value, final Object... others) {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+    }
+}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/GeoapiAssert.java b/core/sis-utility/src/test/java/org/apache/sis/test/GeoapiAssert.java
new file mode 100644
index 0000000..6914ff5
--- /dev/null
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/GeoapiAssert.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.test;
+
+import org.opengis.metadata.citation.Citation;
+import org.opengis.referencing.ReferenceIdentifier;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.util.InternationalString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * Temporary class for test methods that are expected to be provided in next GeoAPI release.
+ * Those methods are defined in a separated class in order to make easier for us to identify
+ * which methods may be removed from SIS (actually moved to GeoAPI) in a future GeoAPI release.
+ *
+ * <p>This class is needed for Apache SIS trunk, since the later is linked to GeoAPI official release.
+ * But this class can be removed on Apache SIS branches which are linked to a GeoAPI development branch.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+strictfp class GeoapiAssert extends org.opengis.test.Assert {
+    /**
+     * A flag for code that are pending next GeoAPI release before to be enabled.
+     * This flag is always set to {@code false}, except occasionally just before
+     * a GeoAPI release for testing purpose. It shall be used as below:
+     *
+     * {@preformat java
+     *     if (PENDING_NEXT_GEOAPI_RELEASE) {
+     *         // Do some stuff here.
+     *     }
+     * }
+     *
+     * The intend is to make easier to identify test cases that fail with the current version
+     * of the {@code geoapi-conformance} module, but should pass with the development snapshot.
+     */
+    public static final boolean PENDING_NEXT_GEOAPI_RELEASE = false;
+
+    /**
+     * The keyword for unrestricted value in {@link String} arguments.
+     */
+    private static final String UNRESTRICTED = "##unrestricted";
+
+    /**
+     * For subclass constructor only.
+     */
+    GeoapiAssert() {
+    }
+
+    /**
+     * Returns the concatenation of the given message with the given extension.
+     * This method returns the given extension if the message is null or empty.
+     *
+     * <p>Invoking this method is equivalent to invoking {@code nonNull(message) + ext},
+     * but avoid the creation of temporary objects in the common case where the message
+     * is null.</p>
+     *
+     * @param  message The message, or {@code null}.
+     * @param  ext The extension to append after the message.
+     * @return The concatenated string.
+     */
+    private static String concat(String message, final String ext) {
+        if (message == null || (message = message.trim()).isEmpty()) {
+            return ext;
+        }
+        return message + ' ' + ext;
+    }
+
+    /**
+     * Verifies if we expected a null value, then returns {@code true} if the value is null as expected.
+     */
+    private static boolean isNull(final String message, final Object expected, final Object actual) {
+        final boolean isNull = (actual == null);
+        if (isNull != (expected == null)) {
+            fail(concat(message, isNull ? "Value is null." : "Expected null."));
+        }
+        return isNull;
+    }
+
+    /**
+     * Asserts that the title or an alternate title of the given citation is equal to the given string.
+     * This method is typically used for testing if a citation stands for the OGC, OGP or EPSG authority
+     * for instance. Such abbreviations are often declared as {@linkplain Citation#getAlternateTitles()
+     * alternate titles} rather than the main {@linkplain Citation#getTitle() title}, but this method
+     * tests both for safety.
+     *
+     * @param message  Header of the exception message in case of failure, or {@code null} if none.
+     * @param expected The expected title or alternate title.
+     * @param actual   The citation to test.
+     */
+    public static void assertAnyTitleEquals(final String message, final String expected, final Citation actual) {
+        if (isNull(message, expected, actual)) {
+            return;
+        }
+        InternationalString title = actual.getTitle();
+        if (title != null && expected.equals(title.toString())) {
+            return;
+        }
+        for (final InternationalString t : actual.getAlternateTitles()) {
+            if (expected.equals(t.toString())) {
+                return;
+            }
+        }
+        fail(concat(message, '"' + expected + "\" not found in title or alternate titles."));
+    }
+
+    /**
+     * Asserts that the given identifier is equals to the given authority, code space, version and code.
+     * If any of the above-cited properties is {@code ""##unrestricted"}, then it will not be verified.
+     * This flexibility is useful in the common case where a test accepts any {@code version} value.
+     *
+     * @param message    Header of the exception message in case of failure, or {@code null} if none.
+     * @param authority  The expected authority title or alternate title (may be {@code null}), or {@code "##unrestricted"}.
+     * @param codeSpace  The expected code space (may be {@code null}), or {@code "##unrestricted"}.
+     * @param version    The expected version    (may be {@code null}), or {@code "##unrestricted"}.
+     * @param code       The expected code value (may be {@code null}), or {@code "##unrestricted"}.
+     * @param actual     The identifier to test.
+     */
+    public static void assertIdentifierEquals(final String message, final String authority, final String codeSpace,
+            final String version, final String code, final ReferenceIdentifier actual)
+    {
+        if (actual == null) {
+            fail(concat(message, "Identifier is null"));
+        } else {
+            if (!UNRESTRICTED.equals(authority)) assertAnyTitleEquals(message,                      authority, actual.getAuthority());
+            if (!UNRESTRICTED.equals(codeSpace)) assertEquals (concat(message, "Wrong code space"), codeSpace, actual.getCodeSpace());
+            if (!UNRESTRICTED.equals(version))   assertEquals (concat(message, "Wrong version"),    version,   actual.getVersion());
+            if (!UNRESTRICTED.equals(code))      assertEquals (concat(message, "Wrong code"),       code,      actual.getCode());
+        }
+    }
+
+    /**
+     * Asserts that all axes in the given coordinate system are pointing toward the given directions, in the same order.
+     *
+     * @param message  Header of the exception message in case of failure, or {@code null} if none.
+     * @param cs       The coordinate system to test.
+     * @param expected The expected axis directions.
+     */
+    public static void assertAxisDirectionsEqual(String message,
+            final CoordinateSystem cs, final AxisDirection... expected)
+    {
+        assertEquals(concat(message, "Wrong coordinate system dimension."), expected.length, cs.getDimension());
+        message = concat(message, "Wrong axis direction.");
+        for (int i=0; i<expected.length; i++) {
+            assertEquals(message, expected[i], cs.getAxis(i).getDirection());
+        }
+    }
+
+    /**
+     * Asserts that the given matrix is equals to the expected one, up to the given tolerance value.
+     *
+     * @param message   Header of the exception message in case of failure, or {@code null} if none.
+     * @param expected  The expected matrix, which may be {@code null}.
+     * @param actual    The matrix to compare, or {@code null}.
+     * @param tolerance The tolerance threshold.
+     */
+    public static void assertMatrixEquals(final String message, final Matrix expected, final Matrix actual, final double tolerance) {
+        if (isNull(message, expected, actual)) {
+            return;
+        }
+        final int numRow = actual.getNumRow();
+        final int numCol = actual.getNumCol();
+        assertEquals("numRow", expected.getNumRow(), numRow);
+        assertEquals("numCol", expected.getNumCol(), numCol);
+        for (int j=0; j<numRow; j++) {
+            for (int i=0; i<numCol; i++) {
+                final double e = expected.getElement(j,i);
+                final double a = actual.getElement(j,i);
+                if (!(StrictMath.abs(e - a) <= tolerance) && Double.doubleToLongBits(a) != Double.doubleToLongBits(e)) {
+                    fail("Matrix.getElement(" + j + ", " + i + "): expected " + e + " but got " + a);
+                }
+            }
+        }
+    }
+}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java b/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java
index 0c025a3..9ef36b2 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/LoggingWatcher.java
@@ -24,11 +24,13 @@
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
 import java.util.logging.SimpleFormatter;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
 
 import static org.junit.Assert.*;
 
+// Branch-specific imports
+import org.junit.rules.TestWatchman;
+import org.junit.runners.model.FrameworkMethod;
+
 
 /**
  * Watches the logs sent to the given logger.
@@ -61,7 +63,7 @@
  * @since   0.6
  * @module
  */
-public final strictfp class LoggingWatcher extends TestWatcher implements Filter {
+public final strictfp class LoggingWatcher extends TestWatchman implements Filter {
     /**
      * The logged messages.
      */
@@ -106,7 +108,7 @@
      * @see #isLoggable(LogRecord)
      */
     @Override
-    protected final void starting(final Description description) {
+    public final void starting(final FrameworkMethod description) {
         assertNull(logger.getFilter());
         logger.setFilter(this);
     }
@@ -118,7 +120,7 @@
      * @param  description  a description of the JUnit test that finished.
      */
     @Override
-    protected final void finished(final Description description) {
+    public final void finished(final FrameworkMethod description) {
         logger.setFilter(null);
     }
 
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
index 7c7c000..9809940 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
@@ -44,8 +44,6 @@
 import java.io.NotSerializableException;
 import java.io.Serializable;
 import java.awt.geom.Point2D;
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.GeographicCRS;
@@ -121,10 +119,7 @@
     /**
      * Dummy class for {@link #testGetLeafInterfaces()}.
      */
-    private abstract static class T1 implements GeographicCRS {
-        @Override public InternationalString getScope() {return null;}
-        @Override public Extent getDomainOfValidity() {return null;}
-    }
+    private abstract static class T1 implements GeographicCRS {}
     private abstract static class T2 extends T1 implements SingleCRS, CoordinateOperation {}
     private abstract static class T3 extends T2 implements Transformation {}
 
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
index baa7545..ff6d985 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
@@ -255,7 +255,7 @@
         tf = new TreeTableFormat(Locale.FRENCH, null);
         assertMultilinesEquals(
                 "Root\n" +
-                "  ├─CodeList…… Point de contact\n" +
+                "  ├─CodeList…… Point of contact\n" + // Not yet localized.
                 "  ├─Enum……………… Half down\n" +                      // No localization provided.
                 "  └─i18n……………… Une phrase en français\n", tf.format(table));
 
diff --git a/ide-project/NetBeans/README.txt b/ide-project/NetBeans/README.txt
index a61c0c9..6724fc5 100644
--- a/ide-project/NetBeans/README.txt
+++ b/ide-project/NetBeans/README.txt
@@ -4,37 +4,6 @@
 
 
 ==============================================================================
-Installation
-==============================================================================
-The configuration provided in this directory requires a checkout of GeoAPI
-source code. The recommended installation steps is as below (from the root
-directory of all SIS-related projects):
-
-  mkdir SIS
-  git clone -b geoapi-4.0 https://gitbox.apache.org/repos/asf/sis.git SIS/dev
-  mkdir GeoAPI
-  git clone http://github.com/opengeospatial/geoapi GeoAPI/master
-
-Above commands should create the following directory structure:
-
-  +-- GeoAPI
-  |   +-- master
-  |       +-- README.md
-  |       +-- etc...
-  +-- SIS
-      +-- dev
-          +-- README
-          +-- etc...
-
-If a different directory layout is desired, this is possible provided that
-the following line is added to "nbproject/private/private.properties" file:
-
-  project.GeoAPI = <path to your GeoAPI checkout>/ide-project/NetBeans
-
-
-
-
-==============================================================================
 Recommendations for NetBeans project configuration changes
 ==============================================================================
 There is 3 important files that should be edited BY HAND for preserving user-
diff --git a/ide-project/NetBeans/build.xml b/ide-project/NetBeans/build.xml
index fcaedcf..e5ccf32 100644
--- a/ide-project/NetBeans/build.xml
+++ b/ide-project/NetBeans/build.xml
@@ -49,6 +49,11 @@
     before it can be built by the NetBeans IDE.
   -->
   <target name="-post-compile">
+    <copy todir="${build.classes.dir}/org/apache/sis/util/iso">
+      <fileset dir="${project.root}/core/sis-metadata/src/main/resources/org/apache/sis/util/iso">
+        <include name="class-index.properties"/>
+      </fileset>
+    </copy>
     <copy todir="${build.classes.dir}">
       <fileset dir="${project.root}/core/sis-utility/target/generated-resources">
         <include name="**/*.utf"/>
@@ -84,9 +89,6 @@
         <include name="**/*.utf"/>
       </fileset>
       -->
-      <fileset dir="${project.root}/application/sis-javafx/target/generated-resources">
-        <include name="**/*.utf"/>
-      </fileset>
 
       <!-- Other resources (properties files, SQL scripts, native libraries). -->
       <fileset dir="${project.root}/core/sis-utility/src/main/resources">
@@ -107,10 +109,6 @@
         <include name="**/*.dll"/>
         <include name="**/*.so"/>
       </fileset>
-      <fileset dir="${project.root}/application/sis-javafx/src/main/resources">
-        <include name="**/*.fxml"/>
-        <include name="**/*.png"/>
-      </fileset>
       <fileset dir="${project.root}/application/sis-console/src/main/resources">
         <include name="**/*.properties"/>
       </fileset>
diff --git a/ide-project/NetBeans/nbproject/build-impl.xml b/ide-project/NetBeans/nbproject/build-impl.xml
index 51266d8..89727fe 100644
--- a/ide-project/NetBeans/nbproject/build-impl.xml
+++ b/ide-project/NetBeans/nbproject/build-impl.xml
@@ -19,7 +19,7 @@
   - cleanup
 
         -->
-<project xmlns:if="ant:if" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.1-impl">
+<project xmlns:if="ant:if" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" xmlns:unless="ant:unless" basedir=".." default="default" name="Apache_SIS_on_GeoAPI_3.0-impl">
     <fail message="Please build using Ant 1.8.0 or higher.">
         <condition>
             <not>
@@ -805,7 +805,7 @@
                     </fileset>
                 </union>
                 <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
-                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="Apache_SIS_on_GeoAPI_3.1" testname="TestNG tests" workingDir="${work.dir}">
+                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="Apache_SIS_on_GeoAPI_3.0" testname="TestNG tests" workingDir="${work.dir}">
                     <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
                     <propertyset>
                         <propertyref prefix="test-sys-prop."/>
@@ -902,7 +902,7 @@
                 <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
                     <isset property="test.method"/>
                 </condition>
-                <condition else="-suitename Apache_SIS_on_GeoAPI_3.1 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+                <condition else="-suitename Apache_SIS_on_GeoAPI_3.0 -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
                     <matches pattern=".*\.xml" string="@{testClass}"/>
                 </condition>
                 <delete dir="${build.test.results.dir}" quiet="true"/>
@@ -1243,7 +1243,7 @@
         <delete file="${built-jar.properties}" quiet="true"/>
     </target>
     <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
-        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.0 was already built"/>
     </target>
     <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
         <mkdir dir="${build.dir}"/>
@@ -2066,7 +2066,7 @@
         <delete file="${built-clean.properties}" quiet="true"/>
     </target>
     <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
-        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.1 was already built"/>
+        <echo level="warn" message="Cycle detected: Apache SIS on GeoAPI 3.0 was already built"/>
     </target>
     <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
         <mkdir dir="${build.dir}"/>
diff --git a/ide-project/NetBeans/nbproject/genfiles.properties b/ide-project/NetBeans/nbproject/genfiles.properties
index 2039f39..3f5e676 100644
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=c66331aa
-nbproject/build-impl.xml.script.CRC32=a8b9b922
+nbproject/build-impl.xml.data.CRC32=82e7f46a
+nbproject/build-impl.xml.script.CRC32=dbe1a9a7
 nbproject/build-impl.xml.stylesheet.CRC32=3a2fa800@1.92.0.48
diff --git a/ide-project/NetBeans/nbproject/project.properties b/ide-project/NetBeans/nbproject/project.properties
index ffd3b0b..0b1421b 100644
--- a/ide-project/NetBeans/nbproject/project.properties
+++ b/ide-project/NetBeans/nbproject/project.properties
@@ -96,7 +96,7 @@
 # Those dependencies must exist in the local Maven repository.
 # Those numbers should match the ones declared in the pom.xml files.
 #
-geoapi.version       = 3.1-SNAPSHOT
+geoapi.version       = 3.0.1
 jsr363.version       = 1.0
 jaxb.version         = 2.3.2
 istack.version       = 3.0.8
@@ -132,7 +132,7 @@
 maven.repository   = ${user.home}/.m2/repository
 endorsed.classpath =
 javac.classpath=\
-    ${maven.repository}/org/opengis/geoapi-pending/${geoapi.version}/geoapi-pending-${geoapi.version}.jar:\
+    ${maven.repository}/org/opengis/geoapi/${geoapi.version}/geoapi-${geoapi.version}.jar:\
     ${maven.repository}/javax/measure/unit-api/${jsr363.version}/unit-api-${jsr363.version}.jar:\
     ${maven.repository}/jakarta/xml/bind/jakarta.xml.bind-api/${jaxb.version}/jakarta.xml.bind-api-${jaxb.version}.jar:\
     ${maven.repository}/com/esri/geometry/esri-geometry-api/${esri.api.version}/esri-geometry-api-${esri.api.version}.jar:\
diff --git a/ide-project/NetBeans/nbproject/project.xml b/ide-project/NetBeans/nbproject/project.xml
index c7d2ae0..6c68123 100644
--- a/ide-project/NetBeans/nbproject/project.xml
+++ b/ide-project/NetBeans/nbproject/project.xml
@@ -21,7 +21,7 @@
     <type>org.netbeans.modules.java.j2seproject</type>
     <configuration>
         <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
-            <name>Apache SIS on GeoAPI 3.1</name>
+            <name>Apache SIS on GeoAPI 3.0</name>
             <source-roots>
                 <root id="src.local-src.dir" name="Local sources (unversioned)"/>
                 <root id="src.webapp.dir" name="Web application"/>
diff --git a/pom.xml b/pom.xml
index f228b94..c28b1e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,7 +49,7 @@
        ============================================================== -->
   <groupId>org.apache.sis</groupId>
   <artifactId>parent</artifactId>
-  <version>1.x-SNAPSHOT</version>
+  <version>1.1-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>Apache SIS</name>
@@ -425,7 +425,7 @@
       </dependency>
       <dependency>
         <groupId>org.opengis</groupId>
-        <artifactId>geoapi-pending</artifactId>
+        <artifactId>geoapi</artifactId>
         <version>${geoapi.version}</version>
       </dependency>
       <dependency>
@@ -561,7 +561,7 @@
     <maven.compiler.target>8</maven.compiler.target>
     <sis.plugin.version>${project.version}</sis.plugin.version>
     <sis.non-free.version>1.0</sis.non-free.version>                 <!-- Used only if "non-free" profile is enabled. -->
-    <geoapi.version>3.1-SNAPSHOT</geoapi.version>
+    <geoapi.version>3.0.1</geoapi.version>
     <jaxb.version>2.3.2</jaxb.version>
   </properties>
 
@@ -864,7 +864,7 @@
           <links>
             <link>https://docs.oracle.com/javase/8/docs/api</link>
             <link>http://unitsofmeasurement.github.io/unit-api/site/apidocs</link>
-            <link>http://www.geoapi.org/snapshot/pending</link>
+            <link>http://www.geoapi.org/3.0/javadoc</link>
             <link>http://www.unidata.ucar.edu/software/thredds/current/netcdf-java/javadoc</link>
           </links>
 
@@ -990,17 +990,6 @@
     </pluginRepository>
   </pluginRepositories>
 
-  <!-- Used for GeoAPI snapshots only.
-       Shall be removed on SIS master. -->
-  <repositories>
-    <repository>
-      <id>geotoolkit</id>
-      <name>Geotoolkit.org repository</name>
-      <url>http://maven.geotoolkit.org</url>
-    </repository>
-  </repositories>
-
-
 
   <!-- ==============================================================
          Group of modules to build in approximate dependency order.
diff --git a/profiles/pom.xml b/profiles/pom.xml
index e7d9779..37775ad 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
@@ -94,7 +94,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
diff --git a/profiles/sis-french-profile/pom.xml b/profiles/sis-french-profile/pom.xml
index 16aea32..0ca3802 100644
--- a/profiles/sis-french-profile/pom.xml
+++ b/profiles/sis-french-profile/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>profiles</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/profiles/sis-japan-profile/pom.xml b/profiles/sis-japan-profile/pom.xml
index ac4b852..06858c2 100644
--- a/profiles/sis-japan-profile/pom.xml
+++ b/profiles/sis-japan-profile/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>profiles</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/pom.xml b/storage/pom.xml
index de07a91..e6b26fd 100644
--- a/storage/pom.xml
+++ b/storage/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>parent</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
@@ -136,7 +136,7 @@
     </dependency>
     <dependency>
       <groupId>org.opengis</groupId>
-      <artifactId>geoapi-pending</artifactId>
+      <artifactId>geoapi</artifactId>
     </dependency>
 
     <!-- Test dependencies -->
diff --git a/storage/sis-earth-observation/pom.xml b/storage/sis-earth-observation/pom.xml
index 9044b7a..690e918 100644
--- a/storage/sis-earth-observation/pom.xml
+++ b/storage/sis-earth-observation/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index 0e50216..92d0f1d 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -893,7 +893,7 @@
      */
     final Metadata getMetadata() throws FactoryException {
         addLanguage(Locale.ENGLISH, MetadataBuilder.Scope.METADATA);
-        addResourceScope(ScopeCode.COVERAGE, null);
+        addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
         addTopicCategory(TopicCategory.GEOSCIENTIFIC_INFORMATION);
         try {
             flushSceneTime();
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
index 5e0567f..d7c66ca 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
@@ -22,23 +22,14 @@
 import java.io.InputStreamReader;
 import org.apache.sis.internal.storage.AbstractResource;
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.acquisition.Context;
-import org.opengis.metadata.acquisition.OperationType;
-import org.opengis.metadata.citation.DateType;
-import org.opengis.metadata.content.CoverageContentType;
-import org.opengis.metadata.content.TransferFunctionType;
-import org.opengis.metadata.identification.Progress;
-import org.opengis.metadata.identification.TopicCategory;
-import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.FactoryException;
-import org.opengis.test.dataset.ContentVerifier;
+import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.apache.sis.test.Assert.*;
-import static org.apache.sis.test.TestUtilities.date;
+import static org.apache.sis.test.TestUtilities.formatMetadata;
 import static org.apache.sis.storage.earthobservation.LandsatReader.DIM;
 
 
@@ -46,7 +37,6 @@
  * Tests {@link LandsatReader}.
  *
  * @author  Thi Phuong Hao Nguyen (VNSC)
- * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   0.8
  * @module
@@ -89,6 +79,7 @@
      * @throws FactoryException if an error occurred while creating the Coordinate Reference System.
      */
     @Test
+    @org.junit.Ignore("Requires GeoAPI 3.1.")
     public void testRead() throws IOException, DataStoreException, FactoryException {
         final Metadata actual;
         try (BufferedReader in = new BufferedReader(new InputStreamReader(
@@ -98,174 +89,205 @@
             reader.read(in);
             actual = reader.getMetadata();
         }
-        final ContentVerifier verifier = new ContentVerifier();
-        verifier.addPropertyToIgnore(Metadata.class, "metadataStandard");           // Because hard-coded in SIS.
-        verifier.addPropertyToIgnore(Metadata.class, "referenceSystemInfo");        // Very verbose and depends on EPSG connection.
-        verifier.addMetadataToVerify(actual);
-        verifier.assertMetadataEquals(
-            "defaultLocale+otherLocale[0]",                                                          "en",
-            "metadataIdentifier.code",                                                               "LandsatTest",
-            "metadataScope[0].resourceScope",                                                        ScopeCode.COVERAGE,
-            "dateInfo[0].date",                                                                      date("2016-06-27 16:48:12"),
-            "dateInfo[0].dateType",                                                                  DateType.CREATION,
-            "identificationInfo[0].topicCategory[0]",                                                TopicCategory.GEOSCIENTIFIC_INFORMATION,
-            "identificationInfo[0].citation.date[0].date",                                           date("2016-06-27 16:48:12"),
-            "identificationInfo[0].citation.date[0].dateType",                                       DateType.CREATION,
-            "identificationInfo[0].citation.title",                                                  "LandsatTest",
-            "identificationInfo[0].credit[0]",                                                       "Derived from U.S. Geological Survey data",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title",             "GeoTIFF Coverage Encoding Profile",
-            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]", "GeoTIFF",
-            "identificationInfo[0].extent[0].geographicElement[0].extentTypeCode",                   true,
-            "identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude",               108.34,
-            "identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude",               110.44,
-            "identificationInfo[0].extent[0].geographicElement[0].southBoundLatitude",                10.50,
-            "identificationInfo[0].extent[0].geographicElement[0].northBoundLatitude",                12.62,
-            "identificationInfo[0].spatialResolution[0].distance",                                    15.0,
-            "identificationInfo[0].spatialResolution[1].distance",                                    30.0,
-
-            "acquisitionInformation[0].platform[0].identifier.code",               "Pseudo LANDSAT",
-            "acquisitionInformation[0].platform[0].instrument[0].identifier.code", "Pseudo TIRS",
-            "acquisitionInformation[0].acquisitionRequirement[0].identifier.code", "Software unit tests",
-            "acquisitionInformation[0].operation[0].significantEvent[0].context",  Context.ACQUISITION,
-            "acquisitionInformation[0].operation[0].significantEvent[0].time",     date("2016-06-26 03:02:01.090"),
-            "acquisitionInformation[0].operation[0].status",                       Progress.COMPLETED,
-            "acquisitionInformation[0].operation[0].type",                         OperationType.REAL,
-
-            "contentInfo[0].processingLevelCode.authority.title",          "Landsat",
-            "contentInfo[0].processingLevelCode.codeSpace",                "Landsat",
-            "contentInfo[0].processingLevelCode.code",                     "Pseudo LT1",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].name[0].code",  "TestImage_B1.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[1].name[0].code",  "TestImage_B2.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[2].name[0].code",  "TestImage_B3.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[3].name[0].code",  "TestImage_B4.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[4].name[0].code",  "TestImage_B5.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[5].name[0].code",  "TestImage_B6.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[6].name[0].code",  "TestImage_B7.TIF",
-            "contentInfo[0].attributeGroup[0].attribute[7].name[0].code",  "TestImage_B9.TIF",
-            "contentInfo[0].attributeGroup[1].attribute[0].name[0].code",  "TestImage_B8.TIF",
-            "contentInfo[0].attributeGroup[2].attribute[0].name[0].code",  "TestImage_B10.TIF",
-            "contentInfo[0].attributeGroup[2].attribute[1].name[0].code",  "TestImage_B11.TIF",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].description",   "Coastal Aerosol",
-            "contentInfo[0].attributeGroup[0].attribute[1].description",   "Blue",
-            "contentInfo[0].attributeGroup[0].attribute[2].description",   "Green",
-            "contentInfo[0].attributeGroup[0].attribute[3].description",   "Red",
-            "contentInfo[0].attributeGroup[0].attribute[4].description",   "Near-Infrared",
-            "contentInfo[0].attributeGroup[0].attribute[5].description",   "Short Wavelength Infrared (SWIR) 1",
-            "contentInfo[0].attributeGroup[0].attribute[6].description",   "Short Wavelength Infrared (SWIR) 2",
-            "contentInfo[0].attributeGroup[0].attribute[7].description",   "Cirrus",
-            "contentInfo[0].attributeGroup[1].attribute[0].description",   "Panchromatic",
-            "contentInfo[0].attributeGroup[2].attribute[0].description",   "Thermal Infrared Sensor (TIRS) 1",
-            "contentInfo[0].attributeGroup[2].attribute[1].description",   "Thermal Infrared Sensor (TIRS) 2",
-
-            "contentInfo[0].attributeGroup[0].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].minValue",      1.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].minValue",      1.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].minValue",      1.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].minValue",      1.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].maxValue",      65535.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].maxValue",      65535.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].peakResponse",    433.0,
-            "contentInfo[0].attributeGroup[0].attribute[1].peakResponse",    482.0,
-            "contentInfo[0].attributeGroup[0].attribute[2].peakResponse",    562.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].peakResponse",    655.0,
-            "contentInfo[0].attributeGroup[0].attribute[4].peakResponse",    865.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].peakResponse",   1610.0,
-            "contentInfo[0].attributeGroup[0].attribute[6].peakResponse",   2200.0,
-            "contentInfo[0].attributeGroup[0].attribute[7].peakResponse",   1375.0,
-            "contentInfo[0].attributeGroup[1].attribute[0].peakResponse",    590.0,
-            "contentInfo[0].attributeGroup[2].attribute[0].peakResponse",  10800.0,
-            "contentInfo[0].attributeGroup[2].attribute[1].peakResponse",  12000.0,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[1].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[2].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[3].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[4].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[5].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[6].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[0].attribute[7].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[1].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[2].attribute[0].transferFunctionType",  TransferFunctionType.LINEAR,
-            "contentInfo[0].attributeGroup[2].attribute[1].transferFunctionType",  TransferFunctionType.LINEAR,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].scaleFactor",  0.0127,
-            "contentInfo[0].attributeGroup[0].attribute[1].scaleFactor",  0.013,
-            "contentInfo[0].attributeGroup[0].attribute[2].scaleFactor",  0.012,
-            "contentInfo[0].attributeGroup[0].attribute[3].scaleFactor",  0.0101,
-            "contentInfo[0].attributeGroup[0].attribute[4].scaleFactor",  0.00619,
-            "contentInfo[0].attributeGroup[0].attribute[5].scaleFactor",  0.00154,
-            "contentInfo[0].attributeGroup[0].attribute[6].scaleFactor",  0.000519,
-            "contentInfo[0].attributeGroup[0].attribute[7].scaleFactor",  0.00242,
-            "contentInfo[0].attributeGroup[1].attribute[0].scaleFactor",  0.0115,
-            "contentInfo[0].attributeGroup[2].attribute[0].scaleFactor",  0.000334,
-            "contentInfo[0].attributeGroup[2].attribute[1].scaleFactor",  0.000334,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].offset",       -63.6,
-            "contentInfo[0].attributeGroup[0].attribute[1].offset",       -65.1,
-            "contentInfo[0].attributeGroup[0].attribute[2].offset",       -60.0,
-            "contentInfo[0].attributeGroup[0].attribute[3].offset",       -50.6,
-            "contentInfo[0].attributeGroup[0].attribute[4].offset",       -31.0,
-            "contentInfo[0].attributeGroup[0].attribute[5].offset",       -7.7,
-            "contentInfo[0].attributeGroup[0].attribute[6].offset",       -2.6,
-            "contentInfo[0].attributeGroup[0].attribute[7].offset",       -12.1,
-            "contentInfo[0].attributeGroup[1].attribute[0].offset",       -57.3,
-            "contentInfo[0].attributeGroup[2].attribute[0].offset",       0.1,
-            "contentInfo[0].attributeGroup[2].attribute[1].offset",       0.1,
-
-            "contentInfo[0].attributeGroup[0].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[1].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[2].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[3].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[4].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[5].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[6].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[0].attribute[7].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[1].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[2].attribute[0].boundUnits",   "nm",
-            "contentInfo[0].attributeGroup[2].attribute[1].boundUnits",   "nm",
-
-            "contentInfo[0].attributeGroup[0].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-            "contentInfo[0].attributeGroup[1].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-            "contentInfo[0].attributeGroup[2].contentType[0]", CoverageContentType.PHYSICAL_MEASUREMENT,
-
-            "contentInfo[0].cloudCoverPercentage",         8.3,
-            "contentInfo[0].illuminationAzimuthAngle",   116.9,
-            "contentInfo[0].illuminationElevationAngle",  58.8,
-
-            "spatialRepresentationInfo[0].numberOfDimensions",                       2,
-            "spatialRepresentationInfo[1].numberOfDimensions",                       2,
-            "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE,
-            "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionName", DimensionNameType.SAMPLE,
-            "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE,
-            "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionName", DimensionNameType.LINE,
-            "spatialRepresentationInfo[0].axisDimensionProperties[0].dimensionSize", 15000,
-            "spatialRepresentationInfo[0].axisDimensionProperties[1].dimensionSize", 15500,
-            "spatialRepresentationInfo[1].axisDimensionProperties[0].dimensionSize", 7600,
-            "spatialRepresentationInfo[1].axisDimensionProperties[1].dimensionSize", 7800,
-            "spatialRepresentationInfo[0].transformationParameterAvailability",      false,
-            "spatialRepresentationInfo[1].transformationParameterAvailability",      false,
-            "spatialRepresentationInfo[0].checkPointAvailability",                   false,
-            "spatialRepresentationInfo[1].checkPointAvailability",                   false,
-
-            "resourceLineage[0].source[0].description", "Pseudo GLS");
+        final String text = formatMetadata(DefaultMetadata.castOrCopy(actual).asTreeTable());
+        assertMultilinesEquals(
+                "Metadata\n"
+                + "  ├─Metadata identifier……………………………………………………………… LandsatTest\n"
+                + "  ├─Metadata standard (1 of 2)…………………………………………… Geographic Information — Metadata Part 1: Fundamentals\n"
+                + "  │   ├─Alternate title……………………………………………………………… ISO 19115-1\n"
+                + "  │   ├─Edition…………………………………………………………………………………… ISO 19115-1:2014(E)\n"
+                + "  │   ├─Identifier…………………………………………………………………………… 19115-1\n"
+                + "  │   ├─Cited responsible party\n"
+                + "  │   │   ├─Role………………………………………………………………………………… Principal investigator\n"
+                + "  │   │   └─Party……………………………………………………………………………… International Organization for Standardization\n"
+                + "  │   └─Presentation form………………………………………………………… Document digital\n"
+                + "  ├─Metadata standard (2 of 2)…………………………………………… Geographic Information — Metadata Part 2: Extensions for imagery and gridded data\n"
+                + "  │   ├─Alternate title……………………………………………………………… ISO 19115-2\n"
+                + "  │   ├─Edition…………………………………………………………………………………… ISO 19115-2:2009(E)\n"
+                + "  │   ├─Identifier…………………………………………………………………………… 19115-2\n"
+                + "  │   ├─Cited responsible party\n"
+                + "  │   │   ├─Role………………………………………………………………………………… Principal investigator\n"
+                + "  │   │   └─Party……………………………………………………………………………… International Organization for Standardization\n"
+                + "  │   └─Presentation form………………………………………………………… Document digital\n"
+                + "  ├─Spatial representation info (1 of 2)\n"
+                + "  │   ├─Number of dimensions………………………………………………… 2\n"
+                + "  │   ├─Axis dimension properties (1 of 2)…………… Sample\n"
+                + "  │   │   └─Dimension size……………………………………………………… 15000\n"
+                + "  │   ├─Axis dimension properties (2 of 2)…………… Line\n"
+                + "  │   │   └─Dimension size……………………………………………………… 15500\n"
+                + "  │   ├─Transformation parameter availability…… false\n"
+                + "  │   └─Check point availability……………………………………… false\n"
+                + "  ├─Spatial representation info (2 of 2)\n"
+                + "  │   ├─Number of dimensions………………………………………………… 2\n"
+                + "  │   ├─Axis dimension properties (1 of 2)…………… Sample\n"
+                + "  │   │   └─Dimension size……………………………………………………… 7600\n"
+                + "  │   ├─Axis dimension properties (2 of 2)…………… Line\n"
+                + "  │   │   └─Dimension size……………………………………………………… 7800\n"
+                + "  │   ├─Transformation parameter availability…… false\n"
+                + "  │   └─Check point availability……………………………………… false\n"
+                + "  ├─Reference system info………………………………………………………… EPSG:WGS 84 / UTM zone 49N\n"
+                + "  ├─Identification info\n"
+                + "  │   ├─Citation………………………………………………………………………………… LandsatTest\n"
+                + "  │   │   └─Date………………………………………………………………………………… 2016-06-27 16:48:12\n"
+                + "  │   │       └─Date type………………………………………………………… Creation\n"
+                + "  │   ├─Credit……………………………………………………………………………………… Derived from U.S. Geological Survey data\n"
+                + "  │   ├─Spatial resolution (1 of 2)\n"
+                + "  │   │   └─Distance……………………………………………………………………… 15.0\n"
+                + "  │   ├─Spatial resolution (2 of 2)\n"
+                + "  │   │   └─Distance……………………………………………………………………… 30.0\n"
+                + "  │   ├─Topic category………………………………………………………………… Geoscientific information\n"
+                + "  │   ├─Extent\n"
+                + "  │   │   └─Geographic element\n"
+                + "  │   │       ├─West bound longitude…………………………… 108°20′24″E\n"
+                + "  │   │       ├─East bound longitude…………………………… 110°26′24″E\n"
+                + "  │   │       ├─South bound latitude…………………………… 10°30′N\n"
+                + "  │   │       ├─North bound latitude…………………………… 12°37′12″N\n"
+                + "  │   │       └─Extent type code……………………………………… true\n"
+                + "  │   └─Resource format\n"
+                + "  │       └─Format specification citation……………… GeoTIFF Coverage Encoding Profile\n"
+                + "  │           └─Alternate title………………………………………… GeoTIFF\n"
+                + "  ├─Content info\n"
+                + "  │   ├─Processing level code……………………………………………… Pseudo LT1\n"
+                + "  │   │   ├─Authority…………………………………………………………………… Landsat\n"
+                + "  │   │   └─Code space………………………………………………………………… Landsat\n"
+                + "  │   ├─Attribute group (1 of 3)\n"
+                + "  │   │   ├─Content type…………………………………………………………… Physical measurement\n"
+                + "  │   │   ├─Attribute (1 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Coastal Aerosol\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B1.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 0.0127\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -63.6\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 433.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   ├─Attribute (2 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Blue\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B2.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 0.013\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -65.1\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 482.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   ├─Attribute (3 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Green\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B3.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 0.012\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -60.0\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 562.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   ├─Attribute (4 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Red\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B4.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 0.0101\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -50.6\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 655.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   ├─Attribute (5 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Near-Infrared\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B5.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 0.00619\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -31.0\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 865.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   ├─Attribute (6 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Short Wavelength Infrared (SWIR) 1\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B6.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 0.00154\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -7.7\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 1610.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   ├─Attribute (7 of 8)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Short Wavelength Infrared (SWIR) 2\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B7.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 5.19E-4\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… -2.6\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 2200.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   └─Attribute (8 of 8)\n"
+                + "  │   │       ├─Description…………………………………………………… Cirrus\n"
+                + "  │   │       ├─Name……………………………………………………………………… TestImage_B9.TIF\n"
+                + "  │   │       ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │       ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │       ├─Scale factor………………………………………………… 0.00242\n"
+                + "  │   │       ├─Offset………………………………………………………………… -12.1\n"
+                + "  │   │       ├─Bound units…………………………………………………… nm\n"
+                + "  │   │       ├─Peak response……………………………………………… 1375.0\n"
+                + "  │   │       └─Transfer function type……………………… Linear\n"
+                + "  │   ├─Attribute group (2 of 3)\n"
+                + "  │   │   ├─Content type…………………………………………………………… Physical measurement\n"
+                + "  │   │   └─Attribute\n"
+                + "  │   │       ├─Description…………………………………………………… Panchromatic\n"
+                + "  │   │       ├─Name……………………………………………………………………… TestImage_B8.TIF\n"
+                + "  │   │       ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │       ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │       ├─Scale factor………………………………………………… 0.0115\n"
+                + "  │   │       ├─Offset………………………………………………………………… -57.3\n"
+                + "  │   │       ├─Bound units…………………………………………………… nm\n"
+                + "  │   │       ├─Peak response……………………………………………… 590.0\n"
+                + "  │   │       └─Transfer function type……………………… Linear\n"
+                + "  │   ├─Attribute group (3 of 3)\n"
+                + "  │   │   ├─Content type…………………………………………………………… Physical measurement\n"
+                + "  │   │   ├─Attribute (1 of 2)\n"
+                + "  │   │   │   ├─Description…………………………………………………… Thermal Infrared Sensor (TIRS) 1\n"
+                + "  │   │   │   ├─Name……………………………………………………………………… TestImage_B10.TIF\n"
+                + "  │   │   │   ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │   │   ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │   │   ├─Scale factor………………………………………………… 3.34E-4\n"
+                + "  │   │   │   ├─Offset………………………………………………………………… 0.1\n"
+                + "  │   │   │   ├─Bound units…………………………………………………… nm\n"
+                + "  │   │   │   ├─Peak response……………………………………………… 10800.0\n"
+                + "  │   │   │   └─Transfer function type……………………… Linear\n"
+                + "  │   │   └─Attribute (2 of 2)\n"
+                + "  │   │       ├─Description…………………………………………………… Thermal Infrared Sensor (TIRS) 2\n"
+                + "  │   │       ├─Name……………………………………………………………………… TestImage_B11.TIF\n"
+                + "  │   │       ├─Max value………………………………………………………… 65535.0\n"
+                + "  │   │       ├─Min value………………………………………………………… 1.0\n"
+                + "  │   │       ├─Scale factor………………………………………………… 3.34E-4\n"
+                + "  │   │       ├─Offset………………………………………………………………… 0.1\n"
+                + "  │   │       ├─Bound units…………………………………………………… nm\n"
+                + "  │   │       ├─Peak response……………………………………………… 12000.0\n"
+                + "  │   │       └─Transfer function type……………………… Linear\n"
+                + "  │   ├─Illumination elevation angle…………………………… 58.8\n"
+                + "  │   ├─Illumination azimuth angle………………………………… 116.9\n"
+                + "  │   └─Cloud cover percentage…………………………………………… 8.3\n"
+                + "  ├─Resource lineage\n"
+                + "  │   └─Source……………………………………………………………………………………… Pseudo GLS\n"
+                + "  ├─Metadata scope\n"
+                + "  │   └─Resource scope………………………………………………………………… COVERAGE\n"
+                + "  ├─Acquisition information\n"
+                + "  │   ├─Acquisition requirement\n"
+                + "  │   │   └─Identifier………………………………………………………………… Software unit tests\n"
+                + "  │   ├─Operation\n"
+                + "  │   │   ├─Status…………………………………………………………………………… Completed\n"
+                + "  │   │   ├─Type………………………………………………………………………………… Real\n"
+                + "  │   │   └─Significant event\n"
+                + "  │   │       ├─Context……………………………………………………………… Acquisition\n"
+                + "  │   │       └─Time……………………………………………………………………… 2016-06-26 03:02:01\n"
+                + "  │   └─Platform\n"
+                + "  │       ├─Identifier………………………………………………………………… Pseudo LANDSAT\n"
+                + "  │       └─Instrument\n"
+                + "  │           └─Identifier……………………………………………………… Pseudo TIRS\n"
+                + "  ├─Date info………………………………………………………………………………………… 2016-06-27 16:48:12\n"
+                + "  │   └─Date type……………………………………………………………………………… Creation\n"
+                + "  └─Default locale+other locale………………………………………… en\n", text);
     }
 }
diff --git a/storage/sis-gdal/pom.xml b/storage/sis-gdal/pom.xml
index 71174da..de200df 100644
--- a/storage/sis-gdal/pom.xml
+++ b/storage/sis-gdal/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java b/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java
index 5e9553d..a197b7c 100644
--- a/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java
+++ b/storage/sis-gdal/src/main/java/org/apache/sis/storage/gdal/PJ.java
@@ -21,6 +21,7 @@
 import java.io.ObjectStreamException;
 import java.io.InvalidObjectException;
 import org.opengis.util.FactoryException;
+import org.opengis.util.InternationalString;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -188,6 +189,15 @@
     public native String getCode();
 
     /**
+     * Returns the string representation of the PJ structure.
+     *
+     * @return the string representation, or {@code null} if none.
+     */
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * Returns the Coordinate Reference System type.
      *
      * @return the CRS type.
diff --git a/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/MTFactory.java b/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/MTFactory.java
index 13e8e77..461fce4 100644
--- a/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/MTFactory.java
+++ b/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/MTFactory.java
@@ -21,7 +21,6 @@
 import org.opengis.util.FactoryException;
 import org.opengis.util.NoSuchIdentifierException;
 import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
@@ -65,16 +64,6 @@
     }
 
     /**
-     * Unsupported by the {@literal Proj.4} wrapper — delegates to the Apache SIS default factory.
-     */
-    @Override
-    public OperationMethod createOperationMethod(Map<String,?> properties, Integer sourceDimension,
-            Integer targetDimension, ParameterDescriptorGroup parameters) throws FactoryException
-    {
-        return opFactory().createOperationMethod(properties, sourceDimension, targetDimension, parameters);
-    }
-
-    /**
      * Creates an operation for conversion or transformation between two coordinate reference systems.
      * This implementation always uses Proj.4 for performing the coordinate operations, regardless if
      * the given CRS were created from Proj.4 definition strings or not. This method fails if it can
@@ -198,4 +187,13 @@
             throw new FactoryException(e);
         }
     }
+
+    /**
+     * No XML format is defined for math transform.
+     */
+    @Override
+    @Deprecated
+    public MathTransform createFromXML(String xml) throws FactoryException {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java b/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java
index bbc6438..9201416 100644
--- a/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java
+++ b/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/Proj4FactoryTest.java
@@ -35,7 +35,7 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/TransformTest.java b/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/TransformTest.java
deleted file mode 100644
index 703b5a6..0000000
--- a/storage/sis-gdal/src/test/java/org/apache/sis/storage/gdal/TransformTest.java
+++ /dev/null
@@ -1,103 +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.storage.gdal;
-
-import java.util.Set;
-import java.util.HashSet;
-import java.util.Arrays;
-import org.opengis.parameter.ParameterValueGroup;
-import org.opengis.util.NoSuchIdentifierException;
-import org.opengis.test.referencing.ParameterizedTransformTest;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.junit.BeforeClass;
-import org.junit.AfterClass;
-
-import static org.apache.sis.test.Assert.*;
-
-
-/**
- * Tests various map projections using {@literal Proj.4} wrappers.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   0.8
- * @module
- */
-@RunWith(JUnit4.class)
-public class TransformTest extends ParameterizedTransformTest {
-    /**
-     * The operation methods that we failed to instantiate.
-     * We use this set for verifying that there is no more failures than the expected ones.
-     */
-    private static final Set<String> FAILURES = new HashSet<>();
-
-    /**
-     * Creates a new test suite.
-     */
-    public TransformTest() {
-        super(new MTFactory(null) {
-            @Override
-            public ParameterValueGroup getDefaultParameters(final String method) throws NoSuchIdentifierException {
-                try {
-                    return super.getDefaultParameters(method);
-                } catch (NoSuchIdentifierException e) {
-                    FAILURES.add(method);
-                    throw e;                            // Instructs ParameterizedTransformTest to skip the test.
-                }
-            }
-        });
-        isDerivativeSupported = false;
-    }
-
-    /**
-     * Verifies if the {@literal Proj.4} library is available.
-     */
-    @BeforeClass
-    public static void verifyNativeLibraryAvailability() {
-        PJTest.verifyNativeLibraryAvailability();
-    }
-
-    /**
-     * Invoked after all the tests have been run for comparing the list of failures with the expected list.
-     * This method checks for the exact same content, so this method detects both unexpected failures and
-     * "unexpected" successes. Note that a failure is not necessarily because Proj.4 does not support the
-     * map projection; it may also be because Apache SIS does not yet declare the corresponding operation
-     * method.
-     */
-    @AfterClass
-    public static void verifyFailureList() {
-        /*
-         * The list of failures is empty if verifyNativeLibraryAvailability() failed,
-         * in which case no test have been run.
-         */
-        if (!FAILURES.isEmpty()) {
-            assertSetEquals(Arrays.asList(
-                    "Abridged Molodensky",
-                    "Cassini-Soldner",                          // No OperationMethod in SIS yet.
-                    "Krovak",                                   // No OperationMethod in SIS yet.
-                    "Lambert Azimuthal Equal Area",             // No OperationMethod in SIS yet.
-                    "Lambert Conic Conformal (2SP Belgium)",
-                    "Lambert Conic Conformal (2SP Michigan)",
-                    "Mercator (variant C)",                     // Need to verify if Proj4 handles easting/northing correctly.
-                    "Polar Stereographic (variant C)",          // Need to verify if Proj4 handles northing correctly.
-                    "Popular Visualisation Pseudo Mercator",
-                    "Transverse Mercator (South Orientated)"), FAILURES);
-            FAILURES.clear();
-        }
-    }
-}
diff --git a/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java b/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java
index 71e6d50..5e2a5af 100644
--- a/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java
+++ b/storage/sis-gdal/src/test/java/org/apache/sis/test/suite/GDALTestSuite.java
@@ -34,7 +34,6 @@
     org.apache.sis.storage.gdal.Proj4Test.class,
     org.apache.sis.storage.gdal.Proj4ParserTest.class,
     org.apache.sis.storage.gdal.Proj4FactoryTest.class,
-    org.apache.sis.storage.gdal.TransformTest.class,
     org.apache.sis.storage.gdal.IntegrationTest.class
 })
 public final strictfp class GDALTestSuite extends TestSuite {
diff --git a/storage/sis-geotiff/pom.xml b/storage/sis-geotiff/pom.xml
index 0530376..b648a72 100644
--- a/storage/sis-geotiff/pom.xml
+++ b/storage/sis-geotiff/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index d8175b6..a3707ea 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -201,7 +201,7 @@
                 listeners.warning(e);
             }
             builder.addEncoding(encoding, MetadataBuilder.Scope.METADATA);
-            builder.addResourceScope(ScopeCode.COVERAGE, null);
+            builder.addResourceScope(ScopeCode.valueOf("COVERAGE"), null);
             final Locale locale = getLocale();
             int n = 0;
             try {
diff --git a/storage/sis-netcdf/pom.xml b/storage/sis-netcdf/pom.xml
index 5d08cda..0a28d73 100644
--- a/storage/sis-netcdf/pom.xml
+++ b/storage/sis-netcdf/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
@@ -120,7 +120,6 @@
       <optional>true</optional>
     </dependency>
 
-    <!-- Leverage GeoAPI tests. -->
     <dependency>
       <groupId>org.apache.sis.core</groupId>
       <artifactId>sis-referencing</artifactId>
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index 5be89ad..25c99fb 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@ -531,7 +531,7 @@
          */
         final String alt = coordinates.getAttributeAsString(CDM.LONG_NAME);
         if (alt != null && !similar(alt, name)) {
-            properties.put(org.opengis.metadata.Identifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
+            properties.put(org.apache.sis.referencing.ImmutableIdentifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
             if (!similar(alt, standardName)) {
                 aliases.add(new NamedIdentifier(null, alt));                        // Additional alias.
             }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index 7d3ed5f..1f0e0a7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -53,6 +53,9 @@
 import org.apache.sis.measure.Units;
 import org.apache.sis.math.Vector;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+
 
 /**
  * Temporary object for building a coordinate reference system from the variables in a netCDF file.
@@ -644,7 +647,8 @@
          */
         private static final Conversion UNKNOWN_PROJECTION;
         static {
-            final CoordinateOperationFactory factory = DefaultFactories.forBuildin(CoordinateOperationFactory.class);
+            final DefaultCoordinateOperationFactory factory = DefaultFactories.forBuildin(
+                    CoordinateOperationFactory.class, DefaultCoordinateOperationFactory.class);
             try {
                 final OperationMethod method = factory.getOperationMethod(Equirectangular.NAME);
                 UNKNOWN_PROJECTION = factory.createDefiningConversion(
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 95e3046..2de32ee 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -46,6 +46,9 @@
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 
+// Branch-dependent imports
+import org.apache.sis.util.iso.DefaultNameFactory;
+
 
 /**
  * The API used internally by Apache SIS for fetching variables and attribute values from a netCDF file.
@@ -90,7 +93,7 @@
     /**
      * The factory to use for creating variable identifiers.
      */
-    public final NameFactory nameFactory;
+    public final DefaultNameFactory nameFactory;
 
     /**
      * The library for geometric objects, or {@code null} for the default.
@@ -153,7 +156,7 @@
         Objects.requireNonNull(listeners);
         this.geomlib      = geomlib;
         this.listeners    = listeners;
-        this.nameFactory  = DefaultFactories.forBuildin(NameFactory.class);
+        this.nameFactory  = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
         this.datumCache   = new Datum[CRSBuilder.DATUM_CACHE_SIZE];
         this.gridMapping  = new HashMap<>();
         localizationGrids = new HashMap<>();
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
index 6c52aa5..de8dc68 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
@@ -33,7 +33,6 @@
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
-import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.opengis.referencing.operation.OperationMethod;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.Conversion;
@@ -69,6 +68,9 @@
 import org.apache.sis.measure.Units;
 import ucar.nc2.constants.CF;
 
+// Branch-dependent imports
+import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+
 
 /**
  * Temporary objects for creating a {@link GridGeometry} instance defined by attributes on a variable.
@@ -201,7 +203,7 @@
              * than OGC names, but Apache SIS implementations of map projections know how to handle them, including
              * the redundant parameters like "inverse_flattening" and "earth_radius".
              */
-            final CoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
+            final DefaultCoordinateOperationFactory opFactory = node.decoder.getCoordinateOperationFactory();
             final OperationMethod method = opFactory.getOperationMethod((String) definition.remove(CF.GRID_MAPPING_NAME));
             final ParameterValueGroup parameters = method.getParameters().createValue();
             for (final Map.Entry<String,Object> entry : definition.entrySet()) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java
index ffefe7b..1144c9b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Raster.java
@@ -20,7 +20,6 @@
 import java.awt.image.DataBuffer;
 import java.awt.image.RenderedImage;
 import java.awt.image.RasterFormatException;
-import org.opengis.coverage.CannotEvaluateException;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
@@ -87,7 +86,7 @@
             }
             return renderer.image();
         } catch (IllegalArgumentException | ArithmeticException | RasterFormatException e) {
-            throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotRender_2, label, e), e);
+            throw new RuntimeException(Resources.format(Resources.Keys.CanNotRender_2, label, e), e);
         }
     }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
index 8f30f9d..25e3b14 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
@@ -36,16 +36,14 @@
 import org.apache.sis.internal.netcdf.Resources;
 import org.apache.sis.internal.feature.MovingFeature;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.feature.DefaultFeatureType;
-import org.apache.sis.feature.DefaultAttributeType;
 import org.apache.sis.util.collection.BackingStoreException;
 import ucar.nc2.constants.CF;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -88,7 +86,7 @@
     /**
      * The type of all features to be read by this {@code FeaturesInfo}.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * Creates a new discrete sampling parser for features identified by the given variable.
@@ -116,8 +114,8 @@
          * Creates a description of the features to be read.
          */
         final Map<String,Object> info = new HashMap<>(4);
-        final PropertyType[] pt = new PropertyType[this.properties.length + 2];
-        AttributeType[] characteristics = null;
+        final AbstractIdentifiedType[] pt = new AbstractIdentifiedType[this.properties.length + 2];
+        DefaultAttributeType[] characteristics = null;
         for (int i=0; i<pt.length; i++) {
             final VariableInfo variable;
             final Class<?> valueClass;
@@ -132,7 +130,7 @@
                 case 1: {
                     variable        = null;
                     valueClass      = factory.polylineClass;
-                    characteristics = new AttributeType[] {MovingFeature.TIME};
+                    characteristics = new DefaultAttributeType[] {MovingFeature.TIME};
                     break;
                 }
                 default: {
@@ -290,7 +288,7 @@
      * Returns the type of all features to be read by this {@code FeaturesInfo}.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -310,14 +308,14 @@
      * @param  parallel  ignored, since current version does not support parallelism.
      */
     @Override
-    public Stream<Feature> features(boolean parallel) {
+    public Stream<AbstractFeature> features(boolean parallel) {
         return StreamSupport.stream(new Iter(), false);
     }
 
     /**
      * Implementation of the iterator returned by {@link #features(boolean)}.
      */
-    private final class Iter implements Spliterator<Feature> {
+    private final class Iter implements Spliterator<AbstractFeature> {
         /**
          * Index of the next feature to read.
          */
@@ -344,7 +342,7 @@
          * @todo current reading process implies lot of seeks, which is inefficient.
          */
         @Override
-        public boolean tryAdvance(final Consumer<? super Feature> action) {
+        public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
             final int length = counts.intValue(index);
             final GridExtent extent = new GridExtent(null, new long[] {position},
                             new long[] {Math.addExact(position, length)}, false);
@@ -375,7 +373,7 @@
             } catch (IOException | DataStoreException e) {
                 throw new BackingStoreException(canNotReadFile(), e);
             }
-            final Feature feature = type.newInstance();
+            final AbstractFeature feature = type.newInstance();
             feature.setPropertyValue(identifiers.getName(), id.intValue(index));
             for (int i=0; i<properties.length; i++) {
                 feature.setPropertyValue(properties[i].getName(), props[i]);
@@ -397,7 +395,7 @@
          * Current implementation can not split this iterator.
          */
         @Override
-        public Spliterator<Feature> trySplit() {
+        public Spliterator<AbstractFeature> trySplit() {
             return null;
         }
 
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
index 8d8b3cb..3a2074c 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
@@ -23,8 +23,8 @@
 import ucar.nc2.ft.FeatureCollection;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -54,7 +54,7 @@
     }
 
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         throw new UnsupportedOperationException();      // TODO
     }
 
@@ -62,7 +62,7 @@
      * Returns the stream of features.
      */
     @Override
-    public Stream<Feature> features(boolean parallel) {
+    public Stream<AbstractFeature> features(boolean parallel) {
         throw new UnsupportedOperationException();      // TODO
     }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
index 19a2d83..232ecb7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
@@ -352,7 +352,7 @@
      * This is a character array with a line for each invocation of a program that has modified the dataset.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getResourceLineages() resourceLineage} /
+     * {@link org.apache.sis.metadata.iso.DefaultMetadata#getResourceLineages() resourceLineage} /
      * {@link Lineage#getStatement() statement}</li></ul>
      *
      * <div class="note"><b>Note:</b>
@@ -369,7 +369,7 @@
      * The {@value} attribute name for the method of production of the original data (<em>Recommended</em>).
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getResourceLineages() resourceLineage} /
+     * {@link org.apache.sis.metadata.iso.DefaultMetadata#getResourceLineages() resourceLineage} /
      * {@link Lineage#getSources() source} /
      * {@link Source#getDescription() description}</li></ul>
      *
@@ -403,7 +403,7 @@
      * subgroup.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getDateInfo() dateInfo}
+     * {@code dateInfo}
      * {@link CitationDate#getDate() date} with {@link DateType#CREATION}</li></ul>
      */
     public static final String METADATA_CREATION = "metadata_creation";
@@ -413,7 +413,7 @@
      * (<em>Suggested</em>).
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getDateInfo() dateInfo}
+     * {@code dateInfo}
      * {@link CitationDate#getDate() date} with {@link DateType#REVISION}</li></ul>
      *
      * @since 0.8
@@ -1162,7 +1162,7 @@
      * For example it may be the URL to an ISO 19115 metadata in XML format.
      *
      * <p><b>Path in ISO 19115:</b></p> <ul><li>{@link Metadata} /
-     * {@link Metadata#getMetadataLinkages() metadataLinkage} /
+     * {@code metadataLinkage} /
      * {@link OnlineResource#getLinkage() linkage}</li></ul>
      *
      * @see <a href="http://wiki.esipfed.org/index.php/Attribute_Convention_for_Data_Discovery#metadata_link">ESIP reference</a>
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 1e8c1e4..1ad3631 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -35,7 +35,6 @@
 import javax.measure.format.ParserException;
 
 import org.opengis.util.CodeList;
-import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.Identifier;
@@ -50,6 +49,7 @@
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.iso.DefaultNameFactory;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.metadata.iso.DefaultMetadata;
@@ -80,7 +80,6 @@
 
 import static java.util.Collections.singleton;
 import static org.apache.sis.storage.netcdf.AttributeNames.*;
-import static org.apache.sis.internal.util.CollectionsExt.first;
 
 
 /**
@@ -478,16 +477,15 @@
          * If we can not share the whole existing instance, we usually can share parts of it like the address.
          */
         ResponsibleParty responsibility = pointOfContact;
-        Contact        contact        = null;
-        Address        address        = null;
-        OnlineResource resource       = null;
+        Contact          contact        = null;
+        Address          address        = null;
+        OnlineResource   resource       = null;
         if (responsibility != null) {
-            final Party party = first(responsibility.getParties());
-            if (party != null) {
-                contact = first(party.getContactInfo());
+            { // Additional indentation for having the same level than SIS branches for GeoAPI snapshots (makes merges easier).
+                contact = responsibility.getContactInfo();
                 if (contact != null) {
-                    address  = first(contact.getAddresses());
-                    resource = first(contact.getOnlineResources());
+                    address  = contact.getAddress();
+                    resource = contact.getOnlineResource();
                 }
                 if (!canShare(resource, url)) {
                     resource       = null;
@@ -500,14 +498,9 @@
                     responsibility = null;
                 }
                 if (responsibility != null) {
-                    if (party instanceof Organisation) {
-                        // Individual (if any) is considered an organisation member. See comment in next block.
-                        if (!canShare(party.getName(), organisationName) ||
-                            !canShare(first(((Organisation) party).getIndividual()).getName(), individualName))
-                        {
-                            responsibility = null;
-                        }
-                    } else if (!canShare(party.getName(), individualName)) {
+                    if (!canShare(responsibility.getOrganisationName(), organisationName) ||
+                        !canShare(responsibility.getIndividualName(),   individualName))
+                    {
                         responsibility = null;
                     }
                 }
@@ -527,7 +520,7 @@
             if (individualName != null || organisationName != null || contact != null) {        // Do not test role.
                 AbstractParty party = null;
                 if (individualName   != null) party = new DefaultIndividual(individualName, null, null);
-                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (Individual) party, null);
+                if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (DefaultIndividual) party, null);
                 if (party            == null) party = isOrganisation(keys) ? new DefaultOrganisation() : new DefaultIndividual();
                 if (contact          != null) party.setContactInfo(singleton(contact));
                 responsibility = new DefaultResponsibleParty(role);
@@ -608,9 +601,9 @@
                 addCitedResponsibleParty(contributor, null);
             }
             final ResponsibleParty r = createResponsibleParty(PUBLISHER, false);
-            if (r != null) {
+            if (r instanceof DefaultResponsibility) {
                 addDistributor(r);
-                for (final Party party : r.getParties()) {
+                for (final AbstractParty party : ((DefaultResponsibility) r).getParties()) {
                     publisher = addIfNonNull(publisher, party.getName());
                 }
             }
@@ -670,8 +663,8 @@
         addUseLimitation          (stringValue(LICENSE));
         addKeywords(standard,  KeywordType.THEME,       stringValue(STANDARD_NAME.VOCABULARY));
         addKeywords(keywords,  KeywordType.THEME,       stringValue(KEYWORDS.VOCABULARY));
-        addKeywords(project,   KeywordType.PROJECT,     null);
-        addKeywords(publisher, KeywordType.DATA_CENTRE, null);
+        addKeywords(project,   KeywordType.valueOf("PROJECT"), null);
+        addKeywords(publisher, KeywordType.valueOf("DATA_CENTRE"), null);
         /*
          * Add geospatial bounds as a geometric object. This optional operation requires
          * an external library (ESRI or JTS) to be present on the classpath.
@@ -947,7 +940,7 @@
         newSampleDimension();
         final String name = trim(variable.getName());
         if (name != null) {
-            final NameFactory f = decoder.nameFactory;
+            final DefaultNameFactory f = decoder.nameFactory;
             final StringBuilder buffer = new StringBuilder(20);
             variable.writeDataTypeName(buffer);
             setBandIdentifier(f.createMemberName(null, name, f.createTypeName(null, buffer.toString())));
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
index d55df4f..6a0ebf8 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
@@ -19,7 +19,6 @@
 import java.util.Date;
 import java.io.IOException;
 import org.apache.sis.storage.DataStoreException;
-import org.opengis.test.dataset.TestData;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
index f9781f6..5863eaf 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
@@ -22,7 +22,6 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
-import org.opengis.test.dataset.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
index fc8365a..2db1bb0 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
@@ -26,7 +26,6 @@
 import org.apache.sis.internal.storage.AbstractResource;
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
 import org.apache.sis.setup.GeometryLibrary;
-import org.opengis.test.dataset.TestData;
 import ucar.nc2.dataset.NetcdfDataset;
 import ucar.nc2.NetcdfFile;
 import org.junit.AfterClass;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestData.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestData.java
new file mode 100644
index 0000000..0f6f435
--- /dev/null
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestData.java
@@ -0,0 +1,48 @@
+/*
+ * 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.netcdf;
+
+import java.net.URL;
+import java.io.InputStream;
+
+import static org.junit.Assume.*;
+
+
+/**
+ * Place-holder for a GeoAPI 3.1 class. Used only for allowing the code to compile.
+ * For real test execution, see the development branches on GeoAPI 4.0-SNAPSHOT.
+ */
+public enum TestData {
+    NETCDF_2D_GEOGRAPHIC,
+
+    NETCDF_4D_PROJECTED;
+
+    public URL location() {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+        return null;
+    }
+
+    public InputStream open() {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+        return null;
+    }
+
+    public byte[] content() {
+        assumeTrue("This test requires GeoAPI 3.1.", false);
+        return null;
+    }
+}
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
index d13313a..efab117 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
@@ -26,7 +26,6 @@
 import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
 import org.apache.sis.measure.Units;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
index a18cf5e..2488cac 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/ChannelDecoderTest.java
@@ -20,6 +20,7 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
+import org.apache.sis.internal.netcdf.TestData;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.DecoderTest;
 import org.apache.sis.internal.storage.AbstractResource;
@@ -27,7 +28,6 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
 
 
 /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
index 1ae3598..ab80436 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
@@ -25,7 +25,7 @@
 import org.apache.sis.test.DependsOn;
 
 // Branch-specific imports
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 
 
 /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
index 4bc4e4d..a945a82 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
@@ -19,9 +19,9 @@
 import java.io.IOException;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.VariableTest;
+import org.apache.sis.internal.netcdf.TestData;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
 
 
 /**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
index 779a47c..a65451d 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
@@ -26,12 +26,12 @@
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.metadata.spatial.CellGeometry;
 import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.test.dataset.ContentVerifier;
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 import org.apache.sis.internal.netcdf.TestCase;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.impl.ChannelDecoderTest;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.test.ContentVerifier;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
index 8acae77..f293886 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreProviderTest.java
@@ -29,7 +29,7 @@
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.Version;
 import org.apache.sis.test.DependsOn;
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
index 2b4a6dc..f342a3c 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/NetcdfStoreTest.java
@@ -22,7 +22,7 @@
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.util.Version;
-import org.opengis.test.dataset.TestData;
+import org.apache.sis.internal.netcdf.TestData;
 import org.junit.Test;
 
 import static org.opengis.test.Assert.*;
diff --git a/storage/sis-shapefile/pom.xml b/storage/sis-shapefile/pom.xml
index d74c5a9..216f872 100644
--- a/storage/sis-shapefile/pom.xml
+++ b/storage/sis-shapefile/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java
index e1fad49..5e8787f 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/ShapefileByteReader.java
@@ -30,8 +30,8 @@
 import org.apache.sis.internal.shapefile.jdbc.*;
 import org.apache.sis.storage.shapefile.InvalidShapefileFormatException;
 import org.apache.sis.storage.shapefile.ShapeTypeEnum;
+import org.apache.sis.feature.AbstractFeature;
 import org.apache.sis.util.logging.Logging;
-import org.opengis.feature.Feature;
 
 import com.esri.core.geometry.*;
 
@@ -259,7 +259,7 @@
      * @param feature Feature to complete.
      * @throws InvalidShapefileFormatException if a validation problem occurs.
      */
-    public void completeFeature(Feature feature) throws InvalidShapefileFormatException {
+    public void completeFeature(AbstractFeature feature) throws InvalidShapefileFormatException {
         // insert points into some type of list
         int RecordNumber = getByteBuffer().getInt();
         @SuppressWarnings("unused")
@@ -297,7 +297,7 @@
      * Load point feature.
      * @param feature Feature to fill.
      */
-    private void loadPointFeature(Feature feature) {
+    private void loadPointFeature(AbstractFeature feature) {
         double x = getByteBuffer().getDouble();
         double y = getByteBuffer().getDouble();
         Point pnt = new Point(x, y);
@@ -308,7 +308,7 @@
      * Load polygon feature.
      * @param feature Feature to fill.
      */
-    private void loadPolygonFeature(Feature feature) {
+    private void loadPolygonFeature(AbstractFeature feature) {
         /* double xmin = */getByteBuffer().getDouble();
         /* double ymin = */getByteBuffer().getDouble();
         /* double xmax = */getByteBuffer().getDouble();
@@ -346,7 +346,6 @@
     @Deprecated // As soon as the readMultiplePolygonParts method proofs working well, this readUniquePolygonPart method can be removed and all calls be deferred to readMultiplePolygonParts.
     private Polygon readUniquePolygonPart(int numPoints) {
         /*int part = */ getByteBuffer().getInt();
-
         Polygon poly = new Polygon();
 
         // create a line from the points
@@ -430,7 +429,7 @@
      * Load polyline feature.
      * @param feature Feature to fill.
      */
-    private void loadPolylineFeature(Feature feature) {
+    private void loadPolylineFeature(AbstractFeature feature) {
         /* double xmin = */getByteBuffer().getDouble();
         /* double ymin = */getByteBuffer().getDouble();
         /* double xmax = */getByteBuffer().getDouble();
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java
index e151e65..d05aec9 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/AbstractDbase3ByteReader.java
@@ -111,7 +111,7 @@
     @Override public Date getDateOfLastUpdate() {
         return toDate(this.dbaseLastUpdate);
     }
-    
+
     /**
      * Returns the first record position, in bytes, in the DBase file.
      * @return First record position.
@@ -121,13 +121,13 @@
     }
 
     /**
-     * Returns the length (in bytes) of one record in this DBase file, including the delete flag. 
+     * Returns the length (in bytes) of one record in this DBase file, including the delete flag.
      * @return Record length.
      */
     @Override public short getRecordLength() {
         return this.recordLength;
     }
-    
+
     /**
      * Returns the record count.
      * @return Record count.
@@ -248,7 +248,7 @@
 
         return(knownConversions.get(Byte.toUnsignedInt(pageCodeBinaryValue)));
     }
-    
+
     /**
      * Set a charset.
      * @param cs Charset.
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java
index 2c58598..3f10500 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/DBase3FieldDescriptor.java
@@ -32,7 +32,7 @@
 public class DBase3FieldDescriptor extends AutoChecker {
     /** Field name. */
     private byte[] fieldName = new byte[11];
-    
+
     /** Field name as String, for performance issues. */
     private String stringFieldName;
 
@@ -124,10 +124,10 @@
             while (length != 0 && Byte.toUnsignedInt(this.fieldName[length - 1]) <= ' ') {
                 length--;
             }
-            
+
             this.stringFieldName = new String(this.fieldName, 0, length);
         }
-        
+
         return this.stringFieldName;
     }
 
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java
index 1374c85..d9fecf9 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/Dbase3ByteReader.java
@@ -24,7 +24,7 @@
 
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLIllegalColumnIndexException;
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLNoSuchFieldException;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 /**
  * Database byte reader contract. Used to allow refactoring of core byte management of a DBase file.
@@ -114,7 +114,7 @@
      * Load a row into a feature.
      * @param feature Feature to fill.
      */
-    public void loadRowIntoFeature(Feature feature);
+    public void loadRowIntoFeature(AbstractFeature feature);
 
     /**
      * Checks if a next row is available. Warning : it may be a deleted one.
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java
index 77e52e4..294be4a 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/MappedByteReader.java
@@ -26,7 +26,7 @@
 
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLIllegalColumnIndexException;
 import org.apache.sis.internal.shapefile.jdbc.resultset.SQLNoSuchFieldException;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -43,7 +43,7 @@
 
     /** Connection properties. */
     private Properties info;
-    
+
     /**
      * Construct a mapped byte reader on a file.
      * @param dbase3File File.
@@ -54,18 +54,18 @@
     public MappedByteReader(File dbase3File, Properties connectionInfos) throws SQLInvalidDbaseFileFormatException, SQLDbaseFileNotFoundException {
         super(dbase3File);
         this.info = connectionInfos;
-        
+
         // React to special features asked.
         if (this.info != null) {
             // Sometimes, DBF files have a wrong charset, or more often : none, and you have to specify it.
             String recordCharset = (String)this.info.get("record_charset");
-            
+
             if (recordCharset != null) {
                 Charset cs = Charset.forName(recordCharset);
                 setCharset(cs);
             }
         }
-        
+
         loadDescriptor();
     }
 
@@ -73,7 +73,7 @@
      * Load a row into a feature.
      * @param feature Feature to fill.
      */
-    @Override public void loadRowIntoFeature(Feature feature) {
+    @Override public void loadRowIntoFeature(AbstractFeature feature) {
         // TODO: ignore deleted records
         getByteBuffer().get(); // denotes whether deleted or current
         // read first part of record
@@ -102,13 +102,13 @@
         if (getByteBuffer().hasRemaining() == false) {
             return false;
         }
-        
+
         // 2) Check that the immediate next byte read isn't the EOF signal.
         byte eofCheck = getByteBuffer().get();
-        
+
         boolean isEOF = (eofCheck == 0x1A);
         this.log(Level.FINER, "log.delete_status", getRowNum(), eofCheck, isEOF ? "EOF" : "Active");
-        
+
         if (eofCheck == 0x1A) {
             return false;
         }
@@ -138,7 +138,7 @@
     public Map<String, byte[]> readNextRowAsObjects() {
         // TODO: ignore deleted records
         /* byte isDeleted = */ getByteBuffer().get(); // denotes whether deleted or current
-        
+
         // read first part of record
         HashMap<String, byte[]> fieldsValues = new HashMap<>();
 
@@ -148,18 +148,18 @@
 
             // Trim the bytes right.
             int length = data.length;
-            
+
             while (length != 0 && Byte.toUnsignedInt(data[length - 1]) <= ' ') {
                 length--;
             }
-            
+
             if (length != data.length) {
                 byte[] dataTrimmed = new byte[length];
-                
+
                 for(int index=0; index < length; index ++) {
                     dataTrimmed[index] = data[index];
                 }
-                
+
                 fieldsValues.put(fd.getName(), dataTrimmed);
             }
             else {
@@ -194,7 +194,7 @@
 
             // Translate code page value to a known charset.
             this.codePage = getByteBuffer().get();
-            
+
             if (this.charset == null) {
                 try {
                     this.charset = toCharset(this.codePage);
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java
index 019e7f3..cd60673 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/connection/DBFConnection.java
@@ -317,7 +317,7 @@
     public Map<String, byte[]> readNextRowAsObjects() {
         return this.byteReader.readNextRowAsObjects();
     }
-    
+
     /**
      * Returns the record number of the last record red.
      * @return The record number.
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java
index 86d799b..4751416 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/internal/shapefile/jdbc/resultset/DBFRecordBasedResultSet.java
@@ -48,10 +48,10 @@
 
     /** Indicates that the last result set record matching conditions has already been returned, and a further call of next() shall throw a "no more record" exception. */
     private boolean lastResultSetRecordAlreadyReturned;
-    
+
     /** The record number of this record. */
     private int recordNumber;
-    
+
     /**
      * Constructs a result set.
      * @param stmt Parent statement.
@@ -396,7 +396,7 @@
     @Override
     public Object getObject(String columnLabel) throws SQLConnectionClosedException, SQLFeatureNotSupportedException, SQLNoSuchFieldException, SQLNotNumericException, SQLNotDateException {
         int index = -1;
-        
+
         try {
             index = findColumn(columnLabel);
             return getObject(index);
@@ -414,7 +414,7 @@
     public int getRowNum()  {
         return this.recordNumber;
     }
-    
+
     /**
      * @see java.sql.ResultSet#getShort(java.lang.String)
      * @throws SQLConnectionClosedException if the connection is closed.
@@ -469,7 +469,7 @@
         // If a non null value has been readed, convert it to the wished Charset (provided one has been given).
         DBFConnection cnt = (DBFConnection)((DBFStatement)getStatement()).getConnection();
         Charset charset = cnt.getCharset();
-        
+
         if (charset == null) {
             return new String(bytes);
         }
@@ -582,7 +582,7 @@
 
         try(DBFBuiltInMemoryResultSetForColumnsListing rs = (DBFBuiltInMemoryResultSetForColumnsListing)getFieldDesc(columnLabel, this.sql)) {
             String textValue = getString(columnLabel);
-            
+
             if (textValue == null) {
                 return null;
             }
diff --git a/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java b/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java
index 1d19f09..a7b9add 100644
--- a/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java
+++ b/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/InputFeatureStream.java
@@ -38,7 +38,7 @@
 import org.apache.sis.internal.shapefile.jdbc.statement.DBFStatement;
 import org.apache.sis.storage.DataStoreClosedException;
 import org.apache.sis.util.logging.Logging;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 /**
  * Input Stream of features.
@@ -214,7 +214,7 @@
      * @throws DataStoreQueryResultException if the shapefile results cause a trouble (wrong format, for example).
      * @throws InvalidShapefileFormatException if the shapefile structure shows a problem.
      */
-    public Feature readFeature() throws DataStoreClosedException, DataStoreQueryException, DataStoreQueryResultException, InvalidShapefileFormatException {
+    public AbstractFeature readFeature() throws DataStoreClosedException, DataStoreQueryException, DataStoreQueryResultException, InvalidShapefileFormatException {
         try {
             return internalReadFeature();
         }
@@ -278,7 +278,7 @@
      * @throws InvalidShapefileFormatException if the shapefile format is invalid.
      * @throws SQLNoDirectAccessAvailableException if the underlying SQL statement requires a direct access in the shapefile, but the shapefile cannot allow it.
      */
-    private Feature internalReadFeature() throws SQLConnectionClosedException, SQLInvalidStatementException, SQLIllegalParameterException, SQLNoSuchFieldException, SQLUnsupportedParsingFeatureException, SQLNotNumericException, SQLNotDateException, SQLFeatureNotSupportedException, InvalidShapefileFormatException, SQLNoDirectAccessAvailableException {
+    private AbstractFeature internalReadFeature() throws SQLConnectionClosedException, SQLInvalidStatementException, SQLIllegalParameterException, SQLNoSuchFieldException, SQLUnsupportedParsingFeatureException, SQLNotNumericException, SQLNotDateException, SQLFeatureNotSupportedException, InvalidShapefileFormatException, SQLNoDirectAccessAvailableException {
         try {
             if (this.endOfFile) {
                 return null;
@@ -317,7 +317,7 @@
                 }
             }
 
-            Feature feature = this.featuresType.newInstance();
+            AbstractFeature feature = this.featuresType.newInstance();
             this.shapefileReader.completeFeature(feature);
             DBFDatabaseMetaData metadata = (DBFDatabaseMetaData)this.connection.getMetaData();
 
diff --git a/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java b/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
index 6605b24..ad42bdf 100644
--- a/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
+++ b/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
@@ -28,7 +28,8 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Ignore;
 import org.junit.Test;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractAttribute;
 
 
 /**
@@ -117,12 +118,12 @@
      @Test @Ignore // TODO Adapt with another shapefile.
      public void testHandleEofNotification() throws URISyntaxException, DataStoreException {
          ShapeFile shp = new ShapeFile(path("DEPARTEMENT.SHP"));
-         Feature first = null, last = null;
+         AbstractFeature first = null, last = null;
 
          Logger log = org.apache.sis.util.logging.Logging.getLogger(ShapeFileTest.class);
 
          try(InputFeatureStream is = shp.findAll()) {
-             Feature feature = is.readFeature();
+             AbstractFeature feature = is.readFeature();
 
              // Read and retain the first and the last feature.
              while(feature != null) {
@@ -131,7 +132,7 @@
                  }
 
                  // Advice : To debug just before the last record, put a conditional breakpoint on department name "MEURTHE-ET-MOSELLE".
-                 String deptName = (String)feature.getProperty("NOM_DEPT").getValue();
+                 String deptName = (String)((AbstractAttribute) feature.getProperty("NOM_DEPT")).getValue();
                  log.info(deptName);
 
                  last = feature;
@@ -141,8 +142,8 @@
 
          assertNotNull("No record has been found in the DBase file or Shapefile.", first);
          assertNotNull("This test is not working well : last feature should always be set if any feature has been found.", last);
-         assertEquals("The first record red must be JURA department.", "JURA", first.getProperty("NOM_DEPT").getValue());
-         assertEquals("The last record red must be DEUX-SEVRES department.", "DEUX-SEVRES", last.getProperty("NOM_DEPT").getValue());
+         assertEquals("The first record red must be JURA department.", "JURA", ((AbstractAttribute) first.getProperty("NOM_DEPT")).getValue());
+         assertEquals("The last record red must be DEUX-SEVRES department.", "DEUX-SEVRES", ((AbstractAttribute) last.getProperty("NOM_DEPT")).getValue());
      }
 
      /**
@@ -155,7 +156,7 @@
          ShapeFile shp = new ShapeFile(path("ABRALicenseePt_4326_clipped.shp"));
 
          // 1) Find the third record, sequentially.
-         Feature thirdFeature;
+         AbstractFeature thirdFeature;
 
          try(InputFeatureStream isSequential = shp.findAll()) {
              isSequential.readFeature();
@@ -164,12 +165,12 @@
          }
 
          // Take one of its key fields and another field for reference, and its geometry.
-         Double sequentialAddressId = Double.valueOf((String)(thirdFeature.getProperty("ADDRID")).getValue());
-         String sequentialAddress = (String)(thirdFeature.getProperty("ADDRESS")).getValue();
+         Double sequentialAddressId = Double.valueOf((String)(((AbstractAttribute) thirdFeature.getProperty("ADDRID"))).getValue());
+         String sequentialAddress = (String)(((AbstractAttribute) thirdFeature.getProperty("ADDRESS"))).getValue();
          Object sequentialGeometry = thirdFeature.getPropertyValue("geometry");
 
          // 2) Now attempt a direct access to this feature.
-         Feature directFeature;
+         AbstractFeature directFeature;
          String sql = MessageFormat.format("SELECT * FROM ABRALicenseePt_4326_clipped WHERE ADDRID = {0,number,#0}", sequentialAddressId);
 
          try(InputFeatureStream isDirect = shp.find(sql)) {
@@ -179,8 +180,8 @@
 
          assertNotNull("The field ADDRID in the direct access feature has not been found again.", directFeature.getProperty("ADDRID"));
 
-         Double directAddressId = Double.valueOf((String)(directFeature.getProperty("ADDRID")).getValue());
-         String directAddress = (String)(directFeature.getProperty("ADDRESS")).getValue();
+         Double directAddressId = Double.valueOf((String)(((AbstractAttribute) directFeature.getProperty("ADDRID"))).getValue());
+         String directAddress = (String)(((AbstractAttribute) directFeature.getProperty("ADDRESS"))).getValue();
          Object directGeometry = directFeature.getPropertyValue("geometry");
 
          assertEquals("DBase part : direct access didn't returned the same address id than sequential access.", sequentialAddressId, directAddressId);
@@ -195,7 +196,7 @@
      */
     private void readAll(ShapeFile shp) throws DataStoreException {
         try(InputFeatureStream is = shp.findAll()) {
-            Feature feature = is.readFeature();
+            AbstractFeature feature = is.readFeature();
 
             while(feature != null) {
                 feature = is.readFeature();
diff --git a/storage/sis-sqlstore/pom.xml b/storage/sis-sqlstore/pom.xml
index c42d674..29cefc2 100644
--- a/storage/sis-sqlstore/pom.xml
+++ b/storage/sis-sqlstore/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
index 57e9c1f..cc64a80 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
@@ -37,8 +37,8 @@
 import org.apache.sis.util.ArraysExt;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -49,7 +49,7 @@
  * @since   1.0
  * @module
  */
-final class Features implements Spliterator<Feature>, Runnable {
+final class Features implements Spliterator<AbstractFeature>, Runnable {
     /**
      * An empty array of iterators, used when there is no dependency.
      */
@@ -58,7 +58,7 @@
     /**
      * The type of features to create.
      */
-    private final FeatureType featureType;
+    private final DefaultFeatureType featureType;
 
     /**
      * Name of attributes in feature instances, excluding operations and associations to other tables.
@@ -324,7 +324,7 @@
      * @return always {@code null}.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         return null;
     }
 
@@ -332,7 +332,7 @@
      * Gives the next feature to the given consumer.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
         try {
             return fetch(action, false);
         } catch (SQLException e) {
@@ -344,7 +344,7 @@
      * Gives all remaining features to the given consumer.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
         try {
             fetch(action, true);
         } catch (SQLException e) {
@@ -356,13 +356,13 @@
      * Gives at least the next feature to the given consumer.
      * Gives all remaining features if {@code all} is {@code true}.
      *
-     * @param  action  the action to execute for each {@link Feature} instances fetched by this method.
+     * @param  action  the action to execute for each {@link AbstractFeature} instances fetched by this method.
      * @param  all     {@code true} for reading all remaining feature instances, or {@code false} for only the next one.
      * @return {@code true} if we have read an instance and {@code all} is {@code false} (so there is maybe other instances).
      */
-    private boolean fetch(final Consumer<? super Feature> action, final boolean all) throws SQLException {
+    private boolean fetch(final Consumer<? super AbstractFeature> action, final boolean all) throws SQLException {
         while (result.next()) {
-            final Feature feature = featureType.newInstance();
+            final AbstractFeature feature = featureType.newInstance();
             for (int i=0; i < attributeNames.length; i++) {
                 final Object value = result.getObject(i+1);
                 if (!result.wasNull()) {
@@ -423,14 +423,14 @@
      * @param  owner  if the features to fetch are components of another feature, that container feature instance.
      * @return the feature as a singleton {@code Feature} or as a {@code Collection<Feature>}.
      */
-    private Object fetchReferenced(final Object key, final Feature owner) throws SQLException {
+    private Object fetchReferenced(final Object key, final AbstractFeature owner) throws SQLException {
         if (key != null) {
             Object existing = instances.get(key);
             if (existing != null) {
                 return existing;
             }
         }
-        final List<Feature> features = new ArrayList<>();
+        final List<AbstractFeature> features = new ArrayList<>();
         try (ResultSet r = statement.executeQuery()) {
             result = r;
             fetch(features::add, true);
@@ -438,7 +438,7 @@
             result = null;
         }
         if (owner != null && deferredAssociation != null) {
-            for (final Feature feature : features) {
+            for (final AbstractFeature feature : features) {
                 feature.setPropertyValue(deferredAssociation, owner);
             }
         }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
index 8c2f35a..7fd2c5d 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
@@ -53,17 +53,16 @@
 import org.apache.sis.util.Debug;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
  * Description of a table in the database, including columns, primary keys and foreigner keys.
- * This class contains a {@link FeatureType} inferred from the table structure. The {@link FeatureType}
- * contains an {@link AttributeType} for each table column, except foreigner keys which are represented
- * by {@link FeatureAssociationRole}s.
+ * This class contains a {@code FeatureType} inferred from the table structure. The {@code FeatureType}
+ * contains an {@code AttributeType} for each table column, except foreigner keys which are represented
+ * by {@link DefaultAssociationRole}s.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -82,7 +81,7 @@
      * except synthetic attributes like "sis:identifier". The feature may also contain associations
      * inferred from foreigner keys that are not immediately apparent in the table.
      */
-    final FeatureType featureType;
+    final DefaultFeatureType featureType;
 
     /**
      * The name in the database of this {@code Table} object, together with its schema and catalog.
@@ -444,7 +443,7 @@
                 for (final Relation relation : relations) {
                     if (!relation.isSearchTableDefined()) {
                         // A ClassCastException below would be a bug since 'relation.propertyName' shall be for an association.
-                        FeatureAssociationRole association = (FeatureAssociationRole) featureType.getProperty(relation.propertyName);
+                        DefaultAssociationRole association = (DefaultAssociationRole) featureType.getProperty(relation.propertyName);
                         final Table table = tables.get(association.getValueType().getName());
                         if (table == null) {
                             throw new InternalDataStoreException(association.toString());
@@ -528,7 +527,7 @@
      * Returns the feature type inferred from the database structure analysis.
      */
     @Override
-    public final FeatureType getType() {
+    public final DefaultFeatureType getType() {
         return featureType;
     }
 
@@ -601,7 +600,7 @@
      * @throws DataStoreException if an error occurred while creating the stream.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         DataStoreException ex;
         Connection connection = null;
         try {
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
index 2c198a8..b1e0273 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
@@ -17,7 +17,7 @@
 
 
 /**
- * Build {@link org.opengis.feature.FeatureType}s by inspection of database schemas.
+ * Build {@link org.apache.sis.feature.DefaultFeatureType}s by inspection of database schemas.
  * The work done here is similar to reverse engineering.
  *
  * <STRONG>Do not use!</STRONG>
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
index b22b6d2..67a7c0d 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
@@ -72,7 +72,7 @@
     private final DataSource source;
 
     /**
-     * The result of inspecting database schema for deriving {@link org.opengis.feature.FeatureType}s.
+     * The result of inspecting database schema for deriving {@link org.apache.sis.feature.DefaultFeatureType}s.
      * Created when first needed. May be discarded and recreated if the store needs a refresh.
      */
     private Database model;
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
index 0945bfc..a5f2dd8 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
@@ -19,11 +19,11 @@
 /**
  * Data store capable to read and create features from a JDBC connection to a database.
  * {@link org.apache.sis.storage.sql.SQLStore} takes a one or more tables at construction time.
- * Each enumerated table is represented by a {@link org.opengis.feature.FeatureType}.
- * Each row in those table represents a {@link org.opengis.feature.Feature} instance.
- * Each relation defined by a foreigner key is represented by an {@link org.opengis.feature.FeatureAssociationRole}
+ * Each enumerated table is represented by a {@code FeatureType}.
+ * Each row in those table represents a {@code Feature} instance.
+ * Each relation defined by a foreigner key is represented by an {@code FeatureAssociationRole}
  * to another feature (with transitive dependencies automatically resolved), and the other columns are represented
- * by {@link org.opengis.feature.AttributeType}.
+ * by {@code AttributeType}.
  *
  * <p>The storage of spatial features in SQL databases is described by the
  * <a href="http://www.opengeospatial.org/standards/sfs">OGC Simple feature access - Part 2: SQL option</a>
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
index 7269f20..4d6f526 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -31,11 +31,11 @@
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultAssociationRole;
 
 
 /**
@@ -62,7 +62,7 @@
      * This feature should appear twice, and all those occurrences should use the exact same instance.
      * We use that for verifying the {@code Table.instanceForPrimaryKeys} caching.
      */
-    private Feature canada;
+    private AbstractFeature canada;
 
     /**
      * Tests on Derby.
@@ -127,7 +127,7 @@
                         new String[] {"sis:identifier", "pk:country", "FK_City", "city",       "native_name", "english_name"},
                         new Object[] {null,             String.class, "Cities",  String.class, String.class,  String.class});
 
-                try (Stream<Feature> features = cities.features(false)) {
+                try (Stream<AbstractFeature> features = cities.features(false)) {
                     features.forEach((f) -> verifyContent(f));
                 }
             }
@@ -141,9 +141,9 @@
     /**
      * Verifies the result of analyzing the structure of the {@code "Cities"} table.
      */
-    private static void verifyFeatureType(final FeatureType type, final String[] expectedNames, final Object[] expectedTypes) {
+    private static void verifyFeatureType(final DefaultFeatureType type, final String[] expectedNames, final Object[] expectedTypes) {
         int i = 0;
-        for (PropertyType pt : type.getProperties(false)) {
+        for (AbstractIdentifiedType pt : type.getProperties(false)) {
             assertEquals("name", expectedNames[i], pt.getName().toString());
             final Object expectedType = expectedTypes[i];
             if (expectedType != null) {
@@ -151,10 +151,10 @@
                 final Object value;
                 if (expectedType instanceof Class<?>) {
                     label = "attribute type";
-                    value = ((AttributeType<?>) pt).getValueClass();
+                    value = ((DefaultAttributeType<?>) pt).getValueClass();
                 } else {
                     label = "association type";
-                    value = ((FeatureAssociationRole) pt).getValueType().getName().toString();
+                    value = ((DefaultAssociationRole) pt).getValueType().getName().toString();
                 }
                 assertEquals(label, expectedType, value);
             }
@@ -167,7 +167,7 @@
      * Verifies the content of the {@code Cities} table.
      * The features are in no particular order.
      */
-    private void verifyContent(final Feature feature) {
+    private void verifyContent(final AbstractFeature feature) {
         final String city = feature.getPropertyValue("native_name").toString();
         final String country, countryName, englishName;
         final String[] parks;
@@ -226,7 +226,7 @@
          */
         assertEquals("country", countryName, getIndirectPropertyValue(feature, "country", "native_name"));
         if (isCanada) {
-            final Feature f = (Feature) feature.getPropertyValue("country");
+            final AbstractFeature f = (AbstractFeature) feature.getPropertyValue("country");
             if (canada == null) {
                 canada = f;
             } else {
@@ -243,7 +243,7 @@
         assertEquals("parks.length", parks.length, actualParks.size());
         final Collection<String> expectedParks = new HashSet<>(Arrays.asList(parks));
         for (final Object park : actualParks) {
-            final Feature pf = (Feature) park;
+            final AbstractFeature pf = (AbstractFeature) park;
             final String npn = (String) pf.getPropertyValue("native_name");
             final String epn = (String) pf.getPropertyValue("english_name");
             assertNotNull("park.native_name",  npn);
@@ -261,10 +261,10 @@
     /**
      * Follows an association in the given feature.
      */
-    private static Object getIndirectPropertyValue(final Feature feature, final String p1, final String p2) {
+    private static Object getIndirectPropertyValue(final AbstractFeature feature, final String p1, final String p2) {
         final Object dependency = feature.getPropertyValue(p1);
         assertNotNull(p1, dependency);
-        assertInstanceOf(p1, Feature.class, dependency);
-        return ((Feature) dependency).getPropertyValue(p2);
+        assertInstanceOf(p1, AbstractFeature.class, dependency);
+        return ((AbstractFeature) dependency).getPropertyValue(p2);
     }
 }
diff --git a/storage/sis-storage/pom.xml b/storage/sis-storage/pom.xml
index 369c059..493a148 100644
--- a/storage/sis-storage/pom.xml
+++ b/storage/sis-storage/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
index 910a6be..4d38cf7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
@@ -25,7 +25,7 @@
 import org.apache.sis.storage.event.StoreListeners;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -71,7 +71,7 @@
      */
     @Override
     public Optional<GenericName> getIdentifier() throws DataStoreException {
-        final FeatureType type = getType();
+        final DefaultFeatureType type = getType();
         return (type != null) ? Optional.of(type.getName()) : Optional.empty();
     }
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
deleted file mode 100644
index 5b48f15..0000000
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AggregatedFeatureSet.java
+++ /dev/null
@@ -1,159 +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.internal.storage;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Optional;
-import org.opengis.geometry.Envelope;
-import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.event.StoreListeners;
-
-// Branch-dependent imports
-import org.opengis.feature.FeatureType;
-
-
-/**
- * A feature set made from the aggregation of other feature sets. The features may be aggregated in different ways,
- * depending on the subclass. The aggregation may be all features from one set followed by all features from another set,
- * or it may be features of the two sets merged together in a way similar to SQL JOIN statement.
- *
- * <p>This class provides default implementations of {@link #getEnvelope()} and {@link #getMetadata()}.
- * Subclasses need to implement {@link #dependencies()}.</p>
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-abstract class AggregatedFeatureSet extends AbstractFeatureSet {
-    /**
-     * The envelope, computed when first needed and cached for reuse.
-     *
-     * @see #getEnvelope()
-     */
-    private ImmutableEnvelope envelope;
-
-    /**
-     * Whether {@link #envelope} has been computed. The result may still be null.
-     */
-    private boolean isEnvelopeComputed;
-
-    /**
-     * Creates a new aggregated feature set.
-     *
-     * @param  parent  listeners of the parent resource, or {@code null} if none.
-     */
-    protected AggregatedFeatureSet(final StoreListeners parent) {
-        super(parent);
-    }
-
-    /**
-     * Returns all feature set used by this aggregation. This method is invoked for implementation of
-     * {@link #getEnvelope()} and {@link #createMetadata(MetadataBuilder)}.
-     *
-     * @return all feature sets in this aggregation.
-     */
-    abstract Collection<FeatureSet> dependencies();
-
-    /**
-     * Adds the envelopes of the aggregated feature sets in the given list. If some of the feature sets
-     * are themselves aggregated feature sets, then this method traverses them recursively. We compute
-     * the union of all envelopes at once after we got all envelopes.
-     *
-     * <p>If any source has an absent value, then this method stops the collect immediately and returns {@code false}.
-     * The rational is that if at least one source has unknown location, providing a location based on other sources
-     * may be misleading since they may be very far from the missing resource location.</p>
-     *
-     * @return {@code false} if the collect has been interrupted because an envelope is absent.
-     */
-    private boolean getEnvelopes(final List<Envelope> addTo) throws DataStoreException {
-        for (final FeatureSet fs : dependencies()) {
-            if (fs instanceof AggregatedFeatureSet) {
-                if (!((AggregatedFeatureSet) fs).getEnvelopes(addTo)) {
-                    return false;
-                }
-            } else {
-                final Optional<Envelope> e = fs.getEnvelope();
-                if (!e.isPresent()) return false;                   // TODO: use isEmpty() with JDK11.
-                addTo.add(e.get());
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Returns the union of the envelopes in all aggregated feature sets.
-     * This method tries to find a CRS common to all feature sets.
-     * If no common CRS can be found, then the envelope is absent.
-     *
-     * <div class="note"><b>Implementation note:</b>
-     * this method is final because overriding it would invalidate the unwrapping of other {@code AggregatedFeatureSet} instances.
-     * If we wish to allow overrides in a future version, we would need to revisit {@link #getEnvelopes(List)} first.</div>
-     *
-     * @return union of envelopes from all dependencies.
-     * @throws DataStoreException if an error occurred while computing the envelope.
-     */
-    @Override
-    public final synchronized Optional<Envelope> getEnvelope() throws DataStoreException {
-        if (!isEnvelopeComputed) {
-            final List<Envelope> envelopes = new ArrayList<>();
-            if (getEnvelopes(envelopes)) try {
-                envelope = ImmutableEnvelope.castOrCopy(Envelopes.union(envelopes.toArray(new Envelope[envelopes.size()])));
-            } catch (TransformException e) {
-                warning(e);
-            }
-            isEnvelopeComputed = true;
-        }
-        return Optional.ofNullable(envelope);
-    }
-
-    /**
-     * Invoked the first time that {@link #getMetadata()} is invoked. The default implementation adds
-     * the information documented in {@link AbstractFeatureSet#createMetadata(MetadataBuilder)}, then
-     * adds the dependencies as lineages.
-     *
-     * @param  metadata  the builder where to set metadata properties.
-     * @throws DataStoreException if an error occurred while reading metadata from the data stores.
-     */
-    @Override
-    protected void createMetadata(final MetadataBuilder metadata) throws DataStoreException {
-        super.createMetadata(metadata);
-        for (final FeatureSet fs : dependencies()) {
-            final FeatureType type = fs.getType();
-            metadata.addSource(fs.getMetadata(), ScopeCode.FEATURE_TYPE,
-                    (type == null) ? null : new CharSequence[] {type.getName().toInternationalString()});
-        }
-    }
-
-    /**
-     * Clears any cache in this resource, forcing the data to be recomputed when needed again.
-     * This method should be invoked if the data in underlying data store changed.
-     */
-    @Override
-    protected synchronized void clearCache() {
-        isEnvelopeComputed = false;
-        envelope = null;
-        super.clearCache();
-    }
-}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java
index 09e799c..4de188d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Capability.java
@@ -25,8 +25,8 @@
 import org.apache.sis.util.collection.BackingStoreException;
 
 // Branch-dependent imports
-import org.opengis.util.InternationalString;
-import org.opengis.metadata.citation.Citation;
+import org.opengis.metadata.distribution.Format;
+import org.apache.sis.util.iso.Types;
 
 
 /**
@@ -99,9 +99,13 @@
              * Get a title for the format, followed by the short name between parenthesis
              * if it does not repeat the main title.
              */
+            final Format format = provider.getFormat();
             String title, complement;
             try {
-                title = title(provider.getFormat().getFormatSpecificationCitation()).toString(locale);
+                title = Types.toString(format.getSpecification(), locale);
+                if (title == null) {
+                    title = Types.toString(format.getName(), locale);
+                }
                 complement = provider.getShortName();
             } catch (BackingStoreException e) {
                 title = provider.getShortName();
@@ -115,16 +119,4 @@
         }
         return list;
     }
-
-    /**
-     * Returns the title or alternate title of the given citation, or "untitled" if none.
-     */
-    private static InternationalString title(final Citation specification) {
-        final InternationalString title = specification.getTitle();
-        if (title != null) return title;
-        for (final InternationalString t : specification.getAlternateTitles()) {
-            if (t != null) return t;
-        }
-        return Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
-    }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
deleted file mode 100644
index 3e0b5a7..0000000
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ConcatenatedFeatureSet.java
+++ /dev/null
@@ -1,240 +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.internal.storage;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Collection;
-import java.util.OptionalLong;
-import java.util.stream.Stream;
-import org.apache.sis.feature.Features;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.DataStoreContentException;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.apache.sis.storage.event.StoreListeners;
-import org.apache.sis.storage.Query;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-
-
-/**
- * Exposes a sequence of {@link FeatureSet}s as a single one. A few notes:
- * <ol>
- *   <li>The feature set concatenation must be built with a non-empty array or collection of feature set.
- *     It is copied verbatim in an unmodifiable list, meaning that duplicated instances won't be removed,
- *     and iteration order is driven by input sequence.</li>
- *   <li>All input feature sets must share a common type, or at least a common super-type. If you want to sequence
- *     sets which does not share any common parent, please pre-process them to modify their public type.</li>
- *   <li>There is no {@linkplain #getIdentifier() identifier} since this feature set is a computation result.</li>
- * </ol>
- *
- * @author  Alexis Manin (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
-    /**
-     * The sequence of feature sets whose feature instances will be returned.
-     */
-    private final List<FeatureSet> sources;
-
-    /**
-     * The most specific feature type common to all feature sets in the {@linkplain #sources} list.
-     */
-    private final FeatureType commonType;
-
-    /**
-     * Creates a new concatenated feature set with the same types than the given feature set,
-     * but different sources.
-     */
-    private ConcatenatedFeatureSet(final FeatureSet[] sources, final ConcatenatedFeatureSet original) {
-        super(original);
-        this.sources = UnmodifiableArrayList.wrap(sources);
-        commonType = original.commonType;
-    }
-
-    /**
-     * Creates a new feature set as a concatenation of the sequence of features given by the {@code sources}.
-     * This constructor does not verify that the given {@code sources} array contains at least two elements;
-     * this verification must be done by the caller. This constructor retains the given {@code sources} array
-     * by direct reference; clone, if desired, shall be done by the caller.
-     *
-     * @param  parent   listeners of the parent resource, or {@code null} if none.
-     * @param  sources  the sequence of feature sets to expose in a single set.
-     *                  Must neither be null, empty nor contain a single element only.
-     * @throws DataStoreException if given feature sets does not share any common type.
-     */
-    protected ConcatenatedFeatureSet(final StoreListeners parent, final FeatureSet[] sources) throws DataStoreException {
-        super(parent);
-        for (int i=0; i<sources.length; i++) {
-            ArgumentChecks.ensureNonNullElement("sources", i, sources[i]);
-        }
-        this.sources = UnmodifiableArrayList.wrap(sources);
-        final FeatureType[] types = new FeatureType[sources.length];
-        for (int i=0; i<types.length; i++) {
-            types[i] = sources[i].getType();
-        }
-        commonType = Features.findCommonParent(Arrays.asList(types));
-        if (commonType == null) {
-            // TODO: localize.
-            throw new DataStoreContentException("Cannot find a common super type across all feature sets to concatenate");
-        }
-    }
-
-    /**
-     * Creates a new feature set as a concatenation of the sequence of features given by the {@code sources}.
-     * The given array shall be non-empty. If the array contains only 1 element, that element is returned.
-     *
-     * @param  sources  the sequence of feature sets to expose in a single set.
-     * @return the concatenation of given feature set.
-     * @throws DataStoreException if given feature sets does not share any common type.
-     */
-    public static FeatureSet create(final FeatureSet... sources) throws DataStoreException {
-        ArgumentChecks.ensureNonEmpty("sources", sources);
-        if (sources.length == 1) {
-            final FeatureSet fs = sources[0];
-            ArgumentChecks.ensureNonNullElement("sources", 0, fs);
-            return fs;
-        } else {
-            return new ConcatenatedFeatureSet(null, sources.clone());
-        }
-    }
-
-    /**
-     * Creates a new feature set as a concatenation of the sequence of features given by the {@code sources}.
-     * The given collection shall be non-empty. If the collection contains only 1 element, that element is returned.
-     *
-     * @param  sources  the sequence of feature sets to expose in a single set.
-     * @return the concatenation of given feature set.
-     * @throws DataStoreException if given feature sets does not share any common type.
-     */
-    public static FeatureSet create(final Collection<? extends FeatureSet> sources) throws DataStoreException {
-        ArgumentChecks.ensureNonNull("sources", sources);
-        final int size = sources.size();
-        switch (size) {
-            case 0: throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "sources"));
-            case 1: {
-                final FeatureSet fs = CollectionsExt.first(sources);
-                ArgumentChecks.ensureNonNullElement("sources", 0, fs);
-                return fs;
-            }
-            default: {
-                return new ConcatenatedFeatureSet(null, sources.toArray(new FeatureSet[size]));
-            }
-        }
-    }
-
-    /**
-     * Returns all feature set used by this aggregation. This method is invoked for implementation of
-     * {@link #getEnvelope()} and {@link #createMetadata(MetadataBuilder)}.
-     */
-    @Override
-    @SuppressWarnings("ReturnOfCollectionOrArrayField")         // Safe because the list is unmodifiable.
-    final List<FeatureSet> dependencies() {
-        return sources;
-    }
-
-    /**
-     * Returns the most specific feature type common to all feature sets given to the constructor.
-     *
-     * @return the common type of all features returned by this set.
-     */
-    @Override
-    public FeatureType getType() {
-        return commonType;
-    }
-
-    /**
-     * Returns an estimation of the number of features in this set, or an empty value if unknown.
-     * This is the sum of the estimations provided by all source sets, or empty if at least one
-     * source could not provide an estimation.
-     *
-     * @return estimation of the number of features.
-     */
-    @Override
-    protected OptionalLong getFeatureCount() {
-        long sum = 0;
-        for (final FeatureSet fs : sources) {
-            if (fs instanceof AbstractFeatureSet) {
-                final OptionalLong count = ((AbstractFeatureSet) fs).getFeatureCount();
-                if (count.isPresent()) {
-                    final long c = count.getAsLong();
-                    if (c >= 0) {                               // Paranoiac check.
-                        sum += c;
-                        if (sum < 0) {                          // Integer overflow.
-                            sum = Long.MAX_VALUE;
-                            break;
-                        }
-                        continue;
-                    }
-                }
-            }
-            return null;                 // A source can not provide estimation.
-        }
-        return OptionalLong.of(sum);
-    }
-
-    /**
-     * Returns a stream of all features contained in this concatenated dataset. If the {@code parallel} argument
-     * is {@code false}, then datasets are traversed in the order they were specified at construction time.
-     * If the {@code parallel} argument is {@code true}, then datasets are traversed in no determinist order.
-     * If an error occurred while reading the feature instances from a source, then the error is wrapped in a
-     * {@link BackingStoreException}.
-     *
-     * @param  parallel  {@code true} for a parallel stream, or {@code false} for a sequential stream.
-     * @return all features contained in this dataset.
-     */
-    @Override
-    public Stream<Feature> features(final boolean parallel) {
-        final Stream<FeatureSet> sets = parallel ? sources.parallelStream() : sources.stream();
-        return sets.flatMap(set -> {
-            try {
-                return set.features(parallel);
-            } catch (DataStoreException e) {
-                throw new BackingStoreException(e);
-            }
-        });
-    }
-
-    /**
-     * Requests a subset of features and/or feature properties from this resource.
-     *
-     * @param  query  definition of feature and feature properties filtering applied at reading time.
-     * @return resulting subset of features (never {@code null}).
-     * @throws DataStoreException if an error occurred while processing the query.
-     */
-    @Override
-    public FeatureSet subset(final Query query) throws DataStoreException {
-        final FeatureSet[] subsets = new FeatureSet[sources.size()];
-        boolean modified = false;
-        for (int i=0; i<subsets.length; i++) {
-            FeatureSet source = sources.get(i);
-            subsets[i] = source.subset(query);
-            modified |= subsets[i] != source;
-        }
-        return modified ? new ConcatenatedFeatureSet(subsets, this) : this;
-    }
-}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java
index 5b38bf3..bf883d1 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/FeatureCatalogBuilder.java
@@ -23,12 +23,12 @@
 import org.apache.sis.metadata.iso.DefaultMetadata;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
  * Helper methods for the feature metadata created by {@code DataStore} implementations.
- * This is a convenience class for chaining {@link #addFeatureType(FeatureType, long)}
+ * This is a convenience class for chaining {@code addFeatureType(FeatureType, long)}
  * method calls with {@link FeatureNaming#add(DataStore, GenericName, Object)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -51,7 +51,7 @@
      * {@code DataStore} implementations can keep the reference to this {@code FeatureNaming}
      * after the {@link #build(boolean)} method has been invoked.
      */
-    public final FeatureNaming<FeatureType> features;
+    public final FeatureNaming<DefaultFeatureType> features;
 
     /**
      * Creates a new builder for the given data store.
@@ -74,9 +74,9 @@
      * @param  type  the feature type to add, or {@code null}.
      * @throws IllegalNameException if a feature of the same name has already been added.
      *
-     * @see #addFeatureType(FeatureType, long)
+     * @see #addFeatureType(DefaultFeatureType, long)
      */
-    public final void define(final FeatureType type) throws IllegalNameException {
+    public final void define(final DefaultFeatureType type) throws IllegalNameException {
         final GenericName name = addFeatureType(type, -1);
         if (name != null) {
             features.add(store, name, type);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
deleted file mode 100644
index a3365a0..0000000
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
+++ /dev/null
@@ -1,564 +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.internal.storage;
-
-import java.util.Map;
-import java.util.List;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Spliterator;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-import org.opengis.util.GenericName;
-import org.apache.sis.feature.FeatureOperations;
-import org.apache.sis.feature.DefaultFeatureType;
-import org.apache.sis.feature.DefaultAssociationRole;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.apache.sis.internal.storage.query.SimpleQuery;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.event.StoreListeners;
-import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.util.collection.Containers;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.PropertyIsEqualTo;
-import org.opengis.filter.expression.Expression;
-import org.apache.sis.filter.DefaultFilterFactory;
-
-
-/**
- * Features containing association to features from two different sources, joined by a SQL-like {@code JOIN} condition.
- * Each feature in this {@code FeatureSet} contains two or three properties:
- *
- * <ul>
- *   <li>An optional identifier created from the identifiers of the left and right features.</li>
- *   <li>Zero or one association to a "left"  feature.</li>
- *   <li>Zero or one association to a "right" feature.</li>
- * </ul>
- *
- * The left and right features appear together in an {@code JoinFeatureSet} instance when a value from
- * {@code leftProperty} in the first feature is equal to a value from {@code rightProperty} in the second feature.
- *
- * <h2>Implementation note</h2>
- * If iterations in one feature set is cheaper than iterations in the other feature set, then the "costly" or larger
- * {@code FeatureSet} should be on the left side and the "cheap" {@code FeatureSet} should be on the right side.
- *
- * <p>This implementation is read-only.</p>
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public class JoinFeatureSet extends AggregatedFeatureSet {
-    /**
-     * Specifies whether values on both sides are required (inner join), or only one side (outer join).
-     */
-    public enum Type {
-        /**
-         * Only records having a value on both side will be included.
-         * The {@link JoinFeatureSet} {@code "left"} and {@code "right"} properties will never be null.
-         */
-        INNER(false, false),
-
-        /**
-         * All records from the left side will be included. If there is no matching feature on the right side,
-         * then the {@link JoinFeatureSet} {@code "right"} property will be {@code null}.
-         */
-        LEFT_OUTER(true, false),
-
-        /**
-         * All records from the right side will be included. If there is no matching feature on the left side,
-         * then the {@link JoinFeatureSet} {@code "left"} property will be {@code null}.
-         */
-        RIGHT_OUTER(true, true);
-
-        /**
-         * Whether to include all "main" feature instances even if there is no match in the other side.
-         * This is {@code true} for outer joins and {@code false} for inner joins.
-         */
-        final boolean isOuterJoin;
-
-        /**
-         * {@code true} if the "main" side is the right side instead than the left side.
-         * See {@link JoinFeatureSet.Iterator} for a definition of "main side".
-         */
-        final boolean swapSides;
-
-        /**
-         * Creates an enumeration.
-         */
-        private Type(final boolean isOuterJoin, final boolean swapSides) {
-            this.isOuterJoin = isOuterJoin;
-            this.swapSides   = swapSides;
-        }
-
-        /**
-         * Returns the minimum occurrences for properties on the left or right side.
-         *
-         * @param right  {@code false} for the left side, or {@code true} for the right side.
-         */
-        final int minimumOccurs(final boolean right) {
-            return !isOuterJoin | (swapSides == right) ? 1 : 0;
-        }
-
-        /**
-         * Returns the enumeration value for the given characteristics.
-         */
-        static Type valueOf(final boolean isOuterJoin, final boolean swapSides) {
-            return isOuterJoin ? (swapSides ? RIGHT_OUTER : LEFT_OUTER) : INNER;
-        }
-    }
-
-    /**
-     * The type of features included in this set. Contains two associations as described in class javadoc.
-     */
-    private final FeatureType type;
-
-    /**
-     * The first source of features.
-     */
-    public final FeatureSet left;
-
-    /**
-     * The second source of features.
-     */
-    public final FeatureSet right;
-
-    /**
-     * Name of the associations to the {@link #left} features.
-     * This may be the name of the {@link #left} feature type, but not necessarily.
-     */
-    private final String leftName;
-
-    /**
-     * Name of the associations to the {@link #right} features.
-     * This may be the name of the {@link #right} feature type, but not necessarily.
-     */
-    private final String rightName;
-
-    /**
-     * {@code true} if the "main" side is the right side instead than the left side.
-     * See {@link JoinFeatureSet.Iterator} for a definition of "main side".
-     */
-    private final boolean swapSides;
-
-    /**
-     * Whether to include all "main" feature instances even if there is no match in the other side.
-     * This is {@code true} for outer joins and {@code false} for inner joins.
-     */
-    private final boolean isOuterJoin;
-
-    /**
-     * The join condition in the form <var>property from left feature</var> = <var>property from right feature</var>.
-     * This condition specifies also if the comparison is {@linkplain PropertyIsEqualTo#isMatchingCase() case sensitive}
-     * and {@linkplain PropertyIsEqualTo#getMatchAction() how to compare multi-values}.
-     */
-    public final PropertyIsEqualTo condition;
-
-    /**
-     * The factory to use for creating {@code Query} expressions for retrieving subsets of feature sets.
-     */
-    private final FilterFactory factory;
-
-    /**
-     * Creates a new feature set joining the two given sets. The {@code featureInfo} map defines the name,
-     * description or other information for the {@code FeatureType} created by this method. It can contain all
-     * the properties described in {@link org.apache.sis.feature.DefaultFeatureType} plus the following ones:
-     *
-     * <ul>
-     *   <li>{@code "identifierDelimiter"} — string to insert between left and right identifiers in the identifiers
-     *     generated by the join operation. If this property is not specified, then no identifier will be generated.</li>
-     *   <li>{@code "identifierPrefix"} — string to insert at the beginning of join identifiers (optional).</li>
-     *   <li>{@code "identifierSuffix"} — string to insert at the end of join identifiers (optional).</li>
-     * </ul>
-     *
-     * @param  parent       listeners of the parent resource, or {@code null} if none.
-     * @param  left         the first source of features. This is often (but not necessarily) the largest set.
-     * @param  leftAlias    name of the associations to the {@code left} features, or {@code null} for a default name.
-     * @param  right        the second source of features. Should be the set in which iterations are cheapest.
-     * @param  rightAlias   name of the associations to the {@code right} features, or {@code null} for a default name.
-     * @param  joinType     whether values on both sides are required (inner join), or only one side (outer join).
-     * @param  condition    join condition as <var>property from left feature</var> = <var>property from right feature</var>.
-     * @param  featureInfo  information about the {@link FeatureType} of this
-     * @throws DataStoreException if an error occurred while creating the feature set.
-     */
-    public JoinFeatureSet(final StoreListeners parent,
-                          final FeatureSet left,  String leftAlias,
-                          final FeatureSet right, String rightAlias,
-                          final Type joinType, final PropertyIsEqualTo condition,
-                          Map<String,?> featureInfo)
-            throws DataStoreException
-    {
-        super(parent);
-        final FeatureType leftType  = left.getType();
-        final FeatureType rightType = right.getType();
-        final GenericName leftName  = leftType.getName();
-        final GenericName rightName = rightType.getName();
-        if (leftAlias  == null) leftAlias  = leftName.toString();
-        if (rightAlias == null) rightAlias = rightName.toString();
-        this.left        = left;
-        this.right       = right;
-        this.leftName    = leftAlias;
-        this.rightName   = rightAlias;
-        this.swapSides   = joinType.swapSides;
-        this.isOuterJoin = joinType.isOuterJoin;
-        this.condition   = condition;
-        this.factory     = new DefaultFilterFactory();       // TODO: replace by some static instance?
-        /*
-         * We could build the FeatureType only when first needed, but the type is required by the iterators.
-         * Since we are going to need the type for any use of this JoinFeatureSet, better to create it now.
-         */
-        PropertyType[] properties = new PropertyType[] {
-            new DefaultAssociationRole(name(leftAlias),  leftType,  joinType.minimumOccurs(false), 1),
-            new DefaultAssociationRole(name(rightAlias), rightType, joinType.minimumOccurs(true),  1)
-        };
-        final String identifierDelimiter = Containers.property(featureInfo, "identifierDelimiter", String.class);
-        if (identifierDelimiter != null && AttributeConvention.hasIdentifier(leftType)
-                                        && AttributeConvention.hasIdentifier(rightType))
-        {
-            final Operation identifier = FeatureOperations.compound(
-                    name(AttributeConvention.IDENTIFIER_PROPERTY), identifierDelimiter,
-                    Containers.property(featureInfo, "identifierPrefix", String.class),
-                    Containers.property(featureInfo, "identifierSuffix", String.class), properties);
-            properties = ArraysExt.insert(properties, 0, 1);
-            properties[0] = identifier;
-        }
-        if (featureInfo == null) {
-            featureInfo = name(leftName.tip().toString() + '-' + rightName.tip());
-        }
-        type = new DefaultFeatureType(featureInfo, false, null, properties);
-    }
-
-    /**
-     * Creates a minimal {@code properties} map for feature type or property type constructors.
-     * This minimalist map contain only the mandatory entry, which is the name.
-     */
-    private static Map<String,?> name(final Object name) {
-        return Collections.singletonMap(DefaultFeatureType.NAME_KEY, name);
-    }
-
-    /**
-     * Returns the two feature sets used by this {@code JoinFeatureSet}.
-     * The "main" dependency is at index 0 and the other dependency at index 1.
-     *
-     * @return the dependencies in a list of size 2 with the "main" dependency first.
-     */
-    @Override
-    final List<FeatureSet> dependencies() {
-        final FeatureSet[] sets = new FeatureSet[] {left, right};
-        if (swapSides) ArraysExt.swap(sets, 0, 1);
-        return Arrays.asList(sets);
-    }
-
-    /**
-     * Specifies whether values on both sides are required (inner join), or only one side (outer join).
-     *
-     * @return whether values on both sides are required (inner join), or only one side (outer join).
-     */
-    public Type getJoinType() {
-        return Type.valueOf(isOuterJoin, swapSides);
-    }
-
-    /**
-     * Returns a description of properties that are common to all features in this dataset.
-     * This type may contain one identifier and always contains two associations,
-     * to the {@linkplain #left} and {@link #right} set of features respectively.
-     *
-     * @return a description of properties that are common to all features in this dataset.
-     */
-    @Override
-    public FeatureType getType() {
-        return type;
-    }
-
-    /**
-     * Returns a stream of all features contained in this dataset.
-     *
-     * @param  parallel  {@code true} for a parallel stream (if supported), or {@code false} for a sequential stream.
-     * @return all features contained in this dataset.
-     * @throws DataStoreException if an error occurred while creating the stream.
-     */
-    @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
-        final Iterator it = new Iterator();
-        return StreamSupport.stream(it, parallel).onClose(it);
-    }
-
-    /**
-     * Creates a new features containing an association to the two given features.
-     * The {@code main} feature can not be null (this is not verified).
-     */
-    private Feature join(Feature main, Feature filtered) {
-        if (swapSides) {
-            final Feature t = main;
-            main = filtered;
-            filtered = t;
-        }
-        final Feature f = type.newInstance();
-        f.setPropertyValue(leftName,  main);
-        f.setPropertyValue(rightName, filtered);
-        return f;
-    }
-
-    /**
-     * Iterator over the features resulting from the inner or outer join operation.
-     * The {@link #run()} method disposes the resources.
-     */
-    private final class Iterator implements Spliterator<Feature>, Consumer<Feature>, Runnable {
-        /**
-         * The main stream or a split iterator to close when the {@link #run()} method will be invoked.
-         * This is initially the stream from which {@link #mainIterator} has been created. However if
-         * {@link #trySplit()} has been invoked, then this handler may be the other {@code Iterator}
-         * instance which itself contains a reference to the stream to close, thus forming a chain.
-         */
-        private Runnable mainCloseHandler;
-
-        /**
-         * An iterator over all features in the "main" (usually left) side. The "main" side is the side which
-         * may include all features: in a "left outer join" this is the left side, and in a "right outer join"
-         * this is the right side. For inner join we arbitrarily take the left side in accordance with public
-         * class javadoc, which suggests to put the most costly or larger set on the left side.
-         *
-         * <p>Only one iteration will be performed on those features, contrarily to the other side where we may
-         * iterate over the same elements many times.</p>
-         */
-        private final Spliterator<Feature> mainIterator;
-
-        /**
-         * A feature fetched from the {@link #mainIterator}. The join operation will match this feature with
-         * zero, one or more features from the other side. A {@code null} value means that this feature needs
-         * to be retrieved with {@code mainIterator.tryAdvance(…)}.
-         */
-        private Feature mainFeature;
-
-        /**
-         * The stream over features in the other (usually right) side. A new stream will be created every time a new
-         * feature from the main side is processed. For this reason, it should be the cheapest stream if possible.
-         */
-        private Stream<Feature> filteredStream;
-
-        /**
-         * Iterator for the {@link #filteredStream}. A new iterator will be recreated every time a new feature
-         * from the main side is processed.
-         */
-        private Spliterator<Feature> filteredIterator;
-
-        /**
-         * A feature fetched from the {@link #filteredIterator}, or {@code null} if none.
-         */
-        private Feature filteredFeature;
-
-        /**
-         * Creates a new iterator. We do not use parallelized {@code mainStream} here because the {@code accept(…)}
-         * methods used by this {@code Iterator} can not be invoked concurrently by different threads. It does not
-         * present parallelization at a different level since this {@code Iterator} supports {@link #trySplit()},
-         * so the {@link Stream} wrapping it can use parallelization.
-         */
-        Iterator() throws DataStoreException {
-            final Stream<Feature> mainStream = (swapSides ? right : left).features(false);
-            mainCloseHandler = mainStream::close;
-            mainIterator = mainStream.spliterator();
-        }
-
-        /**
-         * Creates an iterator resulting from the call to {@link #trySplit()}.
-         */
-        private Iterator(final Spliterator<Feature> it) {
-            mainIterator = it;
-        }
-
-        /**
-         * If this iterator can be partitioned, returns a spliterator covering a prefix of the feature set.
-         * Upon return from this method, this iterator will cover a suffix of the feature set.
-         * Returns {@code null} if this iterator can not be partitioned.
-         */
-        @Override
-        public Spliterator<Feature> trySplit() {
-            final Spliterator<Feature> s = mainIterator.trySplit();
-            if (s == null) {
-                return null;
-            }
-            final Iterator it = new Iterator(s);
-            it.mainCloseHandler = mainCloseHandler;
-            mainCloseHandler = it;
-            return it;
-        }
-
-        /**
-         * Specifies that the iterator will return only non-null elements. Whether those elements will
-         * be ordered depends on whether the main iterator provides ordered elements in the first place.
-         *
-         * <p><b>NOTE:</b> to be strict, we should check if the "filtered" stream is also ordered. But this
-         * is more difficult to check. Current implementation assumes that if the "mean" stream is ordered,
-         * then the other stream is ordered too. Furthermore the {@link #trySplit()} method works only on
-         * the main stream, so at least the {@code trySplit} requirement about prefix and suffix order is
-         * still fulfill even if the other stream is unordered.</p>
-         */
-        @Override
-        public int characteristics() {
-            return (mainIterator.characteristics() & ORDERED) | NONNULL;
-        }
-
-        /**
-         * Estimated size is unknown.
-         */
-        @Override
-        public long estimateSize() {
-            return Long.MAX_VALUE;
-        }
-
-        /**
-         * Closes the streams used by this iterator, together with the streams used by any spliterator
-         * created by {@link #trySplit()}. This method is registered to {@link Stream#onClose(Runnable)}.
-         */
-        @Override
-        public void run() {
-            closeFilteredIterator();
-            final Runnable toClose = mainCloseHandler;
-            if (toClose != null) {
-                mainCloseHandler = null;            // Cleared first in case of error.
-                toClose.run();
-            }
-        }
-
-        /**
-         * Invoked when iteration on the filtered stream ended, before to move on the next feature of the main stream.
-         * This method is idempotent: it has no effect if the stream is already closed.
-         */
-        private void closeFilteredIterator() {
-            final Stream<Feature> stream = filteredStream;
-            filteredStream   = null;                // Cleared before call to close() in case of error.
-            filteredIterator = null;
-            filteredFeature  = null;                // Used as a sentinel value by this.forEachRemaining(…).
-            mainFeature      = null;                // Indicate that we will need to advance in mainIterator.
-            if (stream != null) {
-                stream.close();
-            }
-        }
-
-        /**
-         * Creates a new iterator over the filtered set of features (usually the right side).
-         * The filtering condition is determined by the current {@link #mainFeature}.
-         */
-        private void createFilteredIterator() {
-            Expression expression1 = condition.getExpression1();
-            Expression expression2 = condition.getExpression2();
-            FeatureSet filteredSet = right;
-            if (swapSides) {
-                filteredSet  = left;
-                Expression t = expression2;
-                expression2  = expression1;
-                expression1  = t;
-            }
-            final Object mainValue = expression1.evaluate(mainFeature);
-            final Filter filter;
-            if (mainValue != null) {
-                filter = factory.equal(expression2, factory.literal(mainValue),
-                            condition.isMatchingCase(), condition.getMatchAction());
-            } else {
-                filter = factory.isNull(expression2);
-            }
-            final SimpleQuery query = new SimpleQuery();
-            query.setFilter(filter);
-            try {
-                filteredStream = filteredSet.subset(query).features(false);
-            } catch (DataStoreException e) {
-                throw new BackingStoreException(e);
-            }
-            filteredIterator = filteredStream.spliterator();
-        }
-
-        /**
-         * Executes the given action on all remaining features in the {@code JoinFeatureSet}.
-         */
-        @Override
-        public void forEachRemaining(final Consumer<? super Feature> action) {
-            final Consumer<Feature> forFiltered = (final Feature feature) -> {
-                if (feature != null) {
-                    action.accept(join(mainFeature, filteredFeature = feature));
-                }
-            };
-            final Consumer<Feature> forMain = (final Feature feature) -> {
-                if (feature != null) {
-                    mainFeature = feature;
-                    createFilteredIterator();
-                    filteredIterator.forEachRemaining(forFiltered);
-                    final boolean none = (filteredFeature == null);
-                    closeFilteredIterator();
-                    if (none && isOuterJoin) {
-                        action.accept(join(feature, null));
-                    }
-                    // Do not close the main stream since it may be in use by other Spliterators.
-                }
-            };
-            forMain.accept(mainFeature);                // In case some 'tryAdvance' has been invoked before.
-            mainIterator.forEachRemaining(forMain);
-        }
-
-        /**
-         * Callback for {@code Spliterator.tryAdvance(this)} on {@link #filteredIterator}.
-         * Used by {@link #tryAdvance(Consumer)} implementation only.
-         */
-        @Override
-        public void accept(final Feature feature) {
-            filteredFeature = feature;
-        }
-
-        /**
-         * Executes the given action on the next feature in the {@code JoinFeatureSet}.
-         */
-        @Override
-        public boolean tryAdvance​(final Consumer<? super Feature> action) {
-            for (;;) {
-                if (mainFeature == null) {
-                    do if (!mainIterator.tryAdvance(this)) {
-                        return false;
-                    } while (filteredFeature == null);
-                    mainFeature = filteredFeature;
-                    filteredFeature = null;
-                }
-                if (filteredIterator == null) {
-                    createFilteredIterator();
-                }
-                final boolean none = (filteredFeature == null);
-                while (filteredIterator.tryAdvance(this)) {
-                    if (filteredFeature != null) {
-                        action.accept(join(mainFeature, filteredFeature));
-                        return true;
-                    }
-                }
-                final Feature feature = mainFeature;
-                closeFilteredIterator();
-                if (none && isOuterJoin) {
-                    action.accept(join(feature, null));
-                    return true;
-                }
-            }
-        }
-    }
-}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
index 061bbdf..451e3de 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
@@ -23,8 +23,8 @@
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -41,24 +41,26 @@
     /**
      * The type specified at construction time and returned by {@link #getType()}.
      */
-    private final FeatureType type;
+    private final DefaultFeatureType type;
 
     /**
      * The features specified at construction time, potentially as a modifiable collection.
-     * For all features in this collection, {@link Feature#getType()} shall be {@link #type}.
+     * For all features in this collection, {@link AbstractFeature#getType()} shall be {@link #type}.
      */
-    private final Collection<Feature> features;
+    private final Collection<AbstractFeature> features;
 
     /**
      * Creates a new set of features stored in memory. It is caller responsibility to ensure that
-     * <code>{@linkplain Feature#getType()} == type</code> for all elements in the given collection
+     * <code>{@linkplain AbstractFeature#getType()} == type</code> for all elements in the given collection
      * (this is not verified).
      *
      * @param parent     listeners of the parent resource, or {@code null} if none.
      * @param type       the type of all features in the given collection.
      * @param features   collection of stored features. This collection will not be copied.
      */
-    public MemoryFeatureSet(final StoreListeners parent, final FeatureType type, final Collection<Feature> features) {
+    public MemoryFeatureSet(final StoreListeners parent,
+                            final DefaultFeatureType type, final Collection<AbstractFeature> features)
+{
         super(parent);
         ArgumentChecks.ensureNonNull("type",     type);
         ArgumentChecks.ensureNonNull("features", features);
@@ -72,7 +74,7 @@
      * @return a description of properties that are common to all features in this dataset.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return type;
     }
 
@@ -93,7 +95,7 @@
      * @return all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) {
+    public Stream<AbstractFeature> features(final boolean parallel) {
         return parallel ? features.parallelStream() : features.stream();
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
index d024582..ebfe86a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
@@ -70,7 +70,6 @@
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.internal.simple.SimpleDuration;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
@@ -103,6 +102,7 @@
 import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
 import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
 import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 import org.apache.sis.metadata.iso.identification.DefaultKeywords;
 import org.apache.sis.metadata.iso.identification.DefaultResolution;
 import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
@@ -138,7 +138,7 @@
 // Branch-dependent imports
 import org.opengis.metadata.citation.ResponsibleParty;
 import org.opengis.metadata.identification.CharacterSet;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty;
 
 
@@ -973,6 +973,11 @@
         if (abbreviation != null && abbreviation.length() != 0) {
             if (format == null) {
                 format = MetadataSource.getProvided().lookup(Format.class, abbreviation);
+                /*
+                 * Additional step for converting deprecated "name" and "specification" into non-deprecated properties.
+                 * This step is not required on SIS branches that depend on development branches of GeoAPI 3.1 or 4.0.
+                 */
+                format = DefaultFormat.castOrCopy(format);
             } else {
                 addFormatName(abbreviation);
             }
@@ -1058,7 +1063,7 @@
      * </ul>
      *
      * If a date already exists for the given type, then the earliest date is retained (oldest date are discarded)
-     * except for {@link DateType#LAST_REVISION}, {@link DateType#LAST_UPDATE LAST_UPDATE} or any other date type
+     * except for {@code DateType.LAST_REVISION}, {@code DateType.LAST_UPDATE LAST_UPDATE} or any other date type
      * prefixed by {@code "LATE_"}, where only the latest date is kept.
      *
      * @param  date   the date to add, or {@code null} for no-operation..
@@ -1630,7 +1635,7 @@
             final DefaultCitation c = new DefaultCitation(notice);
             if (year != 0) {
                 final Date date = new Date(LocalDate.of(year, 1, 1).toEpochDay() * MILLISECONDS_PER_DAY);
-                c.setDates(Collections.singleton(new DefaultCitationDate(date, DateType.IN_FORCE)));
+                c.setDates(Collections.singleton(new DefaultCitationDate(date, DateType.valueOf("IN_FORCE"))));
             }
             if (i != 0) {
                 buffer.setLength(i);
@@ -1879,9 +1884,9 @@
      * @param  occurrences  number of instances of the given feature type, or a negative value if unknown.
      * @return the name of the added feature, or {@code null} if none.
      *
-     * @see FeatureCatalogBuilder#define(FeatureType)
+     * @see FeatureCatalogBuilder#define(DefaultFeatureType)
      */
-    public final GenericName addFeatureType(final FeatureType type, final long occurrences) {
+    public final GenericName addFeatureType(final DefaultFeatureType type, final long occurrences) {
         if (type != null) {
             final GenericName name = type.getName();
             if (name != null) {
@@ -2001,22 +2006,6 @@
     }
 
     /**
-     * Adds a temporal resolution in days.
-     * Storage location is:
-     *
-     * <ul>
-     *   <li>{@code metadata/identificationInfo/temporalResolution}</li>
-     * </ul>
-     *
-     * @param  duration  the resolution in days, or {@code NaN} for no-operation.
-     */
-    public final void addTemporalResolution(final double duration) {
-        if (!Double.isNaN(duration)) {
-            addIfNotPresent(identification().getTemporalResolutions(), new SimpleDuration(duration));
-        }
-    }
-
-    /**
      * Sets identification of grid data as point or cell.
      * Storage location is:
      *
@@ -2779,8 +2768,11 @@
             source.setSourceReferenceSystem(CollectionsExt.first(metadata.getReferenceSystemInfo()));
             for (final Identification id : metadata.getIdentificationInfo()) {
                 source.setSourceCitation(id.getCitation());
-                source.setSourceSpatialResolution(CollectionsExt.first(id.getSpatialResolutions()));
-                scope.setExtents(id.getExtents());
+                if (id instanceof AbstractIdentification) {
+                    final AbstractIdentification aid = (AbstractIdentification) id;
+                    source.setSourceSpatialResolution(CollectionsExt.first(aid.getSpatialResolutions()));
+                    scope.setExtents(aid.getExtents());
+                }
                 if (features != null && features.length != 0) {
                     /*
                      * Note: the same ScopeDescription may be shared by many Source instances
@@ -2990,7 +2982,7 @@
      *
      * <ul>
      *   <li>{@code metadata/metadataLinkage/linkage}
-     *     with {@code function} set to {@link OnLineFunction#COMPLETE_METADATA}</li>
+     *     with {@code function} set to {@code OnLineFunction.COMPLETE_METADATA}</li>
      * </ul>
      *
      * @param  link  URL to a more complete description of the metadata, or {@code null}.
@@ -2998,7 +2990,7 @@
     public final void addCompleteMetadata(final URI link) {
         if (link != null) {
             final DefaultOnlineResource ln = new DefaultOnlineResource(link);
-            ln.setFunction(OnLineFunction.COMPLETE_METADATA);
+            ln.setFunction(OnLineFunction.valueOf("COMPLETE_METADATA"));
             ln.setProtocol(link.getScheme());
             addIfNotPresent(metadata().getMetadataLinkages(), ln);
         }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
index c48cb66..1c18480 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
@@ -45,7 +45,8 @@
 import org.apache.sis.util.Classes;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 
 
 /**
@@ -159,7 +160,10 @@
         GeneralEnvelope bounds = null;
         if (metadata != null) {
             for (final Identification identification : metadata.getIdentificationInfo()) {
-                for (final Extent extent : identification.getExtents()) {
+                if (!(identification instanceof AbstractIdentification)) {
+                    continue;       // Following cast is specific to GeoAPI 3.0 branch.
+                }
+                for (final Extent extent : ((AbstractIdentification) identification).getExtents()) {
                     for (final GeographicExtent ge : extent.getGeographicElements()) {
                         if (ge instanceof GeographicBoundingBox) {
                             final GeneralEnvelope env = new GeneralEnvelope((GeographicBoundingBox) ge);
@@ -328,7 +332,7 @@
      */
     public static void copy(final FeatureSet source, final WritableFeatureSet target) throws DataStoreException {
         target.updateType(source.getType());
-        try (Stream<Feature> stream = source.features(false)) {
+        try (Stream<AbstractFeature> stream = source.features(false)) {
             target.add(stream.iterator());
         }
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java
index 3acd08f..a387f6a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FeatureIterator.java
@@ -27,9 +27,9 @@
 import org.apache.sis.util.collection.BackingStoreException;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -52,7 +52,7 @@
  * @since   0.7
  * @module
  */
-class FeatureIterator implements Spliterator<Feature> {
+class FeatureIterator implements Spliterator<AbstractFeature> {
     /**
      * Index of the column containing trajectory coordinates.
      * Columns before the trajectory are Moving Feature identifier {@code mfIdRef}, start time and end time.
@@ -94,12 +94,12 @@
     @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
     FeatureIterator(final Store store) {
         this.store = store;
-        final Collection<? extends PropertyType> properties = store.featureType.getProperties(true);
+        final Collection<? extends AbstractIdentifiedType> properties = store.featureType.getProperties(true);
         converters    = new ObjectConverter[properties.size()];
         values        = new Object[converters.length];
         propertyNames = new String[converters.length];
         int i = -1;
-        for (final PropertyType p : properties) {
+        for (final AbstractIdentifiedType p : properties) {
             propertyNames[++i] = p.getName().tip().toString();
             /*
              * According Moving Features specification:
@@ -137,7 +137,7 @@
                      */
                 }
                 default: {
-                    c = ObjectConverters.find(String.class, ((AttributeType) p).getValueClass());
+                    c = ObjectConverters.find(String.class, ((DefaultAttributeType) p).getValueClass());
                     break;
                 }
             }
@@ -163,7 +163,7 @@
      * is not guaranteed to cover a strict prefix of the elements.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         if (splitCount == null) {
             splitCount = new AtomicInteger();
         }
@@ -177,7 +177,7 @@
      * Executes the given action only on the next feature, if any.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
         try {
             return read(action, false);
         } catch (IOException | IllegalArgumentException | DateTimeException e) {
@@ -189,7 +189,7 @@
      * Executes the given action on all remaining features.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
         try {
             read(action, true);
         } catch (IOException | IllegalArgumentException | DateTimeException e) {
@@ -215,12 +215,12 @@
      * @throws IllegalArgumentException if parsing of a number failed, or other error.
      * @throws DateTimeException if parsing of a date failed.
      */
-    private boolean read(final Consumer<? super Feature> action, final boolean all) throws IOException {
+    private boolean read(final Consumer<? super AbstractFeature> action, final boolean all) throws IOException {
         final FixedSizeList elements = new FixedSizeList(values);
         String line;
         while ((line = store.readLine()) != null) {
             Store.split(line, elements);
-            final Feature feature = store.featureType.newInstance();
+            final AbstractFeature feature = store.featureType.newInstance();
             int i, n = elements.size();
             for (i=0; i<n; i++) {
                 values[i] = converters[i].apply((String) values[i]);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java
index ee3e955..68badec 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/MovingFeatureIterator.java
@@ -26,8 +26,8 @@
 import org.apache.sis.internal.feature.MovingFeature;
 
 // Branch-dependent imports
-import org.opengis.feature.Attribute;
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -78,10 +78,10 @@
      * This method can only be invoked after {@link #readMoving(Consumer, boolean)} completion.
      * This method is ignored if the CSV file contains only static features.
      */
-    Feature[] createMovingFeatures() {
+    AbstractFeature[] createMovingFeatures() {
         int n = 0;
         final int np = values.length - TRAJECTORY_COLUMN;
-        final Feature[] features = new Feature[builders.size()];
+        final AbstractFeature[] features = new AbstractFeature[builders.size()];
         for (final Map.Entry<String,MovingFeature> entry : builders.entrySet()) {
             features[n++] = createMovingFeature(entry.getKey(), entry.getValue(), np);
         }
@@ -96,18 +96,18 @@
      * @param  np           number of properties, ignoring the ones before the trajectory column.
      */
     @SuppressWarnings("unchecked")
-    private Feature createMovingFeature(final String featureName, final MovingFeature mf, final int np) {
-        final Feature feature = store.featureType.newInstance();
+    private AbstractFeature createMovingFeature(final String featureName, final MovingFeature mf, final int np) {
+        final AbstractFeature feature = store.featureType.newInstance();
         feature.setPropertyValue(propertyNames[0], featureName);
         mf.storeTimeRange(propertyNames[1], propertyNames[2], feature);
         int column = 0;
         if (store.hasTrajectories()) {
             mf.storeGeometry(featureName, column, store.spatialDimensionCount(), store.geometries,
-                    (Attribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this);
+                    (AbstractAttribute) feature.getProperty(propertyNames[TRAJECTORY_COLUMN]), this);
             column++;
         }
         while (column < np) {
-            mf.storeAttribute(column, (Attribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column]));
+            mf.storeAttribute(column, (AbstractAttribute<?>) feature.getProperty(propertyNames[TRAJECTORY_COLUMN + column]));
             column++;
         }
         return feature;
@@ -124,7 +124,7 @@
      * @throws IllegalArgumentException if parsing of a number failed, or other error.
      * @throws DateTimeException if parsing of a date failed.
      */
-    boolean readMoving(final Consumer<? super Feature> action, final boolean all) throws IOException {
+    boolean readMoving(final Consumer<? super AbstractFeature> action, final boolean all) throws IOException {
         final FixedSizeList elements = new FixedSizeList(values);
         final int np = values.length - TRAJECTORY_COLUMN;
         String line;
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
index 2497cf5..5903e0c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
@@ -78,10 +78,8 @@
 import org.apache.sis.measure.Units;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -172,7 +170,7 @@
      *
      * @see #parseFeatureType(List)
      */
-    final FeatureType featureType;
+    final DefaultFeatureType featureType;
 
     /**
      * {@code true} if {@link #featureType} contains a trajectory column.
@@ -222,7 +220,7 @@
      * All parsed moving features, or {@code null} if none or if not yet parsed. If {@link #dissociate}
      * is {@code false}, then this list will be created by {@link #features(boolean)} when first needed.
      */
-    private transient List<Feature> movingFeatures;
+    private transient List<AbstractFeature> movingFeatures;
 
     /**
      * Creates a new CSV store from the given file, URL or stream.
@@ -246,7 +244,7 @@
         geometries = Geometries.implementation(connector.getOption(OptionKey.GEOMETRY_LIBRARY));
         dissociate = FoliationRepresentation.FRAGMENTED.equals(connector.getOption(DataOptionKey.FOLIATION_REPRESENTATION));
         GeneralEnvelope envelope    = null;
-        FeatureType     featureType = null;
+        DefaultFeatureType featureType = null;
         Foliation       foliation   = null;
         try {
             final List<String> elements = new ArrayList<>();
@@ -512,10 +510,10 @@
      * @return the column metadata, or {@code null} if the given list does not contain enough elements.
      */
     @SuppressWarnings("rawtypes")               // "rawtypes" because of generic array creation.
-    private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
-        AttributeType[] characteristics = null;
+    private DefaultFeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
+        DefaultAttributeType[] characteristics = null;
         final int size = elements.size();
-        final List<PropertyType> properties = new ArrayList<>();
+        final List<AbstractIdentifiedType> properties = new ArrayList<>();
         for (int i=1; i<size; i++) {
             final String name = elements.get(i);
             Class<?> type = null;
@@ -570,7 +568,7 @@
                                 type = double[].class;
                             } else {
                                 type = geometries.polylineClass;
-                                characteristics = new AttributeType[] {MovingFeature.TIME};
+                                characteristics = new DefaultAttributeType[] {MovingFeature.TIME};
                             }
                             minOccurrence = 1;
                             maxOccurrence = 1;
@@ -587,15 +585,15 @@
             name = name.substring(0, s);
         }
         return new DefaultFeatureType(Collections.singletonMap(DefaultFeatureType.NAME_KEY, name),
-                false, null, properties.toArray(new PropertyType[properties.size()]));
+                false, null, properties.toArray(new AbstractIdentifiedType[properties.size()]));
     }
 
     /**
      * Creates a property type for the given name and type.
      * This is a helper method for {@link #parseFeatureType(List)}.
      */
-    private static PropertyType createProperty(final String name, final Class<?> type,
-            final int minOccurrence, final int maxOccurrence, final AttributeType<?>[] characteristics)
+    private static AbstractIdentifiedType createProperty(final String name, final Class<?> type,
+            final int minOccurrence, final int maxOccurrence, final DefaultAttributeType<?>[] characteristics)
     {
         return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),
                 type, minOccurrence, maxOccurrence, null, characteristics);
@@ -691,7 +689,7 @@
      * @return type of features in the CSV file.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return featureType;
     }
 
@@ -706,7 +704,7 @@
      * @todo If sequential order, publish Feature as soon as identifier changed.
      */
     @Override
-    public final synchronized Stream<Feature> features(final boolean parallel) throws DataStoreException {
+    public final synchronized Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
         /*
          * If the user asks for one feature instance per line, then we can return a FeatureIter instance directly.
          * Since each feature is fully constructed from a single line and each line are read atomically, we can
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
index 6e3d5a4..e83a06c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
@@ -48,6 +48,9 @@
 import org.apache.sis.internal.storage.StoreUtilities;
 import org.apache.sis.setup.OptionKey;
 
+// Branch-dependent imports
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+
 
 /**
  * The provider of {@link Store} instances. This provider is intentionally <strong>not</strong> registered
@@ -109,7 +112,7 @@
      * Creates a parameter descriptor equals to the given one except for the remarks which are set to the given value.
      */
     private static <T> ParameterDescriptor<T> annotate(ParameterBuilder builder, ParameterDescriptor<T> e, InternationalString remark) {
-        return builder.addName(e.getName()).setDescription(e.getDescription()).setRemarks(remark).create(e.getValueClass(), null);
+        return builder.addName(e.getName()).setDescription(((DefaultParameterDescriptor) e).getDescription()).setRemarks(remark).create(e.getValueClass(), null);
     }
 
     /**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index df63841..d331551 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -265,7 +265,7 @@
     public synchronized Metadata getMetadata() {
         if (metadata == null) {
             final MetadataBuilder mb = new MetadataBuilder();
-            mb.addResourceScope(ScopeCode.COLLECTION, Resources.formatInternational(Resources.Keys.DirectoryContent_1, getDisplayName()));
+            mb.addResourceScope(ScopeCode.valueOf("COLLECTION"), Resources.formatInternational(Resources.Keys.DirectoryContent_1, getDisplayName()));
             mb.addLanguage(locale,   MetadataBuilder.Scope.RESOURCE);
             mb.addEncoding(encoding, MetadataBuilder.Scope.RESOURCE);
             mb.addTitleOrIdentifier(identifier.toString(), MetadataBuilder.Scope.RESOURCE);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
index 2a2b887..563f30e 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
@@ -16,23 +16,17 @@
  */
 package org.apache.sis.internal.storage.query;
 
-import java.util.List;
 import java.util.stream.Stream;
-import org.apache.sis.internal.feature.FeatureUtilities;
 import org.apache.sis.internal.storage.AbstractFeatureSet;
 import org.apache.sis.internal.storage.Resources;
-import org.apache.sis.filter.InvalidExpressionException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.event.StoreListeners;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.expression.Expression;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -62,7 +56,7 @@
      * The type of features in this set. May or may not be the same as {@link #source}.
      * This is computed when first needed.
      */
-    private FeatureType resultType;
+    private DefaultFeatureType resultType;
 
     /**
      * Creates a new set of features by filtering the given set using the given query.
@@ -77,12 +71,12 @@
      * Returns a description of properties that are common to all features in this dataset.
      */
     @Override
-    public synchronized FeatureType getType() throws DataStoreException {
+    public synchronized DefaultFeatureType getType() throws DataStoreException {
         if (resultType == null) {
-            final FeatureType type = source.getType();
+            final DefaultFeatureType type = source.getType();
             try {
                 resultType = query.expectedType(type);
-            } catch (IllegalArgumentException | InvalidExpressionException e) {
+            } catch (IllegalArgumentException e) {
                 throw new DataStoreContentException(Resources.forLocale(getLocale())
                         .getString(Resources.Keys.CanNotDeriveTypeFromFeature_1, type.getName()), e);
             }
@@ -94,22 +88,8 @@
      * Returns a stream of all features contained in this dataset.
      */
     @Override
-    public Stream<Feature> features(final boolean parallel) throws DataStoreException {
-        Stream<Feature> stream = source.features(parallel);
-        /*
-         * Apply filter.
-         */
-        final Filter filter = query.getFilter();
-        if (!Filter.INCLUDE.equals(filter)) {
-            stream = stream.filter(filter::evaluate);
-        }
-        /*
-         * Apply sorting.
-         */
-        final SortBy[] sortBy = query.getSortBy();
-        if (sortBy.length > 0) {
-            stream = stream.sorted(new SortByComparator(sortBy));
-        }
+    public Stream<AbstractFeature> features(final boolean parallel) throws DataStoreException {
+        Stream<AbstractFeature> stream = source.features(parallel);
         /*
          * Apply offset.
          */
@@ -124,25 +104,6 @@
         if (limit >= 0) {
             stream = stream.limit(limit);
         }
-        /*
-         * Transform feature instances.
-         */
-        final List<SimpleQuery.Column> columns = query.getColumns();
-        if (columns != null) {
-            final Expression[] expressions = new Expression[columns.size()];
-            for (int i=0; i<expressions.length; i++) {
-                expressions[i] = columns.get(i).expression;
-            }
-            final FeatureType type = getType();
-            final String[] names = FeatureUtilities.getNames(type.getProperties(false));
-            stream = stream.map(t -> {
-                final Feature f = type.newInstance();
-                for (int i=0; i < expressions.length; i++) {
-                    f.setPropertyValue(names[i], expressions[i].evaluate(t));
-                }
-                return f;
-            });
-        }
         return stream;
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java
index 8752f73..a907391 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java
@@ -16,30 +16,12 @@
  */
 package org.apache.sis.internal.storage.query;
 
-import java.util.Arrays;
-import java.util.Map;
-import java.util.List;
-import java.util.LinkedHashMap;
-import java.util.Objects;
-import org.opengis.util.GenericName;
-import org.apache.sis.filter.InvalidExpressionException;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.apache.sis.internal.storage.Resources;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.Query;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.Classes;
-import org.apache.sis.util.collection.Containers;
-import org.apache.sis.util.iso.Names;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.sort.SortBy;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -60,22 +42,6 @@
     private static final long UNLIMITED = -1;
 
     /**
-     * The columns to retrieve, or {@code null} if all columns shall be included in the query.
-     *
-     * @see #getColumns()
-     * @see #setColumns(Column...)
-     */
-    private Column[] columns;
-
-    /**
-     * The filter for trimming feature instances.
-     *
-     * @see #getFilter()
-     * @see #setFilter(Filter)
-     */
-    private Filter filter;
-
-    /**
      * The number of records to skip from the beginning.
      *
      * @see #getOffset()
@@ -94,82 +60,13 @@
     private long limit;
 
     /**
-     * The expressions to use for sorting the feature instances.
-     *
-     * @see #getSortBy()
-     * @see #setSortBy(SortBy...)
-     */
-    private SortBy[] sortBy;
-
-    /**
      * Creates a new query retrieving no column and applying no filter.
      */
     public SimpleQuery() {
-        filter = Filter.INCLUDE;
-        sortBy = SortBy.UNSORTED;
         limit  = UNLIMITED;
     }
 
     /**
-     * Sets the columns to retrieve, or {@code null} if all columns shall be included in the query.
-     * A query column may use a simple or complex expression and an alias to create a new type of
-     * property in the returned features.
-     * This is equivalent to the column names in the {@code SELECT} clause of a SQL statement.
-     *
-     * @param  columns  columns to retrieve, or {@code null} to retrieve all properties.
-     * @throws IllegalArgumentException if a column or an alias is duplicated.
-     */
-    @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
-    public void setColumns(Column... columns) {
-        if (columns != null) {
-            columns = columns.clone();
-            final Map<Object,Integer> uniques = new LinkedHashMap<>(Containers.hashMapCapacity(columns.length));
-            for (int i=0; i<columns.length; i++) {
-                final Column c = columns[i];
-                ArgumentChecks.ensureNonNullElement("columns", i, c);
-                final Object key = c.alias != null ? c.alias : c.expression;
-                final Integer p = uniques.putIfAbsent(key, i);
-                if (p != null) {
-                    throw new IllegalArgumentException(Resources.format(Resources.Keys.DuplicatedQueryProperty_3, key, p, i));
-                }
-            }
-        }
-        this.columns = columns;
-    }
-
-    /**
-     * Returns the columns to retrieve, or {@code null} if all columns shall be included in the query.
-     * This is the columns specified in the last call to {@link #setColumns(Column...)}.
-     *
-     * @return columns to retrieve, or {@code null} to retrieve all feature properties.
-     */
-    public List<Column> getColumns() {
-        return UnmodifiableArrayList.wrap(columns);
-    }
-
-    /**
-     * Sets a filter for trimming feature instances.
-     * Features that do not pass the filter are discarded.
-     * Discarded features are not counted for the {@linkplain #setLimit(long) query limit}.
-     *
-     * @param  filter  the filter, or {@link Filter#INCLUDE} if none.
-     */
-    public void setFilter(final Filter filter) {
-        ArgumentChecks.ensureNonNull("filter", filter);
-        this.filter = filter;
-    }
-
-    /**
-     * Returns the filter for trimming feature instances.
-     * This is the value specified in the last call to {@link #setFilter(Filter)}.
-     *
-     * @return the filter, or {@link Filter#INCLUDE} if none.
-     */
-    public Filter getFilter() {
-        return filter;
-    }
-
-    /**
      * Sets the number of records to skip from the beginning.
      * Offset and limit are often combined to obtain paging.
      * The offset can not be negative.
@@ -221,163 +118,6 @@
     }
 
     /**
-     * Sets the expressions to use for sorting the feature instances.
-     * {@code SortBy} objects are used to order the {@link org.opengis.feature.Feature} instances
-     * returned by the {@link org.apache.sis.storage.FeatureSet}.
-     * {@code SortBy} clauses are applied in declaration order, like SQL.
-     *
-     * @param  sortBy  expressions to use for sorting the feature instances.
-     */
-    @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
-    public void setSortBy(SortBy... sortBy) {
-        if (sortBy == null || sortBy.length == 0) {
-            sortBy = SortBy.UNSORTED;
-        } else {
-            sortBy = sortBy.clone();
-            for (int i=0; i < sortBy.length; i++) {
-                ArgumentChecks.ensureNonNullElement("sortBy", i, sortBy[i]);
-            }
-        }
-        this.sortBy = sortBy;
-    }
-
-    /**
-     * Returns the expressions to use for sorting the feature instances.
-     * They are the values specified in the last call to {@link #setSortBy(SortBy...)}.
-     *
-     * @return expressions to use for sorting the feature instances, or an empty array if none.
-     */
-    public SortBy[] getSortBy() {
-        return (sortBy.length == 0) ? SortBy.UNSORTED : sortBy.clone();
-    }
-
-    /**
-     * A property or expression to be retrieved by a {@code Query}, together with the name to assign to it.
-     * Columns can be given to the {@link SimpleQuery#setColumns(Column...)} method.
-     */
-    public static class Column {
-        /**
-         * The literal, property name or more complex expression to be retrieved by a {@code Query}.
-         */
-        public final Expression expression;
-
-        /**
-         * The name to assign to the expression result, or {@code null} if unspecified.
-         */
-        public final GenericName alias;
-
-        /**
-         * Creates a new column with the given expression and no name.
-         *
-         * @param expression  the literal, property name or expression to be retrieved by a {@code Query}.
-         */
-        public Column(final Expression expression) {
-            ArgumentChecks.ensureNonNull("expression", expression);
-            this.expression = expression;
-            this.alias = null;
-        }
-
-        /**
-         * Creates a new column with the given expression and the given name.
-         *
-         * @param expression  the literal, property name or expression to be retrieved by a {@code Query}.
-         * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
-         */
-        public Column(final Expression expression, final GenericName alias) {
-            ArgumentChecks.ensureNonNull("expression", expression);
-            this.expression = expression;
-            this.alias = alias;
-        }
-
-        /**
-         * Creates a new column with the given expression and the given name.
-         * This constructor creates a {@link org.opengis.util.LocalName} from the given string.
-         *
-         * @param expression  the literal, property name or expression to be retrieved by a {@code Query}.
-         * @param alias       the name to assign to the expression result, or {@code null} if unspecified.
-         */
-        public Column(final Expression expression, final String alias) {
-            ArgumentChecks.ensureNonNull("expression", expression);
-            this.expression = expression;
-            this.alias = (alias != null) ? Names.createLocalName(null, null, alias) : null;
-        }
-
-        /**
-         * Adds in the given builder the type of results computed by this column.
-         *
-         * @param  column     index of this column. Used for error message only.
-         * @param  valueType  the type of features to be evaluated by the expression in this column.
-         * @param  addTo      where to add the type of properties evaluated by expression in this column.
-         * @throws IllegalArgumentException if this method can operate only on some feature types
-         *         and the given type is not one of them.
-         * @throws InvalidExpressionException if this method can not determine the result type of the expression
-         *         in this column. It may be because that expression is backed by an unsupported implementation.
-         *
-         * @see SimpleQuery#expectedType(FeatureType)
-         */
-        final void expectedType(final int column, final FeatureType valueType, final FeatureTypeBuilder addTo) {
-            final PropertyTypeBuilder resultType = FeatureExpression.expectedType(expression, valueType, addTo);
-            if (resultType == null) {
-                throw new InvalidExpressionException(expression, column);
-            }
-            if (alias != null && !alias.equals(resultType.getName())) {
-                resultType.setName(alias);
-            }
-        }
-
-        /**
-         * Returns a hash code value for this column.
-         *
-         * @return a hash code value.
-         */
-        @Override
-        public int hashCode() {
-            return 37 * expression.hashCode() + Objects.hashCode(alias);
-        }
-
-        /**
-         * Compares this column with the given object for equality.
-         *
-         * @param  obj  the object to compare with this column.
-         * @return whether the two objects are equal.
-         */
-        @Override
-        public boolean equals(final Object obj) {
-            if (obj == this) {
-                return true;
-            }
-            if (obj != null && getClass() == obj.getClass()) {
-                final Column other = (Column) obj;
-                return expression.equals(other.expression) && Objects.equals(alias, other.alias);
-            }
-            return false;
-        }
-
-        /**
-         * Returns a string representation of this column for debugging purpose.
-         *
-         * @return a string representation of this column.
-         */
-        @Override
-        public String toString() {
-            final StringBuilder buffer = new StringBuilder();
-            buffer.append(getClass().getSimpleName()).append('[');      // Class name without enclosing class.
-            appendTo(buffer);
-            return buffer.append(']').toString();
-        }
-
-        /**
-         * Appends a string representation of this column in the given buffer.
-         */
-        final void appendTo(final StringBuilder buffer) {
-            buffer.append(Classes.getShortClassName(expression));       // Class name with enclosing class if any.
-            if (alias != null) {
-                buffer.append(" AS “").append(alias).append('”');
-            }
-        }
-    }
-
-    /**
      * Applies this query on the given feature set. The default implementation executes the query using the default
      * {@link java.util.stream.Stream} methods.  Queries executed by this method may not benefit from accelerations
      * provided for example by databases. This method should be used only as a fallback when the query can not be
@@ -401,18 +141,9 @@
      * @return type resulting from expressions evaluation (never null).
      * @throws IllegalArgumentException if this method can operate only on some feature types
      *         and the given type is not one of them.
-     * @throws InvalidExpressionException if this method can not determine the result type of an expression
-     *         in this query. It may be because that expression is backed by an unsupported implementation.
      */
-    final FeatureType expectedType(final FeatureType valueType) {
-        if (columns == null) {
-            return valueType;           // All columns included: result is of the same type.
-        }
-        final FeatureTypeBuilder ftb = new FeatureTypeBuilder().setName(valueType.getName());
-        for (int i=0; i<columns.length; i++) {
-            columns[i].expectedType(i, valueType, ftb);
-        }
-        return ftb.build();
+    final DefaultFeatureType expectedType(final DefaultFeatureType valueType) {
+        return valueType;       // More elaborated code in geoapi-4.0 branch.
     }
 
     /**
@@ -422,8 +153,7 @@
      */
     @Override
     public int hashCode() {
-        return 97 * Arrays.hashCode(columns) + 31 * filter.hashCode()
-                + 7 * Arrays.hashCode(sortBy) + Long.hashCode(limit ^ skip);
+        return Long.hashCode(limit ^ skip);
     }
 
     /**
@@ -440,10 +170,7 @@
         if (obj != null && getClass() == obj.getClass()) {
             final SimpleQuery other = (SimpleQuery) obj;
             return skip  == other.skip &&
-                   limit == other.limit &&
-                   filter.equals(other.filter) &&
-                   Arrays.equals(columns, other.columns) &&
-                   Arrays.equals(sortBy,  other.sortBy);
+                   limit == other.limit;
         }
         return false;
     }
@@ -457,24 +184,7 @@
     public String toString() {
         final StringBuilder sb = new StringBuilder(80);
         sb.append("SELECT ");
-        if (columns != null) {
-            for (int i=0; i<columns.length; i++) {
-                if (i != 0) sb.append(", ");
-                columns[i].appendTo(sb);
-            }
-        } else {
-            sb.append('*');
-        }
-        if (filter != Filter.INCLUDE) {
-            sb.append(" WHERE ").append(filter);
-        }
-        if (sortBy != SortBy.UNSORTED) {
-            sb.append(" ORDER BY ");
-            for (int i=0; i<sortBy.length; i++) {
-                if (i != 0) sb.append(", ");
-                sb.append(sortBy[i]);
-            }
-        }
+        sb.append('*');
         if (limit != UNLIMITED) {
             sb.append(" LIMIT ").append(limit);
         }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SortByComparator.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SortByComparator.java
deleted file mode 100644
index 101c87b..0000000
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SortByComparator.java
+++ /dev/null
@@ -1,116 +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.internal.storage.query;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import org.apache.sis.util.collection.Containers;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-import org.opengis.filter.expression.Expression;
-
-
-/**
- * Comparator sorting features by an array of {@code SortBy} expressions, applied in order.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- *
- * @todo Current implementation has unchecked casts. Fixing that may require a revision of filter interfaces.
- *       See <a href="https://github.com/opengeospatial/geoapi/issues/32">GeoAPI issue #32</a>.
- */
-final class SortByComparator implements Comparator<Feature> {
-    /**
-     * The expression to evaluate for getting the values to sort.
-     */
-    private final Expression[] properties;
-
-    /**
-     * {@code false} for ascending order, {@code true} for descending order.
-     * If unspecified or unknown, we assume ascending order.
-     */
-    private final boolean[] descending;
-
-    /**
-     * Creates a new comparator for the given sort expressions.
-     * It is caller responsibility to ensure that the given array is non-empty.
-     */
-    SortByComparator(final SortBy[] orders) {
-        properties = new Expression[orders.length];
-        descending = new boolean   [orders.length];
-        for (int i=0; i<orders.length; i++) {
-            final SortBy order = orders[i];
-            properties[i] = order.getPropertyName();
-            descending[i] = SortOrder.DESCENDING.equals(order.getSortOrder());
-        }
-    }
-
-    /**
-     * Compares two features for order. Returns -1 if {@code f1} should be sorted before {@code f2},
-     * +1 if {@code f2} should be after {@code f1}, or 0 if both are equal. Null features are sorted
-     * after all non-null features, regardless sorting order.
-     */
-    @Override
-    public int compare(final Feature f1, final Feature f2) {
-        if (f1 != f2) {
-            if (f1 == null) return +1;
-            if (f2 == null) return -1;
-            for (int i=0; i<properties.length; i++) {
-                final Expression property = properties[i];
-                Object o1 = property.evaluate(f1);
-                Object o2 = property.evaluate(f2);
-                if (o1 != o2) {
-                    if (o1 == null) return +1;
-                    if (o2 == null) return -1;
-                    final int result;
-                    /*
-                     * No @SuppressWarnings("unchecked") below: those casts are really unsafe;
-                     * we can not make them safe with current Filter API. See GeoAPI issue #32.
-                     */
-                    if (o1 instanceof Iterable<?>) {
-                        result = Containers.compare(((Iterable) o1).iterator(), iterator(o2));
-                    } else if (o2 instanceof Iterable<?>) {
-                        result = Containers.compare(iterator(o1), ((Iterable) o2).iterator());
-                    } else {
-                        result = ((Comparable) o1).compareTo(o2);
-                    }
-                    if (result != 0) {
-                        return descending[i] ? -result : result;
-                    }
-                }
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * Returns an iterator for the given object.
-     *
-     * @todo Intentionally raw return type, but no {@literal @SuppressWarning} annotation because this
-     *       is a real problem with current Filter API which needs to be fixed. See GeoAPI issue #32.
-     */
-    private static Iterator iterator(final Object o) {
-        return (o instanceof Iterable<?>) ? ((Iterable<?>) o).iterator() : Collections.singleton(o).iterator();
-    }
-}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java
index 8c9ff33..93742b7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/GeographicEnvelope.java
@@ -23,6 +23,9 @@
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.GeographicExtent;
+import org.opengis.metadata.extent.TemporalExtent;
+import org.opengis.metadata.extent.VerticalExtent;
+import org.opengis.util.InternationalString;
 import org.apache.sis.referencing.CommonCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
@@ -112,6 +115,17 @@
     }
 
     /**
+     * Returns the spatial and temporal extent for the referring object.
+     * The default implementation unconditionally returns {@code null}.
+     *
+     * @return the spatial and temporal extent, or {@code null} in none.
+     */
+    @Override
+    public InternationalString getDescription() {
+        return null;
+    }
+
+    /**
      * Provides geographic component of the extent of the referring object.
      * The default implementation returns a singleton containing only this
      * geographic bounding box.
@@ -124,6 +138,28 @@
     }
 
     /**
+     * Provides temporal component of the extent of the referring object.
+     * The default implementation unconditionally returns an empty set.
+     *
+     * @return the temporal extent, or an empty set if none.
+     */
+    @Override
+    public Collection<? extends TemporalExtent> getTemporalElements() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Provides vertical component of the extent of the referring object.
+     * The default implementation unconditionally returns an empty set.
+     *
+     * @return the vertical extent, or an empty set if none.
+     */
+    @Override
+    public Collection<? extends VerticalExtent> getVerticalElements() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Indication of whether the bounding box encompasses an area covered by the data
      * (<cite>inclusion</cite>) or an area where data is not present (<cite>exclusion</cite>).
      * The default implementation unconditionally returns {@link Boolean#TRUE}.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java
index c63da03..f4bc884 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Aggregate.java
@@ -48,10 +48,10 @@
  * {@link org.apache.sis.metadata.iso.DefaultMetadata#getMetadataScopes() metadataScope} /
  * {@link org.apache.sis.metadata.iso.DefaultMetadataScope#getResourceScope() resourceScope} sets to
  * {@link org.opengis.metadata.maintenance.ScopeCode#SERIES} or
- * {@link org.opengis.metadata.maintenance.ScopeCode#INITIATIVE} if applicable.
+ * {@code ScopeCode.INITIATIVE} if applicable.
  * If not too expensive to compute, the names of all components should be listed as
  * {@linkplain org.apache.sis.metadata.iso.identification.AbstractIdentification#getAssociatedResources()
- * associated resources} with an {@link org.opengis.metadata.identification.AssociationType#IS_COMPOSED_OF} relation.
+ * associated resources} with an {@code AssociationType.IS_COMPOSED_OF} relation.
  *
  * @author  Johann Sorel (Geomatys)
  * @version 1.0
@@ -68,7 +68,7 @@
      * <blockquote><code><b>this</b>.metadata</code> /
      * {@link org.apache.sis.metadata.iso.DefaultMetadata#getIdentificationInfo() identificationInfo} /
      * {@link org.apache.sis.metadata.iso.identification.AbstractIdentification#getAssociatedResources() associatedResource}
-     * with {@link org.opengis.metadata.identification.AssociationType#IS_COMPOSED_OF}</blockquote>
+     * with {@code AssociationType.IS_COMPOSED_OF}</blockquote>
      *
      * The name of each child resource in the returned collection is given by the following metadata element:
      *
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 6814fb5..dfa6f30 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@ -37,6 +37,9 @@
 import org.apache.sis.storage.event.StoreListener;
 import org.apache.sis.storage.event.StoreListeners;
 
+// Branch-specific imports
+import org.opengis.referencing.ReferenceIdentifier;
+
 
 /**
  * Manages a series of features, coverages or sensor data.
@@ -328,12 +331,12 @@
                 }
             }
             if (citation != null) {
-                Identifier first = null;
+                ReferenceIdentifier first = null;
                 for (final Identifier c : citation.getIdentifiers()) {
                     if (c instanceof GenericName) {
                         return Optional.of((GenericName) c);
-                    } else if (first == null) {
-                        first = c;
+                    } else if (first == null && c instanceof ReferenceIdentifier) {
+                        first = (ReferenceIdentifier) c;
                     }
                 }
                 if (first != null) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java
index 3657eb7..eec19ef 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureNaming.java
@@ -31,8 +31,8 @@
 
 /**
  * Helper class for mapping {@link GenericName} instances and their shortened names to features.
- * The features are typically represented by instances of {@link org.opengis.feature.FeatureType}
- * or {@link org.opengis.coverage.Coverage} (sometime seen as a kind of features), but this class
+ * The features are typically represented by instances of {@code FeatureType}
+ * or {@code Coverage} (sometime seen as a kind of features), but this class
  * actually puts no restriction on the kind of object associated to {@code GenericName}s;
  * {@link DataStore} implementations are free to choose their internal object.
  * Those objects can be stored and fetched using the {@code String} representation of their name
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
index 105e7f1..69c8f04 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
@@ -21,8 +21,8 @@
 import org.apache.sis.internal.storage.query.SimpleQuery;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -70,14 +70,14 @@
      * @return description of common properties (never {@code null}).
      * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
      */
-    FeatureType getType() throws DataStoreException;
+    DefaultFeatureType getType() throws DataStoreException;
 
     /**
      * Requests a subset of features and/or feature properties from this resource.
      * The filtering can be applied in two domains:
      *
      * <ul>
-     *   <li>The returned {@code FeatureSet} may contain a smaller number of {@link Feature} instances.</li>
+     *   <li>The returned {@code FeatureSet} may contain a smaller number of {@code Feature} instances.</li>
      *   <li>In each {@code Feature} instance of the returned set, the number of
      *       {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty properties} may be smaller.</li>
      * </ul>
@@ -144,5 +144,5 @@
      * @return all features contained in this dataset.
      * @throws DataStoreException if an error occurred while creating the stream.
      */
-    Stream<Feature> features(boolean parallel) throws DataStoreException;
+    Stream<AbstractFeature> features(boolean parallel) throws DataStoreException;
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
index 267c21a..c2571ed 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/IllegalFeatureTypeException.java
@@ -23,7 +23,7 @@
 
 /**
  * Thrown when a store can not write the given feature because its type is not one of the supported types.
- * The {@link org.opengis.feature.FeatureType} is given by {@link org.opengis.feature.Feature#getType()},
+ * The {@code FeatureType} is given by {@code Feature.getType()},
  * and the type expected by the data store is given by {@link FeatureSet#getType()}. Those two values must
  * match, except when the type of the feature set is {@linkplain WritableFeatureSet#updateType updated}.
  *
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
index 2527de7..ea1a1c3 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
@@ -16,23 +16,21 @@
  */
 package org.apache.sis.storage;
 
-import org.opengis.feature.Feature;
-
 
 /**
  * Definition of filtering to apply for fetching a resource subset.
  * Filtering can happen in two domains:
  *
  * <ol>
- *   <li>By filtering the {@link Feature} instances.</li>
+ *   <li>By filtering the {@code Feature} instances.</li>
  *   <li>By filtering the {@linkplain org.apache.sis.feature.DefaultFeatureType#getProperty properties}
  *       in each feature instance.</li>
  * </ol>
  *
  * Compared to Java functional interfaces, the first domain is equivalent to using
- * <code>{@linkplain java.util.function.Predicate}&lt;{@linkplain Feature}&gt;</code>
+ * <code>{@linkplain java.util.function.Predicate}&lt;Feature&gt;</code>
  * while the second domain is equivalent to using
- * <code>{@linkplain java.util.function.UnaryOperator}&lt;{@linkplain Feature}&gt;</code>.
+ * <code>{@linkplain java.util.function.UnaryOperator}&lt;Feature&gt;</code>.
  *
  * <div class="note"><b>Note:</b>
  * it is technically possible to use {@code Query} for performing more generic feature transformations,
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
index e37a1ca..fd5152c 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Resource.java
@@ -111,7 +111,7 @@
      *       {@link org.opengis.metadata.maintenance.ScopeCode#DATASET} if the resource is a {@link DataSet}, or
      *       {@link org.opengis.metadata.maintenance.ScopeCode#SERVICE} if the resource is a web service, or
      *       {@link org.opengis.metadata.maintenance.ScopeCode#SERIES} or
-     *       {@link org.opengis.metadata.maintenance.ScopeCode#INITIATIVE}
+     *       {@code ScopeCode.INITIATIVE}
      *       if the resource is an {@link Aggregate} other than a transfer aggregate.</li>
      *   <li>{@code metadata} /
      *       {@link org.apache.sis.metadata.iso.DefaultMetadata#getContentInfo() contentInfo} /
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java
index 29cc254..12b7458 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/WritableFeatureSet.java
@@ -22,8 +22,8 @@
 import java.util.function.UnaryOperator;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -51,12 +51,12 @@
      * @throws IllegalFeatureTypeException if the given type is not compatible with the types supported by the store.
      * @throws DataStoreException if another error occurred while changing the feature type.
      */
-    void updateType(FeatureType newType) throws DataStoreException;
+    void updateType(DefaultFeatureType newType) throws DataStoreException;
 
     /**
      * Inserts new feature instances in this {@code FeatureSet}.
      * Any feature already present in this {@link FeatureSet} will remain unmodified.
-     * If a {@linkplain Feature#getProperty feature property} is used as unique identifier, then:
+     * If a {@linkplain AbstractFeature#getProperty feature property} is used as unique identifier, then:
      *
      * <ul>
      *   <li>If a given feature assigns to that property a value already in use, an exception will be thrown.</li>
@@ -76,7 +76,7 @@
      * @throws IllegalFeatureTypeException if a feature given by the iterator is not of the type expected by this {@code FeatureSet}.
      * @throws DataStoreException if another error occurred while storing new features.
      */
-    void add(Iterator<? extends Feature> features) throws DataStoreException;
+    void add(Iterator<? extends AbstractFeature> features) throws DataStoreException;
 
     /**
      * Removes all feature instances from this {@code FeatureSet} which matches the given predicate.
@@ -85,25 +85,25 @@
      * @return {@code true} if any elements were removed.
      * @throws DataStoreException if an error occurred while removing features.
      */
-    boolean removeIf(Predicate<? super Feature> filter) throws DataStoreException;
+    boolean removeIf(Predicate<? super AbstractFeature> filter) throws DataStoreException;
 
     /**
      * Updates all feature instances from this {@code FeatureSet} which match the given predicate.
-     * For each {@link Feature} instance matching the given {@link Predicate},
+     * For each {@code Feature} instance matching the given {@link Predicate},
      * the <code>{@linkplain UnaryOperator#apply UnaryOperator.apply(Feature)}</code> method will be invoked.
      * {@code UnaryOperator}s are free to modify the given {@code Feature} <i>in-place</i>
      * or to return a different feature instance. Two behaviors are possible:
      *
      * <ul>
-     *   <li>If the operator returns a non-null {@link Feature}, then the modified feature is stored
+     *   <li>If the operator returns a non-null {@code Feature}, then the modified feature is stored
      *       in replacement of the previous feature (not necessarily at the same location).</li>
      *   <li>If the operator returns {@code null}, then the feature will be removed from the {@code FeatureSet}.</li>
      * </ul>
      *
      * @param  filter   a predicate which returns {@code true} for feature instances to be updated.
-     * @param  updater  operation called for each matching {@link Feature} instance.
+     * @param  updater  operation called for each matching {@code Feature} instance.
      * @throws IllegalFeatureTypeException if a feature given by the operator is not of the type expected by this {@code FeatureSet}.
      * @throws DataStoreException if another error occurred while replacing features.
      */
-    void replaceIf(Predicate<? super Feature> filter, UnaryOperator<Feature> updater) throws DataStoreException;
+    void replaceIf(Predicate<? super AbstractFeature> filter, UnaryOperator<AbstractFeature> updater) throws DataStoreException;
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java
deleted file mode 100644
index a6006ab..0000000
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ConcatenatedFeatureSetTest.java
+++ /dev/null
@@ -1,187 +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.internal.storage;
-
-import java.util.Arrays;
-import java.util.Collections;
-import org.opengis.metadata.Metadata;
-import org.opengis.metadata.content.FeatureCatalogueDescription;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.storage.DataStoreContentException;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.test.DependsOnMethod;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.apache.sis.test.MetadataAssert.*;
-import static org.apache.sis.test.TestUtilities.getSingleton;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-
-
-/**
- * Tests {@link ConcatenatedFeatureSet}.
- *
- * @author  Alexis Manin (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class ConcatenatedFeatureSetTest extends TestCase {
-    /**
-     * Tests the concatenation of two feature sets having the same feature type.
-     *
-     * @throws DataStoreException if an error occurred while concatenating the feature sets.
-     */
-    @Test
-    public void testSameType() throws DataStoreException {
-        final FeatureTypeBuilder builder = new FeatureTypeBuilder();
-        builder.setName("City");
-        builder.addAttribute(String.class).setName("name");
-        builder.addAttribute(Integer.class).setName("population");
-
-        final FeatureType ft = builder.build();
-        final FeatureSet cfs = ConcatenatedFeatureSet.create(
-                new MemoryFeatureSet(null, ft, Arrays.asList(ft.newInstance(), ft.newInstance())),
-                new MemoryFeatureSet(null, ft, Arrays.asList(ft.newInstance())));
-
-        assertSame("getType()", ft, cfs.getType());
-        assertEquals("features.count()", 3, cfs.features(false).count());
-
-        final Metadata md = cfs.getMetadata();
-        assertNotNull("getMetadata()", md);
-        assertContentInfoEquals("City", 3, (FeatureCatalogueDescription) getSingleton(md.getContentInfo()));
-        assertFeatureSourceEquals("City", new String[] {"City"},
-                getSingleton(getSingleton(md.getResourceLineages()).getSources()));
-    }
-
-    /**
-     * Tests the concatenation of two feature sets having different feature types.
-     *
-     * @throws DataStoreException if an error occurred while concatenating the feature sets.
-     */
-    @Test
-    @DependsOnMethod("testSameType")
-    public void testCommonSuperType() throws DataStoreException {
-        /*
-         * First, we prepare two types sharing a common ancestor. We'll create two types using same properties,
-         * so we can ensure that all data is exposed upon traversal, not only data defined in the super type.
-         */
-        final FeatureTypeBuilder builder = new FeatureTypeBuilder();
-        builder.addAttribute(Integer.class).setName("value");
-        builder.setName("parent");
-
-        final FeatureType superType = builder.build();
-
-        builder.clear();
-        builder.setSuperTypes(superType);
-        builder.addAttribute(String.class).setName("label");
-
-        final FeatureType t1 = builder.setName("t1").build();
-        final FeatureType t2 = builder.setName("t2").build();
-
-        // Populate a feature set for first type.
-        final Feature t1f1 = t1.newInstance();
-        t1f1.setPropertyValue("value", 2);
-        t1f1.setPropertyValue("label", "first-first");
-
-        final Feature t1f2 = t1.newInstance();
-        t1f2.setPropertyValue("value", 3);
-        t1f2.setPropertyValue("label", "first-second");
-
-        final FeatureSet t1fs = new MemoryFeatureSet(null, t1, Arrays.asList(t1f1, t1f2));
-
-        // Populate a feature set for second type.
-        final Feature t2f1 = t2.newInstance();
-        t2f1.setPropertyValue("value", 3);
-        t2f1.setPropertyValue("label", "second-first");
-
-        final Feature t2f2 = t2.newInstance();
-        t2f2.setPropertyValue("value", 4);
-        t2f2.setPropertyValue("label", "second-second");
-
-        final FeatureSet t2fs = new MemoryFeatureSet(null, t2, Arrays.asList(t2f1, t2f2));
-        /*
-         * First, we'll test that total sum of value property is coherent with initialized features.
-         * After that, we will ensure that we can get back the right labels for each subtype.
-         */
-        final FeatureSet set = ConcatenatedFeatureSet.create(t1fs, t2fs);
-        final int sum = set.features(true)
-                .mapToInt(f -> (int) f.getPropertyValue("value"))
-                .sum();
-        assertEquals("Sum of feature `value` property", 12, sum);
-
-        final Object[] t1labels = set.features(false)
-                .filter(f -> t1.equals(f.getType()))
-                .map(f -> f.getPropertyValue("label"))
-                .toArray();
-        assertArrayEquals("First type labels", new String[] {"first-first", "first-second"}, t1labels);
-
-        final Object[] t2labels = set.features(false)
-                .filter(f -> t2.equals(f.getType()))
-                .map(f -> f.getPropertyValue("label"))
-                .toArray();
-        assertArrayEquals("First type labels", new String[] {"second-first", "second-second"}, t2labels);
-    }
-
-    /**
-     * Tests the concatenation of two feature sets having no common parent.
-     * Creation of {@link ConcatenatedFeatureSet} is expected to fail.
-     */
-    @Test
-    @DependsOnMethod("testCommonSuperType")
-    public void noCommonType() {
-        final FeatureTypeBuilder builder = new FeatureTypeBuilder();
-        builder.setName("super");
-        final FeatureType mockSuperType = builder.build();
-        final FeatureType firstType  = builder.setSuperTypes(mockSuperType).setName("first").build();
-        final FeatureType secondType = builder.clear().setName("second").build();
-        final FeatureSet fs1 = new MemoryFeatureSet(null, firstType,  Collections.emptyList());
-        final FeatureSet fs2 = new MemoryFeatureSet(null, secondType, Collections.emptyList());
-        try {
-            FeatureSet concatenation = ConcatenatedFeatureSet.create(fs1, fs2);
-            fail("Concatenation succeeded despite the lack of common type. Result is:\n" + concatenation);
-        } catch (DataStoreContentException e) {
-            // Expected behavior.
-        } catch (DataStoreException e) {
-            fail("Concatenation failed with an error reserved for other kind of error.");
-        }
-    }
-
-    /**
-     * Tests that no concatenated transform is created from less than 2 types.
-     *
-     * @throws DataStoreException if an error occurred while invoking the create method.
-     */
-    @Test
-    public void lackOfInput() throws DataStoreException {
-        try {
-            final FeatureSet set = ConcatenatedFeatureSet.create();
-            fail("An empty concatenation has been created:\n" + set);
-        } catch (IllegalArgumentException e) {
-            // This is the expected exception.
-        }
-        final FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("mock");
-        final FeatureType mockType = builder.build();
-        final FeatureSet fs1 = new MemoryFeatureSet(null, mockType, Collections.emptyList());
-        final FeatureSet set = ConcatenatedFeatureSet.create(fs1);
-        assertSame("A concatenation has been created from a single type.", fs1, set);
-    }
-}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java
deleted file mode 100644
index fada5a1..0000000
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java
+++ /dev/null
@@ -1,310 +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.internal.storage;
-
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.test.DependsOnMethod;
-import org.apache.sis.test.TestCase;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-// Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterFactory;
-import org.opengis.filter.PropertyIsEqualTo;
-import org.apache.sis.filter.DefaultFilterFactory;
-
-
-/**
- * Tests {@link JoinFeatureSet}.
- *
- * @author  Johann Sorel (Geomatys)
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-public final strictfp class JoinFeatureSetTest extends TestCase {
-    /**
-     * The set of features to be joined together.
-     */
-    private final FeatureSet featureSet1, featureSet2;
-
-    /**
-     * {@code true} if testing parallel execution, or {@code false} for testing sequential execution.
-     * Parallel execution will be tested only if all sequential execution succeed.
-     */
-    private boolean parallel;
-
-    /**
-     * Creates a new test case.
-     */
-    public JoinFeatureSetTest() {
-        FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("Type1");
-        builder.addAttribute( String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
-        builder.addAttribute( String.class).setName("myNameSpace", "att1");
-        builder.addAttribute(Integer.class).setName("myNameSpace", "att2");
-        final FeatureType type1 = builder.build();
-        featureSet1 = new MemoryFeatureSet(null, type1, Arrays.asList(
-                newFeature1(type1, "fid_1_0", "str1",   1),
-                newFeature1(type1, "fid_1_1", "str2",   2),
-                newFeature1(type1, "fid_1_2", "str3",   3),
-                newFeature1(type1, "fid_1_3", "str50", 50),
-                newFeature1(type1, "fid_1_4", "str51", 51)));
-
-        builder = new FeatureTypeBuilder().setName("Type2");
-        builder.addAttribute( String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY);
-        builder.addAttribute(Integer.class).setName("otherNameSpace", "att3");
-        builder.addAttribute( Double.class).setName("otherNameSpace", "att4");
-        final FeatureType type2 = builder.build();
-        featureSet2 = new MemoryFeatureSet(null, type2, Arrays.asList(
-                newFeature2(type2, "fid_2_0",  1, 10),
-                newFeature2(type2, "fid_2_1",  2, 20),
-                newFeature2(type2, "fid_2_2",  2, 30),
-                newFeature2(type2, "fid_2_3",  3, 40),
-                newFeature2(type2, "fid_2_4", 60, 60),
-                newFeature2(type2, "fid_2_5", 61, 61)));
-    }
-
-    /**
-     * Creates a new feature of type 1 with the given identifier and attribute values.
-     * This is a helper method for the constructor only.
-     */
-    private static Feature newFeature1(final FeatureType type, final String id, final String att1, final int att2) {
-        final Feature f = type.newInstance();
-        f.setPropertyValue(AttributeConvention.IDENTIFIER, id);
-        f.setPropertyValue("att1", att1);
-        f.setPropertyValue("att2", att2);
-        return f;
-    }
-
-    /**
-     * Creates a new feature of type 2 with the given identifier and attribute values.
-     * This is a helper method for the constructor only.
-     */
-    private static Feature newFeature2(final FeatureType type, final String id, final int att3, final double att4) {
-        final Feature f = type.newInstance();
-        f.setPropertyValue(AttributeConvention.IDENTIFIER, id);
-        f.setPropertyValue("att3", att3);
-        f.setPropertyValue("att4", att4);
-        return f;
-    }
-
-    /**
-     * Creates a new join feature set of the given type using the {@link #featureSet1} and {@link #featureSet2}.
-     */
-    private FeatureSet create(final JoinFeatureSet.Type type) throws DataStoreException {
-        final FilterFactory factory = new DefaultFilterFactory();
-        final PropertyIsEqualTo condition = factory.equals(factory.property("att2"), factory.property("att3"));
-        final Map<String,Object> properties = new HashMap<>(4);
-        assertNull(properties.put("name", "JoinSet"));
-        assertNull(properties.put("identifierDelimiter", " "));
-        return new JoinFeatureSet(null, featureSet1, "s1", featureSet2, "s2", type, condition, properties);
-    }
-
-    /**
-     * Creates a stream over the features from the given set. If parallelization is enabled,
-     * then this method copies the features in a temporary list using parallelized paths
-     * before to return the stream of that list.
-     */
-    private Stream<Feature> stream(final FeatureSet col) throws DataStoreException {
-        if (parallel) {
-            return col.features(true).collect(Collectors.toList()).stream();
-        } else {
-            return col.features(false);
-        }
-    }
-
-    /**
-     * Returns the identifier of the given feature.
-     */
-    private static String getId(final Feature feature) {
-        return String.valueOf(feature.getPropertyValue(AttributeConvention.IDENTIFIER));
-    }
-
-    /**
-     * Tests inner join feature set.
-     *
-     * @throws DataStoreException if an error occurred while creating the feature set.
-     */
-    @Test
-    public void testInnerJoin() throws DataStoreException {
-        final FeatureSet col = create(JoinFeatureSet.Type.INNER);
-        try (Stream<Feature> stream = stream(col)) {
-            final Iterator<Feature> ite = stream.iterator();
-            int count = 0;
-            while (ite.hasNext()) {
-                count++;
-                final Feature f = ite.next();
-                final String att1;          // Expected value of "att1".
-                final int    join;          // Expected value of "att2" and "att3", on which the join operation is done.
-                final double att4;          // Expected value of "att4".
-                switch (getId(f)) {
-                    case "fid_1_0 fid_2_0":  att1 = "str1"; join = 1; att4 = 10; break;
-                    case "fid_1_1 fid_2_1":  att1 = "str2"; join = 2; att4 = 20; break;
-                    case "fid_1_1 fid_2_2":  att1 = "str2"; join = 2; att4 = 30; break;
-                    case "fid_1_2 fid_2_3":  att1 = "str3"; join = 3; att4 = 40; break;
-                    default: fail("unexpected feature"); continue;
-                }
-                final Feature c1 = (Feature) f.getPropertyValue("s1");
-                final Feature c2 = (Feature) f.getPropertyValue("s2");
-                assertEquals("att1", att1, c1.getProperty("att1").getValue());
-                assertEquals("att2", join, c1.getProperty("att2").getValue());
-                assertEquals("att3", join, c2.getProperty("att3").getValue());
-                assertEquals("att4", att4, c2.getProperty("att4").getValue());
-            }
-            assertEquals("Unexpected amount of features.", 4, count);
-        }
-    }
-
-    /**
-     * Tests outer join feature set.
-     *
-     * @throws DataStoreException if an error occurred while creating the feature set.
-     */
-    @Test
-    public void testOuterLeft() throws DataStoreException {
-        final FeatureSet col = create(JoinFeatureSet.Type.LEFT_OUTER);
-        testOuter(col, 1, 0);
-    }
-
-    /**
-     * Tests outer join feature set.
-     *
-     * @throws DataStoreException if an error occurred while creating the feature set.
-     */
-    @Test
-    public void testOuterRight() throws DataStoreException {
-        final FeatureSet col = create(JoinFeatureSet.Type.RIGHT_OUTER);
-        testOuter(col, 0, 1);
-    }
-
-    /**
-     * Implementation of {@link #testOuterLeft()} and {@link #testOuterRight()}.
-     *
-     * @param  nl  1 if testing outer left,  0 otherwise.
-     * @param  nr  1 if testing outer right, 0 otherwise.
-     */
-    private void testOuter(final FeatureSet col, final int nl, final int nr) throws DataStoreException {
-        try (Stream<Feature> stream = stream(col)) {
-            final Iterator<Feature> ite = stream.iterator();
-            int foundStr1 = 0, foundStr20 = 0, foundStr50 = 0, foundStr60 = 0,
-                foundStr3 = 0, foundStr21 = 0, foundStr51 = 0, foundStr61 = 0, count = 0;
-            while (ite.hasNext()) {
-                final Feature f  = ite.next();
-                final Feature c1 = (Feature) f.getPropertyValue("s1");
-                final Feature c2 = (Feature) f.getPropertyValue("s2");
-                if (c1 != null) {
-                    switch ((String) c1.getProperty("att1").getValue()) {
-                        case "str1": {
-                            assertEquals("att2",  1,  c1.getProperty("att2").getValue());
-                            assertEquals("att3",  1,  c2.getProperty("att3").getValue());
-                            assertEquals("att4", 10d, c2.getProperty("att4").getValue());
-                            foundStr1++;
-                            break;
-                        }
-                        case "str2": {
-                            assertEquals("att2", 2, c1.getProperty("att2").getValue());
-                            assertEquals("att3", 2, c2.getProperty("att3").getValue());
-                            double att4 = (Double)  c2.getProperty("att4").getValue();
-                            if (att4 == 20) foundStr20++;
-                            if (att4 == 30) foundStr21++;
-                            break;
-                        }
-                        case "str3": {
-                            assertEquals("att2",  3,  c1.getProperty("att2").getValue());
-                            assertEquals("att3",  3,  c2.getProperty("att3").getValue());
-                            assertEquals("att4", 40d, c2.getProperty("att4").getValue());
-                            foundStr3++;
-                            break;
-                        }
-                        case "str50": {
-                            assertEquals("att2", 50, c1.getProperty("att2").getValue());
-                            assertNull("right", c2);
-                            foundStr50++;
-                            break;
-                        }
-                        case "str51": {
-                            assertEquals("att2", 51, c1.getProperty("att2").getValue());
-                            assertNull("right", c2);
-                            foundStr51++;
-                            break;
-                        }
-                        default: {
-                            fail("unexpected feature");
-                            break;
-                        }
-                    }
-                } else {
-                    switch ((Integer) c2.getProperty("att3").getValue()) {
-                        case 60: {
-                            assertEquals(c2.getProperty("att4").getValue(), 60d);
-                            foundStr60++;
-                            break;
-                        }
-                        case 61: {
-                            assertEquals(c2.getProperty("att4").getValue(), 61d);
-                            foundStr61++;
-                            break;
-                        }
-                        default: {
-                            fail("unexpected feature");
-                            break;
-                        }
-                    }
-                }
-                count++;
-            }
-            assertEquals("str1",  1,  foundStr1);
-            assertEquals("str2",  1,  foundStr20);
-            assertEquals("str2",  1,  foundStr21);
-            assertEquals("str3",  1,  foundStr3);
-            assertEquals("str50", nl, foundStr50);
-            assertEquals("str51", nl, foundStr51);
-            assertEquals("str60", nr, foundStr60);
-            assertEquals("str61", nr, foundStr61);
-            assertEquals("Unexpected amount of features.", 6, count);
-        }
-    }
-
-    /**
-     * Tests inner join, outer left and outer right using parallelized paths. This will test the
-     * {@code Spliterator.trySplit()} / {@code forEachRemaining(Consumer)} implementations of
-     * {@link JoinFeatureSet}.
-     *
-     * @throws DataStoreException if an error occurred while creating the feature set.
-     */
-    @Test
-    @DependsOnMethod({"testInnerJoin", "testOuterLeft", "testOuterRight"})
-    public void testParallelization() throws DataStoreException {
-        parallel = true;
-        testInnerJoin();
-        testOuterLeft();
-        testOuterRight();
-    }
-}
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
index 416e438..fbde0e3 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
@@ -16,9 +16,10 @@
  */
 package org.apache.sis.internal.storage;
 
-import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.Restriction;
 import org.opengis.metadata.citation.Citation;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -72,13 +73,13 @@
     private static void verifyCopyrightParsing(final String notice) {
         final MetadataBuilder builder = new MetadataBuilder();
         builder.parseLegalNotice(notice);
-        final LegalConstraints constraints = (LegalConstraints) getSingleton(getSingleton(
+        final DefaultLegalConstraints constraints = (DefaultLegalConstraints) getSingleton(getSingleton(
                 builder.build(false).getIdentificationInfo()).getResourceConstraints());
 
         assertEquals("useConstraints", Restriction.COPYRIGHT, getSingleton(constraints.getUseConstraints()));
         final Citation ref = getSingleton(constraints.getReferences());
         assertTitleEquals("reference.title", notice, ref);
-        assertPartyNameEquals("reference.citedResponsibleParty", "John Smith", ref);
+        assertPartyNameEquals("reference.citedResponsibleParty", "John Smith", (DefaultCitation) ref);
         assertEquals("date", date("1992-01-01 00:00:00"), getSingleton(ref.getDates()).getDate());
     }
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
index 160c39d..06f0768 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
@@ -38,10 +38,11 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 
 
 /**
@@ -100,7 +101,7 @@
         try (Store store = open()) {
             metadata = store.getMetadata();
         }
-        final Extent extent = getSingleton(getSingleton(metadata.getIdentificationInfo()).getExtents());
+        final Extent extent = getSingleton(((AbstractIdentification) getSingleton(metadata.getIdentificationInfo())).getExtents());
         final GeographicBoundingBox bbox = (GeographicBoundingBox) getSingleton(extent.getGeographicElements());
         assertEquals("westBoundLongitude", 50.23, bbox.getWestBoundLongitude(), STRICT);
         assertEquals("eastBoundLongitude", 50.31, bbox.getEastBoundLongitude(), STRICT);
@@ -119,7 +120,7 @@
         try (Store store = open()) {
             verifyFeatureType(store.featureType, double[].class, 1);
             assertEquals("foliation", Foliation.TIME, store.foliation);
-            final Iterator<Feature> it = store.features(false).iterator();
+            final Iterator<AbstractFeature> it = store.features(false).iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:11", new double[] {11, 2, 12, 3},        "walking", 1);
             assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        "walking", 2);
             assertPropertyEquals(it.next(), "a", "12:36:11", "12:36:51", new double[] {12, 3, 10, 3},        "walking", 2);
@@ -148,7 +149,7 @@
         try (Store store = new Store(null, new StorageConnector(testData()))) {
             verifyFeatureType(store.featureType, Polyline.class, Integer.MAX_VALUE);
             assertEquals("foliation", Foliation.TIME, store.foliation);
-            final Iterator<Feature> it = store.features(false).iterator();
+            final Iterator<AbstractFeature> it = store.features(false).iterator();
             assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:51", new double[] {11, 2, 12, 3, 10, 3}, singletonList("walking"), Arrays.asList(1, 2));
             assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        singletonList("walking"), singletonList(2));
             assertPropertyEquals(it.next(), "c", "12:33:51", "12:36:51", new double[] {12, 1, 10, 2, 11, 3}, singletonList("vehicle"), singletonList(1));
@@ -159,21 +160,21 @@
     /**
      * Verifies that the feature type is equal to the expected one.
      */
-    private static void verifyFeatureType(final FeatureType type, final Class<?> geometryType, final int maxOccurs) {
-        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref",       String.class,   1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime",     Instant.class,  1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime",       Instant.class,  1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory",    geometryType,   1, 1);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "state",         String.class,   0, maxOccurs);
-        assertPropertyTypeEquals((AttributeType<?>) it.next(), "\"type\" code", Integer.class,  0, maxOccurs);
+    private static void verifyFeatureType(final DefaultFeatureType type, final Class<?> geometryType, final int maxOccurs) {
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "mfidref",       String.class,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "startTime",     Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "endTime",       Instant.class,  1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "trajectory",    geometryType,   1, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "state",         String.class,   0, maxOccurs);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "\"type\" code", Integer.class,  0, maxOccurs);
         assertFalse(it.hasNext());
     }
 
     /**
      * Asserts that the given property type has the given information.
      */
-    private static void assertPropertyTypeEquals(final AttributeType<?> p,
+    private static void assertPropertyTypeEquals(final DefaultAttributeType<?> p,
             final String name, final Class<?> valueClass, final int minOccurs, final int maxOccurs)
     {
         assertEquals("name",       name,       p.getName().toString());
@@ -185,7 +186,7 @@
     /**
      * Asserts that the property of the given name in the given feature has expected information.
      */
-    private void assertPropertyEquals(final Feature f, final String mfidref,
+    private void assertPropertyEquals(final AbstractFeature f, final String mfidref,
             final String startTime, final String endTime, final double[] trajectory,
             final Object state, final Object typeCode)
     {
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/SimpleQueryTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/SimpleQueryTest.java
index f29bb82..8da441f 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/SimpleQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/SimpleQueryTest.java
@@ -23,21 +23,14 @@
 import org.apache.sis.internal.storage.MemoryFeatureSet;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
-import org.opengis.feature.AttributeType;
-import org.opengis.filter.Filter;
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.sort.SortOrder;
-import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -52,7 +45,7 @@
     /**
      * An arbitrary amount of features, all of the same type.
      */
-    private final Feature[] features;
+    private final AbstractFeature[] features;
 
     /**
      * The {@link #features} array wrapped in a in-memory feature set.
@@ -72,8 +65,8 @@
         ftb.setName("Test");
         ftb.addAttribute(Integer.class).setName("value1");
         ftb.addAttribute(Integer.class).setName("value2");
-        final FeatureType type = ftb.build();
-        features = new Feature[] {
+        final DefaultFeatureType type = ftb.build();
+        features = new AbstractFeature[] {
             feature(type, 3, 1),
             feature(type, 2, 2),
             feature(type, 2, 1),
@@ -84,8 +77,8 @@
         query      = new SimpleQuery();
     }
 
-    private static Feature feature(final FeatureType type, final int value1, final int value2) {
-        final Feature f = type.newInstance();
+    private static AbstractFeature feature(final DefaultFeatureType type, final int value1, final int value2) {
+        final AbstractFeature f = type.newInstance();
         f.setPropertyValue("value1", value1);
         f.setPropertyValue("value2", value2);
         return f;
@@ -99,11 +92,11 @@
      */
     private void verifyQueryResult(final int... indices) throws DataStoreException {
         final FeatureSet fs = query.execute(featureSet);
-        final List<Feature> result = fs.features(false).collect(Collectors.toList());
+        final List<AbstractFeature> result = fs.features(false).collect(Collectors.toList());
         assertEquals("size", indices.length, result.size());
         for (int i=0; i<indices.length; i++) {
-            final Feature expected = features[indices[i]];
-            final Feature actual   = result.get(i);
+            final AbstractFeature expected = features[indices[i]];
+            final AbstractFeature actual   = result.get(i);
             if (!expected.equals(actual)) {
                 fail(String.format("Unexpected feature at index %d%n"
                                  + "Expected:%n%s%n"
@@ -133,65 +126,4 @@
         query.setOffset(2);
         verifyQueryResult(2, 3, 4);
     }
-
-    /**
-     * Verifies the effect of {@link SimpleQuery#setSortBy(SortBy...)}.
-     *
-     * @throws DataStoreException if an error occurred while executing the query.
-     */
-    @Test
-    public void testSortBy() throws DataStoreException {
-        final DefaultFilterFactory factory = new DefaultFilterFactory();
-        query.setSortBy(factory.sort("value1", SortOrder.ASCENDING),
-                        factory.sort("value2", SortOrder.DESCENDING));
-        verifyQueryResult(3, 1, 2, 0, 4);
-    }
-
-    /**
-     * Verifies the effect of {@link SimpleQuery#setFilter(Filter)}.
-     *
-     * @throws DataStoreException if an error occurred while executing the query.
-     */
-    @Test
-    public void testFilter() throws DataStoreException {
-        final DefaultFilterFactory factory = new DefaultFilterFactory();
-        query.setFilter(factory.equal(factory.property("value1"), factory.literal(2), true, MatchAction.ALL));
-        verifyQueryResult(1, 2);
-    }
-
-    /**
-     * Verifies the effect of {@link SimpleQuery#setColumns(SimpleQuery.Column...)}.
-     *
-     * @throws DataStoreException if an error occurred while executing the query.
-     */
-    @Test
-    public void testColumns() throws DataStoreException {
-        final DefaultFilterFactory factory = new DefaultFilterFactory();
-        query.setColumns(new SimpleQuery.Column(factory.property("value1"),   (String) null),
-                         new SimpleQuery.Column(factory.property("value1"),   "renamed1"),
-                         new SimpleQuery.Column(factory.literal("a literal"), "computed"));
-        query.setLimit(1);
-
-        final FeatureSet fs = query.execute(featureSet);
-        final Feature result = TestUtilities.getSingleton(fs.features(false).collect(Collectors.toList()));
-
-        // Check result type.
-        final FeatureType resultType = result.getType();
-        assertEquals("Test", resultType.getName().toString());
-        assertEquals(3, resultType.getProperties(true).size());
-        final PropertyType pt1 = resultType.getProperty("value1");
-        final PropertyType pt2 = resultType.getProperty("renamed1");
-        final PropertyType pt3 = resultType.getProperty("computed");
-        assertTrue(pt1 instanceof AttributeType);
-        assertTrue(pt2 instanceof AttributeType);
-        assertTrue(pt3 instanceof AttributeType);
-        assertEquals(Integer.class, ((AttributeType) pt1).getValueClass());
-        assertEquals(Integer.class, ((AttributeType) pt2).getValueClass());
-        assertEquals(String.class,  ((AttributeType) pt3).getValueClass());
-
-        // Check feature.
-        assertEquals(3, result.getPropertyValue("value1"));
-        assertEquals(3, result.getPropertyValue("renamed1"));
-        assertEquals("a literal", result.getPropertyValue("computed"));
-    }
 }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java
index 44ee1c7..ddc13a4 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/wkt/StoreTest.java
@@ -29,7 +29,7 @@
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.opengis.test.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
index bc72797..ab742f1 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/xml/StoreTest.java
@@ -18,9 +18,9 @@
 
 import java.util.Locale;
 import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.citation.*;
+import org.opengis.metadata.identification.CharacterSet;
 import org.apache.sis.xml.Namespaces;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
@@ -96,16 +96,14 @@
             metadata = store.getMetadata();
             assertSame("Expected cached value.", metadata, store.getMetadata());
         }
-        final Responsibility resp     = getSingleton(metadata.getContacts());
-        final Party          party    = getSingleton(resp.getParties());
-        final Contact        contact  = getSingleton(party.getContactInfo());
-        final OnlineResource resource = getSingleton(contact.getOnlineResources());
+        final ResponsibleParty resp     = getSingleton(metadata.getContacts());
+        final Contact          contact  = resp.getContactInfo();
+        final OnlineResource   resource = contact.getOnlineResource();
 
-        assertInstanceOf("party", Organisation.class, party);
-        assertEquals(Locale.ENGLISH,              getSingleton(metadata.getLocalesAndCharsets().keySet()));
-        assertEquals(StandardCharsets.UTF_8,      getSingleton(metadata.getLocalesAndCharsets().values()));
+        assertEquals(Locale.ENGLISH,              metadata.getLanguage());
+        assertEquals(CharacterSet.UTF_8,          metadata.getCharacterSet());
         assertEquals(Role.PRINCIPAL_INVESTIGATOR, resp.getRole());
-        assertEquals("Apache SIS",                String.valueOf(party.getName()));
+        assertEquals("Apache SIS",                String.valueOf(resp.getOrganisationName()));
         assertEquals("http://sis.apache.org",     String.valueOf(resource.getLinkage()));
         assertEquals(OnLineFunction.INFORMATION,  resource.getFunction());
     }
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
index afa697e..b69e4a0 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
@@ -53,8 +53,6 @@
     org.apache.sis.internal.storage.csv.StoreProviderTest.class,
     org.apache.sis.internal.storage.csv.StoreTest.class,
     org.apache.sis.internal.storage.folder.StoreTest.class,
-    org.apache.sis.internal.storage.JoinFeatureSetTest.class,
-    org.apache.sis.internal.storage.ConcatenatedFeatureSetTest.class,
     org.apache.sis.storage.DataStoresTest.class
 })
 public final strictfp class StorageTestSuite extends TestSuite {
diff --git a/storage/sis-xmlstore/pom.xml b/storage/sis-xmlstore/pom.xml
index 1ebc6ed..532bbfb 100644
--- a/storage/sis-xmlstore/pom.xml
+++ b/storage/sis-xmlstore/pom.xml
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache.sis</groupId>
     <artifactId>storage</artifactId>
-    <version>1.x-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
   </parent>
 
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java
index 5a77c2c..4cdf96d 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Copyright.java
@@ -25,22 +25,26 @@
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
+import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.CitationDate;
+import org.opengis.metadata.citation.Contact;
 import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.citation.OnlineResource;
 import org.opengis.metadata.citation.PresentationForm;
 import org.opengis.metadata.citation.Role;
+import org.opengis.metadata.citation.Series;
 import org.opengis.metadata.constraint.LegalConstraints;
 import org.opengis.metadata.constraint.Restriction;
-import org.opengis.metadata.identification.BrowseGraphic;
 import org.opengis.util.InternationalString;
-import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.ResponsibleParty;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.constraint.DefaultConstraints;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 
 
 /**
@@ -63,15 +67,11 @@
  * @since   0.8
  * @module
  */
-public final class Copyright implements LegalConstraints, Responsibility, Party, Citation, CitationDate {
+public final class Copyright implements LegalConstraints, ResponsibleParty, Citation, CitationDate {
     /**
      * The copyright holder.
      * This field is mandatory in principle, but {@code Copyright} implementation is robust to null value.
-     * This field is mapped to the {@linkplain #getName() responsible party name} in ISO 19115 metadata.
-     *
-     * @see #getResponsibleParties()
-     * @see #getParties()
-     * @see #getName()
+     * This field is mapped to the {@code getName() responsible party name} in ISO 19115 metadata.
      */
     @XmlAttribute(name = Attributes.AUTHOR, required = true)
     public String author;
@@ -87,9 +87,7 @@
 
     /**
      * Link to an external file containing the license text, or {@code null} if none.
-     * This field is mapped to the {@linkplain #getOnlineResources() online resources} in ISO 19115 metadata.
-     *
-     * @see #getOnlineResources()
+     * This field is mapped to the online resources in ISO 19115 metadata.
      */
     @XmlElement(name = Tags.LICENSE)
     public URI license;
@@ -105,13 +103,16 @@
      * Copies properties from the given ISO 19115 metadata.
      */
     private Copyright(final LegalConstraints c, final Locale locale) {
-resp:   for (final Responsibility r : c.getResponsibleParties()) {
-            for (final Party p : r.getParties()) {
+        if (!(c instanceof DefaultConstraints)) {
+            return;
+        }
+resp:   for (final DefaultResponsibility r : ((DefaultConstraints) c).getResponsibleParties()) {
+            for (final AbstractParty p : r.getParties()) {
                 author = Types.toString(p.getName(), locale);
                 if (author != null) break resp;
             }
         }
-        for (final Citation ci : c.getReferences()) {
+        for (final Citation ci : ((DefaultConstraints) c).getReferences()) {
             for (final CitationDate d : ci.getDates()) {
                 final Date date = d.getDate();
                 if (date != null) {
@@ -119,7 +120,8 @@
                     break;
                 }
             }
-            for (final OnlineResource r : ci.getOnlineResources()) {
+            if (!(ci instanceof DefaultCitation)) continue;
+            for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
                 license = r.getLinkage();
                 if (license != null) break;
             }
@@ -152,13 +154,11 @@
      * ISO 19115 metadata property determined by the {@link #license} field.
      *
      * @return restrictions or limitations or warnings on using the data.
-     *
-     * @see #getReferences()
      */
     @Override
     public Collection<Restriction> getUseConstraints() {
         if (license != null) {
-            return Arrays.asList(Restriction.COPYRIGHT, Restriction.LICENCE);
+            return Arrays.asList(Restriction.COPYRIGHT, Restriction.valueOf("LICENCE"));
         } else {
             return Collections.singleton(Restriction.COPYRIGHT);
         }
@@ -167,44 +167,21 @@
     /**
      * ISO 19115 metadata property not specified by GPX.
      *
-     * @return graphics or symbols indicating the constraint.
+     * @return other restrictions and legal prerequisites for accessing and using the resource.
      */
     @Override
-    public Collection<BrowseGraphic> getGraphics() {
+    public Collection<InternationalString> getOtherConstraints() {
         return Collections.emptySet();
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #year} and {@link #license} fields.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getDate()} and {@link #getOnlineResources()} methods.
+     * ISO 19115 metadata property not specified by GPX.
      *
-     * @return citations for the limitation of constraint.
-     *
-     * @see #getTitle()
-     * @see #getDates()
-     * @see #getPresentationForms()
-     * @see #getOnlineResources()
+     * @return limitation affecting the fitness for use of the resource.
      */
     @Override
-    public Collection<? extends Citation> getReferences() {
-        return thisOrEmpty(year != null || license != null);
-    }
-
-    /**
-     * ISO 19115 metadata property determined by the {@link #author} field.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getName()} method.
-     *
-     * @return parties responsible for the resource constraints.
-     *
-     * @see #getRole()
-     * @see #getParties()
-     * @see #getCitedResponsibleParties()
-     */
-    @Override
-    public Collection<? extends Responsibility> getResponsibleParties() {
-        return thisOrEmpty(author != null);
+    public Collection<InternationalString> getUseLimitations() {
+        return Collections.emptySet();
     }
 
 
@@ -215,7 +192,7 @@
 
     /**
      * ISO 19115 metadata property fixed to {@link Role#OWNER}.
-     * This is part of the properties returned by {@link #getResponsibleParties()}.
+     * This is part of the properties returned by {@code getResponsibleParties()}.
      *
      * @return function performed by the responsible party.
      */
@@ -225,29 +202,46 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #author} field.
-     * This is part of the properties returned by {@link #getResponsibleParties()}.
-     * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
-     * to the {@link #getName()} method.
+     * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #author},
+     * but we have no way to know if the author is an individual or an organization.
      *
-     * @return information about the parties.
-     *
-     * @see #getName()
+     * @return name of the organization, or {@code null} if none.
      */
     @Override
-    public Collection<? extends Party> getParties() {
-        return thisOrEmpty(author != null);
+    public InternationalString getOrganisationName() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return position of the individual in the organization, or {@code null} if none.
+     */
+    @Override
+    public InternationalString getPositionName() {
+        return null;
     }
 
     /**
      * ISO 19115 metadata property determined by the {@link #author} field.
-     * This is part of the properties returned by {@link #getParties()}.
+     * This is part of the properties returned by {@code getParties()}.
      *
      * @return name of the party, or {@code null} if none.
      */
     @Override
-    public InternationalString getName() {
-        return (author != null) ? new SimpleInternationalString(author) : null;
+    public String getIndividualName() {
+        return author;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getParties()}.
+     *
+     * @return contact information for the party.
+     */
+    @Override
+    public Contact getContactInfo() {
+        return null;
     }
 
 
@@ -258,7 +252,7 @@
 
     /**
      * ISO 19115 metadata property not specified by GPX.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      * It would be the license title if that information was provided.
      *
      * @return the license name.
@@ -269,8 +263,19 @@
     }
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return other names for the resource.
+     */
+    @Override
+    public Collection<InternationalString> getAlternateTitles() {
+        return Collections.emptySet();
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #year} field.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      * Invoking this method is one of the steps in the path from the {@code LegalConstraints} root
      * to the {@link #getDate()} method.
      *
@@ -299,19 +304,64 @@
     }
 
     /**
-     * ISO 19115 metadata property fixed to {@link DateType#IN_FORCE}.
+     * ISO 19115 metadata property fixed to {@code DateType.IN_FORCE}.
      * This is part of the properties returned by {@link #getDates()}.
      *
      * @return event used for reference date.
      */
     @Override
     public DateType getDateType() {
-        return DateType.IN_FORCE;
+        return DateType.valueOf("IN_FORCE");
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the license version, or {@code null} if none.
+     */
+    @Override
+    public InternationalString getEdition() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the license edition date, or {@code null} if none.
+     */
+    @Override
+    public Date getEditionDate() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the identifiers of the license.
+     */
+    @Override
+    public Collection<Identifier> getIdentifiers() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     * It would be the license author if that information was provided.
+     *
+     * @return the information for individuals or organisations that are responsible for the license.
+     */
+    @Override
+    public Collection<ResponsibleParty> getCitedResponsibleParties() {
+        return Collections.emptySet();
     }
 
     /**
      * ISO 19115 metadata property fixed to {@link PresentationForm#DOCUMENT_DIGITAL}.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * This is part of the properties returned by {@code getReferences()}.
      *
      * @return the presentation mode of the license.
      */
@@ -321,14 +371,59 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #license} field.
-     * This is part of the properties returned by {@link #getReferences()}.
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
      *
-     * @return online references to the cited resource.
+     * @return the series or aggregate dataset of which the dataset is a part.
      */
     @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (license != null) ? Collections.singleton(new Link(license)) : Collections.emptySet();
+    public Series getSeries() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return other details.
+     */
+    @Override
+    public InternationalString getOtherCitationDetails() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the common title.
+     */
+    @Override
+    @Deprecated
+    public InternationalString getCollectiveTitle() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the International Standard Book Number.
+     */
+    @Override
+    public String getISBN() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     * This is part of the properties returned by {@code getReferences()}.
+     *
+     * @return the International Standard Serial Number.
+     */
+    @Override
+    public String getISSN() {
+        return null;
     }
 
     /**
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
index 83d044a..166da7c 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java
@@ -31,10 +31,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.Property;
-import org.opengis.feature.Attribute;
-import org.opengis.feature.AttributeType;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -82,7 +79,7 @@
     /**
      * The expected result type to be returned by {@link #getResult()}.
      */
-    private final AttributeType<?> result;
+    private final DefaultAttributeType<?> result;
 
     /**
      * Creates a new operation which will look for geometries in the given feature association.
@@ -91,7 +88,7 @@
      * @param  association     name of the property to follow in order to get the geometries to add to a polyline.
      * @param  result          the expected result type to be returned by {@link #getResult()}.
      */
-    GroupAsPolylineOperation(final Map<String,?> identification, final String association, final AttributeType<?> result) {
+    GroupAsPolylineOperation(final Map<String,?> identification, final String association, final DefaultAttributeType<?> result) {
         super(identification);
         this.association = association;
         this.result = result;
@@ -103,7 +100,7 @@
      *
      * @param  geometries  accessor to the geometry implementation in use (Java2D, ESRI or JTS).
      */
-    static <G> AttributeType<? extends G> getResult(final Geometries<G> geometries) {
+    static <G> DefaultAttributeType<? extends G> getResult(final Geometries<G> geometries) {
         return new DefaultAttributeType<>(Collections.singletonMap(NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY),
                 geometries.polylineClass, 1, 1, null);
     }
@@ -120,7 +117,7 @@
      * Returns the expected result type.
      */
     @Override
-    public final AttributeType<?> getResult() {
+    public final DefaultAttributeType<?> getResult() {
         return result;
     }
 
@@ -131,7 +128,7 @@
      */
     @Override
     @SuppressWarnings({"rawtypes", "unchecked"})
-    public final Property apply(Feature feature, ParameterValueGroup parameters) {
+    public final Object apply(AbstractFeature feature, ParameterValueGroup parameters) {
         return new Result(feature, association, result);
     }
 
@@ -139,7 +136,7 @@
     /**
      * The attribute resulting from execution if the {@link GroupAsPolylineOperation}.
      * The value is computed when first requested, then cached for this {@code Result} instance only.
-     * Note that the cache is not used when {@link #apply(Feature, ParameterValueGroup)} is invoked,
+     * Note that the cache is not used when {@code apply(Feature, ParameterValueGroup)} is invoked,
      * causing a new value to be computed again. The intent is to behave as if the operation has been
      * executed at {@code apply(…)} invocation time, even if we deferred the actual execution.
      *
@@ -154,7 +151,7 @@
         /**
          * The feature on which to execute the operation.
          */
-        private final Feature feature;
+        private final AbstractFeature feature;
 
         /**
          * Name of the property to follow in order to get the geometries to add to a polyline.
@@ -171,7 +168,7 @@
          * Creates a new result for an execution on the given feature.
          * The actual computation is deferred to the first call of {@link #getValue()}.
          */
-        Result(final Feature feature, final String association, final AttributeType<G> result) {
+        Result(final AbstractFeature feature, final String association, final DefaultAttributeType<G> result) {
             super(result);
             this.feature = feature;
             this.association = association;
@@ -193,7 +190,7 @@
                     }
 
                     @Override public Object next() {
-                        return ((Feature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
+                        return ((AbstractFeature) it.next()).getPropertyValue(AttributeConvention.GEOMETRY);
                     }
                 });
                 geometry = getType().getValueClass().cast(geom);
@@ -206,7 +203,7 @@
          */
         @Override
         public void setValue(G value) {
-            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class));
+            throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, AbstractAttribute.class));
         }
     }
 }
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java
index efe2fef..27c0108 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Link.java
@@ -158,6 +158,26 @@
     }
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return connection protocol to be used.
+     */
+    @Override
+    public String getProtocol() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return application profile that can be used with the online resource.
+     */
+    @Override
+    public String getApplicationProfile() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #text} field.
      *
      * @return name of the online resource.
@@ -188,16 +208,6 @@
     }
 
     /**
-     * ISO 19115 metadata property not specified by GPX.
-     *
-     * @return request used to access the resource, or {@code null}.
-     */
-    @Override
-    public String getProtocolRequest() {
-        return null;
-    }
-
-    /**
      * Compares this {@code Link} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Link}.
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java
index f0b5e01..4a73c79 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Metadata.java
@@ -45,7 +45,6 @@
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.internal.simple.SimpleMetadata;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
-import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.citation.DefaultCitationDate;
 import org.apache.sis.metadata.iso.identification.DefaultKeywords;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -54,8 +53,9 @@
 import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.identification.AbstractIdentification;
 import org.opengis.metadata.citation.ResponsibleParty;
-import org.opengis.metadata.citation.Responsibility;
 
 
 /**
@@ -137,8 +137,6 @@
 
     /**
      * URLs associated with the location described in the file, or {@code null} if none.
-     *
-     * @see #getOnlineResources()
      */
     @XmlElement(name = Tags.LINK)
     public List<Link> links;
@@ -211,8 +209,10 @@
                         if (time != null) break;
                     }
                 }
-                for (final OnlineResource r : ci.getOnlineResources()) {
-                    links = addIfNonNull(links, Link.castOrCopy(r, locale));
+                if (ci instanceof DefaultCitation) {
+                    for (final OnlineResource r : ((DefaultCitation) ci).getOnlineResources()) {
+                        links = addIfNonNull(links, Link.castOrCopy(r, locale));
+                    }
                 }
             }
             if (description == null) {
@@ -222,7 +222,7 @@
              * identificationInfo.pointOfContact.party.name   →   creator        if role is ORIGINATOR
              * identificationInfo.pointOfContact.party.name   →   author.name    if role is AUTHOR
              */
-            for (final Responsibility r : id.getPointOfContacts()) {
+            for (final ResponsibleParty r : id.getPointOfContacts()) {
                 final Person p = Person.castOrCopy(r, locale);
                 if (p != null) {
                     if (p.isCreator) {
@@ -258,8 +258,8 @@
             /*
              * identificationInfo.extent.geographicElement   →   bounds
              */
-            if (bounds == null) {
-                for (final Extent e : id.getExtents()) {
+            if (bounds == null && id instanceof AbstractIdentification) {
+                for (final Extent e : ((AbstractIdentification) id).getExtents()) {
                     bounds = Bounds.castOrCopy(Extents.getGeographicBoundingBox(e));
                     if (bounds != null) break;
                 }
@@ -379,17 +379,6 @@
     }
 
     /**
-     * ISO 19115 metadata property determined by the {@link #links} field.
-     * This is part of the information returned by {@link #getCitation()}.
-     *
-     * @return online references to the cited resource.
-     */
-    @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (links != null) ? Collections.unmodifiableList(links) : Collections.emptyList();
-    }
-
-    /**
      * Names of features types available for the GPX format, or an empty list if none.
      * This property is not part of metadata described in GPX file; it is rather a hard-coded value shared by all
      * GPX files. Users could however filter the list of features, for example with only routes and no tracks.
@@ -424,16 +413,6 @@
     }
 
     /**
-     * Citation(s) for the standard(s) to which the metadata conform.
-     *
-     * @return ISO 19115-1 citation.
-     */
-    @Override
-    public Collection<Citation> getMetadataStandards() {
-        return Collections.singleton(Citations.ISO_19115.get(0));
-    }
-
-    /**
      * Compares this {@code Metadata} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Metadata}.
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java
index a2f3357..8f16893 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Person.java
@@ -16,10 +16,8 @@
  */
 package org.apache.sis.internal.storage.gpx;
 
-import java.util.AbstractSet;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.Locale;
 import java.util.Objects;
 import javax.xml.bind.annotation.XmlElement;
@@ -30,12 +28,8 @@
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.Telephone;
 import org.opengis.util.InternationalString;
-import org.apache.sis.util.iso.SimpleInternationalString;
-import org.apache.sis.util.iso.Types;
 
 // Branch-dependent imports
-import org.opengis.metadata.citation.Party;
-import org.opengis.metadata.citation.Responsibility;
 import org.opengis.metadata.citation.ResponsibleParty;
 
 
@@ -52,8 +46,8 @@
  * Those properties can be read or modified directly. All methods defined in this class are bridges to
  * the ISO 19115 metadata model and can be ignored if the user only wants to manipulate the GPX model.
  *
- * <p>Note that {@link Party} is an abstract type in ISO 19115 model. We are supposed to implement a subtype
- * ({@link org.opengis.metadata.citation.Individual} or {@link org.opengis.metadata.citation.Organisation}).
+ * <p>Note that {@code Party} is an abstract type in ISO 19115 model. We are supposed to implement a subtype
+ * ({@code Individual} or {@code Organisation}).
  * However the GPX metadata does not specifies whether the "person" is actually an individual or an organization.
  * In this situation of doubt, we do not select a subtype for avoiding to provide a wrong information.</p>
  *
@@ -63,11 +57,9 @@
  * @since   0.8
  * @module
  */
-public final class Person implements ResponsibleParty, Party, Contact, Address {
+public final class Person implements ResponsibleParty, Contact, Address {
     /**
      * Name of person or organization.
-     *
-     * @see #getName()
      */
     @XmlElement(name = Tags.NAME)
     public String name;
@@ -88,8 +80,6 @@
 
     /**
      * Link to Web site or other external information about person.
-     *
-     * @see #getOnlineResources()
      */
     @XmlElement(name = Tags.LINK)
     public Link link;
@@ -116,21 +106,19 @@
      * @param  locale  the locale to use for localized strings.
      * @return the GPX metadata, or {@code null}.
      */
-    public static Person castOrCopy(final Responsibility r, final Locale locale) {
+    public static Person castOrCopy(final ResponsibleParty r, final Locale locale) {
         if (r == null || r instanceof Person) {
             return (Person) r;
         }
         final Role role = r.getRole();
         final boolean isCreator = Role.ORIGINATOR.equals(role);
         if (isCreator || Role.AUTHOR.equals(role)) {
-            for (final Party p : r.getParties()) {
-                final String name = Types.toString(p.getName(), locale);
-                if (name != null) {
-                    final Person pr = new Person();
-                    pr.name = name;
-                    pr.isCreator = isCreator;
-                    return pr;
-                }
+            final String name = r.getIndividualName();
+            if (name != null) {
+                final Person pr = new Person();
+                pr.name = name;
+                pr.isCreator = isCreator;
+                return pr;
             }
         }
         return null;
@@ -146,21 +134,6 @@
         return isCreator ? Role.ORIGINATOR : Role.AUTHOR;
     }
 
-    /**
-     * ISO 19115 metadata property determined by the {@link #name}, {@link #email} and {@link #link} fields.
-     * Invoking this method is one of the steps in the path from the {@code Responsibility} root to the
-     * {@link #getName()}, {@link #getElectronicMailAddresses()} and {@link #getOnlineResources()} methods.
-     *
-     * @return information about the parties.
-     *
-     * @see #getName()
-     * @see #getContactInfo()
-     */
-    @Override
-    public Collection<? extends Party> getParties() {
-        return thisOrEmpty(name != null || email != null || link != null);
-    }
-
 
     /* ---------------------------------------------------------------------------------
      * Implementation of the Party object returned by Responsibility.getParties().
@@ -168,16 +141,6 @@
      * --------------------------------------------------------------------------------- */
 
     /**
-     * ISO 19115 metadata property determined by the {@link #name} field.
-     *
-     * @return name of the party, or {@code null} if none.
-     */
-    @Override
-    public InternationalString getName() {
-        return (name != null) ? new SimpleInternationalString(name) : null;
-    }
-
-    /**
      * ISO 19115 metadata property not specified by GPX. Actually could be the {@link #name},
      * but we have no way to know if the author is an individual or an organization.
      *
@@ -211,36 +174,13 @@
     /**
      * ISO 19115 metadata property determined by the {@link #email} and {@link #link} fields.
      * Invoking this method is one of the steps in the path from the {@code Responsibility} root
-     * to the {@link #getElectronicMailAddresses()} and {@link #getOnlineResources()} methods.
+     * to the {@link #getElectronicMailAddresses()} and {@link #getOnlineResource()} methods.
      *
      * @return contact information for the party.
-     *
-     * @see #getAddresses()
-     * @see #getOnlineResources()
      */
     @Override
-    public Proxy getContactInfo() {        // Both Contact singleton and Collection<Contact>.
-        return new Proxy();
-    }
-
-    private final class Proxy extends AbstractSet<Contact> implements Contact {
-        @Override public int size() {
-            return (email != null || link != null) ? 1 : 0;
-        }
-
-        @Override public Iterator<Contact> iterator() {
-            return Collections.<Contact>singleton(Person.this).iterator();
-        }
-
-        @Override public Collection<? extends Telephone> getPhones()            {return Person.this.getPhones();}
-        @Override public Telephone                     getPhone()               {return Person.this.getPhone();}
-        @Override public Collection<? extends Address> getAddresses()           {return Person.this.getAddresses();}
-        @Override public Address                       getAddress()             {return Person.this.getAddress();}
-        @Override public Collection<OnlineResource>    getOnlineResources()     {return Person.this.getOnlineResources();}
-        @Override public OnlineResource                getOnlineResource()      {return Person.this.getOnlineResource();}
-        @Override public InternationalString           getHoursOfService()      {return Person.this.getHoursOfService();}
-        @Override public InternationalString           getContactInstructions() {return Person.this.getContactInstructions();}
-        @Override public InternationalString           getContactType()         {return Person.this.getContactType();}
+    public Contact getContactInfo() {
+        return (email != null || link != null) ? this : null;
     }
 
 
@@ -250,6 +190,16 @@
      * --------------------------------------------------------------------------------- */
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return telephone numbers at which the organization or individual may be contacted.
+     */
+    @Override
+    public Telephone getPhone() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #email} field.
      * Invoking this method is one of the steps in the path from the {@code Responsibility} root
      * to the {@link #getElectronicMailAddresses()} method.
@@ -259,8 +209,8 @@
      * @see #getElectronicMailAddresses()
      */
     @Override
-    public Collection<? extends Address> getAddresses() {
-        return thisOrEmpty(email != null);
+    public Address getAddress() {
+        return (email != null) ? this : null;
     }
 
     /**
@@ -269,8 +219,28 @@
      * @return on-line information that can be used to contact the individual or organization.
      */
     @Override
-    public Collection<OnlineResource> getOnlineResources() {
-        return (link != null) ? Collections.singleton(link) : Collections.emptySet();
+    public OnlineResource getOnlineResource() {
+        return link;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return time period when individuals can contact the organization or individual.
+     */
+    @Override
+    public InternationalString getHoursOfService() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return supplemental instructions on how or when to contact the individual or organization.
+     */
+    @Override
+    public InternationalString getContactInstructions() {
+        return null;
     }
 
 
@@ -280,6 +250,56 @@
      * --------------------------------------------------------------------------------- */
 
     /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return address line for the location.
+     */
+    @Override
+    public Collection<String> getDeliveryPoints() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return the city of the location.
+     */
+    @Override
+    public InternationalString getCity() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return state, province of the location.
+     */
+    @Override
+    public InternationalString getAdministrativeArea() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return ZIP or other postal code, or {@code null}.
+     */
+    @Override
+    public String getPostalCode() {
+        return null;
+    }
+
+    /**
+     * ISO 19115 metadata property not specified by GPX.
+     *
+     * @return country of the physical address, or {@code null}.
+     */
+    @Override
+    public InternationalString getCountry() {
+        return null;
+    }
+
+    /**
      * ISO 19115 metadata property determined by the {@link #email} field.
      *
      * @return address of the electronic mailbox of the responsible organization or individual.
@@ -290,14 +310,6 @@
     }
 
     /**
-     * Returns this object as a singleton if the given condition is {@code true},
-     * or an empty set if the given condition is {@code false}.
-     */
-    private Collection<Person> thisOrEmpty(final boolean condition) {
-        return condition ? Collections.singleton(this) : Collections.emptySet();
-    }
-
-    /**
      * Compares this {@code Person} with the given object for equality.
      *
      * @param  obj  the object to compare with this {@code Person}.
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
index a1d95be..4caa8df 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java
@@ -37,7 +37,7 @@
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -319,7 +319,7 @@
      *         or various {@link RuntimeException}.
      */
     @Override
-    public boolean tryAdvance(final Consumer<? super Feature> action) throws BackingStoreException {
+    public boolean tryAdvance(final Consumer<? super AbstractFeature> action) throws BackingStoreException {
         try {
             return parse(action, false);
         } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
@@ -336,7 +336,7 @@
      *         or various {@link RuntimeException}.
      */
     @Override
-    public void forEachRemaining(final Consumer<? super Feature> action) throws BackingStoreException {
+    public void forEachRemaining(final Consumer<? super AbstractFeature> action) throws BackingStoreException {
         try {
             parse(action, true);
         } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
@@ -360,7 +360,7 @@
      * @throws EOFException if the file seems to be truncated.
      */
     @SuppressWarnings("fallthrough")
-    private boolean parse(final Consumer<? super Feature> action, final boolean all) throws Exception {
+    private boolean parse(final Consumer<? super AbstractFeature> action, final boolean all) throws Exception {
         for (int type = reader.getEventType(); ; type = reader.next()) {
             /*
              * We do not need to check 'reader.hasNext()' in above loop
@@ -368,7 +368,7 @@
              */
             switch (type) {
                 case START_ELEMENT: {
-                    final Feature f;
+                    final AbstractFeature f;
                     switch (isGPX() ? reader.getLocalName() : "") {
                         case Tags.WAY_POINT: f = parseWayPoint(++wayPointId); break;
                         case Tags.ROUTES:    f = parseRoute   (++routeId);    break;
@@ -394,7 +394,7 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseWayPoint(final int index) throws Exception {
+    private AbstractFeature parseWayPoint(final int index) throws Exception {
         assert reader.isStartElement();
         /*
          * Way points might be located in different elements: <wpt>, <rtept> and <trkpt>.
@@ -409,7 +409,7 @@
                     (lat == null) ? Attributes.LATITUDE : Attributes.LONGITUDE, tagName));
         }
         final Types types = ((Store) owner).types;
-        final Feature feature = types.wayPoint.newInstance();
+        final AbstractFeature feature = types.wayPoint.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
         feature.setPropertyValue(AttributeConvention.GEOMETRY, types.geometries.createPoint(parseDouble(lon), parseDouble(lat)));
         List<Link> links = null;
@@ -472,11 +472,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseRoute(final int index) throws Exception {
+    private AbstractFeature parseRoute(final int index) throws Exception {
         assert reader.isStartElement() && Tags.ROUTES.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.route.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.route.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> wayPoints = null;
+        List<AbstractFeature> wayPoints = null;
         List<Link> links = null;
         while (true) {
             /*
@@ -528,11 +528,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseTrackSegment(final int index) throws Exception {
+    private AbstractFeature parseTrackSegment(final int index) throws Exception {
         assert reader.isStartElement() && Tags.TRACK_SEGMENTS.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.trackSegment.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.trackSegment.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> wayPoints = null;
+        List<AbstractFeature> wayPoints = null;
         while (true) {
             /*
              * We do not need to check 'reader.hasNext()' in above loop
@@ -571,11 +571,11 @@
      *
      * @throws Exception see the list of exceptions documented in {@link #parse(Consumer, boolean)}.
      */
-    private Feature parseTrack(final int index) throws Exception {
+    private AbstractFeature parseTrack(final int index) throws Exception {
         assert reader.isStartElement() && Tags.TRACKS.equals(reader.getLocalName());
-        final Feature feature = ((Store) owner).types.track.newInstance();
+        final AbstractFeature feature = ((Store) owner).types.track.newInstance();
         feature.setPropertyValue(AttributeConvention.IDENTIFIER, index);
-        List<Feature> segments = null;
+        List<AbstractFeature> segments = null;
         List<Link> links = null;
         while (true) {
             /*
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
index 2509711..5eea22b 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
@@ -46,8 +46,8 @@
 import org.apache.sis.metadata.iso.distribution.DefaultFormat;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -77,7 +77,7 @@
     private Reader reader;
 
     /**
-     * The {@link org.opengis.feature.FeatureType} for routes, tracks, way points, <i>etc</i>.
+     * The {@code FeatureType} for routes, tracks, way points, <i>etc</i>.
      * Currently always {@link Types#DEFAULT}, but we use a field for keeping {@code Reader}
      * and {@code Writer} ready to handle profiles or extensions.
      */
@@ -187,7 +187,7 @@
      * @return base type of all GPX types.
      */
     @Override
-    public FeatureType getType() {
+    public DefaultFeatureType getType() {
         return types.parent;
     }
 
@@ -203,7 +203,7 @@
      *             more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
      */
     @Deprecated
-    public FeatureType getFeatureType(final String name) throws IllegalNameException {
+    public DefaultFeatureType getFeatureType(final String name) throws IllegalNameException {
         return types.names.get(this, name);
     }
 
@@ -215,7 +215,7 @@
      * @throws DataStoreException if an error occurred while creating the feature stream.
      */
     @Override
-    public final synchronized Stream<Feature> features(boolean parallel) throws DataStoreException {
+    public final synchronized Stream<AbstractFeature> features(boolean parallel) throws DataStoreException {
         Reader r = reader;
         reader = null;
         if (r == null) try {
@@ -228,7 +228,7 @@
         } catch (Exception e) {
             throw new DataStoreException(e);
         }
-        final Stream<Feature> features = StreamSupport.stream(r, false);
+        final Stream<AbstractFeature> features = StreamSupport.stream(r, false);
         return features.onClose(r);
     }
 
@@ -240,7 +240,7 @@
      * @throws ConcurrentReadException if the {@code features} stream was provided by this data store.
      * @throws DataStoreException if an error occurred while writing the data.
      */
-    public synchronized void write(final Metadata metadata, final Stream<? extends Feature> features) throws DataStoreException {
+    public synchronized void write(final Metadata metadata, final Stream<? extends AbstractFeature> features) throws DataStoreException {
         try {
             /*
              * If we created a reader for reading metadata, we need to close that reader now otherwise the call
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
index 4d80617..9ce5d48 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Types.java
@@ -48,8 +48,8 @@
 import org.apache.sis.util.iso.DefaultNameFactory;
 
 // Branch-dependent imports
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAttributeType;
 
 
 /**
@@ -66,27 +66,27 @@
     /**
      * The parent of all other feature types.
      */
-    final FeatureType parent;
+    final DefaultFeatureType parent;
 
     /**
      * Way point GPX feature type.
      */
-    final FeatureType wayPoint;
+    final DefaultFeatureType wayPoint;
 
     /**
      * Route GPX feature type.
      */
-    final FeatureType route;
+    final DefaultFeatureType route;
 
     /**
      * Track GPX feature type.
      */
-    final FeatureType track;
+    final DefaultFeatureType track;
 
     /**
      * Track segment GPX feature type.
      */
-    final FeatureType trackSegment;
+    final DefaultFeatureType trackSegment;
 
     /**
      * The list of feature types to be given to GPC metadata objects.
@@ -101,7 +101,7 @@
      *             more experience with the API proposed by {@link org.apache.sis.storage.FeatureSet}.
      */
     @Deprecated
-    final FeatureNaming<FeatureType> names;
+    final FeatureNaming<DefaultFeatureType> names;
 
     /**
      * Accessor to the geometry implementation in use (Java2D, ESRI or JTS).
@@ -223,7 +223,7 @@
          * │ rtept          │ WayPoint       │ gpx:wptType           │   [0 … ∞]    │
          * └────────────────┴────────────────┴───────────────────────┴──────────────┘
          */
-        final AttributeType<?> groupResult = GroupAsPolylineOperation.getResult(geometries);
+        final DefaultAttributeType<?> groupResult = GroupAsPolylineOperation.getResult(geometries);
         GroupAsPolylineOperation groupOp = new GroupAsPolylineOperation(geomInfo, Tags.ROUTE_POINTS, groupResult);
         builder.clear().setSuperTypes(parent).setNameSpace(Tags.PREFIX).setName("Route");
         builder.addProperty(groupOp);
@@ -305,7 +305,7 @@
      * @param  previous  previously created international strings as array of length 2.
      *                   The first element is the designation and the second element is the definition.
      */
-    private static FeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
+    private static DefaultFeatureType create(final FeatureTypeBuilder builder, final Map<String,InternationalString[]> previous) {
         for (final PropertyTypeBuilder p : builder.properties()) {
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
index 710df49..0394ea6 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Writer.java
@@ -29,8 +29,8 @@
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
+import org.apache.sis.feature.AbstractFeature;
+import org.apache.sis.feature.DefaultFeatureType;
 
 
 /**
@@ -77,7 +77,7 @@
 
     /**
      * Writes the XML declaration followed by GPX metadata.
-     * This method shall be invoked exactly once before {@link #write(Feature)}.
+     * This method shall be invoked exactly once before {@code write(Feature)}.
      *
      * @throws Exception if an error occurred while writing to the XML file.
      */
@@ -141,10 +141,10 @@
      * @throws JAXBException if underlying JAXB marshaller encounter an error.
      */
     @Override
-    public void write(final Feature feature) throws DataStoreException, XMLStreamException, JAXBException {
+    public void write(final AbstractFeature feature) throws DataStoreException, XMLStreamException, JAXBException {
         if (feature != null) {
             final Types types = ((Store) owner).types;
-            final FeatureType type = feature.getType();
+            final DefaultFeatureType type = feature.getType();
             if (types.wayPoint.isAssignableFrom(type)) {
                 writeWayPoint(feature, Tags.WAY_POINT);
             } else {
@@ -164,14 +164,14 @@
                 }
                 if (isRoute) {
                     for (Object prop : (Collection<?>) feature.getPropertyValue(Tags.ROUTE_POINTS)) {
-                        writeWayPoint((Feature) prop, Tags.ROUTE_POINTS);
+                        writeWayPoint((AbstractFeature) prop, Tags.ROUTE_POINTS);
                     }
                 } else {
                     for (Object segment : (Collection<?>) feature.getPropertyValue(Tags.TRACK_SEGMENTS)) {
                         if (segment != null) {
                             writer.writeStartElement(Tags.TRACK_SEGMENTS);
-                            for (Object prop : (Collection<?>) ((Feature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
-                                writeWayPoint((Feature) prop, Tags.TRACK_POINTS);
+                            for (Object prop : (Collection<?>) ((AbstractFeature) segment).getPropertyValue(Tags.TRACK_POINTS)) {
+                                writeWayPoint((AbstractFeature) prop, Tags.TRACK_POINTS);
                             }
                             writer.writeEndElement();
                         }
@@ -190,7 +190,7 @@
      * @throws XMLStreamException if underlying STAX writer encounter an error.
      * @throws JAXBException if underlying JAXB marshaller encounter an error.
      */
-    private void writeWayPoint(final Feature feature, final String tagName) throws XMLStreamException, JAXBException {
+    private void writeWayPoint(final AbstractFeature feature, final String tagName) throws XMLStreamException, JAXBException {
         if (feature != null) {
             final double[] pt = Geometries.getCoordinate(feature.getPropertyValue(AttributeConvention.GEOMETRY));
             if (pt != null && pt.length >= 2) {
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
index 3f3e32d..a0f68ed 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamReader.java
@@ -48,7 +48,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -106,7 +106,7 @@
  * @since   0.8
  * @module
  */
-public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<Feature>, Runnable {
+public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<AbstractFeature>, Runnable {
     /**
      * The XML stream reader.
      */
@@ -167,7 +167,7 @@
      *         or various {@link RuntimeException} among others.
      */
     @Override
-    public abstract boolean tryAdvance(Consumer<? super Feature> action) throws BackingStoreException;
+    public abstract boolean tryAdvance(Consumer<? super AbstractFeature> action) throws BackingStoreException;
 
     /**
      * Returns {@code null} by default since non-binary XML files are hard to split.
@@ -175,7 +175,7 @@
      * @return {@code null}.
      */
     @Override
-    public Spliterator<Feature> trySplit() {
+    public Spliterator<AbstractFeature> trySplit() {
         return null;
     }
 
diff --git a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
index ea86c85..6638e32 100644
--- a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
+++ b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxStreamWriter.java
@@ -34,7 +34,7 @@
 import org.apache.sis.util.resources.Errors;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -89,7 +89,7 @@
  * @since   0.8
  * @module
  */
-public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<Feature> {
+public abstract class StaxStreamWriter extends StaxStreamIO implements Consumer<AbstractFeature> {
     /**
      * The XML stream writer.
      */
@@ -169,16 +169,16 @@
      *         but also {@link JAXBException} if JAXB is used for marshalling metadata objects,
      *         {@link DataStoreException}, {@link ClassCastException}, <i>etc.</i>
      */
-    public abstract void write(Feature feature) throws Exception;
+    public abstract void write(AbstractFeature feature) throws Exception;
 
     /**
-     * Delegates to {@link #write(Feature)}, wrapping {@code Exception} into unchecked {@code BackingStoreException}.
+     * Delegates to {@code write(Feature)}, wrapping {@code Exception} into unchecked {@code BackingStoreException}.
      *
      * @param  feature  the feature to write.
      * @throws BackingStoreException if an error occurred while writing to the XML file.
      */
     @Override
-    public void accept(final Feature feature) throws BackingStoreException {
+    public void accept(final AbstractFeature feature) throws BackingStoreException {
         try {
             write(feature);
         } catch (BackingStoreException e) {
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java
index 567bd76..27840c9 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/MetadataTest.java
@@ -23,6 +23,7 @@
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
+import org.junit.Ignore;
 
 import static org.junit.Assert.*;
 import static org.apache.sis.test.TestUtilities.date;
@@ -62,6 +63,11 @@
      */
     @Test
     @DependsOnMethod("testEqualsAndHashCode")
+    @Ignore("Can not execute this test on this branch because it depends on Citation.getOnlineResources() "
+          + "and Identification.getExtents() methods, which are not present in GeoAPI 3.0 interfaces. "
+          + "Despite this test failure, the copy constructor should nevertheless works in practice "
+          + "if the Citation and Identification objects are instances of DefaultCitation or AbstractExtent "
+          + "(the SIS implementations of GeoAPI interfaces).")
     public void testCopyConstructor() throws URISyntaxException {
         final Metadata md1 = create();
         final Metadata md2 = new Metadata(md1, null);
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java
index 0fe1bcc..1a08eb0 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/ReaderTest.java
@@ -41,8 +41,8 @@
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
-import org.opengis.metadata.content.FeatureTypeInfo;
+import org.apache.sis.feature.AbstractFeature;
+import org.opengis.util.GenericName;
 
 
 /**
@@ -206,10 +206,10 @@
          */
         final FeatureCatalogueDescription content = (FeatureCatalogueDescription) getSingleton(md.getContentInfo());
         assertTrue("isIncludedWithDataset", content.isIncludedWithDataset());
-        final Iterator<? extends FeatureTypeInfo> it = content.getFeatureTypeInfo().iterator();
-        assertStringEquals("Route",    it.next().getFeatureTypeName().tip());
-        assertStringEquals("Track",    it.next().getFeatureTypeName().tip());
-        assertStringEquals("WayPoint", it.next().getFeatureTypeName().tip());
+        final Iterator<? extends GenericName> it = content.getFeatureTypes().iterator();
+        assertStringEquals("Route",    it.next().tip());
+        assertStringEquals("Track",    it.next().tip());
+        assertStringEquals("WayPoint", it.next().tip());
         assertFalse("hasNext", it.hasNext());
     }
 
@@ -224,8 +224,8 @@
         try (Store reader = create("1.0/waypoint.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyPoint(it.next(), 0, false);
                 verifyPoint(it.next(), 1, false);
                 verifyPoint(it.next(), 2, false);
@@ -245,8 +245,8 @@
         try (Store reader = create("1.1/waypoint.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_1, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyPoint(it.next(), 0, true);
                 verifyPoint(it.next(), 1, true);
                 verifyPoint(it.next(), 2, true);
@@ -266,8 +266,8 @@
         try (Store reader = create("1.0/route.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyRoute(it.next(), false, 1);
                 verifyEmpty(it.next(), "rtept");
                 assertFalse("hasNext", it.hasNext());
@@ -295,8 +295,8 @@
      * This verification is shared by {@link #testRoute110()} and {@link #testSequentialReads()}.
      */
     static void verifyRoute110(final Store reader) throws DataStoreException {
-        try (Stream<Feature> features = reader.features(false)) {
-            final Iterator<Feature> it = features.iterator();
+        try (Stream<AbstractFeature> features = reader.features(false)) {
+            final Iterator<AbstractFeature> it = features.iterator();
             verifyRoute(it.next(), true, 3);
             verifyEmpty(it.next(), "rtept");
             assertFalse("hasNext", it.hasNext());
@@ -311,7 +311,7 @@
      * @param  numLinks  expected number of links.
      */
     @SuppressWarnings("fallthrough")
-    private static void verifyRoute(final Feature f, final boolean v11, final int numLinks) {
+    private static void verifyRoute(final AbstractFeature f, final boolean v11, final int numLinks) {
         assertEquals("name",       "Route name",          f.getPropertyValue("name"));
         assertEquals("cmt",        "Route comment",       f.getPropertyValue("cmt"));
         assertEquals("desc",       "Route description",   f.getPropertyValue("desc"));
@@ -331,9 +331,9 @@
 
         final List<?> points = (List<?>) f.getPropertyValue("rtept");
         assertEquals("points.size()", 3, points.size());
-        verifyPoint((Feature) points.get(0), 0, v11);
-        verifyPoint((Feature) points.get(1), 1, v11);
-        verifyPoint((Feature) points.get(2), 2, v11);
+        verifyPoint((AbstractFeature) points.get(0), 0, v11);
+        verifyPoint((AbstractFeature) points.get(1), 1, v11);
+        verifyPoint((AbstractFeature) points.get(2), 2, v11);
 
         final Polyline p = (Polyline) f.getPropertyValue("sis:geometry");
         assertEquals("pointCount", 3, p.getPointCount());
@@ -349,7 +349,7 @@
      * @param  f    the route or track to verify.
      * @param  dep  {@code "rtept"} if verifying a route, or {@code "trkseg"} if verifying a track.
      */
-    private static void verifyEmpty(final Feature f, final String dep) {
+    private static void verifyEmpty(final AbstractFeature f, final String dep) {
         assertNull("name",   f.getPropertyValue("name"));
         assertNull("cmt",    f.getPropertyValue("cmt"));
         assertNull("desc",   f.getPropertyValue("desc"));
@@ -373,8 +373,8 @@
         try (Store reader = create("1.0/track.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_0, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyTrack(it.next(), false, 1);
                 verifyEmpty(it.next(), "trkseg");
                 assertFalse("hasNext", it.hasNext());
@@ -393,8 +393,8 @@
         try (Store reader = create("1.1/track.xml")) {
             verifyAlmostEmptyMetadata((Metadata) reader.getMetadata());
             assertEquals("version", StoreProvider.V1_1, reader.getVersion());
-            try (Stream<Feature> features = reader.features(false)) {
-                final Iterator<Feature> it = features.iterator();
+            try (Stream<AbstractFeature> features = reader.features(false)) {
+                final Iterator<AbstractFeature> it = features.iterator();
                 verifyTrack(it.next(), true, 3);
                 verifyEmpty(it.next(), "trkseg");
                 assertFalse("hasNext", it.hasNext());
@@ -410,7 +410,7 @@
      * @param  numLinks  expected number of links.
      */
     @SuppressWarnings("fallthrough")
-    private static void verifyTrack(final Feature f, final boolean v11, final int numLinks) {
+    private static void verifyTrack(final AbstractFeature f, final boolean v11, final int numLinks) {
         assertEquals("name",       "Track name",          f.getPropertyValue("name"));
         assertEquals("cmt",        "Track comment",       f.getPropertyValue("cmt"));
         assertEquals("desc",       "Track description",   f.getPropertyValue("desc"));
@@ -430,13 +430,13 @@
 
         final List<?> segments = (List<?>) f.getPropertyValue("trkseg");
         assertEquals("segments.size()", 2, segments.size());
-        final Feature seg1 = (Feature) segments.get(0);
-        final Feature seg2 = (Feature) segments.get(1);
+        final AbstractFeature seg1 = (AbstractFeature) segments.get(0);
+        final AbstractFeature seg2 = (AbstractFeature) segments.get(1);
         final List<?> points = (List<?>) seg1.getPropertyValue("trkpt");
         assertEquals("points.size()", 3, points.size());
-        verifyPoint((Feature) points.get(0), 0, v11);
-        verifyPoint((Feature) points.get(1), 1, v11);
-        verifyPoint((Feature) points.get(2), 2, v11);
+        verifyPoint((AbstractFeature) points.get(0), 0, v11);
+        verifyPoint((AbstractFeature) points.get(1), 1, v11);
+        verifyPoint((AbstractFeature) points.get(2), 2, v11);
         assertTrue(((Collection<?>) seg2.getPropertyValue("trkpt")).isEmpty());
 
         final Polyline p = (Polyline) f.getPropertyValue("sis:geometry");
@@ -454,7 +454,7 @@
      * @param  index  index of the point being verified: 0, 1 or 2.
      * @param  v11    {@code true} for GPX 1.1, or {@code false} for GPX 1.0.
      */
-    private static void verifyPoint(final Feature f, final int index, final boolean v11) {
+    private static void verifyPoint(final AbstractFeature f, final int index, final boolean v11) {
         assertEquals("sis:identifier", index + 1, f.getPropertyValue("sis:identifier"));
         switch (index) {
             case 0: {
@@ -610,14 +610,14 @@
     @DependsOnMethod("testSequentialReads")
     public void testConcurrentReads() throws DataStoreException {
         try (Store reader = createFromURL()) {
-            final Stream<Feature>   f1 = reader.features(false);
-            final Iterator<Feature> i1 = f1.iterator();
+            final Stream<AbstractFeature>   f1 = reader.features(false);
+            final Iterator<AbstractFeature> i1 = f1.iterator();
             verifyRoute(i1.next(), true, 3);
-            final Stream<Feature>   f2 = reader.features(false);
-            final Iterator<Feature> i2 = f2.iterator();
+            final Stream<AbstractFeature>   f2 = reader.features(false);
+            final Iterator<AbstractFeature> i2 = f2.iterator();
             verifyEmpty(i1.next(), "rtept");
-            final Stream<Feature>   f3 = reader.features(false);
-            final Iterator<Feature> i3 = f3.iterator();
+            final Stream<AbstractFeature>   f3 = reader.features(false);
+            final Iterator<AbstractFeature> i3 = f3.iterator();
             verifyRoute(i2.next(), true, 3);
             verifyRoute(i3.next(), true, 3);
             verifyEmpty(i3.next(), "rtept");
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java
index 411ebff..20db914 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/TypesTest.java
@@ -27,8 +27,8 @@
 import static org.junit.Assert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
 
 
 /**
@@ -58,8 +58,8 @@
     /**
      * Verifies that all designations and definitions can be read from the resources.
      */
-    private static void testResources(final FeatureType type) {
-        for (final PropertyType p : type.getProperties(false)) {
+    private static void testResources(final DefaultFeatureType type) {
+        for (final AbstractIdentifiedType p : type.getProperties(false)) {
             final GenericName name = p.getName();
             if (!AttributeConvention.contains(name)) {
                 final String label = name.toString();
diff --git a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java
index 926458f..acff02b 100644
--- a/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java
+++ b/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/storage/gpx/WriterTest.java
@@ -39,7 +39,7 @@
 import static org.apache.sis.test.MetadataAssert.*;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import org.apache.sis.feature.AbstractFeature;
 
 
 /**
@@ -249,7 +249,7 @@
          * Way Points as defined in "waypoint.xml" test file.
          * Appear also in "route.xml" and "track.xml" files.
          */
-        final Feature point1 = types.wayPoint.newInstance();
+        final AbstractFeature point1 = types.wayPoint.newInstance();
         point1.setPropertyValue("sis:geometry",  new Point(15, 10));
         point1.setPropertyValue("time",          Instant.parse("2010-01-10T00:00:00Z"));
         point1.setPropertyValue("name",          "first point");
@@ -271,7 +271,7 @@
         point1.setPropertyValue("link",          Arrays.asList(new Link(new URI("http://first-address1.org")),
                                                                new Link(new URI("http://first-address2.org")),
                                                                new Link(new URI("http://first-address3.org"))));
-        final Feature point3 = types.wayPoint.newInstance();
+        final AbstractFeature point3 = types.wayPoint.newInstance();
         point3.setPropertyValue("sis:geometry",  new Point(35, 30));
         point3.setPropertyValue("time",          Instant.parse("2010-01-30T00:00:00Z"));
         point3.setPropertyValue("name",          "third point");
@@ -292,17 +292,17 @@
         point3.setPropertyValue("fix",           Fix.THREE_DIMENSIONAL);
         point3.setPropertyValue("link",          Arrays.asList(new Link(new URI("http://third-address1.org")),
                                                                new Link(new URI("http://third-address2.org"))));
-        final Feature point2 = types.wayPoint.newInstance();
+        final AbstractFeature point2 = types.wayPoint.newInstance();
         point2.setPropertyValue("sis:geometry", new Point(25, 20));
-        final List<Feature> wayPoints = Arrays.asList(point1, point2, point3);
-        final List<Feature> features;
+        final List<AbstractFeature> wayPoints = Arrays.asList(point1, point2, point3);
+        final List<AbstractFeature> features;
         switch (type) {
             case WAY_POINT: {
                 features = wayPoints;
                 break;
             }
             case ROUTE: {
-                final Feature route1 = types.route.newInstance();
+                final AbstractFeature route1 = types.route.newInstance();
                 route1.setPropertyValue("name",   "Route name");
                 route1.setPropertyValue("cmt",    "Route comment");
                 route1.setPropertyValue("desc",   "Route description");
@@ -313,16 +313,16 @@
                 route1.setPropertyValue("link",   Arrays.asList(new Link(new URI("http://route-address1.org")),
                                                                 new Link(new URI("http://route-address2.org")),
                                                                 new Link(new URI("http://route-address3.org"))));
-                final Feature route2 = types.route.newInstance();
+                final AbstractFeature route2 = types.route.newInstance();
                 features = Arrays.asList(route1, route2);
                 break;
             }
             case TRACK: {
-                final Feature seg1 = types.trackSegment.newInstance();
-                final Feature seg2 = types.trackSegment.newInstance();
+                final AbstractFeature seg1 = types.trackSegment.newInstance();
+                final AbstractFeature seg2 = types.trackSegment.newInstance();
                 seg1.setPropertyValue("trkpt", wayPoints);
 
-                final Feature track1 = types.track.newInstance();
+                final AbstractFeature track1 = types.track.newInstance();
                 track1.setPropertyValue("name",   "Track name");
                 track1.setPropertyValue("cmt",    "Track comment");
                 track1.setPropertyValue("desc",   "Track description");
@@ -333,7 +333,7 @@
                 track1.setPropertyValue("link",   Arrays.asList(new Link(new URI("http://track-address1.org")),
                                                                 new Link(new URI("http://track-address2.org")),
                                                                 new Link(new URI("http://track-address3.org"))));
-                final Feature track2 = types.track.newInstance();
+                final AbstractFeature track2 = types.track.newInstance();
                 features = Arrays.asList(track1, track2);
                 break;
             }