This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 5f035e907f Tune `SQLStore` in support for Geopackage: - Implement `SQLStore.getIdentifier()`. - Specify a locale for table contents. - Clarification in comments. 5f035e907f is described below commit 5f035e907f290b6ca7ddc3f739e3e030cd47482c Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Aug 26 18:52:27 2024 +0200 Tune `SQLStore` in support for Geopackage: - Implement `SQLStore.getIdentifier()`. - Specify a locale for table contents. - Clarification in comments. --- .../org/apache/sis/storage/sql/DataAccess.java | 37 ++++++- .../main/org/apache/sis/storage/sql/SQLStore.java | 115 +++++++++++++++------ .../apache/sis/storage/sql/SimpleFeatureStore.java | 4 +- .../apache/sis/storage/sql/feature/Database.java | 30 ++++-- .../sis/storage/sql/feature/InfoStatements.java | 3 +- .../apache/sis/storage/sql/postgis/Postgres.java | 18 ++-- .../storage/sql/feature/InfoStatementsTest.java | 2 +- .../main/org/apache/sis/storage/DataStore.java | 22 ++-- 8 files changed, 162 insertions(+), 69 deletions(-) diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java index 4da9b5c468..edc4cbd0d8 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java @@ -16,16 +16,20 @@ */ package org.apache.sis.storage.sql; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLNonTransientConnectionException; +import java.text.ParseException; +import org.opengis.util.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreClosedException; import org.apache.sis.storage.DataStoreContentException; +import org.apache.sis.storage.DataStoreReferencingException; import org.apache.sis.storage.NoSuchDataException; import org.apache.sis.storage.sql.feature.Database; import org.apache.sis.storage.sql.feature.InfoStatements; @@ -82,13 +86,13 @@ public class DataAccess implements AutoCloseable { /** * The SQL connection, created when first needed. */ - Connection connection; + private Connection connection; /** * Helper methods for fetching information such as coordinate reference systems. * Created when first needed. */ - InfoStatements spatialInformation; + private InfoStatements spatialInformation; /** * A read or write lock to unlock when the data access will be closed, or {@code null} if none. @@ -123,6 +127,15 @@ public class DataAccess implements AutoCloseable { this.write = write; } + /** + * Sets the connections and spatial statements to instances that already exist. + * This method is invoked during database model initialization only. + */ + final void initialize(final Connection connection, final InfoStatements spatialInformation) { + this.connection = connection; + this.spatialInformation = spatialInformation; + } + /** * Returns the SQL store for which this object is providing low-level access. * @@ -219,6 +232,7 @@ public class DataAccess implements AutoCloseable { * @return the <abbr>CRS</abbr> associated to the given <abbr>SRID</abbr>, or {@code null} if the given * <abbr>SRID</abbr> is a code explicitly associated to an undefined <abbr>CRS</abbr>. * @throws NoSuchDataException if no <abbr>CRS</abbr> is associated to the given <abbr>SRID</abbr>. + * @throws DataStoreReferencingException if the <abbr>CRS</abbr> definition cannot be parsed. * @throws DataStoreException if the query failed for another reason. */ public CoordinateReferenceSystem findCRS(final int srid) throws DataStoreException { @@ -236,6 +250,8 @@ public class DataAccess implements AutoCloseable { throw new NoSuchDataException(e.getMessage(), e.getCause()); } catch (DataStoreException e) { throw e; + } catch (FactoryException | ParseException e) { + throw new DataStoreReferencingException(Exceptions.unwrap(e)); } catch (Exception e) { throw new DataStoreException(Exceptions.unwrap(e)); } @@ -277,6 +293,23 @@ public class DataAccess implements AutoCloseable { return srid; } + /** + * Returns the locale for the usages identified by the given category. + * If the category is {@code DISPLAY}, then this method returns {@link SQLStore#getLocale()}. + * If the category is {@code FORMAT}, then this method returns {@link SQLStore#contentLocale}. + * Otherwise this method returns {@code null}. + * + * @param category the usage of the desired locale. + * @return locale for the given usage, or {@code null} for the default. + */ + public Locale getLocale(final Locale.Category category) { + switch (category) { + case DISPLAY: return store.getLocale(); + case FORMAT: return store.contentLocale; + default: return null; + } + } + /** * Closes the connection and release the read or write lock (if any). * diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java index c9454b512d..a44f0cf145 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java @@ -16,6 +16,7 @@ */ package org.apache.sis.storage.sql; +import java.util.Locale; import java.util.Optional; import java.util.Collection; import java.util.concurrent.locks.ReadWriteLock; @@ -46,6 +47,7 @@ import org.apache.sis.io.stream.InternalOptionKey; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Exceptions; +import org.apache.sis.util.iso.Names; import org.apache.sis.util.privy.Strings; import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.setup.OptionKey; @@ -54,8 +56,8 @@ import org.apache.sis.setup.OptionKey; /** * An abstract data store for reading or writing resources from/to a spatial database. * {@code SQLStore} requires a {@link DataSource} to be specified (indirectly) at construction time. - * While not mandatory, a pooled data source is recommended because {@code SQLStore} will frequently - * opens and closes connections. + * While not mandatory, a pooled data source is recommended because {@code SQLStore} may open and + * close connections many times. * * <p>This class provides basic support for ISO 19125-2, also known as * <a href="https://www.ogc.org/standards/sfs">OGC Simple feature access - Part 2: SQL option</a>: @@ -70,16 +72,30 @@ import org.apache.sis.setup.OptionKey; */ public abstract class SQLStore extends DataStore implements Aggregate { /** - * Names of possible public getter methods for data source title, in preference order. + * Names of possible public getter methods for fetching a metadata title from a {@link DataSource}. + * Elements are sorted in preference order. The return value shall be a {@link String}. + * + * @see #getMetadata() */ - private static final String[] NAME_GETTERS = { + private static final String[] TITLE_GETTERS = { "getDescription", // PostgreSQL, SQL Server "getDataSourceName", // Derby - "getDatabaseName", // Derby, PostgreSQL, SQL Server - "getUrl", // PostgreSQL + "getDatabaseName", // Derby, PostgreSQL, SQL Server, SQLite + "getUrl", // PostgreSQL, SQLite "getURL" // SQL Server }; + /** + * Names of possible public getter methods for fetching an identifier from a {@link DataSource}. + * Elements are sorted in preference order. The return value shall be a {@link String}. + * + * @see #getIdentifier() + */ + private static final String[] IDENTIFIER_GETTERS = { + "getDatabaseName", + "getDataSourceName" + }; + /** * The data source to use for obtaining connections to the database. * This is the storage given (indirectly, through the {@link StorageConnector} argument) at construction time. @@ -90,6 +106,13 @@ public abstract class SQLStore extends DataStore implements Aggregate { */ protected final DataSource source; + /** + * An identifier inferred from the data source, or {@code null} if none. + * + * @see #getIdentifier() + */ + private final GenericName identifier; + /** * The library to use for creating geometric objects, or {@code null} for system default. */ @@ -140,6 +163,23 @@ public abstract class SQLStore extends DataStore implements Aggregate { */ private Metadata metadata; + /** + * The locale to use for international texts to write in a database table. + * This is not necessarily the same locale as {@link #getLocale()}, because the + * latter is for warning and error messages to shown in user applications. + * + * <p><b>Example:</b> if a new <abbr>CRS</abbr> needs to be added in the {@code "SPATIAL_REF_SYS"} table + * and if that table contains a {@code "description"} column (as in the Geopackage format), then the text + * to write in that column will be localized with this locale.</p> + * + * <p>If the value is {@code null}, then a default locale is used.</p> + * + * @see OptionKey#LOCALE + * + * @since 1.5 + */ + protected final Locale contentLocale; + /** * The user-specified method for customizing the schema inferred by table analysis. * This is {@code null} if there is none. @@ -177,8 +217,11 @@ public abstract class SQLStore extends DataStore implements Aggregate { super(provider, connector); source = connector.getStorageAs(DataSource.class); geomLibrary = connector.getOption(OptionKey.GEOMETRY_LIBRARY); + contentLocale = connector.getOption(OptionKey.LOCALE); customizer = connector.getOption(SchemaModifier.OPTION); transactionLocks = connector.getOption(InternalOptionKey.LOCKS); + identifier = getDataSourceProperty(IDENTIFIER_GETTERS) + .map((id) -> Names.createLocalName(null, null, id)).orElse(null); } /** @@ -248,13 +291,14 @@ public abstract class SQLStore extends DataStore implements Aggregate { /** * Returns an identifier for the root resource of this SQL store, or an empty value if none. - * The default implementation returns an empty name because the root resource of a SQL store has no identifier. + * The default implementation returns the database name if this property can be found in the + * {@link DataSource} implementation. * * @return an identifier for the root resource of this SQL store. */ @Override public Optional<GenericName> getIdentifier() throws DataStoreException { - return Optional.empty(); + return Optional.ofNullable(identifier); } /** @@ -324,12 +368,11 @@ public abstract class SQLStore extends DataStore implements Aggregate { Database<?> current = model; if (current == null) { final DatabaseMetaData md = c.getMetaData(); - current = Database.create(source, md, geomLibrary, listeners, transactionLocks); + current = Database.create(source, md, geomLibrary, contentLocale, listeners, transactionLocks); try (final InfoStatements spatialInformation = current.createInfoStatements(c)) { if (tableNames == null) { final DataAccess dao = newDataAccess(false); - dao.connection = c; - dao.spatialInformation = spatialInformation; + dao.initialize(c, spatialInformation); setModelSources(readResourceDefinitions(dao)); // Do not close the DAO, because we still use the connection and the info statements. } @@ -353,38 +396,44 @@ public abstract class SQLStore extends DataStore implements Aggregate { final var builder = new MetadataBuilder(); builder.addSpatialRepresentation(SpatialRepresentationType.TEXT_TABLE); try (Connection c = source.getConnection()) { - @SuppressWarnings("LocalVariableHidesMemberVariable") - final Database<?> model = model(c); - model.metadata(c.getMetaData(), builder); + model(c).metadata(c.getMetaData(), builder); } catch (DataStoreException e) { throw e; } catch (Exception e) { throw new DataStoreException(Exceptions.unwrap(e)); } - /* - * Try to find a title from the data source description. - */ - for (final String c : NAME_GETTERS) { - try { - final Method method = source.getClass().getMethod(c); - if (method.getReturnType() == String.class) { - final String name = Strings.trimOrNull((String) method.invoke(source)); - if (name != null) { - builder.addTitle(name); - break; - } - } - } catch (NoSuchMethodException | SecurityException e) { - // Ignore - try the next method. - } catch (ReflectiveOperationException e) { - throw new DataStoreException(Exceptions.unwrap(e)); - } - } + getDataSourceProperty(TITLE_GETTERS).ifPresent(builder::addTitle); + builder.addIdentifier(identifier, MetadataBuilder.Scope.ALL); metadata = builder.buildAndFreeze(); } return metadata; } + /** + * Tries to get a property from the data source by invoking all given public getter methods + * until a method exists and returns a non-null value. + * + * @param methodNames {@link #TITLE_GETTERS} or {@link #IDENTIFIER_GETTERS}. + * @return the first value found. + * @throws DataStoreException if an unexpected reflective operation occurred. + */ + private Optional<String> getDataSourceProperty(final String[] methodNames) throws DataStoreException { + for (final String c : methodNames) try { + final Method method = source.getClass().getMethod(c); + if (method.getReturnType() == String.class) { + String name = Strings.trimOrNull((String) method.invoke(source)); + if (name != null) { + return Optional.of(name); + } + } + } catch (NoSuchMethodException | SecurityException e) { + // Ignore - try the next method. + } catch (ReflectiveOperationException e) { + throw new DataStoreException(Exceptions.unwrap(e)); + } + return Optional.empty(); + } + /** * Returns the resources (feature set or coverages) in this SQL store. * The collection of resources should be constructed only when first needed and cached diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java index 512d85f495..fc839f5b60 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java @@ -36,8 +36,8 @@ import org.apache.sis.util.ArgumentChecks; * * <ul> * <li>A {@link DataSource} (specified indirectly) providing connections to the database. While not mandatory, - * a pooled data source is recommended because {@code SimpleFeatureStore} will frequently opens and closes - * connections.</li> + * a pooled data source is recommended because {@code SimpleFeatureStore} may open and close connections + * many times.</li> * <li>A list of tables, views or queries to view as {@link FeatureSet}s. This list is provided by * {@link ResourceDefinition} objects. Only the main tables need to be specified. Dependencies * inferred by foreigner keys will be followed automatically.</li> diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java index d25a292c62..64dd97c6db 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java @@ -210,6 +210,11 @@ public class Database<G> extends Syntax { */ protected final ReadWriteLock transactionLocks; + /** + * The locale to use for international texts to write in the database, or {@code null} for default. + */ + protected final Locale contentLocale; + /** * Where to send warnings. * @@ -237,16 +242,18 @@ public class Database<G> extends Syntax { /** * Creates a new handler for a spatial database. * - * @param source provider of (pooled) connections to the database. - * @param metadata metadata about the database. - * @param dialect additional information not provided by {@code metadata}. - * @param geomLibrary the factory to use for creating geometric objects. - * @param listeners where to send warnings. - * @param locks the read/write locks, or {@code null} if none. + * @param source provider of (pooled) connections to the database. + * @param metadata metadata about the database. + * @param dialect additional information not provided by {@code metadata}. + * @param geomLibrary the factory to use for creating geometric objects. + * @param contentLocale the locale to use for international texts to write in the database, or {@code null} for default. + * @param listeners where to send warnings. + * @param locks the read/write locks, or {@code null} if none. * @throws SQLException if an error occurred while reading database metadata. */ protected Database(final DataSource source, final DatabaseMetaData metadata, final Dialect dialect, - final Geometries<G> geomLibrary, final StoreListeners listeners, final ReadWriteLock locks) + final Geometries<G> geomLibrary, final Locale contentLocale, final StoreListeners listeners, + final ReadWriteLock locks) throws SQLException { super(metadata, true); @@ -281,6 +288,7 @@ public class Database<G> extends Syntax { this.source = source; this.isByteSigned = !unsigned; this.geomLibrary = geomLibrary; + this.contentLocale = contentLocale; this.listeners = listeners; this.cacheOfCRS = new Cache<>(7, 2, false); this.cacheOfSRID = new WeakHashMap<>(); @@ -306,15 +314,15 @@ public class Database<G> extends Syntax { * @throws DataStoreException if a logical error occurred while analyzing the database structure. */ public static Database<?> create(final DataSource source, final DatabaseMetaData metadata, - final GeometryLibrary geomLibrary, final StoreListeners listeners, final ReadWriteLock locks) - throws Exception + final GeometryLibrary geomLibrary, final Locale contentLocale, final StoreListeners listeners, + final ReadWriteLock locks) throws Exception { final Geometries<?> g = Geometries.factory(geomLibrary); final Dialect dialect = Dialect.guess(metadata); final Database<?> db; switch (dialect) { - case POSTGRESQL: db = new Postgres<>(source, metadata, dialect, g, listeners, locks); break; - default: db = new Database<>(source, metadata, dialect, g, listeners, locks); break; + case POSTGRESQL: db = new Postgres<>(source, metadata, dialect, g, contentLocale, listeners, locks); break; + default: db = new Database<>(source, metadata, dialect, g, contentLocale, listeners, locks); break; } return db; } diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java index 72c72efd3e..4628cf934a 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java @@ -144,6 +144,7 @@ public class InfoStatements implements Localized, AutoCloseable { /** * Returns the locale used for warnings and error messages. + * Not to be confused with the locale used for writing international texts in the database. */ @Override public final Locale getLocale() { @@ -785,7 +786,7 @@ public class InfoStatements implements Localized, AutoCloseable { do { final String column = description ? schema.crsDescriptionColumn : schema.crsNameColumn; if (column != null) { - String name = description ? IdentifiedObjects.getDisplayName(search.crs, getLocale()) + String name = description ? IdentifiedObjects.getDisplayName(search.crs, database.contentLocale) : IdentifiedObjects.getName(search.crs, null); if (name != null) { definitions[numDefinitions++] = name; diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java index 586c47c274..c4b0f97c23 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java @@ -67,19 +67,21 @@ public final class Postgres<G> extends Database<G> { /** * Creates a new session for a PostGIS database. * - * @param source provider of (pooled) connections to the database. - * @param metadata metadata about the database for which a session is created. - * @param dialect additional information not provided by {@code metadata}. - * @param geomLibrary the factory to use for creating geometric objects. - * @param listeners where to send warnings. - * @param locks the read/write locks, or {@code null} if none. + * @param source provider of (pooled) connections to the database. + * @param metadata metadata about the database for which a session is created. + * @param dialect additional information not provided by {@code metadata}. + * @param geomLibrary the factory to use for creating geometric objects. + * @param contentLocale the locale to use for international texts to write in the database, or {@code null} for default. + * @param listeners where to send warnings. + * @param locks the read/write locks, or {@code null} if none. * @throws SQLException if an error occurred while reading database metadata. */ public Postgres(final DataSource source, final DatabaseMetaData metadata, final Dialect dialect, - final Geometries<G> geomLibrary, final StoreListeners listeners, final ReadWriteLock locks) + final Geometries<G> geomLibrary, final Locale contentLocale, final StoreListeners listeners, + final ReadWriteLock locks) throws SQLException { - super(source, metadata, dialect, geomLibrary, listeners, locks); + super(source, metadata, dialect, geomLibrary, contentLocale, listeners, locks); Version version = null; try (Statement st = metadata.getConnection().createStatement(); ResultSet result = st.executeQuery("SELECT public.PostGIS_version();")) diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java index 26fe0a1fc5..cea1c8ea4a 100644 --- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java +++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/InfoStatementsTest.java @@ -102,7 +102,7 @@ public final class InfoStatementsTest extends TestCase { test.executeSQL(List.of(createSpatialRefSys())); connection = test.source.getConnection(); database = new Database<>(test.source, connection.getMetaData(), Dialect.DERBY, - Geometries.factory(GeometryLibrary.JAVA2D), + Geometries.factory(GeometryLibrary.JAVA2D), null, new StoreListeners(null, new DataStoreMock("Unused")), null); /* * The `spatialSchema` is private, so we need to use reflection for setting its value. diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java index 61ba67ec4f..3a4abcd10f 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStore.java @@ -91,7 +91,7 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable { * * @see #getDisplayName() */ - private final String name; + private final String displayName; /** * The locale to use for formatting warnings. @@ -112,10 +112,10 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable { */ @SuppressWarnings("this-escape") // `this` appears in a cyclic graph. protected DataStore() { - provider = null; - name = null; - locale = Locale.getDefault(Locale.Category.DISPLAY); - listeners = new StoreListeners(null, this); + provider = null; + displayName = null; + locale = Locale.getDefault(Locale.Category.DISPLAY); + listeners = new StoreListeners(null, this); } /** @@ -131,10 +131,10 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable { */ @SuppressWarnings("this-escape") // `this` appears in a cyclic graph. Should not be accessible before completion. protected DataStore(final DataStoreProvider provider, final StorageConnector connector) throws DataStoreException { - this.provider = provider; - this.name = connector.getStorageName(); - this.locale = Locale.getDefault(Locale.Category.DISPLAY); - this.listeners = new StoreListeners(connector.getOption(DataOptionKey.PARENT_LISTENERS), this); + this.provider = provider; + this.displayName = connector.getStorageName(); + this.locale = Locale.getDefault(Locale.Category.DISPLAY); + this.listeners = new StoreListeners(connector.getOption(DataOptionKey.PARENT_LISTENERS), this); /* * Above locale is NOT OptionKey.LOCALE because we are not talking about the same locale. * The one in this DataStore is for warning and exception messages, not for parsing data. @@ -162,7 +162,7 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable { final boolean hidden) throws DataStoreException { this.provider = provider; - name = connector.getStorageName(); + displayName = connector.getStorageName(); final StoreListeners forwardTo; if (parent != null) { locale = parent.locale; @@ -280,7 +280,7 @@ public abstract class DataStore implements Resource, Localized, AutoCloseable { * @since 0.8 */ public String getDisplayName() { - return name; + return displayName; } /**